mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-09-14 19:45:30 +08:00
feat: Report memory kept alive by contexts in get_heapsnapshot_summary (#2570)
Prints the size of objects kept alive by context objects in the heap snapshot in the initial overview. This can help guide the agent into looking into such objects in more detail.
This commit is contained in:
@@ -592,7 +592,7 @@ in the DevTools Elements panel (if any).
|
||||
|
||||
### `get_heapsnapshot_summary`
|
||||
|
||||
**Description:** Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes. (requires flag: --memoryDebugging=true)
|
||||
**Description:** Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes, and retained by context summary. (requires flag: --memoryDebugging=true)
|
||||
|
||||
**Parameters:**
|
||||
|
||||
|
||||
@@ -756,6 +756,14 @@ export class McpContext implements Context {
|
||||
return await this.#heapSnapshotManager.getNativeContextSizes(filePath);
|
||||
}
|
||||
|
||||
async getHeapSnapshotRetainedByContextSummary(
|
||||
filePath: string,
|
||||
): Promise<DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary> {
|
||||
return await this.#heapSnapshotManager.getRetainedByContextSummary(
|
||||
filePath,
|
||||
);
|
||||
}
|
||||
|
||||
async getHeapSnapshotNodesById(
|
||||
filePath: string,
|
||||
id: number,
|
||||
|
||||
@@ -90,6 +90,7 @@ export class McpResponse implements Response {
|
||||
stats?: DevTools.HeapSnapshotModel.HeapSnapshotModel.Statistics;
|
||||
staticData?: DevTools.HeapSnapshotModel.HeapSnapshotModel.StaticData | null;
|
||||
nativeContextSizes?: DevTools.HeapSnapshotModel.HeapSnapshotModel.NativeContextSizes;
|
||||
retainedByContextSummary?: DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary;
|
||||
nodes?: DevTools.HeapSnapshotModel.HeapSnapshotModel.ItemsRange;
|
||||
retainingPaths?: DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainingPaths;
|
||||
dominators?: DevTools.HeapSnapshotModel.HeapSnapshotModel.DominatorChain;
|
||||
@@ -342,6 +343,7 @@ export class McpResponse implements Response {
|
||||
stats: DevTools.HeapSnapshotModel.HeapSnapshotModel.Statistics,
|
||||
staticData: DevTools.HeapSnapshotModel.HeapSnapshotModel.StaticData | null,
|
||||
nativeContextSizes: DevTools.HeapSnapshotModel.HeapSnapshotModel.NativeContextSizes,
|
||||
retainedByContextSummary: DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary,
|
||||
) {
|
||||
this.#heapSnapshotOptions = {
|
||||
...this.#heapSnapshotOptions,
|
||||
@@ -349,6 +351,7 @@ export class McpResponse implements Response {
|
||||
stats,
|
||||
staticData,
|
||||
nativeContextSizes,
|
||||
retainedByContextSummary,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -786,6 +789,7 @@ export class McpResponse implements Response {
|
||||
stats?: object;
|
||||
staticData?: object;
|
||||
nativeContextSizes?: object;
|
||||
retainedByContextSummary?: object;
|
||||
aggregateStats?: {
|
||||
objectCount: number;
|
||||
totalSelfSize: number;
|
||||
@@ -1106,6 +1110,19 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
||||
structuredContent.heapSnapshot = structuredContent.heapSnapshot || {};
|
||||
structuredContent.heapSnapshot.nativeContextSizes = nativeContextSizes;
|
||||
}
|
||||
const retainedByContextSummary =
|
||||
this.#heapSnapshotOptions.retainedByContextSummary;
|
||||
if (retainedByContextSummary) {
|
||||
response.push('### Retained by Context Summary');
|
||||
response.push(
|
||||
HeapSnapshotFormatter.formatRetainedByContextSummary(
|
||||
retainedByContextSummary,
|
||||
),
|
||||
);
|
||||
structuredContent.heapSnapshot = structuredContent.heapSnapshot || {};
|
||||
structuredContent.heapSnapshot.retainedByContextSummary =
|
||||
retainedByContextSummary;
|
||||
}
|
||||
const aggregateData = this.#heapSnapshotOptions.aggregateData;
|
||||
if (aggregateData) {
|
||||
const sortedEntries = HeapSnapshotFormatter.sort(
|
||||
|
||||
@@ -623,7 +623,7 @@ export const commands: Commands = {
|
||||
},
|
||||
get_heapsnapshot_summary: {
|
||||
description:
|
||||
'Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes. (requires flag: --memoryDebugging=true)',
|
||||
'Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes, and retained by context summary. (requires flag: --memoryDebugging=true)',
|
||||
category: 'Memory',
|
||||
args: {
|
||||
filePath: {
|
||||
|
||||
@@ -159,6 +159,21 @@ export class HeapSnapshotFormatter {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
static formatRetainedByContextSummary(
|
||||
summary: DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary,
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`Context count: ${summary.contextCount}`);
|
||||
lines.push(
|
||||
`Retained by context size: ${formatBytesToKb(summary.retainedByContextSize)} (${summary.retainedByContextCount} objects)`,
|
||||
);
|
||||
lines.push(
|
||||
`Not retained by context size: ${formatBytesToKb(summary.notRetainedByContextSize)} (${summary.notRetainedByContextCount} objects)`,
|
||||
);
|
||||
lines.push(`Total size: ${formatBytesToKb(summary.totalSize)}`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
#getSortedAggregates(): AggregatedInfoWithId[] {
|
||||
return Object.values(this.#aggregates).sort((a, b) => b.maxRet - a.maxRet);
|
||||
}
|
||||
|
||||
@@ -154,6 +154,13 @@ export class HeapSnapshotManager {
|
||||
return await snapshot.getNativeContextSizes();
|
||||
}
|
||||
|
||||
async getRetainedByContextSummary(
|
||||
filePath: string,
|
||||
): Promise<DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary> {
|
||||
const snapshot = await this.getSnapshot(filePath);
|
||||
return await snapshot.getRetainedByContextSummary();
|
||||
}
|
||||
|
||||
async getOrCreateIdForClassKey(
|
||||
filePath: string,
|
||||
classKey: string,
|
||||
|
||||
@@ -124,6 +124,7 @@ export interface Response {
|
||||
stats: DevTools.HeapSnapshotModel.HeapSnapshotModel.Statistics,
|
||||
staticData: DevTools.HeapSnapshotModel.HeapSnapshotModel.StaticData | null,
|
||||
nativeContextSizes: DevTools.HeapSnapshotModel.HeapSnapshotModel.NativeContextSizes,
|
||||
retainedByContextSummary: DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary,
|
||||
): void;
|
||||
setHeapSnapshotNodes(
|
||||
nodes: DevTools.HeapSnapshotModel.HeapSnapshotModel.ItemsRange,
|
||||
@@ -265,6 +266,9 @@ export type Context = Readonly<{
|
||||
getHeapSnapshotNativeContextSizes(
|
||||
filePath: string,
|
||||
): Promise<DevTools.HeapSnapshotModel.HeapSnapshotModel.NativeContextSizes>;
|
||||
getHeapSnapshotRetainedByContextSummary(
|
||||
filePath: string,
|
||||
): Promise<DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainedByContextSummary>;
|
||||
getHeapSnapshotNodesById(
|
||||
filePath: string,
|
||||
id: number,
|
||||
|
||||
+11
-2
@@ -51,7 +51,7 @@ export const takeHeapSnapshot = definePageTool({
|
||||
export const getHeapSnapshotSummary = defineTool({
|
||||
name: 'get_heapsnapshot_summary',
|
||||
description:
|
||||
'Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes.',
|
||||
'Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes, and retained by context summary.',
|
||||
annotations: {
|
||||
category: ToolCategory.MEMORY,
|
||||
readOnlyHint: true,
|
||||
@@ -70,8 +70,17 @@ export const getHeapSnapshotSummary = defineTool({
|
||||
const nativeContextSizes = await context.getHeapSnapshotNativeContextSizes(
|
||||
request.params.filePath,
|
||||
);
|
||||
const retainedByContextSummary =
|
||||
await context.getHeapSnapshotRetainedByContextSummary(
|
||||
request.params.filePath,
|
||||
);
|
||||
|
||||
response.setHeapSnapshotStats(stats, staticData, nativeContextSizes);
|
||||
response.setHeapSnapshotStats(
|
||||
stats,
|
||||
staticData,
|
||||
nativeContextSizes,
|
||||
retainedByContextSummary,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -334,4 +334,28 @@ describe('HeapSnapshotFormatter', () => {
|
||||
assert.strictEqual(result, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatRetainedByContextSummary', () => {
|
||||
it('formats retained by context summary correctly', () => {
|
||||
const mockSummary = {
|
||||
contextCount: 2,
|
||||
retainedByContextSize: 5000,
|
||||
retainedByContextCount: 10,
|
||||
notRetainedByContextSize: 1000,
|
||||
notRetainedByContextCount: 5,
|
||||
totalSize: 6000,
|
||||
};
|
||||
|
||||
const result =
|
||||
HeapSnapshotFormatter.formatRetainedByContextSummary(mockSummary);
|
||||
const expected = [
|
||||
'Context count: 2',
|
||||
`Retained by context size: ${formatBytesToKb(5000)} (10 objects)`,
|
||||
`Not retained by context size: ${formatBytesToKb(1000)} (5 objects)`,
|
||||
`Total size: ${formatBytesToKb(6000)}`,
|
||||
].join('\n');
|
||||
|
||||
assert.strictEqual(result, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -443,4 +443,9 @@ nodeId,nodeName,selfSize,retainedSize,attributedSize
|
||||
7199,system / NativeContext,1.2 kB,84.4 kB,84.4 kB
|
||||
Shared Size: 22.9 kB
|
||||
Unattributed Size: 44.8 kB
|
||||
### Retained by Context Summary
|
||||
Context count: 116
|
||||
Retained by context size: 3.5 kB (148 objects)
|
||||
Not retained by context size: 798 kB (11792 objects)
|
||||
Total size: 802 kB
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user