• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2023 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {assert} from 'chrome://resources/js/assert.js';
6
7/**
8 * Key-Value pair for the summary and metrics table.
9 */
10interface KeyValue {
11  key: string;
12  value: number|string|boolean|[number];
13}
14
15/**
16 * Sequence information for an event.
17 */
18interface SequenceMetadata {
19  id: string;
20  systemUptimeMs: string;
21  resetCounter: string;
22}
23
24/**
25 * An event and its data. This includes metadata about the event and sequence
26 * information if applicable.
27 */
28export interface StructuredMetricEvent {
29  project: string;
30  event: string;
31  type: string;
32  sequenceMetadata?: SequenceMetadata;
33  metrics: KeyValue[];
34}
35
36/**
37 * Summary about Structured Metrics service.
38 */
39export interface StructuredMetricsSummary {
40  enabled: boolean;
41  flags: KeyValue[];
42  crosDeviceId: string;
43}
44
45/**
46 * Contains the search parameters by category.
47 *
48 * Valid categories are: project, event, metric.
49 */
50export type SearchParams = Map<string, string>;
51
52/**
53 * Updates the Summary table with new information.
54 *
55 * @param summaryBody Body of the summary table.
56 * @param summary Summary object to populate the table.
57 * @param template Key-Value pair HTML template.
58 */
59export function updateStructuredMetricsSummary(
60    summaryBody: HTMLElement, summary: StructuredMetricsSummary,
61    template: HTMLTemplateElement): void {
62  // Clear the table first.
63  summaryBody.replaceChildren();
64
65  const enabled =
66      buildKeyValueRow('Enabled', summary.enabled.toString(), template);
67  summaryBody.append(enabled);
68
69  // If we do not get a value, do not display it. This value doesn't make sense
70  // on some platforms.
71  if (summary.crosDeviceId) {
72    const crosDeviceId =
73        buildKeyValueRow('CrOS Device Id', summary.crosDeviceId, template);
74    summaryBody.append(crosDeviceId);
75  }
76}
77
78/**
79 * Updates the events table with the events recorded by the client.
80 *
81 * @param eventBody Body of the event table.
82 * @param events List of events to populate the table.
83 * @param searchParams Optional search parameters.
84 * @param template HTML template for the event table row.
85 * @param kvTemplate Key-Value pair HTML template.
86 */
87export function updateStructuredMetricsEvents(
88    eventBody: HTMLElement, events: StructuredMetricEvent[],
89    searchParams: SearchParams|null, eventTemplate: HTMLTemplateElement,
90    detailsTemplate: HTMLTemplateElement,
91    kvTemplate: HTMLTemplateElement): void {
92  // If chrome://metrics-internal is opened on Windows, Mac, or Linux and
93  // Structured Metrics is disabled, we should do nothing.
94  if (events === null) {
95    return;
96  }
97
98  eventBody.replaceChildren();
99
100  for (const event of events) {
101    // If there is a |searchParams| and the event doesn't satisfy the
102    // |searchParams| then it can be skipped.
103    if (searchParams !== null && !checkSearch(event, searchParams)) {
104      continue;
105    }
106
107    const row = eventTemplate.content.cloneNode(true) as HTMLElement;
108    const [project, evn, type, uptime] = row.querySelectorAll('td');
109
110    assert(project);
111    project.textContent = event.project;
112
113    assert(evn);
114    evn.textContent = event.event;
115
116    assert(type);
117    type.textContent = event.type;
118
119    assert(uptime);
120    uptime.textContent = event.sequenceMetadata?.systemUptimeMs ?? '-';
121
122    const detailsRow = detailsTemplate.content.cloneNode(true) as HTMLElement;
123    const metricsRow = detailsRow.querySelector<HTMLElement>('#metrics-row');
124    assert(metricsRow);
125
126    const [details, metrics] = detailsRow.querySelectorAll('tbody');
127    assert(details);
128    assert(metrics);
129
130    updateEventDetailsTable(details, event, kvTemplate);
131    updateEventMetricsTable(metrics, event, kvTemplate);
132
133    const eventRow = row.querySelector('#event-row');
134    assert(eventRow);
135    eventRow.addEventListener('click', () => {
136      if (metricsRow.style.display === 'none') {
137        metricsRow.style.display = 'table-row';
138      } else {
139        metricsRow.style.display = 'none';
140      }
141    }, false);
142
143    eventBody.append(row);
144    eventBody.append(detailsRow);
145  }
146}
147
148function updateEventDetailsTable(
149    detailTable: HTMLElement, event: StructuredMetricEvent,
150    template: HTMLTemplateElement): void {
151  detailTable.replaceChildren();
152
153  const resetCounter = event.sequenceMetadata?.resetCounter ?? '-';
154  const systemUptime = event.sequenceMetadata?.systemUptimeMs ?? '-';
155  const eventId = event.sequenceMetadata?.id ?? '-';
156
157  const resetCounterRow = buildKeyValueRow('Reset Id', resetCounter, template);
158  const systemUptimeRow =
159      buildKeyValueRow('System Uptime', systemUptime, template);
160  const eventIdRow = buildKeyValueRow('Event Id', eventId, template);
161
162  detailTable.append(resetCounterRow);
163  detailTable.append(systemUptimeRow);
164  detailTable.append(eventIdRow);
165}
166
167function checkSearch(
168    event: StructuredMetricEvent, searchParams: SearchParams): boolean {
169  const projectSearch = searchParams.get('project');
170  const eventSearch = searchParams.get('event');
171  const metricSearch = searchParams.get('metric');
172
173  if (projectSearch &&
174      event.project.toLowerCase().indexOf(projectSearch.toLowerCase()) === -1) {
175    return false;
176  }
177
178  if (eventSearch &&
179      event.event.toLowerCase().indexOf(eventSearch.toLowerCase()) === -1) {
180    return false;
181  }
182
183  if (metricSearch &&
184      event.metrics.find(
185          (metric: KeyValue) =>
186              metric.key.toLowerCase().indexOf(metricSearch.toLowerCase()) !==
187              -1) === undefined) {
188    return false;
189  }
190  return true;
191}
192
193function updateEventMetricsTable(
194    metricsTable: HTMLElement, event: StructuredMetricEvent,
195    template: HTMLTemplateElement): void {
196  metricsTable.replaceChildren();
197  for (const metric of event.metrics) {
198    const metricRow =
199        buildKeyValueRow(metric.key, metric.value.toString(), template);
200    metricsTable.append(metricRow);
201  }
202}
203
204function buildKeyValueRow(
205    key: string, value: string, template: HTMLTemplateElement): HTMLElement {
206  const kvRow = template.content.cloneNode(true) as HTMLElement;
207
208  const [k, v] = kvRow.querySelectorAll('td');
209  assert(k);
210  k.textContent = key;
211  assert(v);
212  v.textContent = value;
213
214  return kvRow;
215}
216