xref: /aosp_15_r20/external/perfetto/ui/src/core/metatracing.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2022  The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {featureFlags} from './feature_flags';
16import protos from '../protos';
17import protobuf from 'protobufjs/minimal';
18
19const METATRACING_BUFFER_SIZE = 100000;
20
21export enum MetatraceTrackId {
22  // 1 is reserved for the Trace Processor track.
23  // Events emitted by the JS main thread.
24  kMainThread = 2,
25  // Async track for the status (e.g. "loading tracks") shown to the user
26  // in the omnibox.
27  kOmniboxStatus = 3,
28}
29
30const AOMT_FLAG = featureFlags.register({
31  id: 'alwaysOnMetatracing',
32  name: 'Enable always-on-metatracing',
33  description: 'Enables trace events in the UI and trace processor',
34  defaultValue: false,
35});
36
37const AOMT_DETAILED_FLAG = featureFlags.register({
38  id: 'alwaysOnMetatracing_detailed',
39  name: 'Detailed always-on-metatracing',
40  description: 'Enables recording additional events for trace event',
41  defaultValue: false,
42});
43
44function getInitialCategories(): protos.MetatraceCategories | undefined {
45  if (!AOMT_FLAG.get()) return undefined;
46  if (AOMT_DETAILED_FLAG.get()) return protos.MetatraceCategories.ALL;
47  return (
48    protos.MetatraceCategories.QUERY_TIMELINE |
49    protos.MetatraceCategories.API_TIMELINE
50  );
51}
52
53let enabledCategories: protos.MetatraceCategories | undefined =
54  getInitialCategories();
55
56export function enableMetatracing(categories?: protos.MetatraceCategories) {
57  enabledCategories =
58    categories === undefined || categories === protos.MetatraceCategories.NONE
59      ? protos.MetatraceCategories.ALL
60      : categories;
61}
62
63export function disableMetatracingAndGetTrace(): Uint8Array {
64  enabledCategories = undefined;
65  return readMetatrace();
66}
67
68export function isMetatracingEnabled(): boolean {
69  return enabledCategories !== undefined;
70}
71
72export function getEnabledMetatracingCategories():
73  | protos.MetatraceCategories
74  | undefined {
75  return enabledCategories;
76}
77
78interface TraceEvent {
79  eventName: string;
80  startNs: number;
81  durNs: number;
82  track: MetatraceTrackId;
83  args?: {[key: string]: string};
84}
85
86const traceEvents: TraceEvent[] = [];
87
88function readMetatrace(): Uint8Array {
89  const eventToPacket = (e: TraceEvent): Uint8Array => {
90    const metatraceEvent = protos.PerfettoMetatrace.create({
91      eventName: e.eventName,
92      threadId: e.track,
93      eventDurationNs: e.durNs,
94    });
95    for (const [key, value] of Object.entries(e.args ?? {})) {
96      metatraceEvent.args.push(
97        protos.PerfettoMetatrace.Arg.create({
98          key,
99          value,
100        }),
101      );
102    }
103    const PROTO_VARINT_TYPE = 0;
104    const PROTO_LEN_DELIMITED_WIRE_TYPE = 2;
105    const TRACE_PACKET_PROTO_TAG = (1 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
106    const TRACE_PACKET_TIMESTAMP_TAG = (8 << 3) | PROTO_VARINT_TYPE;
107    const TRACE_PACKET_CLOCK_ID_TAG = (58 << 3) | PROTO_VARINT_TYPE;
108    const TRACE_PACKET_METATRACE_TAG =
109      (49 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
110
111    const wri = protobuf.Writer.create();
112    wri.uint32(TRACE_PACKET_PROTO_TAG);
113    wri.fork(); // Start of Trace Packet.
114    wri.uint32(TRACE_PACKET_TIMESTAMP_TAG).int64(e.startNs);
115    wri.uint32(TRACE_PACKET_CLOCK_ID_TAG).int32(1);
116    wri
117      .uint32(TRACE_PACKET_METATRACE_TAG)
118      .bytes(protos.PerfettoMetatrace.encode(metatraceEvent).finish());
119    wri.ldelim();
120    return wri.finish();
121  };
122  const packets: Uint8Array[] = [];
123  for (const event of traceEvents) {
124    packets.push(eventToPacket(event));
125  }
126  const totalLength = packets.reduce((acc, arr) => acc + arr.length, 0);
127  const trace = new Uint8Array(totalLength);
128  let offset = 0;
129  for (const packet of packets) {
130    trace.set(packet, offset);
131    offset += packet.length;
132  }
133  return trace;
134}
135
136interface TraceEventParams {
137  track?: MetatraceTrackId;
138  args?: {[key: string]: string};
139}
140
141export type TraceEventScope = {
142  startNs: number;
143  eventName: string;
144  params?: TraceEventParams;
145};
146
147const correctedTimeOrigin = new Date().getTime() - performance.now();
148
149function msToNs(ms: number) {
150  return Math.round(ms * 1e6);
151}
152
153function now(): number {
154  return msToNs(correctedTimeOrigin + performance.now());
155}
156
157export function traceEvent<T>(
158  name: string,
159  event: () => T,
160  params?: TraceEventParams,
161): T {
162  const scope = traceEventBegin(name, params);
163  try {
164    const result = event();
165    return result;
166  } finally {
167    traceEventEnd(scope);
168  }
169}
170
171export function traceEventBegin(
172  eventName: string,
173  params?: TraceEventParams,
174): TraceEventScope {
175  return {
176    eventName,
177    startNs: now(),
178    params: params,
179  };
180}
181
182export function traceEventEnd(traceEvent: TraceEventScope) {
183  if (!isMetatracingEnabled()) return;
184
185  traceEvents.push({
186    eventName: traceEvent.eventName,
187    startNs: traceEvent.startNs,
188    durNs: now() - traceEvent.startNs,
189    track: traceEvent.params?.track ?? MetatraceTrackId.kMainThread,
190    args: traceEvent.params?.args,
191  });
192  while (traceEvents.length > METATRACING_BUFFER_SIZE) {
193    traceEvents.shift();
194  }
195}
196
197// Flatten arbitrary values so they can be used as args in traceEvent() et al.
198export function flattenArgs(
199  input: unknown,
200  parentKey = '',
201): {[key: string]: string} {
202  if (typeof input !== 'object' || input === null) {
203    return {[parentKey]: String(input)};
204  }
205
206  if (Array.isArray(input)) {
207    const result: Record<string, string> = {};
208
209    (input as Array<unknown>).forEach((item, index) => {
210      const arrayKey = `${parentKey}[${index}]`;
211      Object.assign(result, flattenArgs(item, arrayKey));
212    });
213
214    return result;
215  }
216
217  const result: Record<string, string> = {};
218
219  Object.entries(input as Record<string, unknown>).forEach(([key, value]) => {
220    const newKey = parentKey ? `${parentKey}.${key}` : key;
221    Object.assign(result, flattenArgs(value, newKey));
222  });
223
224  return result;
225}
226