• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 '../core/feature_flags';
16import {MetatraceCategories, PerfettoMetatrace} 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(): MetatraceCategories | undefined {
45  if (!AOMT_FLAG.get()) return undefined;
46  if (AOMT_DETAILED_FLAG.get()) return MetatraceCategories.ALL;
47  return MetatraceCategories.QUERY_TIMELINE | MetatraceCategories.API_TIMELINE;
48}
49
50let enabledCategories: MetatraceCategories | undefined = getInitialCategories();
51
52export function enableMetatracing(categories?: MetatraceCategories) {
53  enabledCategories = categories || MetatraceCategories.ALL;
54}
55
56export function disableMetatracingAndGetTrace(): Uint8Array {
57  enabledCategories = undefined;
58  return readMetatrace();
59}
60
61export function isMetatracingEnabled(): boolean {
62  return enabledCategories !== undefined;
63}
64
65export function getEnabledMetatracingCategories():
66  | MetatraceCategories
67  | undefined {
68  return enabledCategories;
69}
70
71interface TraceEvent {
72  eventName: string;
73  startNs: number;
74  durNs: number;
75  track: MetatraceTrackId;
76  args?: {[key: string]: string};
77}
78
79const traceEvents: TraceEvent[] = [];
80
81function readMetatrace(): Uint8Array {
82  const eventToPacket = (e: TraceEvent): Uint8Array => {
83    const metatraceEvent = PerfettoMetatrace.create({
84      eventName: e.eventName,
85      threadId: e.track,
86      eventDurationNs: e.durNs,
87    });
88    for (const [key, value] of Object.entries(e.args ?? {})) {
89      metatraceEvent.args.push(
90        PerfettoMetatrace.Arg.create({
91          key,
92          value,
93        }),
94      );
95    }
96    const PROTO_VARINT_TYPE = 0;
97    const PROTO_LEN_DELIMITED_WIRE_TYPE = 2;
98    const TRACE_PACKET_PROTO_TAG = (1 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
99    const TRACE_PACKET_TIMESTAMP_TAG = (8 << 3) | PROTO_VARINT_TYPE;
100    const TRACE_PACKET_CLOCK_ID_TAG = (58 << 3) | PROTO_VARINT_TYPE;
101    const TRACE_PACKET_METATRACE_TAG =
102      (49 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
103
104    const wri = protobuf.Writer.create();
105    wri.uint32(TRACE_PACKET_PROTO_TAG);
106    wri.fork(); // Start of Trace Packet.
107    wri.uint32(TRACE_PACKET_TIMESTAMP_TAG).int64(e.startNs);
108    wri.uint32(TRACE_PACKET_CLOCK_ID_TAG).int32(1);
109    wri
110      .uint32(TRACE_PACKET_METATRACE_TAG)
111      .bytes(PerfettoMetatrace.encode(metatraceEvent).finish());
112    wri.ldelim();
113    return wri.finish();
114  };
115  const packets: Uint8Array[] = [];
116  for (const event of traceEvents) {
117    packets.push(eventToPacket(event));
118  }
119  const totalLength = packets.reduce((acc, arr) => acc + arr.length, 0);
120  const trace = new Uint8Array(totalLength);
121  let offset = 0;
122  for (const packet of packets) {
123    trace.set(packet, offset);
124    offset += packet.length;
125  }
126  return trace;
127}
128
129interface TraceEventParams {
130  track?: MetatraceTrackId;
131  args?: {[key: string]: string};
132}
133
134export type TraceEventScope = {
135  startNs: number;
136  eventName: string;
137  params?: TraceEventParams;
138};
139
140const correctedTimeOrigin = new Date().getTime() - performance.now();
141
142function msToNs(ms: number) {
143  return Math.round(ms * 1e6);
144}
145
146function now(): number {
147  return msToNs(correctedTimeOrigin + performance.now());
148}
149
150export function traceEvent<T>(
151  name: string,
152  event: () => T,
153  params?: TraceEventParams,
154): T {
155  const scope = traceEventBegin(name, params);
156  try {
157    const result = event();
158    return result;
159  } finally {
160    traceEventEnd(scope);
161  }
162}
163
164export function traceEventBegin(
165  eventName: string,
166  params?: TraceEventParams,
167): TraceEventScope {
168  return {
169    eventName,
170    startNs: now(),
171    params: params,
172  };
173}
174
175export function traceEventEnd(traceEvent: TraceEventScope) {
176  if (!isMetatracingEnabled()) return;
177
178  traceEvents.push({
179    eventName: traceEvent.eventName,
180    startNs: traceEvent.startNs,
181    durNs: now() - traceEvent.startNs,
182    track: traceEvent.params?.track ?? MetatraceTrackId.kMainThread,
183    args: traceEvent.params?.args,
184  });
185  while (traceEvents.length > METATRACING_BUFFER_SIZE) {
186    traceEvents.shift();
187  }
188}
189
190// Flatten arbitrary values so they can be used as args in traceEvent() et al.
191export function flattenArgs(
192  input: unknown,
193  parentKey = '',
194): {[key: string]: string} {
195  if (typeof input !== 'object' || input === null) {
196    return {[parentKey]: String(input)};
197  }
198
199  if (Array.isArray(input)) {
200    const result: Record<string, string> = {};
201
202    (input as Array<unknown>).forEach((item, index) => {
203      const arrayKey = `${parentKey}[${index}]`;
204      Object.assign(result, flattenArgs(item, arrayKey));
205    });
206
207    return result;
208  }
209
210  const result: Record<string, string> = {};
211
212  Object.entries(input as Record<string, unknown>).forEach(([key, value]) => {
213    const newKey = parentKey ? `${parentKey}.${key}` : key;
214    Object.assign(result, flattenArgs(value, newKey));
215  });
216
217  return result;
218}
219