xref: /aosp_15_r20/external/cronet/components/metrics/debug/structured/structured_utils.ts (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 * Updates the Summary table with new information.
47 *
48 * @param summaryBody Body of the summary table.
49 * @param summary Summary object to populate the table.
50 * @param template Key-Value pair HTML template.
51 */
52export function updateStructuredMetricsSummary(
53    summaryBody: HTMLElement, summary: StructuredMetricsSummary,
54    template: HTMLTemplateElement): void {
55  // Clear the table first.
56  summaryBody.replaceChildren();
57
58  const enabled =
59      buildKeyValueRow('Enabled', summary.enabled.toString(), template);
60  summaryBody.append(enabled);
61
62  // If we do not get a value, do not display it. This value doesn't make sense
63  // on some platforms.
64  if (summary.crosDeviceId) {
65    const crosDeviceId =
66        buildKeyValueRow('CrOS Device Id', summary.crosDeviceId, template);
67    summaryBody.append(crosDeviceId);
68  }
69}
70
71/**
72 * Updates the events table with the events recorded by the client.
73 *
74 * @param eventBody Body of the event table.
75 * @param events List of events to populate the table.
76 * @param template HTML template for the event table row.
77 * @param kvTemplate Key-Value pair HTML template.
78 */
79export function updateStructuredMetricsEvents(
80    eventBody: HTMLElement, events: StructuredMetricEvent[],
81    eventTemplate: HTMLTemplateElement, detailsTemplate: HTMLTemplateElement,
82    kvTemplate: HTMLTemplateElement): void {
83  // If chrome://metrics-internal is opened on Windows, Mac, or Linux and
84  // Structured Metrics is disabled, we should do nothing.
85  if (events === null) {
86    return;
87  }
88
89  eventBody.replaceChildren();
90
91  for (const event of events) {
92    const row = eventTemplate.content.cloneNode(true) as HTMLElement;
93    const [project, evn, type, uptime] = row.querySelectorAll('td');
94
95    assert(project);
96    project.textContent = event.project;
97
98    assert(evn);
99    evn.textContent = event.event;
100
101    assert(type);
102    type.textContent = event.type;
103
104    assert(uptime);
105    uptime.textContent = event.sequenceMetadata?.systemUptimeMs ?? '-';
106
107    const detailsRow = detailsTemplate.content.cloneNode(true) as HTMLElement;
108    const metricsRow = detailsRow.querySelector<HTMLElement>('#metrics-row');
109    assert(metricsRow);
110
111    const [details, metrics] = detailsRow.querySelectorAll('tbody');
112    assert(details);
113    assert(metrics);
114
115    updateEventDetailsTable(details, event, kvTemplate);
116    updateEventMetricsTable(metrics, event, kvTemplate);
117
118    const eventRow = row.querySelector('#event-row');
119    assert(eventRow);
120    eventRow.addEventListener('click', () => {
121      if (metricsRow.style.display === 'none') {
122        metricsRow.style.display = 'table-row';
123      } else {
124        metricsRow.style.display = 'none';
125      }
126    }, false);
127
128    eventBody.append(row);
129    eventBody.append(detailsRow);
130  }
131}
132
133function updateEventDetailsTable(
134    detailTable: HTMLElement, event: StructuredMetricEvent,
135    template: HTMLTemplateElement): void {
136  detailTable.replaceChildren();
137
138  const resetCounter = event.sequenceMetadata?.resetCounter ?? '-';
139  const systemUptime = event.sequenceMetadata?.systemUptimeMs ?? '-';
140  const eventId = event.sequenceMetadata?.id ?? '-';
141
142  const resetCounterRow = buildKeyValueRow('Reset Id', resetCounter, template);
143  const systemUptimeRow =
144      buildKeyValueRow('System Uptime', systemUptime, template);
145  const eventIdRow = buildKeyValueRow('Event Id', eventId, template);
146
147  detailTable.append(resetCounterRow);
148  detailTable.append(systemUptimeRow);
149  detailTable.append(eventIdRow);
150}
151
152function updateEventMetricsTable(
153    metricsTable: HTMLElement, event: StructuredMetricEvent,
154    template: HTMLTemplateElement): void {
155  metricsTable.replaceChildren();
156  for (const metric of event.metrics) {
157    const metricRow =
158        buildKeyValueRow(metric.key, metric.value.toString(), template);
159    metricsTable.append(metricRow);
160  }
161}
162
163function buildKeyValueRow(
164    key: string, value: string, template: HTMLTemplateElement): HTMLElement {
165  const kvRow = template.content.cloneNode(true) as HTMLElement;
166
167  const [k, v] = kvRow.querySelectorAll('td');
168  assert(k);
169  k.textContent = key;
170  assert(v);
171  v.textContent = value;
172
173  return kvRow;
174}
175