mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
feat: Add pagination to compare_heapsnapshots (#3140)
Support pagination in the summary diff of compare_heapsnapshots. The "detailed" case will be done in a follow-up. For this `HeapSnapshotManager.getClassDiffs()` now adds an `index` field to each class. It also computes a total for all classes (not just the current page). Both classes and the total are then returned in the new `HeapSnapshotDiff` object. Previously this method only returned classes as `HeapSnapshotClassDiff[]`.
This commit is contained in:
1 parent
9171f77c4d
commit
daebfd1f6f
14 files changed
+257
-59
No files matched your search
@@ -552,6 +552,8 @@ in the DevTools Elements panel (if any).
|
||||
- **currentFilePath** (string) **(required)**: A path to the current .heapsnapshot file (later snapshot).
|
||||
- **classIndex** (number) _(optional)_: Optional 0-based index of the class in the summary list to filter results, showing individual objects.
|
||||
- **maxNameLength** (integer) _(optional)_: Maximum length of names before truncation. Defaults to 100.
|
||||
- **pageIdx** (integer) _(optional)_: The page index for pagination.
|
||||
- **pageSize** (integer) _(optional)_: The page size for pagination.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import {overrideDevToolsGlobals} from './devtools/DevtoolsUtils.js';
|
||||
import type {
|
||||
HeapSnapshotManager,
|
||||
HeapSnapshotAggregateData,
|
||||
HeapSnapshotClassDiff,
|
||||
HeapSnapshotDiff,
|
||||
HeapSnapshotDetailedClassDiff,
|
||||
DuplicateStringGroup,
|
||||
HeapEdgesQueryOptions,
|
||||
@@ -993,7 +993,7 @@ export class McpContext implements Context {
|
||||
async getHeapSnapshotClassDiffs(
|
||||
baseFilePath: string,
|
||||
currentFilePath: string,
|
||||
): Promise<HeapSnapshotClassDiff[]> {
|
||||
): Promise<HeapSnapshotDiff> {
|
||||
return await this.#heapSnapshotManager.getClassDiffs(
|
||||
baseFilePath,
|
||||
currentFilePath,
|
||||
|
||||
+23
-10
@@ -34,7 +34,9 @@ import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
|
||||
import type {
|
||||
HeapSnapshotAggregateData,
|
||||
HeapSnapshotClassDiff,
|
||||
HeapSnapshotClassDiffTotals,
|
||||
HeapSnapshotDetailedClassDiff,
|
||||
HeapSnapshotDiff,
|
||||
DuplicateStringGroup,
|
||||
} from './processors/HeapSnapshotManager.js';
|
||||
import type {McpContext} from './McpContext.js';
|
||||
@@ -113,7 +115,7 @@ export class McpResponse implements Response {
|
||||
nodes?: DevTools.HeapSnapshotModel.HeapSnapshotModel.ItemsRange;
|
||||
retainingPaths?: DevTools.HeapSnapshotModel.HeapSnapshotModel.RetainingPaths;
|
||||
dominators?: DevTools.HeapSnapshotModel.HeapSnapshotModel.DominatorChain;
|
||||
classDiffs?: HeapSnapshotClassDiff[];
|
||||
diff?: HeapSnapshotDiff;
|
||||
detailedClassDiff?: HeapSnapshotDetailedClassDiff;
|
||||
duplicateStrings?: DuplicateStringGroup[];
|
||||
objectInfo?: DevTools.HeapSnapshotModel.HeapSnapshotModel.ObjectInfo;
|
||||
@@ -458,13 +460,14 @@ export class McpResponse implements Response {
|
||||
}
|
||||
|
||||
setHeapSnapshotClassDiffs(
|
||||
classDiffs: HeapSnapshotClassDiff[],
|
||||
options?: HeapSnapshotFormatOptions,
|
||||
diff: HeapSnapshotDiff,
|
||||
options?: PaginationOptions & HeapSnapshotFormatOptions,
|
||||
): void {
|
||||
this.#heapSnapshotOptions = {
|
||||
...this.#heapSnapshotOptions,
|
||||
include: true,
|
||||
classDiffs,
|
||||
diff,
|
||||
pagination: options,
|
||||
formatOptions: options,
|
||||
};
|
||||
}
|
||||
@@ -917,12 +920,13 @@ export class McpResponse implements Response {
|
||||
objectCount: number;
|
||||
totalSelfSize: number;
|
||||
};
|
||||
classDiffStats?: HeapSnapshotClassDiffTotals;
|
||||
};
|
||||
heapSnapshotAggregates?: readonly object[];
|
||||
heapSnapshotNodes?: readonly object[];
|
||||
heapSnapshotRetainingPaths?: object;
|
||||
heapSnapshotDominators?: readonly object[];
|
||||
heapSnapshotClassDiffs?: HeapSnapshotClassDiff[];
|
||||
heapSnapshotClassDiffs?: readonly HeapSnapshotClassDiff[];
|
||||
heapSnapshotDetailedClassDiff?: HeapSnapshotDetailedClassDiff;
|
||||
heapSnapshotDuplicateStrings?: readonly DuplicateStringGroup[];
|
||||
heapSnapshotObjectDetails?: DevTools.HeapSnapshotModel.HeapSnapshotModel.ObjectInfo;
|
||||
@@ -1453,23 +1457,32 @@ Call ${handleDialog(this.#args).name} to handle it before continuing.`);
|
||||
}
|
||||
structuredContent.heapSnapshotDominators = dominators;
|
||||
}
|
||||
const classDiffs = this.#heapSnapshotOptions.classDiffs;
|
||||
if (classDiffs) {
|
||||
const diff = this.#heapSnapshotOptions.diff;
|
||||
if (diff) {
|
||||
response.push('### Heap Snapshot Diff');
|
||||
response.push(HeapSnapshotFormatter.formatDiffTotals(diff.totals));
|
||||
structuredContent.heapSnapshot = structuredContent.heapSnapshot || {};
|
||||
structuredContent.heapSnapshot.classDiffStats = diff.totals;
|
||||
const paginationData = this.#dataWithPagination(
|
||||
diff.classDiffs,
|
||||
this.#heapSnapshotOptions.pagination,
|
||||
);
|
||||
structuredContent.pagination = paginationData.pagination;
|
||||
response.push(...paginationData.info);
|
||||
response.push(
|
||||
compactEncode
|
||||
? compactEncode(
|
||||
classDiffs.map(diff => ({
|
||||
paginationData.items.map(diff => ({
|
||||
...diff,
|
||||
className: formatName(diff.className, formatOptions),
|
||||
})),
|
||||
)
|
||||
: HeapSnapshotFormatter.formatDiffSummary(
|
||||
classDiffs,
|
||||
paginationData.items,
|
||||
formatOptions,
|
||||
),
|
||||
);
|
||||
structuredContent.heapSnapshotClassDiffs = classDiffs;
|
||||
structuredContent.heapSnapshotClassDiffs = paginationData.items;
|
||||
}
|
||||
const detailedClassDiff = this.#heapSnapshotOptions.detailedClassDiff;
|
||||
if (detailedClassDiff) {
|
||||
|
||||
@@ -227,6 +227,20 @@ export const commands: Commands = {
|
||||
required: false,
|
||||
positional: false,
|
||||
},
|
||||
pageIdx: {
|
||||
name: 'pageIdx',
|
||||
type: 'integer',
|
||||
description: 'The page index for pagination.',
|
||||
required: false,
|
||||
positional: false,
|
||||
},
|
||||
pageSize: {
|
||||
name: 'pageSize',
|
||||
type: 'integer',
|
||||
description: 'The page size for pagination.',
|
||||
required: false,
|
||||
positional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
drag: {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type {
|
||||
HeapSnapshotAggregate,
|
||||
HeapSnapshotClassDiff,
|
||||
HeapSnapshotClassDiffTotals,
|
||||
HeapSnapshotDetailedClassDiff,
|
||||
DuplicateStringGroup,
|
||||
} from '../processors/HeapSnapshotManager.js';
|
||||
@@ -267,7 +268,7 @@ export class HeapSnapshotFormatter {
|
||||
}
|
||||
|
||||
static formatDiffSummary(
|
||||
diffs: HeapSnapshotClassDiff[],
|
||||
diffs: readonly HeapSnapshotClassDiff[],
|
||||
options: HeapSnapshotFormatOptions,
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
@@ -275,17 +276,19 @@ export class HeapSnapshotFormatter {
|
||||
'index,className,addedCount,removedCount,countDelta,addedSize,removedSize,sizeDelta',
|
||||
);
|
||||
|
||||
let index = 0;
|
||||
for (const diff of diffs) {
|
||||
lines.push(
|
||||
`${index},${formatName(diff.className, options)},${diff.addedCount},${diff.removedCount},${diff.countDelta},${formatBytesToKb(diff.addedSize)},${formatBytesToKb(diff.removedSize)},${formatBytesToKb(diff.sizeDelta)}`,
|
||||
`${diff.index},${formatName(diff.className, options)},${diff.addedCount},${diff.removedCount},${diff.countDelta},${formatBytesToKb(diff.addedSize)},${formatBytesToKb(diff.removedSize)},${formatBytesToKb(diff.sizeDelta)}`,
|
||||
);
|
||||
index++;
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
static formatDiffTotals(totals: HeapSnapshotClassDiffTotals): string {
|
||||
return `Total: # new: ${totals.addedCount}, # deleted: ${totals.removedCount}, # delta: ${formatSignedCount(totals.countDelta)}, alloc size: ${formatSignedSize(totals.addedSize)}, freed size: ${formatSignedSize(totals.removedSize)}, size delta: ${formatSignedSize(totals.sizeDelta)}`;
|
||||
}
|
||||
|
||||
static formatDiffDetails(
|
||||
diff: HeapSnapshotDetailedClassDiff,
|
||||
options: HeapSnapshotFormatOptions,
|
||||
|
||||
@@ -24,7 +24,14 @@ export interface HeapSnapshotAggregateData {
|
||||
totalSelfSize: number;
|
||||
}
|
||||
|
||||
export interface HeapSnapshotDiff {
|
||||
classDiffs: HeapSnapshotClassDiff[];
|
||||
totals: HeapSnapshotClassDiffTotals;
|
||||
}
|
||||
|
||||
export interface HeapSnapshotClassDiff {
|
||||
/** Position in the full sorted diff list. Use as `classIndex`. */
|
||||
index: number;
|
||||
className: string;
|
||||
addedCount: number;
|
||||
removedCount: number;
|
||||
@@ -41,6 +48,15 @@ export interface HeapSnapshotDetailedClassDiff extends HeapSnapshotClassDiff {
|
||||
deletedSelfSizes: number[];
|
||||
}
|
||||
|
||||
export interface HeapSnapshotClassDiffTotals {
|
||||
addedCount: number;
|
||||
removedCount: number;
|
||||
countDelta: number;
|
||||
addedSize: number;
|
||||
removedSize: number;
|
||||
sizeDelta: number;
|
||||
}
|
||||
|
||||
export type DuplicateStringGroup =
|
||||
DevTools.HeapSnapshotModel.HeapSnapshotModel.DuplicateStringGroup;
|
||||
|
||||
@@ -303,20 +319,39 @@ export class HeapSnapshotManager {
|
||||
async getClassDiffs(
|
||||
baseFilePath: string,
|
||||
currentFilePath: string,
|
||||
): Promise<HeapSnapshotClassDiff[]> {
|
||||
): Promise<HeapSnapshotDiff> {
|
||||
const rawDiffs = await this.#getSortedRawClassDiffs(
|
||||
baseFilePath,
|
||||
currentFilePath,
|
||||
);
|
||||
return rawDiffs.map(rawDiff => ({
|
||||
className: rawDiff.name,
|
||||
addedCount: rawDiff.addedCount,
|
||||
removedCount: rawDiff.removedCount,
|
||||
countDelta: rawDiff.countDelta,
|
||||
addedSize: rawDiff.addedSize,
|
||||
removedSize: rawDiff.removedSize,
|
||||
sizeDelta: rawDiff.sizeDelta,
|
||||
}));
|
||||
const classDiffs: HeapSnapshotClassDiff[] = [];
|
||||
const totals: HeapSnapshotClassDiffTotals = {
|
||||
addedCount: 0,
|
||||
removedCount: 0,
|
||||
countDelta: 0,
|
||||
addedSize: 0,
|
||||
removedSize: 0,
|
||||
sizeDelta: 0,
|
||||
};
|
||||
for (const [index, rawDiff] of rawDiffs.entries()) {
|
||||
classDiffs.push({
|
||||
index,
|
||||
className: rawDiff.name,
|
||||
addedCount: rawDiff.addedCount,
|
||||
removedCount: rawDiff.removedCount,
|
||||
countDelta: rawDiff.countDelta,
|
||||
addedSize: rawDiff.addedSize,
|
||||
removedSize: rawDiff.removedSize,
|
||||
sizeDelta: rawDiff.sizeDelta,
|
||||
});
|
||||
totals.addedCount += rawDiff.addedCount;
|
||||
totals.removedCount += rawDiff.removedCount;
|
||||
totals.countDelta += rawDiff.countDelta;
|
||||
totals.addedSize += rawDiff.addedSize;
|
||||
totals.removedSize += rawDiff.removedSize;
|
||||
totals.sizeDelta += rawDiff.sizeDelta;
|
||||
}
|
||||
return {classDiffs, totals};
|
||||
}
|
||||
|
||||
async getDetailedClassDiff(
|
||||
@@ -335,6 +370,7 @@ export class HeapSnapshotManager {
|
||||
);
|
||||
}
|
||||
return {
|
||||
index: classIndex,
|
||||
className: rawDiff.name,
|
||||
addedCount: rawDiff.addedCount,
|
||||
removedCount: rawDiff.removedCount,
|
||||
|
||||
@@ -1091,6 +1091,14 @@
|
||||
{
|
||||
"name": "max_name_length",
|
||||
"argType": "number"
|
||||
},
|
||||
{
|
||||
"name": "page_idx",
|
||||
"argType": "number"
|
||||
},
|
||||
{
|
||||
"name": "page_size",
|
||||
"argType": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type {ParsedArguments} from '../config/ConfigParser.js';
|
||||
import type {
|
||||
HeapSnapshotAggregateData,
|
||||
HeapSnapshotClassDiff,
|
||||
HeapSnapshotDiff,
|
||||
HeapSnapshotDetailedClassDiff,
|
||||
DuplicateStringGroup,
|
||||
HeapEdgesQueryOptions,
|
||||
@@ -220,8 +220,8 @@ export interface Response {
|
||||
options?: HeapSnapshotFormatOptions,
|
||||
): void;
|
||||
setHeapSnapshotClassDiffs(
|
||||
classDiffs: HeapSnapshotClassDiff[],
|
||||
options?: HeapSnapshotFormatOptions,
|
||||
diff: HeapSnapshotDiff,
|
||||
options?: PaginationOptions & HeapSnapshotFormatOptions,
|
||||
): void;
|
||||
setHeapSnapshotDetailedClassDiff(
|
||||
detailedClassDiff: HeapSnapshotDetailedClassDiff,
|
||||
@@ -400,7 +400,7 @@ export type Context = Readonly<{
|
||||
getHeapSnapshotClassDiffs(
|
||||
baseFilePath: string,
|
||||
currentFilePath: string,
|
||||
): Promise<HeapSnapshotClassDiff[]>;
|
||||
): Promise<HeapSnapshotDiff>;
|
||||
getHeapSnapshotDetailedClassDiff(
|
||||
baseFilePath: string,
|
||||
currentFilePath: string,
|
||||
|
||||
@@ -437,6 +437,8 @@ export const compareHeapSnapshots = defineTool(() => ({
|
||||
'Optional 0-based index of the class in the summary list to filter results, showing individual objects.',
|
||||
),
|
||||
maxNameLength: maxNameLengthSchema,
|
||||
pageIdx: pageIdxSchema,
|
||||
pageSize: pageSizeSchema,
|
||||
},
|
||||
blockedByDialog: false,
|
||||
handler: async (request, response, context) => {
|
||||
@@ -455,6 +457,8 @@ export const compareHeapSnapshots = defineTool(() => ({
|
||||
request.params.currentFilePath,
|
||||
);
|
||||
response.setHeapSnapshotClassDiffs(diff, {
|
||||
pageIdx: request.params.pageIdx,
|
||||
pageSize: request.params.pageSize,
|
||||
maxNameLength: request.params.maxNameLength,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,9 +40,11 @@ import {
|
||||
createMockCSSProperty,
|
||||
createMockCSSStyleDeclaration,
|
||||
createMockCSSStyleRule,
|
||||
createMockClassDiffs,
|
||||
createMockClassDiff,
|
||||
createMockClassDiffTotals,
|
||||
createMockContextAnalysisResult,
|
||||
createMockDetailedClassDiff,
|
||||
createMockHeapSnapshotDiff,
|
||||
createMockHeapSnapshotEdge,
|
||||
createMockHeapSnapshotNode,
|
||||
createMockHeapSnapshotStats,
|
||||
@@ -1688,8 +1690,7 @@ describe('McpResponse heap snapshot formatting', () => {
|
||||
|
||||
it('formats class diff summary and detailed diff headers', async () => {
|
||||
const response = new McpResponse(createMockParsedArguments());
|
||||
const diffs = createMockClassDiffs();
|
||||
response.setHeapSnapshotClassDiffs(diffs);
|
||||
response.setHeapSnapshotClassDiffs(createMockHeapSnapshotDiff());
|
||||
|
||||
const context = createMockMcpContext();
|
||||
const {content} = await response.handle(context);
|
||||
@@ -1708,12 +1709,44 @@ describe('McpResponse heap snapshot formatting', () => {
|
||||
assert.ok(detailedText.includes('TestClass: # new: 1, # deleted: 0'));
|
||||
});
|
||||
|
||||
it('paginates class diff summary and keeps global indices', async () => {
|
||||
const response = new McpResponse(createMockParsedArguments());
|
||||
const classDiffs = [
|
||||
createMockClassDiff({index: 0, className: 'ClassA'}),
|
||||
createMockClassDiff({index: 1, className: 'ClassB'}),
|
||||
createMockClassDiff({index: 2, className: 'ClassC'}),
|
||||
];
|
||||
const totals = createMockClassDiffTotals({addedCount: 3, countDelta: 3});
|
||||
response.setHeapSnapshotClassDiffs(
|
||||
createMockHeapSnapshotDiff({classDiffs, totals}),
|
||||
{pageIdx: 1, pageSize: 2},
|
||||
);
|
||||
|
||||
const context = createMockMcpContext();
|
||||
const {content, structuredContent} = await response.handle(context);
|
||||
const text = getTextContent(content[0]);
|
||||
|
||||
assert.ok(text.includes('Showing 3-3 of 3 (Page 2 of 2).'));
|
||||
assert.ok(text.includes('Previous page: 0'));
|
||||
assert.ok(text.includes('Total: # new: 3, # deleted: 0, # delta: +3'));
|
||||
assert.ok(text.includes('\n2,ClassC,'));
|
||||
assert.ok(!text.includes('ClassA'));
|
||||
assert.ok(!text.includes('ClassB'));
|
||||
assert.deepStrictEqual(
|
||||
Reflect.get(structuredContent, 'heapSnapshotClassDiffs'),
|
||||
[classDiffs[2]],
|
||||
);
|
||||
const heapData: unknown = Reflect.get(structuredContent, 'heapSnapshot');
|
||||
assert.ok(heapData && typeof heapData === 'object');
|
||||
assert.deepStrictEqual(Reflect.get(heapData, 'classDiffStats'), totals);
|
||||
});
|
||||
|
||||
it('truncates class diff names in compact format', async () => {
|
||||
const context = createMockMcpContext();
|
||||
const diffs = createMockClassDiffs();
|
||||
|
||||
const toonDiffResponse = new McpResponse(createMockParsedArguments());
|
||||
toonDiffResponse.setHeapSnapshotClassDiffs(diffs, {maxNameLength: 4});
|
||||
toonDiffResponse.setHeapSnapshotClassDiffs(createMockHeapSnapshotDiff(), {
|
||||
maxNameLength: 4,
|
||||
});
|
||||
const toonDiffResult = await toonDiffResponse.handle(context, 'toon');
|
||||
const toonDiffText = getTextContent(toonDiffResult.content[0]);
|
||||
assert.ok(toonDiffText.includes('Test...'));
|
||||
|
||||
@@ -162,6 +162,7 @@ describe('HeapSnapshotFormatter', () => {
|
||||
it('includes classes with balanced added and removed objects', () => {
|
||||
const summarized = [
|
||||
{
|
||||
index: 0,
|
||||
className: 'Balanced',
|
||||
addedCount: 1,
|
||||
removedCount: 1,
|
||||
@@ -188,6 +189,7 @@ describe('HeapSnapshotFormatter', () => {
|
||||
it('truncates className according to maxNameLength', () => {
|
||||
const summarized = [
|
||||
{
|
||||
index: 0,
|
||||
className: 'LongClassName',
|
||||
addedCount: 1,
|
||||
removedCount: 0,
|
||||
@@ -212,6 +214,7 @@ describe('HeapSnapshotFormatter', () => {
|
||||
describe('formatDiffDetails', () => {
|
||||
it('formats detailed diffs correctly', () => {
|
||||
const details = {
|
||||
index: 0,
|
||||
className: 'MyClass',
|
||||
addedCount: 2,
|
||||
removedCount: 1,
|
||||
@@ -242,6 +245,7 @@ describe('HeapSnapshotFormatter', () => {
|
||||
|
||||
it('truncates className according to maxNameLength', () => {
|
||||
const details = {
|
||||
index: 0,
|
||||
className: 'LongClassName',
|
||||
addedCount: 1,
|
||||
removedCount: 0,
|
||||
@@ -644,13 +648,17 @@ describe('HeapSnapshotFormatter', () => {
|
||||
|
||||
describe('formatDiffSummary and formatDiffDetails', () => {
|
||||
it('formats diff summary comparing heap-1 to heap-2', async t => {
|
||||
const diffs = await manager.getClassDiffs(heap1Path, heap2Path);
|
||||
t.assert.snapshot(HeapSnapshotFormatter.formatDiffSummary(diffs, {}));
|
||||
const {classDiffs} = await manager.getClassDiffs(heap1Path, heap2Path);
|
||||
t.assert.snapshot(
|
||||
HeapSnapshotFormatter.formatDiffSummary(classDiffs, {}),
|
||||
);
|
||||
});
|
||||
|
||||
it('formats diff summary comparing heap-2 to heap-3', async t => {
|
||||
const diffs = await manager.getClassDiffs(heap2Path, heap3Path);
|
||||
t.assert.snapshot(HeapSnapshotFormatter.formatDiffSummary(diffs, {}));
|
||||
const {classDiffs} = await manager.getClassDiffs(heap2Path, heap3Path);
|
||||
t.assert.snapshot(
|
||||
HeapSnapshotFormatter.formatDiffSummary(classDiffs, {}),
|
||||
);
|
||||
});
|
||||
|
||||
it('formats detailed diff comparing heap-1 to heap-2 with classIndex filter', async t => {
|
||||
|
||||
+38
-12
@@ -38,7 +38,9 @@ import type {
|
||||
HeapSnapshotAggregate,
|
||||
HeapSnapshotAggregateData,
|
||||
HeapSnapshotClassDiff,
|
||||
HeapSnapshotClassDiffTotals,
|
||||
HeapSnapshotDetailedClassDiff,
|
||||
HeapSnapshotDiff,
|
||||
} from '../src/processors/HeapSnapshotManager.js';
|
||||
import {
|
||||
CdpBrowser,
|
||||
@@ -1019,22 +1021,46 @@ export function createMockDominatorChain(): DevTools.HeapSnapshotModel.HeapSnaps
|
||||
return [];
|
||||
}
|
||||
|
||||
export function createMockClassDiffs(): HeapSnapshotClassDiff[] {
|
||||
return [
|
||||
{
|
||||
className: 'TestClass',
|
||||
addedCount: 1,
|
||||
removedCount: 0,
|
||||
countDelta: 1,
|
||||
addedSize: 10,
|
||||
removedSize: 0,
|
||||
sizeDelta: 10,
|
||||
},
|
||||
];
|
||||
export function createMockClassDiff(
|
||||
options: Partial<HeapSnapshotClassDiff> = {},
|
||||
): HeapSnapshotClassDiff {
|
||||
return {
|
||||
index: options.index ?? 0,
|
||||
className: options.className ?? 'TestClass',
|
||||
addedCount: options.addedCount ?? 1,
|
||||
removedCount: options.removedCount ?? 0,
|
||||
countDelta: options.countDelta ?? 1,
|
||||
addedSize: options.addedSize ?? 10,
|
||||
removedSize: options.removedSize ?? 0,
|
||||
sizeDelta: options.sizeDelta ?? 10,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockClassDiffTotals(
|
||||
options: Partial<HeapSnapshotClassDiffTotals> = {},
|
||||
): HeapSnapshotClassDiffTotals {
|
||||
return {
|
||||
addedCount: options.addedCount ?? 1,
|
||||
removedCount: options.removedCount ?? 0,
|
||||
countDelta: options.countDelta ?? 1,
|
||||
addedSize: options.addedSize ?? 10,
|
||||
removedSize: options.removedSize ?? 0,
|
||||
sizeDelta: options.sizeDelta ?? 10,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockHeapSnapshotDiff(
|
||||
options: Partial<HeapSnapshotDiff> = {},
|
||||
): HeapSnapshotDiff {
|
||||
return {
|
||||
classDiffs: options.classDiffs ?? [createMockClassDiff()],
|
||||
totals: options.totals ?? createMockClassDiffTotals(),
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockDetailedClassDiff(): HeapSnapshotDetailedClassDiff {
|
||||
return {
|
||||
index: 0,
|
||||
className: 'TestClass',
|
||||
addedCount: 1,
|
||||
removedCount: 0,
|
||||
|
||||
@@ -46,6 +46,32 @@ describe('HeapSnapshotManager', () => {
|
||||
sinon.assert.calledOnce(disposeSpy);
|
||||
});
|
||||
|
||||
it('assigns class diff indices that resolve to the same detailed diff', async () => {
|
||||
const manager = new HeapSnapshotManager();
|
||||
try {
|
||||
const filePathA = 'tests/fixtures/heap-1.heapsnapshot';
|
||||
const filePathB = 'tests/fixtures/heap-2.heapsnapshot';
|
||||
|
||||
const {classDiffs} = await manager.getClassDiffs(filePathA, filePathB);
|
||||
assert.deepStrictEqual(
|
||||
classDiffs.map(diff => diff.index),
|
||||
classDiffs.map((_, position) => position),
|
||||
);
|
||||
|
||||
const lastDiff = classDiffs.at(-1);
|
||||
assert.ok(lastDiff);
|
||||
const detailedDiff = await manager.getDetailedClassDiff(
|
||||
filePathA,
|
||||
filePathB,
|
||||
lastDiff.index,
|
||||
);
|
||||
assert.strictEqual(detailedDiff.index, lastDiff.index);
|
||||
assert.strictEqual(detailedDiff.className, lastDiff.className);
|
||||
} finally {
|
||||
manager.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Verifies that the caching mechanism in HeapSnapshotManager correctly
|
||||
// distinguishes comparisons when the same "current" snapshot is compared
|
||||
// against different "base" snapshots. If the cache key (diffCacheKey) is
|
||||
@@ -60,10 +86,10 @@ describe('HeapSnapshotManager', () => {
|
||||
|
||||
const firstDiff = await manager.getClassDiffs(filePathA, filePathC);
|
||||
const secondDiff = await manager.getClassDiffs(filePathB, filePathC);
|
||||
const firstNewObjectDiff = firstDiff.find(
|
||||
const firstNewObjectDiff = firstDiff.classDiffs.find(
|
||||
entry => entry.className === 'NewObject',
|
||||
);
|
||||
const secondNewObjectDiff = secondDiff.find(
|
||||
const secondNewObjectDiff = secondDiff.classDiffs.find(
|
||||
entry => entry.className === 'NewObject',
|
||||
);
|
||||
assert.ok(firstNewObjectDiff);
|
||||
|
||||
@@ -31,12 +31,12 @@ import {parseByteSizeRange} from '../../src/utils/bytes.js';
|
||||
import {resolveCanonicalPath} from '../../src/utils/files.js';
|
||||
import {
|
||||
createHandlerMocks,
|
||||
createMockClassDiffs,
|
||||
createMockContextAnalysisResult,
|
||||
createMockDetailedClassDiff,
|
||||
createMockDominatorChain,
|
||||
createMockDuplicateStrings,
|
||||
createMockHeapSnapshotAggregateData,
|
||||
createMockHeapSnapshotDiff,
|
||||
createMockHeapSnapshotStats,
|
||||
createMockHeapSnapshotStaticData,
|
||||
createMockItemsRange,
|
||||
@@ -581,8 +581,8 @@ describe('memory', () => {
|
||||
describe('compare_heapsnapshots', () => {
|
||||
it('returns summary diff when classIndex is omitted', async () => {
|
||||
const {context, response, args} = createHandlerMocks();
|
||||
const diffs = createMockClassDiffs();
|
||||
context.getHeapSnapshotClassDiffs.resolves(diffs);
|
||||
const diff = createMockHeapSnapshotDiff();
|
||||
context.getHeapSnapshotClassDiffs.resolves(diff);
|
||||
|
||||
await compareHeapSnapshots(args).handler(
|
||||
{
|
||||
@@ -602,8 +602,33 @@ describe('memory', () => {
|
||||
);
|
||||
sinon.assert.calledOnceWithExactly(
|
||||
response.setHeapSnapshotClassDiffs,
|
||||
diffs,
|
||||
{maxNameLength: undefined},
|
||||
diff,
|
||||
{pageIdx: undefined, pageSize: undefined, maxNameLength: undefined},
|
||||
);
|
||||
});
|
||||
|
||||
it('passes pagination options to the summary diff', async () => {
|
||||
const {context, response, args} = createHandlerMocks();
|
||||
const diff = createMockHeapSnapshotDiff();
|
||||
context.getHeapSnapshotClassDiffs.resolves(diff);
|
||||
|
||||
await compareHeapSnapshots(args).handler(
|
||||
{
|
||||
params: {
|
||||
baseFilePath: 'heap1.heapsnapshot',
|
||||
currentFilePath: 'heap2.heapsnapshot',
|
||||
pageIdx: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
},
|
||||
response,
|
||||
context,
|
||||
);
|
||||
|
||||
sinon.assert.calledOnceWithExactly(
|
||||
response.setHeapSnapshotClassDiffs,
|
||||
diff,
|
||||
{pageIdx: 1, pageSize: 10, maxNameLength: undefined},
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user