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