diff --git a/packages/core/src/lib/Config.ts b/packages/core/src/lib/Config.ts index f66410322..7a91faabc 100644 --- a/packages/core/src/lib/Config.ts +++ b/packages/core/src/lib/Config.ts @@ -71,6 +71,12 @@ export enum TraceObjectMode { SelectedJSObjects = 2, } +/** @internal */ +export enum OutputFormat { + Text = 1, + Json = 2, +} + /** @internal */ export enum ErrorHandling { Halt = 1, @@ -90,6 +96,7 @@ export type MuteConfig = { muteHighLevel?: boolean; muteMidLevel?: boolean; muteLowLevel?: boolean; + muteOutput?: boolean; }; /** @internal */ @@ -260,6 +267,7 @@ export class MemLabConfig { skipBrowserCloseWait: boolean; simplifyCodeSerialization: boolean; heapParserDictFastStoreSize: number; + outputFormat: OutputFormat; constructor(options: ConfigOption = {}) { // init properties, they can be configured manually diff --git a/packages/core/src/lib/Console.ts b/packages/core/src/lib/Console.ts index 11f32c514..bcab48172 100644 --- a/packages/core/src/lib/Console.ts +++ b/packages/core/src/lib/Console.ts @@ -15,7 +15,7 @@ import fs from 'fs'; import path from 'path'; import readline from 'readline'; import stringWidth from 'string-width'; -import type {MemLabConfig} from './Config'; +import {OutputFormat, type MemLabConfig} from './Config'; import {AnyValue, Nullable, Optional} from './Types'; type Message = { @@ -39,7 +39,6 @@ type Sections = { arr: Section[]; }; -const stdout = process.stdout; const TABLE_MAX_WIDTH = 50; const LOG_BUFFER_LENGTH = 100; const prevLine = '\x1b[F'; @@ -149,6 +148,14 @@ class MemLabConsole { return inst; } + private get isTextOutput(): boolean { + return this.config.outputFormat === OutputFormat.Text; + } + + private get outStream() { + return this.isTextOutput ? process.stdout : process.stderr; + } + private style(msg: string, name: keyof MemlabConsoleStyles): string { if (Object.prototype.hasOwnProperty.call(this.styles, name)) { return this.styles[name](msg); @@ -183,6 +190,10 @@ class MemLabConsole { .replace(/\[\d{1,3}m/g, ''), ); this.log.push(...lines); + this.tryFlush(); + } + + private tryFlush(): void { if (this.log.length > LOG_BUFFER_LENGTH) { this.flushLog({sync: true}); } @@ -243,7 +254,7 @@ class MemLabConsole { return; } if (!this.config.muteConsole) { - stdout.write(eraseLine); + this.outStream.write(eraseLine); } const msg = section.msgs.pop(); @@ -254,11 +265,11 @@ class MemLabConsole { const lines = msg.lines; while (lines.length > 0) { const line = lines.pop() ?? 0; - const width = stdout.columns; + const width = this.outStream.columns; let n = line === 0 ? 1 : Math.ceil(line / width); if (!this.config.muteConsole && !this.config.isTest) { while (n-- > 0) { - stdout.write(prevLine + eraseLine); + this.outStream.write(prevLine + eraseLine); } } } @@ -306,8 +317,23 @@ class MemLabConsole { return; } if (this.config.isContinuousTest || !this.config.muteConsole) { - console.log(msg); + if (this.isTextOutput) { + console.log(msg); + } else { + this.outStream.write(msg); + this.outStream.write('\n'); + } + } + } + + public writeOutput(output: string): void { + this.log.push(output); + if (this.config.muteConfig?.muteOutput) { + return; } + process.stdout.write(output); + + this.tryFlush(); } public registerLogFile(logFile: string): void { @@ -498,7 +524,7 @@ class MemLabConsole { public waitForConsole(query: string): Promise { const rl = readline.createInterface({ input: process.stdin, - output: process.stdout, + output: this.outStream, }); this.pushMsg(query); this.logMsg(query); @@ -515,7 +541,7 @@ class MemLabConsole { total: number, options: {message?: string} = {}, ): void { - let width = Math.floor(stdout.columns * 0.8); + let width = Math.floor(this.outStream.columns * 0.8); width = Math.min(width, 80); const messageMaxWidth = Math.floor(width * 0.3); let message = options.message || ''; diff --git a/packages/core/src/lib/Types.ts b/packages/core/src/lib/Types.ts index 9b0b6d614..ba8a27954 100644 --- a/packages/core/src/lib/Types.ts +++ b/packages/core/src/lib/Types.ts @@ -1592,6 +1592,14 @@ export interface IHeapLocation { * get the column number */ column: number; + /** + * convert to a concise readable object that can be used for serialization + * (like calling `JSON.stringify(node, ...args)`). + * + * This API does not contain all the information + * captured by the hosting object. + */ + getJSONifyableObject(): AnyRecord; /** * convert to a concise readable string output * (like calling `JSON.stringify(node, ...args)`). @@ -1679,6 +1687,14 @@ export interface IHeapEdge extends IHeapEdgeBasic { * JS heap object where this reference starts */ fromNode: IHeapNode; + /** + * convert to a concise readable object that can be used for serialization + * (like calling `JSON.stringify(node, ...args)`). + * + * This API does not contain all the information + * captured by the hosting object. + */ + getJSONifyableObject(): AnyRecord; /** * convert to a concise readable string output * (like calling `JSON.stringify(node, ...args)`). @@ -1904,6 +1920,15 @@ export interface IHeapNode extends IHeapNodeBasic { * inside the string node. */ toStringNode(): Nullable; + /** + * convert to a concise readable object that can be used for serialization + * (like calling `JSON.stringify(node, ...args)`). + * + * This API does not contain all the information + * captured by the hosting object. + */ + + getJSONifyableObject(): AnyRecord; /** * convert to a concise readable string output * (like calling `JSON.stringify(node, ...args)`). diff --git a/packages/core/src/trace-cluster/TraceElement.ts b/packages/core/src/trace-cluster/TraceElement.ts index 9cd749b72..4ecddf334 100644 --- a/packages/core/src/trace-cluster/TraceElement.ts +++ b/packages/core/src/trace-cluster/TraceElement.ts @@ -9,6 +9,7 @@ */ import type { + AnyRecord, AnyValue, EdgeIterationCallback, IHeapEdge, @@ -175,8 +176,8 @@ export class NodeRecord implements IHeapNode { throw new Error('NodeRecord.getReferrerNodes is not implemented'); } - toJSONString(...args: Array): string { - const rep = { + getJSONifyableObject(): AnyRecord { + return { id: this.id, kind: this.kind, name: this.name, @@ -187,8 +188,10 @@ export class NodeRecord implements IHeapNode { incomingEdgeCount: this.numOfReferrers, contructorName: this.constructor.name, }; + } - return JSON.stringify(rep, ...args); + toJSONString(...args: Array): string { + return JSON.stringify(this.getJSONifyableObject(), ...args); } constructor(node: IHeapNode) { @@ -233,16 +236,18 @@ export class EdgeRecord implements IHeapEdge { this.to_node = edge.to_node; } - toJSONString(...args: Array): string { - const rep = { + getJSONifyableObject(): AnyRecord { + return { kind: this.kind, name_or_index: this.name_or_index, type: this.type, edgeIndex: this.edgeIndex, to_node: this.to_node, }; + } - return JSON.stringify(rep, ...args); + toJSONString(...args: Array): string { + return JSON.stringify(this.getJSONifyableObject(), ...args); } set snapshot(s: IHeapSnapshot) { diff --git a/packages/heap-analysis/src/PluginUtils.ts b/packages/heap-analysis/src/PluginUtils.ts index cf7fcd7d9..f788aeeec 100644 --- a/packages/heap-analysis/src/PluginUtils.ts +++ b/packages/heap-analysis/src/PluginUtils.ts @@ -19,6 +19,8 @@ import { MemLabConfig, config, takeNodeMinimalHeap, + OutputFormat, + AnyRecord, } from '@memlab/core'; import chalk from 'chalk'; @@ -146,10 +148,47 @@ function filterOutDominators(nodeList: IHeapNode[]): IHeapNode[] { return nodeList.filter(node => candidateIdSet.has(node.id)); } +// Note: be cautious when using printRef = true, it may cause infinite loop +function getNodeRecord(node: IHeapNode, printRef = false): AnyRecord { + const refs = node.references.slice(0, MAX_NUM_OF_EDGES_TO_PRINT); + return { + id: node.id, + name: node.name, + type: node.type, + selfsize: node.self_size, + retainedSize: node.retainedSize, + traceNodeId: node.trace_node_id, + nodeIndex: node.nodeIndex, + references: printRef + ? refs.map(edge => getEdgeRecord(edge)) + : refs.map(edge => ({ + name: edge.name_or_index.toString(), + toNode: edge.toNode.id, + })), + referrers: node.referrers.slice(0, MAX_NUM_OF_EDGES_TO_PRINT).map(edge => ({ + name: edge.name_or_index.toString(), + fromNode: edge.fromNode.id, + })), + }; +} + +function getEdgeRecord(edge: IHeapEdge): AnyRecord { + return { + nameOrIndex: edge.name_or_index, + type: edge.type, + edgeIndex: edge.edgeIndex, + toNode: getNodeRecord(edge.toNode), + fromNode: getNodeRecord(edge.fromNode), + }; +} + type PrintNodeOption = { indent?: string; printReferences?: boolean; }; + +// Note: be cautious when setting printReferences to true, +// it may cause infinite loop function printNodeListInTerminal( nodeList: IHeapNode[], options: AnyOptions & PrintNodeOption = {}, @@ -162,6 +201,14 @@ function printNodeListInTerminal( nodeList = filterOutDominators(nodeList); } + if (config.outputFormat === OutputFormat.Json) { + const jsonNodes = nodeList.map(node => getNodeRecord(node, printRef)); + + info.writeOutput(JSON.stringify(jsonNodes)); + info.writeOutput('\n'); + return; + } + for (const node of nodeList) { const nodeInfo = getHeapObjectString(node); info.topLevel(`${indent}${dot}${nodeInfo}`); @@ -171,6 +218,12 @@ function printNodeListInTerminal( } } +function printNodeInTerminal(node: IHeapNode): void { + const nodeRecord = getNodeRecord(node); + info.writeOutput(JSON.stringify(nodeRecord)); + info.writeOutput('\n'); +} + function isNumeric(v: number | string): boolean { if (typeof v === 'number') { return true; @@ -250,6 +303,13 @@ function printReferencesInTerminal( edgeList: IHeapEdge[], options: AnyOptions & PrintNodeOption = {}, ): void { + if (config.outputFormat === OutputFormat.Json) { + const jsonEdges = edgeList.map(edge => getEdgeRecord(edge)); + + info.writeOutput(JSON.stringify(jsonEdges)); + info.writeOutput('\n'); + } + const dot = chalk.grey('ยท '); const indent = options.indent || ''; let n = 0; @@ -773,6 +833,7 @@ export default { printNodeListInTerminal, printReferencesInTerminal, printReferrersInTerminal, + printNodeInTerminal, snapshotMapReduce, takeNodeFullHeap, }; diff --git a/packages/heap-analysis/src/options/HeapAnalysisOutputOption.ts b/packages/heap-analysis/src/options/HeapAnalysisOutputOption.ts new file mode 100644 index 000000000..e908bbe17 --- /dev/null +++ b/packages/heap-analysis/src/options/HeapAnalysisOutputOption.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @oncall web_perf_infra + */ + +import type {ParsedArgs} from 'minimist'; +import {MemLabConfig, OutputFormat, utils} from '@memlab/core'; +import {BaseOption} from '@memlab/core'; + +export default class HeapAnalysisOutputOption extends BaseOption { + getOptionName(): string { + return 'output'; + } + + getDescription(): string { + return 'specify output format of the analysis (defaults to text)'; + } + + getExampleValues(): string[] { + return ['text', 'json']; + } + + async parse(config: MemLabConfig, args: ParsedArgs): Promise { + const name = this.getOptionName(); + const format = `${args[name]}` ?? 'text'; + config.outputFormat = HeapAnalysisOutputOption.parseOutputFormat(format); + if (config.outputFormat === OutputFormat.Json) { + config.isContinuousTest = true; + } + } + + private static parseOutputFormat(s: string): OutputFormat { + switch (s.toLowerCase()) { + case 'text': + return OutputFormat.Text; + case 'json': + return OutputFormat.Json; + default: + utils.haltOrThrow('Invalid output format, valid output: text, json'); + return OutputFormat.Text; + } + } +} diff --git a/packages/heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts b/packages/heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts index f8491de6f..c529e4ee2 100644 --- a/packages/heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts +++ b/packages/heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts @@ -11,11 +11,14 @@ import type {AnalyzeSnapshotResult, HeapAnalysisOptions} from '../PluginUtils'; import type {IHeapSnapshot, IHeapNode} from '@memlab/core'; -import {info, utils, BaseOption} from '@memlab/core'; +import {info, utils, BaseOption, OutputFormat, config} from '@memlab/core'; import BaseAnalysis from '../BaseAnalysis'; import pluginUtils from '../PluginUtils'; import chalk from 'chalk'; import SnapshotFileOption from '../options/HeapAnalysisSnapshotFileOption'; +import OutputOption from '../options/HeapAnalysisOutputOption'; + +const MAX_COLLECTION_STAT_ITEMS = 20; type CollectionStat = { collection: IHeapNode; @@ -72,7 +75,7 @@ export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis { /** @internal */ getOptions(): BaseOption[] { - return [new SnapshotFileOption()]; + return [new SnapshotFileOption(), new OutputOption()]; } /** @internal */ @@ -99,7 +102,11 @@ export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis { async process(options: HeapAnalysisOptions): Promise { const snapshot = await pluginUtils.loadHeapSnapshot(options); const collectionsStat = this.getCollectionsWithStaleValues(snapshot); - this.print(collectionsStat); + if (config.outputFormat === OutputFormat.Json) { + this.printJson(collectionsStat); + } else { + this.print(collectionsStat); + } } private getCollectionsWithStaleValues( @@ -133,7 +140,7 @@ export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis { } collections.sort((c1, c2) => c2.staleRetainedSize - c1.staleRetainedSize); - collections = collections.slice(0, 20); + collections = collections.slice(0, MAX_COLLECTION_STAT_ITEMS); for (const stat of collections) { const collection = stat.collection; const collectionSize = utils.getReadableBytes(collection.retainedSize); @@ -154,4 +161,20 @@ export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis { info.topLevel(` ${childrenDesc}`); } } + + private printJson(collections: CollectionStat[]): void { + collections.sort((c1, c2) => c2.staleRetainedSize - c1.staleRetainedSize); + const output = collections + .slice(0, MAX_COLLECTION_STAT_ITEMS) + .map(stat => ({ + id: stat.collection.id, + size: utils.getReadableBytes(stat.collection.retainedSize), + childrenSize: stat.childrenSize, + staleChildrenSize: stat.staleChildren.length, + staleChildrenIds: stat.staleChildren.map(node => node.id), + })); + + info.writeOutput(JSON.stringify(output)); + info.writeOutput('\n'); + } } diff --git a/packages/heap-analysis/src/plugins/ObjectContentAnalysis.ts b/packages/heap-analysis/src/plugins/ObjectContentAnalysis.ts index 3d066cc43..b1b0539b4 100644 --- a/packages/heap-analysis/src/plugins/ObjectContentAnalysis.ts +++ b/packages/heap-analysis/src/plugins/ObjectContentAnalysis.ts @@ -12,11 +12,12 @@ import type {IHeapEdge, IHeapNode, IHeapSnapshot} from '@memlab/core'; import type {AnalyzeSnapshotResult, HeapAnalysisOptions} from '../PluginUtils'; import chalk from 'chalk'; -import {BaseOption, config, utils, info} from '@memlab/core'; +import {BaseOption, config, utils, info, OutputFormat} from '@memlab/core'; import NodeIdOption from '../options/HeapAnalysisNodeIdOption'; import SnapshotFileOption from '../options/HeapAnalysisSnapshotFileOption'; import BaseAnalysis from '../BaseAnalysis'; import pluginUtils from '../PluginUtils'; +import OutputOption from '../options/HeapAnalysisOutputOption'; class GlobalVariableAnalysis extends BaseAnalysis { getCommandName(): string { @@ -30,7 +31,7 @@ class GlobalVariableAnalysis extends BaseAnalysis { /** @internal */ getOptions(): BaseOption[] { - return [new SnapshotFileOption(), new NodeIdOption()]; + return [new SnapshotFileOption(), new NodeIdOption(), new OutputOption()]; } /** @internal */ @@ -43,7 +44,15 @@ class GlobalVariableAnalysis extends BaseAnalysis { info.lowLevel(`Specify an object by --node-id`); return; } - // print object info + + if (config.outputFormat === OutputFormat.Json) { + pluginUtils.printNodeInTerminal(node); + } else { + this.print(node, snapshot); + } + } + + private print(node: IHeapNode, snapshot: IHeapSnapshot) { const id = chalk.grey(`@${node.id}`); info.topLevel(`Heap node (${node.type}) ${id}`); const name = chalk.grey(`${node.name}`); diff --git a/website/docs/api/classes/heap_analysis_src.CollectionsHoldingStaleAnalysis.md b/website/docs/api/classes/heap_analysis_src.CollectionsHoldingStaleAnalysis.md index 4e8692922..e559210e0 100644 --- a/website/docs/api/classes/heap_analysis_src.CollectionsHoldingStaleAnalysis.md +++ b/website/docs/api/classes/heap_analysis_src.CollectionsHoldingStaleAnalysis.md @@ -70,4 +70,4 @@ const name = analysis.getCommandName(); ``` * **Source**: - * heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts:64 + * heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts:67 diff --git a/website/docs/api/interfaces/core_src.IHeapEdge.md b/website/docs/api/interfaces/core_src.IHeapEdge.md index c4f7ab84e..685d4bfde 100644 --- a/website/docs/api/interfaces/core_src.IHeapEdge.md +++ b/website/docs/api/interfaces/core_src.IHeapEdge.md @@ -45,7 +45,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; index of this JS reference inside the `edge.snapshot.edges` pseudo array * **Source**: - * core/src/lib/Types.ts:1660 + * core/src/lib/Types.ts:1668 ___ @@ -55,7 +55,7 @@ returns an [IHeapNode](core_src.IHeapNode.md) instance representing the hosting JS heap object where this reference starts * **Source**: - * core/src/lib/Types.ts:1681 + * core/src/lib/Types.ts:1689 ___ @@ -67,7 +67,7 @@ otherwise this is a reference with a string name (`edge.name_or_index` will return a string) * **Source**: - * core/src/lib/Types.ts:1667 + * core/src/lib/Types.ts:1675 ___ @@ -77,7 +77,7 @@ name of the JS reference. If this is a reference to an array element or internal table element, it is an numeric index * **Source**: - * core/src/lib/Types.ts:1616 + * core/src/lib/Types.ts:1624 ___ @@ -86,7 +86,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this JS reference * **Source**: - * core/src/lib/Types.ts:1656 + * core/src/lib/Types.ts:1664 ___ @@ -96,7 +96,7 @@ returns an [IHeapNode](core_src.IHeapNode.md) instance representing the JS heap pointed to by this reference * **Source**: - * core/src/lib/Types.ts:1676 + * core/src/lib/Types.ts:1684 ___ @@ -105,7 +105,7 @@ ___ the index of the JS heap object pointed to by this reference * **Source**: - * core/src/lib/Types.ts:1671 + * core/src/lib/Types.ts:1679 ___ @@ -115,10 +115,24 @@ type of the JS reference, all types: `context`, `element`, `property`, `internal`, `hidden`, `shortcut`, `weak` * **Source**: - * core/src/lib/Types.ts:1621 + * core/src/lib/Types.ts:1629 ## Methods +### **getJSONifyableObject**() + +convert to a concise readable object that can be used for serialization +(like calling `JSON.stringify(node, ...args)`). + +This API does not contain all the information +captured by the hosting object. + + * **Returns**: `AnyRecord` + * **Source**: + * core/src/lib/Types.ts:1697 + +___ + ### **toJSONString**(...`args`) convert to a concise readable string output @@ -136,4 +150,4 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1694 + * core/src/lib/Types.ts:1710 diff --git a/website/docs/api/interfaces/core_src.IHeapEdges.md b/website/docs/api/interfaces/core_src.IHeapEdges.md index 0562d05f5..5d699f7cf 100644 --- a/website/docs/api/interfaces/core_src.IHeapEdges.md +++ b/website/docs/api/interfaces/core_src.IHeapEdges.md @@ -41,7 +41,7 @@ The total number of edges in heap graph (or JS references in heap snapshot). * **Source**: - * core/src/lib/Types.ts:1731 + * core/src/lib/Types.ts:1747 ## Methods @@ -54,7 +54,7 @@ to each element in ascending order of element index. * `callback`: (`edge`: [`IHeapEdge`](core_src.IHeapEdge.md), `index`: `number`) => `boolean` \| `void` | the callback does not need to return any value, if the callback returns `false` when iterating on element at index `i`, then all elements after `i` won't be iterated. * **Returns**: `void` * **Source**: - * core/src/lib/Types.ts:1747 + * core/src/lib/Types.ts:1763 ___ @@ -68,4 +68,4 @@ get an [IHeapEdge](core_src.IHeapEdge.md) element at the specified index at the specified index, otherwise it returns `null`. * **Source**: - * core/src/lib/Types.ts:1739 + * core/src/lib/Types.ts:1755 diff --git a/website/docs/api/interfaces/core_src.IHeapLocation.md b/website/docs/api/interfaces/core_src.IHeapLocation.md index 709fd2c50..45eb69274 100644 --- a/website/docs/api/interfaces/core_src.IHeapLocation.md +++ b/website/docs/api/interfaces/core_src.IHeapLocation.md @@ -83,6 +83,20 @@ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this location inst ## Methods +### **getJSONifyableObject**() + +convert to a concise readable object that can be used for serialization +(like calling `JSON.stringify(node, ...args)`). + +This API does not contain all the information +captured by the hosting object. + + * **Returns**: `AnyRecord` + * **Source**: + * core/src/lib/Types.ts:1602 + +___ + ### **toJSONString**(...`args`) convert to a concise readable string output @@ -100,4 +114,4 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1607 + * core/src/lib/Types.ts:1615 diff --git a/website/docs/api/interfaces/core_src.IHeapNode.md b/website/docs/api/interfaces/core_src.IHeapNode.md index 78cda5c90..97c9c650f 100644 --- a/website/docs/api/interfaces/core_src.IHeapNode.md +++ b/website/docs/api/interfaces/core_src.IHeapNode.md @@ -52,7 +52,7 @@ For more information on what a dominator node is, please check out [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators). * **Source**: - * core/src/lib/Types.ts:1888 + * core/src/lib/Types.ts:1904 ___ @@ -62,7 +62,7 @@ The total number of outgoing JS references (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1842 + * core/src/lib/Types.ts:1858 ___ @@ -72,7 +72,7 @@ returns true if the heap node has been set an incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1864 + * core/src/lib/Types.ts:1880 ___ @@ -81,7 +81,7 @@ ___ unique id of the heap object * **Source**: - * core/src/lib/Types.ts:1769 + * core/src/lib/Types.ts:1785 ___ @@ -91,7 +91,7 @@ check if this a string node (normal string node, concatenated string node or sliced string node) * **Source**: - * core/src/lib/Types.ts:1900 + * core/src/lib/Types.ts:1916 ___ @@ -104,7 +104,7 @@ from the React Fiber tree, `is_detached` will be `true`; otherwise it will be `false` * **Source**: - * core/src/lib/Types.ts:1824 + * core/src/lib/Types.ts:1840 ___ @@ -114,7 +114,7 @@ source location information of this heap object (if it is recorded by the heap snapshot). * **Source**: - * core/src/lib/Types.ts:1893 + * core/src/lib/Types.ts:1909 ___ @@ -125,7 +125,7 @@ for JS object instances (type `object`), `name` is the constructor's name of the object instance. for `string`, `name` is the string value. * **Source**: - * core/src/lib/Types.ts:1765 + * core/src/lib/Types.ts:1781 ___ @@ -134,7 +134,7 @@ ___ index of this heap object inside the `node.snapshot.nodes` pseudo array * **Source**: - * core/src/lib/Types.ts:1873 + * core/src/lib/Types.ts:1889 ___ @@ -144,7 +144,7 @@ Get the number of all incoming references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1859 + * core/src/lib/Types.ts:1875 ___ @@ -154,7 +154,7 @@ The incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1869 + * core/src/lib/Types.ts:1885 ___ @@ -164,7 +164,7 @@ Get a JS array containing all outgoing JS references from this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1849 + * core/src/lib/Types.ts:1865 ___ @@ -174,7 +174,7 @@ Get a JS array containing all incoming JS references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1854 + * core/src/lib/Types.ts:1870 ___ @@ -186,7 +186,7 @@ could be released if this object is released). For difference between [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1880 + * core/src/lib/Types.ts:1896 ___ @@ -198,7 +198,7 @@ by the object itself.). For difference between **shallow size** and [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1837 + * core/src/lib/Types.ts:1853 ___ @@ -207,7 +207,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this heap object * **Source**: - * core/src/lib/Types.ts:1816 + * core/src/lib/Types.ts:1832 ___ @@ -220,7 +220,7 @@ This is engine-specific, for example all types in V8: `symbol`, `bigint` * **Source**: - * core/src/lib/Types.ts:1759 + * core/src/lib/Types.ts:1775 ## Methods @@ -244,7 +244,7 @@ const reference = node.findAnyReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1972 + * core/src/lib/Types.ts:1997 ___ @@ -268,7 +268,7 @@ const referrer = node.findAnyReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1989 + * core/src/lib/Types.ts:2014 ___ @@ -293,7 +293,7 @@ const referrer = node.findAnyReferrerNode((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:2007 + * core/src/lib/Types.ts:2032 ___ @@ -318,7 +318,7 @@ const referrerNodes = node.findReferrerNodes((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:2042 + * core/src/lib/Types.ts:2067 ___ @@ -342,7 +342,7 @@ const referrers = node.findReferrers((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:2024 + * core/src/lib/Types.ts:2049 ___ @@ -367,7 +367,7 @@ node.forEachReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1937 + * core/src/lib/Types.ts:1962 ___ @@ -392,7 +392,7 @@ node.forEachReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1955 + * core/src/lib/Types.ts:1980 ___ @@ -413,7 +413,7 @@ const reference = node.getAnyReferrer('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:2097 + * core/src/lib/Types.ts:2122 ___ @@ -439,7 +439,21 @@ const n2 = node.getAnyReferrer('ref', 'property')?.fromNode; ``` * **Source**: - * core/src/lib/Types.ts:2120 + * core/src/lib/Types.ts:2145 + +___ + +### **getJSONifyableObject**() + +convert to a concise readable object that can be used for serialization +(like calling `JSON.stringify(node, ...args)`). + +This API does not contain all the information +captured by the hosting object. + + * **Returns**: `AnyRecord` + * **Source**: + * core/src/lib/Types.ts:1931 ___ @@ -460,7 +474,7 @@ const reference = node.getReference('map', 'hidden'); ``` * **Source**: - * core/src/lib/Types.ts:2057 + * core/src/lib/Types.ts:2082 ___ @@ -485,7 +499,7 @@ const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode; ``` * **Source**: - * core/src/lib/Types.ts:2079 + * core/src/lib/Types.ts:2104 ___ @@ -512,7 +526,7 @@ const nodes2 = node.getReferrers('ref', 'property') ``` * **Source**: - * core/src/lib/Types.ts:2160 + * core/src/lib/Types.ts:2185 ___ @@ -534,7 +548,7 @@ const referrers = node.getReferrers('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:2139 + * core/src/lib/Types.ts:2164 ___ @@ -555,7 +569,7 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1919 + * core/src/lib/Types.ts:1944 ___ @@ -567,4 +581,4 @@ inside the string node. * **Returns**: [`Nullable`](../modules/core_src.md#nullable)<[`IHeapStringNode`](core_src.IHeapStringNode.md)\> * **Source**: - * core/src/lib/Types.ts:1906 + * core/src/lib/Types.ts:1922 diff --git a/website/docs/api/interfaces/core_src.IHeapNodes.md b/website/docs/api/interfaces/core_src.IHeapNodes.md index 8722bef90..45896de0a 100644 --- a/website/docs/api/interfaces/core_src.IHeapNodes.md +++ b/website/docs/api/interfaces/core_src.IHeapNodes.md @@ -41,7 +41,7 @@ The total number of nodes in heap graph (or JS objects in heap snapshot). * **Source**: - * core/src/lib/Types.ts:2238 + * core/src/lib/Types.ts:2263 ## Methods @@ -54,7 +54,7 @@ to each element in ascending order of element index. * `callback`: (`node`: [`IHeapNode`](core_src.IHeapNode.md), `index`: `number`) => `boolean` \| `void` | the callback does not need to return any value, if the callback returns `false` when iterating on element at index `i`, then all elements after `i` won't be iterated. * **Returns**: `void` * **Source**: - * core/src/lib/Types.ts:2254 + * core/src/lib/Types.ts:2279 ___ @@ -68,4 +68,4 @@ get an [IHeapNode](core_src.IHeapNode.md) element at the specified index at the specified index, otherwise it returns `null`. * **Source**: - * core/src/lib/Types.ts:2246 + * core/src/lib/Types.ts:2271 diff --git a/website/docs/api/interfaces/core_src.IHeapStringNode.md b/website/docs/api/interfaces/core_src.IHeapStringNode.md index 1252a02f2..388575752 100644 --- a/website/docs/api/interfaces/core_src.IHeapStringNode.md +++ b/website/docs/api/interfaces/core_src.IHeapStringNode.md @@ -51,7 +51,7 @@ For more information on what a dominator node is, please check out [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators). * **Source**: - * core/src/lib/Types.ts:1888 + * core/src/lib/Types.ts:1904 ___ @@ -61,7 +61,7 @@ The total number of outgoing JS references (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1842 + * core/src/lib/Types.ts:1858 ___ @@ -71,7 +71,7 @@ returns true if the heap node has been set an incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1864 + * core/src/lib/Types.ts:1880 ___ @@ -80,7 +80,7 @@ ___ unique id of the heap object * **Source**: - * core/src/lib/Types.ts:1769 + * core/src/lib/Types.ts:1785 ___ @@ -90,7 +90,7 @@ check if this a string node (normal string node, concatenated string node or sliced string node) * **Source**: - * core/src/lib/Types.ts:1900 + * core/src/lib/Types.ts:1916 ___ @@ -103,7 +103,7 @@ from the React Fiber tree, `is_detached` will be `true`; otherwise it will be `false` * **Source**: - * core/src/lib/Types.ts:1824 + * core/src/lib/Types.ts:1840 ___ @@ -113,7 +113,7 @@ source location information of this heap object (if it is recorded by the heap snapshot). * **Source**: - * core/src/lib/Types.ts:1893 + * core/src/lib/Types.ts:1909 ___ @@ -124,7 +124,7 @@ for JS object instances (type `object`), `name` is the constructor's name of the object instance. for `string`, `name` is the string value. * **Source**: - * core/src/lib/Types.ts:1765 + * core/src/lib/Types.ts:1781 ___ @@ -133,7 +133,7 @@ ___ index of this heap object inside the `node.snapshot.nodes` pseudo array * **Source**: - * core/src/lib/Types.ts:1873 + * core/src/lib/Types.ts:1889 ___ @@ -143,7 +143,7 @@ Get the number of all incoming references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1859 + * core/src/lib/Types.ts:1875 ___ @@ -153,7 +153,7 @@ The incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1869 + * core/src/lib/Types.ts:1885 ___ @@ -163,7 +163,7 @@ Get a JS array containing all outgoing JS references from this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1849 + * core/src/lib/Types.ts:1865 ___ @@ -173,7 +173,7 @@ Get a JS array containing all incoming JS references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1854 + * core/src/lib/Types.ts:1870 ___ @@ -185,7 +185,7 @@ could be released if this object is released). For difference between [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1880 + * core/src/lib/Types.ts:1896 ___ @@ -197,7 +197,7 @@ by the object itself.). For difference between **shallow size** and [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1837 + * core/src/lib/Types.ts:1853 ___ @@ -206,7 +206,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this heap object * **Source**: - * core/src/lib/Types.ts:1816 + * core/src/lib/Types.ts:1832 ___ @@ -216,7 +216,7 @@ get the string value of the JS string heap object associated with this `IHeapStringNode` instance in heap * **Source**: - * core/src/lib/Types.ts:2201 + * core/src/lib/Types.ts:2226 ___ @@ -229,7 +229,7 @@ This is engine-specific, for example all types in V8: `symbol`, `bigint` * **Source**: - * core/src/lib/Types.ts:1759 + * core/src/lib/Types.ts:1775 ## Methods @@ -253,7 +253,7 @@ const reference = node.findAnyReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1972 + * core/src/lib/Types.ts:1997 ___ @@ -277,7 +277,7 @@ const referrer = node.findAnyReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1989 + * core/src/lib/Types.ts:2014 ___ @@ -302,7 +302,7 @@ const referrer = node.findAnyReferrerNode((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:2007 + * core/src/lib/Types.ts:2032 ___ @@ -327,7 +327,7 @@ const referrerNodes = node.findReferrerNodes((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:2042 + * core/src/lib/Types.ts:2067 ___ @@ -351,7 +351,7 @@ const referrers = node.findReferrers((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:2024 + * core/src/lib/Types.ts:2049 ___ @@ -376,7 +376,7 @@ node.forEachReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1937 + * core/src/lib/Types.ts:1962 ___ @@ -401,7 +401,7 @@ node.forEachReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1955 + * core/src/lib/Types.ts:1980 ___ @@ -422,7 +422,7 @@ const reference = node.getAnyReferrer('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:2097 + * core/src/lib/Types.ts:2122 ___ @@ -448,7 +448,21 @@ const n2 = node.getAnyReferrer('ref', 'property')?.fromNode; ``` * **Source**: - * core/src/lib/Types.ts:2120 + * core/src/lib/Types.ts:2145 + +___ + +### **getJSONifyableObject**() + +convert to a concise readable object that can be used for serialization +(like calling `JSON.stringify(node, ...args)`). + +This API does not contain all the information +captured by the hosting object. + + * **Returns**: `AnyRecord` + * **Source**: + * core/src/lib/Types.ts:1931 ___ @@ -469,7 +483,7 @@ const reference = node.getReference('map', 'hidden'); ``` * **Source**: - * core/src/lib/Types.ts:2057 + * core/src/lib/Types.ts:2082 ___ @@ -494,7 +508,7 @@ const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode; ``` * **Source**: - * core/src/lib/Types.ts:2079 + * core/src/lib/Types.ts:2104 ___ @@ -521,7 +535,7 @@ const nodes2 = node.getReferrers('ref', 'property') ``` * **Source**: - * core/src/lib/Types.ts:2160 + * core/src/lib/Types.ts:2185 ___ @@ -543,7 +557,7 @@ const referrers = node.getReferrers('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:2139 + * core/src/lib/Types.ts:2164 ___ @@ -564,7 +578,7 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1919 + * core/src/lib/Types.ts:1944 ___ @@ -576,4 +590,4 @@ inside the string node. * **Returns**: [`Nullable`](../modules/core_src.md#nullable)<[`IHeapStringNode`](core_src.IHeapStringNode.md)\> * **Source**: - * core/src/lib/Types.ts:1906 + * core/src/lib/Types.ts:1922 diff --git a/website/docs/api/modules/core_src.md b/website/docs/api/modules/core_src.md index 75c7d924f..c0085e2f6 100644 --- a/website/docs/api/modules/core_src.md +++ b/website/docs/api/modules/core_src.md @@ -86,7 +86,7 @@ or [forEachReferrer](../interfaces/core_src.IHeapNode.md#foreachreferrer). * **Returns**: [`Optional`](core_src.md#optional)<{ `stop`: `boolean` }\> \| `void` | this API returns void * **Source**: - * core/src/lib/Types.ts:1779 + * core/src/lib/Types.ts:1795 ___ diff --git a/website/docs/api/modules/heap_analysis_src.md b/website/docs/api/modules/heap_analysis_src.md index 8aa3b5511..00175fa95 100644 --- a/website/docs/api/modules/heap_analysis_src.md +++ b/website/docs/api/modules/heap_analysis_src.md @@ -32,7 +32,7 @@ or [analyzeSnapshotsInDirectory](../classes/heap_analysis_src.BaseAnalysis.md#an | `analysisOutputFile` | `string` | file path of the console output of the heap analysis call | * **Source**: - * heap-analysis/src/PluginUtils.ts:91 + * heap-analysis/src/PluginUtils.ts:93 ___ @@ -47,7 +47,7 @@ For code examples on how this options could be used, see or [snapshotMapReduce](heap_analysis_src.md#snapshotmapreduce). * **Source**: - * heap-analysis/src/PluginUtils.ts:68 + * heap-analysis/src/PluginUtils.ts:70 ___ @@ -61,7 +61,7 @@ and [analyzeSnapshotsInDirectory](../classes/heap_analysis_src.BaseAnalysis.md#a | `workDir?` | `string` | specify the working directory to where the intermediate, logging, and output files should be saved | * **Source**: - * heap-analysis/src/PluginUtils.ts:79 + * heap-analysis/src/PluginUtils.ts:81 ## Functions @@ -107,7 +107,7 @@ class TestObject {} ``` * **Source**: - * heap-analysis/src/PluginUtils.ts:687 + * heap-analysis/src/PluginUtils.ts:747 ___ @@ -132,7 +132,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * heap-analysis/src/PluginUtils.ts:484 + * heap-analysis/src/PluginUtils.ts:544 ___ @@ -144,7 +144,7 @@ ___ * `file`: `string` * **Returns**: `Promise`<`IHeapSnapshot`\> * **Source**: - * heap-analysis/src/PluginUtils.ts:515 + * heap-analysis/src/PluginUtils.ts:575 ___ @@ -191,7 +191,7 @@ The new heap analysis can also be used with [analyze](api_src.md#analyze), in th [BrowserInteractionResultReader](../classes/api_src.BrowserInteractionResultReader.md). * **Source**: - * heap-analysis/src/PluginUtils.ts:396 + * heap-analysis/src/PluginUtils.ts:456 ___ @@ -238,7 +238,7 @@ The new heap analysis can also be used with [analyze](api_src.md#analyze), in th ascending order from [BrowserInteractionResultReader](../classes/api_src.BrowserInteractionResultReader.md). * **Source**: - * heap-analysis/src/PluginUtils.ts:347 + * heap-analysis/src/PluginUtils.ts:407 ___ @@ -286,7 +286,7 @@ The new heap analysis can also be used with [analyze](api_src.md#analyze), in th ascending order from [BrowserInteractionResultReader](../classes/api_src.BrowserInteractionResultReader.md). * **Source**: - * heap-analysis/src/PluginUtils.ts:450 + * heap-analysis/src/PluginUtils.ts:510 ___ @@ -358,7 +358,7 @@ Each heap snapshot could be non-trivial in size, loading them all at once may not be possible. * **Source**: - * heap-analysis/src/PluginUtils.ts:592 + * heap-analysis/src/PluginUtils.ts:652 ___ @@ -383,4 +383,4 @@ import type {takeNodeFullHeap} from '@memlab/heap-analysis'; ``` * **Source**: - * heap-analysis/src/PluginUtils.ts:507 + * heap-analysis/src/PluginUtils.ts:567 diff --git a/website/docs/cli/CLI-commands.md b/website/docs/cli/CLI-commands.md index 4a6b41968..c74aa5ef9 100644 --- a/website/docs/cli/CLI-commands.md +++ b/website/docs/cli/CLI-commands.md @@ -215,6 +215,7 @@ memlab analyze collections-with-stale **Options**: * **`--snapshot`**: set file path of the heap snapshot under analysis + * **`--output`**: specify output format of the analysis (defaults to text) * **`--help`**, **`-h`**: print helper text * **`--verbose`**, **`-v`**: show more details * **`--sc`**: set to continuous test mode @@ -264,6 +265,7 @@ memlab analyze object **Options**: * **`--snapshot`**: set file path of the heap snapshot under analysis * **`--node-id`**: set heap node ID + * **`--output`**: specify output format of the analysis (defaults to text) * **`--help`**, **`-h`**: print helper text * **`--verbose`**, **`-v`**: show more details * **`--sc`**: set to continuous test mode