[node] add node inspector protocol extensions

This commit is contained in:
Kelvin Jin
2018-12-06 11:57:50 -08:00
parent fca9c530d1
commit d4a69ca49c
5 changed files with 341 additions and 26 deletions

View File

@@ -1912,6 +1912,90 @@ declare module "inspector" {
}
}
namespace NodeTracing {
interface TraceConfig {
/**
* Controls how the trace buffer stores data.
*/
recordMode?: string;
/**
* Included category filters.
*/
includedCategories: string[];
}
interface StartParameterType {
traceConfig: TraceConfig;
}
interface GetCategoriesReturnType {
/**
* A list of supported tracing categories.
*/
categories: string[];
}
interface DataCollectedEventDataType {
value: Array<{}>;
}
}
namespace NodeWorker {
type WorkerID = string;
/**
* Unique identifier of attached debugging session.
*/
type SessionID = string;
interface WorkerInfo {
workerId: WorkerID;
type: string;
title: string;
url: string;
}
interface SendMessageToWorkerParameterType {
message: string;
/**
* Identifier of the session.
*/
sessionId: SessionID;
}
interface EnableParameterType {
/**
* Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger`
* message to run them.
*/
waitForDebuggerOnStart: boolean;
}
interface AttachedToWorkerEventDataType {
/**
* Identifier assigned to the session used to send/receive messages.
*/
sessionId: SessionID;
workerInfo: WorkerInfo;
waitingForDebugger: boolean;
}
interface DetachedFromWorkerEventDataType {
/**
* Detached session identifier.
*/
sessionId: SessionID;
}
interface ReceivedMessageFromWorkerEventDataType {
/**
* Identifier of a session which sends a message.
*/
sessionId: SessionID;
message: string;
}
}
/**
* The inspector.Session is used for dispatching messages to the V8 inspector back-end and receiving message responses and notifications.
*/
@@ -1958,6 +2042,7 @@ declare module "inspector" {
* `messageAdded` notification.
*/
post(method: "Console.enable", callback?: (err: Error | null) => void): void;
/**
* Continues execution until specific location is reached.
*/
@@ -2155,6 +2240,7 @@ declare module "inspector" {
* Steps over the statement.
*/
post(method: "Debugger.stepOver", callback?: (err: Error | null) => void): void;
/**
* Enables console to refer to the node with given id via $x (see Command Line API for more details
* $x functions).
@@ -2193,6 +2279,7 @@ declare module "inspector" {
post(method: "HeapProfiler.takeHeapSnapshot", params?: HeapProfiler.TakeHeapSnapshotParameterType, callback?: (err: Error | null) => void): void;
post(method: "HeapProfiler.takeHeapSnapshot", callback?: (err: Error | null) => void): void;
post(method: "Profiler.disable", callback?: (err: Error | null) => void): void;
post(method: "Profiler.enable", callback?: (err: Error | null) => void): void;
@@ -2250,6 +2337,7 @@ declare module "inspector" {
* @experimental
*/
post(method: "Profiler.takeTypeProfile", callback?: (err: Error | null, params: Profiler.TakeTypeProfileReturnType) => void): void;
/**
* Add handler to promise with given promise object id.
*/
@@ -2360,11 +2448,47 @@ declare module "inspector" {
* @experimental
*/
post(method: "Runtime.terminateExecution", callback?: (err: Error | null) => void): void;
/**
* Returns supported domains.
*/
post(method: "Schema.getDomains", callback?: (err: Error | null, params: Schema.GetDomainsReturnType) => void): void;
/**
* Gets supported tracing categories.
*/
post(method: "NodeTracing.getCategories", callback?: (err: Error | null, params: NodeTracing.GetCategoriesReturnType) => void): void;
/**
* Start trace events collection.
*/
post(method: "NodeTracing.start", params?: NodeTracing.StartParameterType, callback?: (err: Error | null) => void): void;
post(method: "NodeTracing.start", callback?: (err: Error | null) => void): void;
/**
* Stop trace events collection. Remaining collected events will be sent as a sequence of
* dataCollected events followed by tracingComplete event.
*/
post(method: "NodeTracing.stop", callback?: (err: Error | null) => void): void;
/**
* Sends protocol message over session with given id.
*/
post(method: "NodeWorker.sendMessageToWorker", params?: NodeWorker.SendMessageToWorkerParameterType, callback?: (err: Error | null) => void): void;
post(method: "NodeWorker.sendMessageToWorker", callback?: (err: Error | null) => void): void;
/**
* Instructs the inspector to attach to running workers. Will also attach to new workers
* as they start
*/
post(method: "NodeWorker.enable", params?: NodeWorker.EnableParameterType, callback?: (err: Error | null) => void): void;
post(method: "NodeWorker.enable", callback?: (err: Error | null) => void): void;
/**
* Detaches from all running workers and disables attaching to new workers as they are started.
*/
post(method: "NodeWorker.disable", callback?: (err: Error | null) => void): void;
// Events
addListener(event: string, listener: (...args: any[]) => void): this;
@@ -2464,6 +2588,33 @@ declare module "inspector" {
*/
addListener(event: "Runtime.inspectRequested", listener: (message: InspectorNotification<Runtime.InspectRequestedEventDataType>) => void): this;
/**
* Contains an bucket of collected trace events.
*/
addListener(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification<NodeTracing.DataCollectedEventDataType>) => void): this;
/**
* Signals that tracing is stopped and there is no trace buffers pending flush, all data were
* delivered via dataCollected events.
*/
addListener(event: "NodeTracing.tracingComplete", listener: () => void): this;
/**
* Issued when attached to a worker.
*/
addListener(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification<NodeWorker.AttachedToWorkerEventDataType>) => void): this;
/**
* Issued when detached from the worker.
*/
addListener(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification<NodeWorker.DetachedFromWorkerEventDataType>) => void): this;
/**
* Notifies about a new protocol message received from the session
* (session ID is provided in attachedToWorker notification).
*/
addListener(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification<NodeWorker.ReceivedMessageFromWorkerEventDataType>) => void): this;
emit(event: string | symbol, ...args: any[]): boolean;
emit(event: "inspectorNotification", message: InspectorNotification<{}>): boolean;
emit(event: "Console.messageAdded", message: InspectorNotification<Console.MessageAddedEventDataType>): boolean;
@@ -2486,6 +2637,11 @@ declare module "inspector" {
emit(event: "Runtime.executionContextDestroyed", message: InspectorNotification<Runtime.ExecutionContextDestroyedEventDataType>): boolean;
emit(event: "Runtime.executionContextsCleared"): boolean;
emit(event: "Runtime.inspectRequested", message: InspectorNotification<Runtime.InspectRequestedEventDataType>): boolean;
emit(event: "NodeTracing.dataCollected", message: InspectorNotification<NodeTracing.DataCollectedEventDataType>): boolean;
emit(event: "NodeTracing.tracingComplete"): boolean;
emit(event: "NodeWorker.attachedToWorker", message: InspectorNotification<NodeWorker.AttachedToWorkerEventDataType>): boolean;
emit(event: "NodeWorker.detachedFromWorker", message: InspectorNotification<NodeWorker.DetachedFromWorkerEventDataType>): boolean;
emit(event: "NodeWorker.receivedMessageFromWorker", message: InspectorNotification<NodeWorker.ReceivedMessageFromWorkerEventDataType>): boolean;
on(event: string, listener: (...args: any[]) => void): this;
@@ -2584,6 +2740,33 @@ declare module "inspector" {
*/
on(event: "Runtime.inspectRequested", listener: (message: InspectorNotification<Runtime.InspectRequestedEventDataType>) => void): this;
/**
* Contains an bucket of collected trace events.
*/
on(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification<NodeTracing.DataCollectedEventDataType>) => void): this;
/**
* Signals that tracing is stopped and there is no trace buffers pending flush, all data were
* delivered via dataCollected events.
*/
on(event: "NodeTracing.tracingComplete", listener: () => void): this;
/**
* Issued when attached to a worker.
*/
on(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification<NodeWorker.AttachedToWorkerEventDataType>) => void): this;
/**
* Issued when detached from the worker.
*/
on(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification<NodeWorker.DetachedFromWorkerEventDataType>) => void): this;
/**
* Notifies about a new protocol message received from the session
* (session ID is provided in attachedToWorker notification).
*/
on(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification<NodeWorker.ReceivedMessageFromWorkerEventDataType>) => void): this;
once(event: string, listener: (...args: any[]) => void): this;
/**
@@ -2681,6 +2864,33 @@ declare module "inspector" {
*/
once(event: "Runtime.inspectRequested", listener: (message: InspectorNotification<Runtime.InspectRequestedEventDataType>) => void): this;
/**
* Contains an bucket of collected trace events.
*/
once(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification<NodeTracing.DataCollectedEventDataType>) => void): this;
/**
* Signals that tracing is stopped and there is no trace buffers pending flush, all data were
* delivered via dataCollected events.
*/
once(event: "NodeTracing.tracingComplete", listener: () => void): this;
/**
* Issued when attached to a worker.
*/
once(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification<NodeWorker.AttachedToWorkerEventDataType>) => void): this;
/**
* Issued when detached from the worker.
*/
once(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification<NodeWorker.DetachedFromWorkerEventDataType>) => void): this;
/**
* Notifies about a new protocol message received from the session
* (session ID is provided in attachedToWorker notification).
*/
once(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification<NodeWorker.ReceivedMessageFromWorkerEventDataType>) => void): this;
prependListener(event: string, listener: (...args: any[]) => void): this;
/**
@@ -2778,6 +2988,33 @@ declare module "inspector" {
*/
prependListener(event: "Runtime.inspectRequested", listener: (message: InspectorNotification<Runtime.InspectRequestedEventDataType>) => void): this;
/**
* Contains an bucket of collected trace events.
*/
prependListener(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification<NodeTracing.DataCollectedEventDataType>) => void): this;
/**
* Signals that tracing is stopped and there is no trace buffers pending flush, all data were
* delivered via dataCollected events.
*/
prependListener(event: "NodeTracing.tracingComplete", listener: () => void): this;
/**
* Issued when attached to a worker.
*/
prependListener(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification<NodeWorker.AttachedToWorkerEventDataType>) => void): this;
/**
* Issued when detached from the worker.
*/
prependListener(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification<NodeWorker.DetachedFromWorkerEventDataType>) => void): this;
/**
* Notifies about a new protocol message received from the session
* (session ID is provided in attachedToWorker notification).
*/
prependListener(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification<NodeWorker.ReceivedMessageFromWorkerEventDataType>) => void): this;
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
/**
@@ -2874,6 +3111,33 @@ declare module "inspector" {
* call).
*/
prependOnceListener(event: "Runtime.inspectRequested", listener: (message: InspectorNotification<Runtime.InspectRequestedEventDataType>) => void): this;
/**
* Contains an bucket of collected trace events.
*/
prependOnceListener(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification<NodeTracing.DataCollectedEventDataType>) => void): this;
/**
* Signals that tracing is stopped and there is no trace buffers pending flush, all data were
* delivered via dataCollected events.
*/
prependOnceListener(event: "NodeTracing.tracingComplete", listener: () => void): this;
/**
* Issued when attached to a worker.
*/
prependOnceListener(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification<NodeWorker.AttachedToWorkerEventDataType>) => void): this;
/**
* Issued when detached from the worker.
*/
prependOnceListener(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification<NodeWorker.DetachedFromWorkerEventDataType>) => void): this;
/**
* Notifies about a new protocol message received from the session
* (session ID is provided in attachedToWorker notification).
*/
prependOnceListener(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification<NodeWorker.ReceivedMessageFromWorkerEventDataType>) => void): this;
}
// Top Level API

