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