1// Copyright 2019 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import * as fs from 'fs'; 6import * as path from 'path'; 7import { Root } from 'protobufjs'; 8 9// Requirements: node 10.4.0+, npm 10 11// Setup: 12// (nvm is optional, you can also just install node manually) 13// $ nvm use 14// $ npm install 15// $ npm run build 16 17// Usage: node proto-to-json.js path_to_trace.proto input_file output_file 18 19// Converts a binary proto file to a 'Trace Event Format' compatible .json file 20// that can be used with chrome://tracing. Documentation of this format: 21// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU 22 23// Attempts to reproduce the logic of the JSONTraceWriter in V8 in terms of the 24// JSON fields it will include/exclude based on the data present in the trace 25// event. 26 27// Convert a string representing an int or uint (64 bit) to a Number or throw 28// if the value won't fit. 29function parseIntOrThrow(int: string) { 30 if (BigInt(int) > Number.MAX_SAFE_INTEGER) { 31 throw new Error("Loss of int precision"); 32 } 33 return Number(int); 34} 35 36function uint64AsHexString(val : string) : string { 37 return "0x" + BigInt(val).toString(16); 38} 39 40function parseArgValue(arg: any) : any { 41 if (arg.jsonValue) { 42 return JSON.parse(arg.jsonValue); 43 } 44 if (typeof arg.stringValue !== 'undefined') { 45 return arg.stringValue; 46 } 47 if (typeof arg.uintValue !== 'undefined') { 48 return parseIntOrThrow(arg.uintValue); 49 } 50 if (typeof arg.intValue !== 'undefined') { 51 return parseIntOrThrow(arg.intValue); 52 } 53 if (typeof arg.boolValue !== 'undefined') { 54 return arg.boolValue; 55 } 56 if (typeof arg.doubleValue !== 'undefined') { 57 // Handle [-]Infinity and NaN which protobufjs outputs as strings here. 58 return typeof arg.doubleValue === 'string' ? 59 arg.doubleValue : Number(arg.doubleValue); 60 } 61 if (typeof arg.pointerValue !== 'undefined') { 62 return uint64AsHexString(arg.pointerValue); 63 } 64} 65 66// These come from 67// https://cs.chromium.org/chromium/src/base/trace_event/common/trace_event_common.h 68const TRACE_EVENT_FLAG_HAS_ID: number = 1 << 1; 69const TRACE_EVENT_FLAG_FLOW_IN: number = 1 << 8; 70const TRACE_EVENT_FLAG_FLOW_OUT: number = 1 << 9; 71 72async function main() { 73 const root = new Root(); 74 const { resolvePath } = root; 75 const numDirectoriesToStrip = 2; 76 let initialOrigin: string|null; 77 root.resolvePath = (origin, target) => { 78 if (!origin) { 79 initialOrigin = target; 80 for (let i = 0; i <= numDirectoriesToStrip; i++) { 81 initialOrigin = path.dirname(initialOrigin); 82 } 83 return resolvePath(origin, target); 84 } 85 return path.resolve(initialOrigin!, target); 86 }; 87 const traceProto = await root.load(process.argv[2]); 88 const Trace = traceProto.lookupType("Trace"); 89 const payload = await fs.promises.readFile(process.argv[3]); 90 const msg = Trace.decode(payload).toJSON(); 91 const output = { 92 traceEvents: msg.packet 93 .filter((packet: any) => !!packet.chromeEvents) 94 .map((packet: any) => packet.chromeEvents.traceEvents) 95 .map((traceEvents: any) => traceEvents.map((e: any) => { 96 97 const bind_id = (e.flags & (TRACE_EVENT_FLAG_FLOW_IN | 98 TRACE_EVENT_FLAG_FLOW_OUT)) ? e.bindId : undefined; 99 const scope = (e.flags & TRACE_EVENT_FLAG_HAS_ID) && e.scope ? 100 e.scope : undefined; 101 102 return { 103 pid: e.processId, 104 tid: e.threadId, 105 ts: parseIntOrThrow(e.timestamp), 106 tts: parseIntOrThrow(e.threadTimestamp), 107 ph: String.fromCodePoint(e.phase), 108 cat: e.categoryGroupName, 109 name: e.name, 110 dur: parseIntOrThrow(e.duration), 111 tdur: parseIntOrThrow(e.threadDuration), 112 bind_id: bind_id, 113 flow_in: e.flags & TRACE_EVENT_FLAG_FLOW_IN ? true : undefined, 114 flow_out: e.flags & TRACE_EVENT_FLAG_FLOW_OUT ? true : undefined, 115 scope: scope, 116 id: (e.flags & TRACE_EVENT_FLAG_HAS_ID) ? 117 uint64AsHexString(e.id) : undefined, 118 args: (e.args || []).reduce((js_args: any, proto_arg: any) => { 119 js_args[proto_arg.name] = parseArgValue(proto_arg); 120 return js_args; 121 }, {}) 122 }; 123 })) 124 .flat() 125 }; 126 await fs.promises.writeFile(process.argv[4], JSON.stringify(output, null, 2)); 127} 128 129main().catch(console.error); 130