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