View File

@@ -4673,6 +4673,10 @@ import * as constants from 'constants';
const pauseReason: string = message.params.reason;
});
session.on('Debugger.resumed', () => {});
// Node Inspector events
session.on('NodeTracing.dataCollected', (message: inspector.InspectorNotification<inspector.NodeTracing.DataCollectedEventDataType>) => {
const value: Array<{}> = message.params.value;
});
}
}

View File

@@ -66,10 +66,11 @@ export const createListeners = (events: Event[]): string[] => {
"",
...createListenerBlockFn("prependOnceListener")(events),
].reduce((acc, next, index, arr) => {
// removes trailing and consecutive empty lines
// removes leading, trailing and consecutive empty lines
const isFirst = index === 0;
const isLast = index === arr.length - 1;
const followsEmptyLine = acc.length > 0 && acc[acc.length - 1] === "";
if ((isLast || followsEmptyLine) && next === "") {
if ((isFirst || isLast || followsEmptyLine) && next === "") {
return acc;
} else {
acc.push(next);

View File

@@ -4,17 +4,21 @@ import { capitalize, createDocs, flattenArgs, hasElements, isObjectReference } f
const INDENT = " ";
// Turns a type into a type representing an array of that type
const arrify = (typeString: string) => {
return typeString === '{}' ? `Array<${typeString}>` : `${typeString}[]`;
}
// Converts DevTools type to TS type
const createTypeString = (type: schema.Field, domain?: string): string => {
return isObjectReference(type) ? type.$ref :
type.type === "any" ? "any" :
type.type === "any" ? "any" :
type.type === "integer" ? "number" :
type.type === "number" ? "number" :
type.type === "boolean" ? "boolean" :
type.type === "string" ? "string" :
type.type === "array" ? `${createTypeString(type.items, domain)}[]` :
type.type === "object" ? "{}" // this code path is likely never exercised
: "never";
type.type === "array" ? arrify(createTypeString(type.items, domain)) :
type.type === "object" ? "{}" : "never"; // "never" has yet to be observed
};
// Helper for createInterface -- constructs a list of interface fields
@@ -138,7 +142,7 @@ export const generateSubstituteArgs = (protocol: schema.Schema): { [propName: st
.map(item => item.commands
.map(command => createPostFunctions(command, item.domain))
.reduce(flattenArgs(""), []))
.reduce(flattenArgs(), []);
.reduce(flattenArgs(""), []);
const eventOverloads: string[] = createListeners(protocol.domains
.map(item => {

View File

@@ -2,25 +2,55 @@
// [tag] corresponds to a tag name in the node-core repository.
import { execSync } from "child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { existsSync, readFileSync, writeFileSync } from "fs";
import * as https from "https";
import * as schema from "./devtools-protocol-schema";
import { generateSubstituteArgs } from "./generate-substitute-args";
import { flattenArgs, substitute, trimRight } from "./utils";
import { substitute, trimRight } from "./utils";
import { string } from "parsimmon";
const httpsGet = (url: string) => new Promise<string>((resolve, reject) => {
https.get(url, res => {
const frames: Buffer[] = [];
res.on("data", (data: Buffer) => {
frames.push(data);
});
res.on("end", () => {
resolve(Buffer.concat(frames).toString("utf8"));
});
res.on("error", (err: Error) => {
reject(err);
});
});
});
// Input arguments
const tag = process.argv[2] || process.version;
const PROTOCOL_URL = `https://raw.githubusercontent.com/nodejs/node/${tag}/deps/v8/src/inspector/js_protocol.json`;
const devToolsPath = `${__dirname}/../../node_modules/devtools-protocol`;
function writeProtocolToFile(json: string) {
const protocol: schema.Schema = JSON.parse(json);
const V8_PROTOCOL_URL = `https://raw.githubusercontent.com/nodejs/node/${tag}/deps/v8/src/inspector/js_protocol.json`;
const NODE_PROTOCOL_URL = `https://raw.githubusercontent.com/nodejs/node/${tag}/src/inspector/node_protocol.pdl`;
const INSPECTOR_PROTOCOL_REMOTE = `https://chromium.googlesource.com/deps/inspector_protocol`;
const INSPECTOR_PROTOCOL_LOCAL_DIR = "/tmp/inspector_protocol";
/**
* Given a list of Inspector protocol definitions, write an inspector.d.ts
* containing all of those definitions merged together.
* @param jsonProtocols A list of Inspector protocol definitions.
*/
function writeProtocolsToFile(jsonProtocols: string[]) {
const combinedProtocol: schema.Schema = {
version: { major: '', minor: '' }, // doesn't matter
domains: []
};
for (const json of jsonProtocols) {
if (json) {
const protocol: schema.Schema = JSON.parse(json);
combinedProtocol.domains.push(...protocol.domains);
}
}
const substituteArgs = generateSubstituteArgs(combinedProtocol);
const template = readFileSync(`${__dirname}/inspector.d.ts.template`, "utf8");
const substituteArgs = generateSubstituteArgs(protocol);
const inspectorDts = substitute(template, substituteArgs).split("\n")
.map(line => trimRight(line))
.join("\n");
@@ -28,12 +58,24 @@ function writeProtocolToFile(json: string) {
writeFileSync("./inspector.d.ts", inspectorDts, "utf8");
}
https.get(PROTOCOL_URL, res => {
const frames: Buffer[] = [];
res.on("data", (data: Buffer) => {
frames.push(data);
});
res.on("end", () => {
writeProtocolToFile(Buffer.concat(frames).toString("utf8"));
});
});
/**
* Given a PDL-formatted string, return a JSON-formatted string.
* Note that this function run blocking shell commands, as it depends on an
* external script to do the conversion.
* @param pdl A PDL-formatted string.
*/
function convertPdlToJson(pdl: string): string {
if (!existsSync(INSPECTOR_PROTOCOL_LOCAL_DIR)) {
execSync(`git clone ${INSPECTOR_PROTOCOL_REMOTE} ${INSPECTOR_PROTOCOL_LOCAL_DIR}`);
}
writeFileSync("/tmp/inspector_protocol.pdl", pdl);
execSync(`${INSPECTOR_PROTOCOL_LOCAL_DIR}/convert_protocol_to_json.py /tmp/inspector_protocol.pdl /tmp/inspector_protocol.json`);
return readFileSync("/tmp/inspector_protocol.json", 'utf8');
}
// "Main" -- get the V8 built-in inspector protocol definition, as well as the
// Node extensions, and then write this to inspector.d.ts.
Promise.all([
httpsGet(V8_PROTOCOL_URL),
httpsGet(NODE_PROTOCOL_URL).then(convertPdlToJson).catch(() => '')
]).then(writeProtocolsToFile).catch(console.error);