Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion bin/puppeteer-heap-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { enableDebugLogging } from "../src/util";

if (require.main === module) {
const castProperties = (value: string): string[] => value.split(/\s*,\s*/);
const toNumberOrIfinity = (value: string): number => {
const maybeValidNumber = Number.parseInt(value);
return Number.isNaN(maybeValidNumber) ? Infinity : maybeValidNumber;
};

program
.option("--debug", "Enable debug mode (non-headless Chrome, debug logging)")
Expand Down Expand Up @@ -38,6 +42,17 @@ if (require.main === module) {
"fetch/read a heap snapshot and output the matching objects in JSON"
)
.option("-u, --url <url>")
.option(
"-d, --depth <value>",
"recursion search depth",
toNumberOrIfinity,
Infinity
)
.option(
"-e, --exclude <...nodeNames>",
"name of nodes (case-sensitive) to be excluded from building graph",
castProperties
)
.option("-f, --filepath <filepath>", "filepath", relativeFilepath)
.requiredOption(
"-p, --properties <...props>",
Expand Down Expand Up @@ -84,11 +99,15 @@ async function querySnapshotCommand({
filepath,
properties,
ignoreProperties,
exclude,
depth,
}: {
url?: string;
filepath?: string;
properties: string[];
ignoreProperties?: string[];
exclude?: string[];
depth?: number;
}) {
if (!url && !filepath) {
throw new Error(`Please specify a URL or a snapshot filepath`);
Expand All @@ -100,8 +119,10 @@ async function querySnapshotCommand({

log(
JSON.stringify(
await findObjectsWithProperties(heapSnapshot, properties, {
findObjectsWithProperties(heapSnapshot, properties, {
ignoreProperties,
maxDepth: depth,
unwantedNodeNames: exclude,
}),
null,
2
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"scripts": {
"prepack": "npm run build",
"prepare": "husky install && npm run lint && npm run test",
"format": "prettier --write '**/*.ts' '!dist/**/*'",
"format": "prettier --write \"**/*.ts\" \"!dist/**/*\"",
"lint": "eslint . --ext .ts",
"test": "jest",
"build:esm": "tsc --project tsconfig.esm.json",
Expand Down
36 changes: 28 additions & 8 deletions src/build-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,36 @@ import {
HeapSnapshotStructuredGraph,
HeapSnapshotStructuredEdge,
} from "./types";
import { createStructuredGraph } from "./structured-graph";
import {
createStructuredGraph,
createStructuredNode,
} from "./structured-graph";

type BuildObjectFromNodeIdOption = {
propertyFilter?: (propertyName: string) => boolean;
maxDepth?: number;
unwantedNodeNames?: readonly string[];
};

export function buildObjectFromNodeId(
heapSnapshot: HeapSnapshot,
nodeId: number,
propertyFilter: (propertyName: string) => boolean = () => true
options: BuildObjectFromNodeIdOption
): BuiltHeapValue {
debug(`building node object for node ${nodeId}`);

const graph = createStructuredGraph(heapSnapshot, nodeId, {
const { propertyFilter = () => true, maxDepth, unwantedNodeNames } = options;

// Get structuredNode early to figure out if we should return quickly in case of unwanted node name
const structuredNode = createStructuredNode(heapSnapshot, nodeId);

if (unwantedNodeNames?.includes(structuredNode.name)) {
debug(`found node with unwanted name ${structuredNode.name}, skipping...`);
return;
}

const graph = createStructuredGraph(heapSnapshot, nodeId, structuredNode, {
maxDepth,
edgeFilter: (edge: HeapSnapshotStructuredEdge) => {
if (edge.type === "property") {
return edge.name! !== "__proto__" && propertyFilter(edge.name!);
Expand Down Expand Up @@ -95,8 +115,8 @@ function compileGraphNodeObject(
);
} else if (isBoolean(graph)) {
return (
graph.edges.find(({ graph }) => graph.node.type === "string").graph.node
.name === "true"
graph.edges.find(({ graph }) => graph?.node.type === "string")?.graph
?.node.name === "true"
);
} else if (isNull(graph)) {
return null;
Expand All @@ -110,14 +130,14 @@ function compileGraphNodeObject(
function isBoolean(graph: HeapSnapshotStructuredGraph): boolean {
return (
graph.node.type === "hidden" &&
!!graph.edges.find(({ graph }) => graph.node.name === "boolean")
!!graph.edges.find(({ graph }) => graph?.node.name === "boolean")
);
}

function isNull(graph: HeapSnapshotStructuredGraph): boolean {
return (
graph.node.type === "hidden" &&
!!graph.edges.find(({ graph }) => graph.node.name === "object") &&
!!graph.edges.find(({ graph }) => graph.node.name === "null")
!!graph.edges.find(({ graph }) => graph?.node.name === "object") &&
!!graph.edges.find(({ graph }) => graph?.node.name === "null")
);
}
23 changes: 13 additions & 10 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { buildObjectFromNodeId } from "./build-object";

type FindObjectOptions = {
ignoreProperties?: readonly string[];
maxDepth?: number;
unwantedNodeNames?: readonly string[];
};

type FindObjectReturnValue<
Expand Down Expand Up @@ -34,16 +36,17 @@ export function findObjectsWithProperties<
);
}

return nodeIds.map((nodeId) => {
debug(`node ${nodeId} found`);
return buildObjectFromNodeId(
heapSnapshot,
nodeId,
options?.ignoreProperties
? (prop) => !options.ignoreProperties!.includes(prop)
: undefined
);
}) as FindObjectReturnValue<Props, Options>[];
return nodeIds
.map((nodeId) => {
debug(`node ${nodeId} found`);
return buildObjectFromNodeId(heapSnapshot, nodeId, {
...options,
propertyFilter: options?.ignoreProperties
? (prop) => !options.ignoreProperties!.includes(prop)
: undefined,
});
})
.filter((v) => v !== undefined) as FindObjectReturnValue<Props, Options>[];
}

export function findObjectWithProperties<
Expand Down
5 changes: 3 additions & 2 deletions src/structured-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
export function createStructuredGraph(
heapSnapshot: HeapSnapshot,
nodeId: number,
structuredNode: HeapSnapshotStructuredNode,
{
maxDepth = Infinity,
edgeFilter = () => true,
Expand All @@ -24,7 +25,6 @@ export function createStructuredGraph(
} = {},
nodeIdStack: number[] = []
): HeapSnapshotStructuredGraph {
const structuredNode = createStructuredNode(heapSnapshot, nodeId);
const structuredEdges = structuredNode.edgeIds
.map((edgeId) => createStructuredEdge(heapSnapshot, edgeId))
.filter(edgeFilter);
Expand All @@ -42,6 +42,7 @@ export function createStructuredGraph(
? createStructuredGraph(
heapSnapshot,
structuredEdge.nodeId,
createStructuredNode(heapSnapshot, structuredEdge.nodeId),
{ maxDepth, edgeFilter },
[...nodeIdStack, nodeId]
)
Expand All @@ -51,7 +52,7 @@ export function createStructuredGraph(
};
}

function createStructuredNode(
export function createStructuredNode(
heapSnapshot: HeapSnapshot,
nodeId: number
): HeapSnapshotStructuredNode {
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export type HeapSnapshot = Omit<SerializedHeapSnapshot, "nodes" | "edges"> & {

export type BuiltHeapValue =
| undefined
| null
| string
| number
| RegExp
Expand Down
Loading