133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
|
// 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);
|