blob: 2427410fd46a033310493802ef6590912b2d3b18 [file] [log] [blame]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as fs from 'fs';
import * as path from 'path';
import { Root } from 'protobufjs';
// Requirements: node 10.4.0+, npm
// Setup:
// (nvm is optional, you can also just install node manually)
// $ nvm use
// $ npm install
// $ npm run build
// Usage: node proto-to-json.js path_to_trace.proto input_file output_file
// Converts a binary proto file to a 'Trace Event Format' compatible .json file
// that can be used with chrome://tracing. Documentation of this format:
// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU
// Attempts to reproduce the logic of the JSONTraceWriter in V8 in terms of the
// JSON fields it will include/exclude based on the data present in the trace
// event.
// TODO(petermarshall): Replace with Array#flat once it lands in Node.js.
const flatten = <T>(a: T[], b: T[]) => { a.push(...b); return a; }
// Convert a string representing an int or uint (64 bit) to a Number or throw
// if the value won't fit.
function parseIntOrThrow(int: string) {
if (BigInt(int) > Number.MAX_SAFE_INTEGER) {
throw new Error("Loss of int precision");
}
return Number(int);
}
function uint64AsHexString(val : string) : string {
return "0x" + BigInt(val).toString(16);
}
function parseArgValue(arg: any) : any {
if (arg.jsonValue) {
return JSON.parse(arg.jsonValue);
}
if (typeof arg.stringValue !== 'undefined') {
return arg.stringValue;
}
if (typeof arg.uintValue !== 'undefined') {
return parseIntOrThrow(arg.uintValue);
}
if (typeof arg.intValue !== 'undefined') {
return parseIntOrThrow(arg.intValue);
}
if (typeof arg.boolValue !== 'undefined') {
return arg.boolValue;
}
if (typeof arg.doubleValue !== 'undefined') {
// Handle [-]Infinity and NaN which protobufjs outputs as strings here.
return typeof arg.doubleValue === 'string' ?
arg.doubleValue : Number(arg.doubleValue);
}
if (typeof arg.pointerValue !== 'undefined') {
return uint64AsHexString(arg.pointerValue);
}
}
// These come from
// https://cs.chromium.org/chromium/src/base/trace_event/common/trace_event_common.h
const TRACE_EVENT_FLAG_HAS_ID: number = 1 << 1;
const TRACE_EVENT_FLAG_FLOW_IN: number = 1 << 8;
const TRACE_EVENT_FLAG_FLOW_OUT: number = 1 << 9;
async function main() {
const root = new Root();
const { resolvePath } = root;
const numDirectoriesToStrip = 2;
let initialOrigin: string|null;
root.resolvePath = (origin, target) => {
if (!origin) {
initialOrigin = target;
for (let i = 0; i <= numDirectoriesToStrip; i++) {
initialOrigin = path.dirname(initialOrigin);
}
return resolvePath(origin, target);
}
return path.resolve(initialOrigin!, target);
};
const traceProto = await root.load(process.argv[2]);
const Trace = traceProto.lookupType("Trace");
const payload = await fs.promises.readFile(process.argv[3]);
const msg = Trace.decode(payload).toJSON();
const output = {
traceEvents: msg.packet
.filter((packet: any) => !!packet.chromeEvents)
.map((packet: any) => packet.chromeEvents.traceEvents)
.map((traceEvents: any) => traceEvents.map((e: any) => {
const bind_id = (e.flags & (TRACE_EVENT_FLAG_FLOW_IN |
TRACE_EVENT_FLAG_FLOW_OUT)) ? e.bindId : undefined;
const scope = (e.flags & TRACE_EVENT_FLAG_HAS_ID) && e.scope ?
e.scope : undefined;
return {
pid: e.processId,
tid: e.threadId,
ts: parseIntOrThrow(e.timestamp),
tts: parseIntOrThrow(e.threadTimestamp),
ph: String.fromCodePoint(e.phase),
cat: e.categoryGroupName,
name: e.name,
dur: parseIntOrThrow(e.duration),
tdur: parseIntOrThrow(e.threadDuration),
bind_id: bind_id,
flow_in: e.flags & TRACE_EVENT_FLAG_FLOW_IN ? true : undefined,
flow_out: e.flags & TRACE_EVENT_FLAG_FLOW_OUT ? true : undefined,
scope: scope,
id: (e.flags & TRACE_EVENT_FLAG_HAS_ID) ?
uint64AsHexString(e.id) : undefined,
args: (e.args || []).reduce((js_args: any, proto_arg: any) => {
js_args[proto_arg.name] = parseArgValue(proto_arg);
return js_args;
}, {})
};
}))
.reduce(flatten, [])
};
await fs.promises.writeFile(process.argv[4], JSON.stringify(output, null, 2));
}
main().catch(console.error);