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