xref: /aosp_15_r20/external/cronet/components/metrics/debug/app.ts (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker// Copyright 2022 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker// Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker// found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Workerimport 'chrome://resources/cr_elements/cr_tab_box/cr_tab_box.js';
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Workerimport {assert} from 'chrome://resources/js/assert.js';
8*6777b538SAndroid Build Coastguard Workerimport {addWebUiListener} from 'chrome://resources/js/cr.js';
9*6777b538SAndroid Build Coastguard Workerimport {CustomElement} from 'chrome://resources/js/custom_element.js';
10*6777b538SAndroid Build Coastguard Worker
11*6777b538SAndroid Build Coastguard Workerimport {getTemplate} from './app.html.js';
12*6777b538SAndroid Build Coastguard Workerimport type {KeyValue, Log, LogData, MetricsInternalsBrowserProxy} from './browser_proxy.js';
13*6777b538SAndroid Build Coastguard Workerimport {MetricsInternalsBrowserProxyImpl} from './browser_proxy.js';
14*6777b538SAndroid Build Coastguard Workerimport {getEventsPeekString, logEventToString, sizeToString, timestampToString, umaLogTypeToString} from './log_utils.js';
15*6777b538SAndroid Build Coastguard Worker
16*6777b538SAndroid Build Coastguard Worker/**
17*6777b538SAndroid Build Coastguard Worker * An empty log. It is appended to a logs table when there are no logs (for
18*6777b538SAndroid Build Coastguard Worker * purely aesthetic reasons).
19*6777b538SAndroid Build Coastguard Worker */
20*6777b538SAndroid Build Coastguard Workerconst EMPTY_LOG: Log = {
21*6777b538SAndroid Build Coastguard Worker  type: 'N/A',
22*6777b538SAndroid Build Coastguard Worker  hash: 'N/A',
23*6777b538SAndroid Build Coastguard Worker  timestamp: '',
24*6777b538SAndroid Build Coastguard Worker  size: -1,
25*6777b538SAndroid Build Coastguard Worker  events: [],
26*6777b538SAndroid Build Coastguard Worker};
27*6777b538SAndroid Build Coastguard Worker
28*6777b538SAndroid Build Coastguard Workerexport class MetricsInternalsAppElement extends CustomElement {
29*6777b538SAndroid Build Coastguard Worker  static get is(): string {
30*6777b538SAndroid Build Coastguard Worker    return 'metrics-internals-app';
31*6777b538SAndroid Build Coastguard Worker  }
32*6777b538SAndroid Build Coastguard Worker
33*6777b538SAndroid Build Coastguard Worker  static override get template() {
34*6777b538SAndroid Build Coastguard Worker    return getTemplate();
35*6777b538SAndroid Build Coastguard Worker  }
36*6777b538SAndroid Build Coastguard Worker
37*6777b538SAndroid Build Coastguard Worker  /**
38*6777b538SAndroid Build Coastguard Worker   * Resolves once the component has finished loading.
39*6777b538SAndroid Build Coastguard Worker   */
40*6777b538SAndroid Build Coastguard Worker  initPromise: Promise<void>;
41*6777b538SAndroid Build Coastguard Worker
42*6777b538SAndroid Build Coastguard Worker  private browserProxy_: MetricsInternalsBrowserProxy =
43*6777b538SAndroid Build Coastguard Worker      MetricsInternalsBrowserProxyImpl.getInstance();
44*6777b538SAndroid Build Coastguard Worker
45*6777b538SAndroid Build Coastguard Worker  /**
46*6777b538SAndroid Build Coastguard Worker   * Previous summary tables data. Used to prevent re-renderings of the tables
47*6777b538SAndroid Build Coastguard Worker   * when the data has not changed.
48*6777b538SAndroid Build Coastguard Worker   */
49*6777b538SAndroid Build Coastguard Worker  private previousVariationsSummaryData_: string = '';
50*6777b538SAndroid Build Coastguard Worker  private previousUmaSummaryData_: string = '';
51*6777b538SAndroid Build Coastguard Worker
52*6777b538SAndroid Build Coastguard Worker  constructor() {
53*6777b538SAndroid Build Coastguard Worker    super();
54*6777b538SAndroid Build Coastguard Worker    this.initPromise = this.init_();
55*6777b538SAndroid Build Coastguard Worker  }
56*6777b538SAndroid Build Coastguard Worker
57*6777b538SAndroid Build Coastguard Worker  /**
58*6777b538SAndroid Build Coastguard Worker   * Returns UMA logs data (with their proto) as a JSON string. Used when
59*6777b538SAndroid Build Coastguard Worker   * exporting UMA logs data. Returns a promise.
60*6777b538SAndroid Build Coastguard Worker   */
61*6777b538SAndroid Build Coastguard Worker  getUmaLogsExportContent(): Promise<string> {
62*6777b538SAndroid Build Coastguard Worker    return this.browserProxy_.getUmaLogData(/*includeLogProtoData*/ true);
63*6777b538SAndroid Build Coastguard Worker  }
64*6777b538SAndroid Build Coastguard Worker
65*6777b538SAndroid Build Coastguard Worker  private async init_(): Promise<void> {
66*6777b538SAndroid Build Coastguard Worker    // Fetch variations summary data and set up a recurring timer.
67*6777b538SAndroid Build Coastguard Worker    await this.updateVariationsSummary_();
68*6777b538SAndroid Build Coastguard Worker    setInterval(() => this.updateVariationsSummary_(), 3000);
69*6777b538SAndroid Build Coastguard Worker
70*6777b538SAndroid Build Coastguard Worker    // Fetch UMA summary data and set up a recurring timer.
71*6777b538SAndroid Build Coastguard Worker    await this.updateUmaSummary_();
72*6777b538SAndroid Build Coastguard Worker    setInterval(() => this.updateUmaSummary_(), 3000);
73*6777b538SAndroid Build Coastguard Worker
74*6777b538SAndroid Build Coastguard Worker    // Set up the UMA table caption.
75*6777b538SAndroid Build Coastguard Worker    const umaTableCaption = this.$('#uma-table-caption') as HTMLElement;
76*6777b538SAndroid Build Coastguard Worker    const isUsingMetricsServiceObserver =
77*6777b538SAndroid Build Coastguard Worker        await this.browserProxy_.isUsingMetricsServiceObserver();
78*6777b538SAndroid Build Coastguard Worker    umaTableCaption.textContent = isUsingMetricsServiceObserver ?
79*6777b538SAndroid Build Coastguard Worker        'List of all UMA logs closed since browser startup.' :
80*6777b538SAndroid Build Coastguard Worker        'List of UMA logs closed since opening this page. Starting the browser \
81*6777b538SAndroid Build Coastguard Worker        with the --export-uma-logs-to-file command line flag will instead show \
82*6777b538SAndroid Build Coastguard Worker        all logs closed since browser startup.';
83*6777b538SAndroid Build Coastguard Worker
84*6777b538SAndroid Build Coastguard Worker    // Set up a listener for UMA logs. Also update UMA log data immediately in
85*6777b538SAndroid Build Coastguard Worker    // case there are logs that we already have data on.
86*6777b538SAndroid Build Coastguard Worker    addWebUiListener(
87*6777b538SAndroid Build Coastguard Worker        'uma-log-created-or-event', () => this.updateUmaLogsData_());
88*6777b538SAndroid Build Coastguard Worker    await this.updateUmaLogsData_();
89*6777b538SAndroid Build Coastguard Worker
90*6777b538SAndroid Build Coastguard Worker    // Set up the UMA "Export logs" button.
91*6777b538SAndroid Build Coastguard Worker    const exportUmaLogsButton = this.$('#export-uma-logs') as HTMLElement;
92*6777b538SAndroid Build Coastguard Worker    exportUmaLogsButton.addEventListener('click', () => this.exportUmaLogs_());
93*6777b538SAndroid Build Coastguard Worker  }
94*6777b538SAndroid Build Coastguard Worker
95*6777b538SAndroid Build Coastguard Worker  /**
96*6777b538SAndroid Build Coastguard Worker   * Callback function to expand/collapse an element on click.
97*6777b538SAndroid Build Coastguard Worker   * @param e The click event.
98*6777b538SAndroid Build Coastguard Worker   */
99*6777b538SAndroid Build Coastguard Worker  private toggleEventsExpand_(e: MouseEvent): void {
100*6777b538SAndroid Build Coastguard Worker    let umaLogEventsDiv = e.target as HTMLElement;
101*6777b538SAndroid Build Coastguard Worker
102*6777b538SAndroid Build Coastguard Worker    // It is possible we have clicked a descendant. Keep checking the parent
103*6777b538SAndroid Build Coastguard Worker    // until we are the the root div of the events.
104*6777b538SAndroid Build Coastguard Worker    while (!umaLogEventsDiv.classList.contains('uma-log-events')) {
105*6777b538SAndroid Build Coastguard Worker      umaLogEventsDiv = umaLogEventsDiv.parentElement as HTMLElement;
106*6777b538SAndroid Build Coastguard Worker    }
107*6777b538SAndroid Build Coastguard Worker    umaLogEventsDiv.classList.toggle('uma-log-events-expanded');
108*6777b538SAndroid Build Coastguard Worker  }
109*6777b538SAndroid Build Coastguard Worker
110*6777b538SAndroid Build Coastguard Worker  /**
111*6777b538SAndroid Build Coastguard Worker   * Fills the passed table element with the given summary.
112*6777b538SAndroid Build Coastguard Worker   */
113*6777b538SAndroid Build Coastguard Worker  private updateSummaryTable_(tableBody: HTMLElement, summary: KeyValue[]):
114*6777b538SAndroid Build Coastguard Worker      void {
115*6777b538SAndroid Build Coastguard Worker    // Clear the table first.
116*6777b538SAndroid Build Coastguard Worker    tableBody.replaceChildren();
117*6777b538SAndroid Build Coastguard Worker
118*6777b538SAndroid Build Coastguard Worker    const template = this.$('#summary-row-template') as HTMLTemplateElement;
119*6777b538SAndroid Build Coastguard Worker    for (const info of summary) {
120*6777b538SAndroid Build Coastguard Worker      const row = template.content.cloneNode(true) as HTMLElement;
121*6777b538SAndroid Build Coastguard Worker      const [key, value] = row.querySelectorAll('td');
122*6777b538SAndroid Build Coastguard Worker
123*6777b538SAndroid Build Coastguard Worker      assert(key);
124*6777b538SAndroid Build Coastguard Worker      key.textContent = info.key;
125*6777b538SAndroid Build Coastguard Worker
126*6777b538SAndroid Build Coastguard Worker      assert(value);
127*6777b538SAndroid Build Coastguard Worker      value.textContent = info.value;
128*6777b538SAndroid Build Coastguard Worker
129*6777b538SAndroid Build Coastguard Worker      tableBody.appendChild(row);
130*6777b538SAndroid Build Coastguard Worker    }
131*6777b538SAndroid Build Coastguard Worker  }
132*6777b538SAndroid Build Coastguard Worker
133*6777b538SAndroid Build Coastguard Worker  /**
134*6777b538SAndroid Build Coastguard Worker   * Fetches variations summary data and updates the view.
135*6777b538SAndroid Build Coastguard Worker   */
136*6777b538SAndroid Build Coastguard Worker  private async updateVariationsSummary_(): Promise<void> {
137*6777b538SAndroid Build Coastguard Worker    const summary: KeyValue[] =
138*6777b538SAndroid Build Coastguard Worker        await this.browserProxy_.fetchVariationsSummary();
139*6777b538SAndroid Build Coastguard Worker    const variationsSummaryTableBody =
140*6777b538SAndroid Build Coastguard Worker        this.$('#variations-summary-body') as HTMLElement;
141*6777b538SAndroid Build Coastguard Worker
142*6777b538SAndroid Build Coastguard Worker    // Don't re-render the table if the data has not changed.
143*6777b538SAndroid Build Coastguard Worker    const newDataString = summary.toString();
144*6777b538SAndroid Build Coastguard Worker    if (newDataString === this.previousVariationsSummaryData_) {
145*6777b538SAndroid Build Coastguard Worker      return;
146*6777b538SAndroid Build Coastguard Worker    }
147*6777b538SAndroid Build Coastguard Worker
148*6777b538SAndroid Build Coastguard Worker    this.previousVariationsSummaryData_ = newDataString;
149*6777b538SAndroid Build Coastguard Worker    this.updateSummaryTable_(variationsSummaryTableBody, summary);
150*6777b538SAndroid Build Coastguard Worker  }
151*6777b538SAndroid Build Coastguard Worker
152*6777b538SAndroid Build Coastguard Worker  /**
153*6777b538SAndroid Build Coastguard Worker   * Fetches UMA summary data and updates the view.
154*6777b538SAndroid Build Coastguard Worker   */
155*6777b538SAndroid Build Coastguard Worker  private async updateUmaSummary_(): Promise<void> {
156*6777b538SAndroid Build Coastguard Worker    const summary: KeyValue[] = await this.browserProxy_.fetchUmaSummary();
157*6777b538SAndroid Build Coastguard Worker    const umaSummaryTableBody = this.$('#uma-summary-body') as HTMLElement;
158*6777b538SAndroid Build Coastguard Worker
159*6777b538SAndroid Build Coastguard Worker    // Don't re-render the table if the data has not changed.
160*6777b538SAndroid Build Coastguard Worker    const newDataString = summary.toString();
161*6777b538SAndroid Build Coastguard Worker    if (newDataString === this.previousUmaSummaryData_) {
162*6777b538SAndroid Build Coastguard Worker      return;
163*6777b538SAndroid Build Coastguard Worker    }
164*6777b538SAndroid Build Coastguard Worker
165*6777b538SAndroid Build Coastguard Worker    this.previousUmaSummaryData_ = newDataString;
166*6777b538SAndroid Build Coastguard Worker    this.updateSummaryTable_(umaSummaryTableBody, summary);
167*6777b538SAndroid Build Coastguard Worker  }
168*6777b538SAndroid Build Coastguard Worker
169*6777b538SAndroid Build Coastguard Worker  /**
170*6777b538SAndroid Build Coastguard Worker   * Fills the passed table element with the given logs.
171*6777b538SAndroid Build Coastguard Worker   */
172*6777b538SAndroid Build Coastguard Worker  private updateLogsTable_(tableBody: HTMLElement, logs: Log[]): void {
173*6777b538SAndroid Build Coastguard Worker    // Clear the table first.
174*6777b538SAndroid Build Coastguard Worker    tableBody.replaceChildren();
175*6777b538SAndroid Build Coastguard Worker
176*6777b538SAndroid Build Coastguard Worker    const template = this.$('#uma-log-row-template') as HTMLTemplateElement;
177*6777b538SAndroid Build Coastguard Worker
178*6777b538SAndroid Build Coastguard Worker    // Iterate through the logs in reverse order so that the most recent log
179*6777b538SAndroid Build Coastguard Worker    // shows up first.
180*6777b538SAndroid Build Coastguard Worker    for (const log of logs.slice(0).reverse()) {
181*6777b538SAndroid Build Coastguard Worker      const row = template.content.cloneNode(true) as HTMLElement;
182*6777b538SAndroid Build Coastguard Worker      const [type, hash, timestamp, size, events] = row.querySelectorAll('td');
183*6777b538SAndroid Build Coastguard Worker
184*6777b538SAndroid Build Coastguard Worker      assert(type);
185*6777b538SAndroid Build Coastguard Worker      type.textContent = umaLogTypeToString(log.type);
186*6777b538SAndroid Build Coastguard Worker
187*6777b538SAndroid Build Coastguard Worker      assert(hash);
188*6777b538SAndroid Build Coastguard Worker      hash.textContent = log.hash;
189*6777b538SAndroid Build Coastguard Worker
190*6777b538SAndroid Build Coastguard Worker      assert(timestamp);
191*6777b538SAndroid Build Coastguard Worker      timestamp.textContent = timestampToString(log.timestamp);
192*6777b538SAndroid Build Coastguard Worker
193*6777b538SAndroid Build Coastguard Worker      assert(size);
194*6777b538SAndroid Build Coastguard Worker      size.textContent = sizeToString(log.size);
195*6777b538SAndroid Build Coastguard Worker
196*6777b538SAndroid Build Coastguard Worker      assert(events);
197*6777b538SAndroid Build Coastguard Worker      const eventsPeekDiv =
198*6777b538SAndroid Build Coastguard Worker          events.querySelector<HTMLElement>('.uma-log-events-peek');
199*6777b538SAndroid Build Coastguard Worker      assert(eventsPeekDiv);
200*6777b538SAndroid Build Coastguard Worker      eventsPeekDiv.addEventListener('click', this.toggleEventsExpand_, false);
201*6777b538SAndroid Build Coastguard Worker      const eventsPeekText =
202*6777b538SAndroid Build Coastguard Worker          events.querySelector<HTMLElement>('.uma-log-events-peek-text');
203*6777b538SAndroid Build Coastguard Worker      assert(eventsPeekText);
204*6777b538SAndroid Build Coastguard Worker      eventsPeekText.textContent = getEventsPeekString(log.events);
205*6777b538SAndroid Build Coastguard Worker      const eventsText =
206*6777b538SAndroid Build Coastguard Worker          events.querySelector<HTMLElement>('.uma-log-events-text');
207*6777b538SAndroid Build Coastguard Worker      assert(eventsText);
208*6777b538SAndroid Build Coastguard Worker      // Iterate through the events in reverse order so that the most recent
209*6777b538SAndroid Build Coastguard Worker      // event shows up first.
210*6777b538SAndroid Build Coastguard Worker      for (const event of log.events.slice(0).reverse()) {
211*6777b538SAndroid Build Coastguard Worker        const div = document.createElement('div');
212*6777b538SAndroid Build Coastguard Worker        div.textContent = logEventToString(event);
213*6777b538SAndroid Build Coastguard Worker        eventsText.appendChild(div);
214*6777b538SAndroid Build Coastguard Worker      }
215*6777b538SAndroid Build Coastguard Worker
216*6777b538SAndroid Build Coastguard Worker      tableBody.appendChild(row);
217*6777b538SAndroid Build Coastguard Worker    }
218*6777b538SAndroid Build Coastguard Worker  }
219*6777b538SAndroid Build Coastguard Worker
220*6777b538SAndroid Build Coastguard Worker  /**
221*6777b538SAndroid Build Coastguard Worker   * Fetches the latest UMA logs and renders them. This is called when the page
222*6777b538SAndroid Build Coastguard Worker   * is loaded and whenever there is a log that created or changed.
223*6777b538SAndroid Build Coastguard Worker   */
224*6777b538SAndroid Build Coastguard Worker  private async updateUmaLogsData_(): Promise<void> {
225*6777b538SAndroid Build Coastguard Worker    const logsData: string =
226*6777b538SAndroid Build Coastguard Worker        await this.browserProxy_.getUmaLogData(/*includeLogProtoData=*/ false);
227*6777b538SAndroid Build Coastguard Worker    const logs: LogData = JSON.parse(logsData);
228*6777b538SAndroid Build Coastguard Worker    // If there are no logs, append an empty log. This is purely for aesthetic
229*6777b538SAndroid Build Coastguard Worker    // reasons. Otherwise, the table may look confusing.
230*6777b538SAndroid Build Coastguard Worker    if (!logs.logs.length) {
231*6777b538SAndroid Build Coastguard Worker      logs.logs = [EMPTY_LOG];
232*6777b538SAndroid Build Coastguard Worker    }
233*6777b538SAndroid Build Coastguard Worker
234*6777b538SAndroid Build Coastguard Worker    // We don't compare the new data with the old data to prevent re-renderings
235*6777b538SAndroid Build Coastguard Worker    // because this should only be called when there is an actual change.
236*6777b538SAndroid Build Coastguard Worker
237*6777b538SAndroid Build Coastguard Worker    const umaLogsTableBody = this.$('#uma-logs-body') as HTMLElement;
238*6777b538SAndroid Build Coastguard Worker    this.updateLogsTable_(umaLogsTableBody, logs.logs);
239*6777b538SAndroid Build Coastguard Worker  }
240*6777b538SAndroid Build Coastguard Worker
241*6777b538SAndroid Build Coastguard Worker  /**
242*6777b538SAndroid Build Coastguard Worker   * Exports the accumulated UMA logs, including their proto data, as a JSON
243*6777b538SAndroid Build Coastguard Worker   * file. This will initiate a download.
244*6777b538SAndroid Build Coastguard Worker   */
245*6777b538SAndroid Build Coastguard Worker  private async exportUmaLogs_(): Promise<void> {
246*6777b538SAndroid Build Coastguard Worker    const logsData: string = await this.getUmaLogsExportContent();
247*6777b538SAndroid Build Coastguard Worker    const file = new Blob([logsData], {type: 'text/plain'});
248*6777b538SAndroid Build Coastguard Worker    const a = document.createElement('a');
249*6777b538SAndroid Build Coastguard Worker    a.href = URL.createObjectURL(file);
250*6777b538SAndroid Build Coastguard Worker    a.download = `uma_logs_${new Date().getTime()}.json`;
251*6777b538SAndroid Build Coastguard Worker    a.click();
252*6777b538SAndroid Build Coastguard Worker  }
253*6777b538SAndroid Build Coastguard Worker}
254*6777b538SAndroid Build Coastguard Worker
255*6777b538SAndroid Build Coastguard Workerdeclare global {
256*6777b538SAndroid Build Coastguard Worker  interface HTMLElementTagNameMap {
257*6777b538SAndroid Build Coastguard Worker    'metrics-internals-app': MetricsInternalsAppElement;
258*6777b538SAndroid Build Coastguard Worker  }
259*6777b538SAndroid Build Coastguard Worker}
260*6777b538SAndroid Build Coastguard Worker
261*6777b538SAndroid Build Coastguard WorkercustomElements.define(
262*6777b538SAndroid Build Coastguard Worker    MetricsInternalsAppElement.is, MetricsInternalsAppElement);
263