Skip to content

Commit 580b967

Browse files
piotrskiclaude
andcommitted
fix: improve profile diff performance, UX, and error handling
- Use Map lookup instead of linear scan for self durations - Clarify before/after order in help text - Add --threshold flag to configure regression sensitivity (default 5%) - Show clear error messages when export files can't be read - Show actual threshold value in "no changes" message - Fix constant naming convention (DEFAULT_THRESHOLD_PCT) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ca44587 commit 580b967

3 files changed

Lines changed: 30 additions & 13 deletions

File tree

packages/agent-react-devtools/src/cli.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Profiling:
5353
profile timeline [--limit N] Commit timeline
5454
profile commit <N | #N> [--limit N] Detail for specific commit
5555
profile export <file> Export as React DevTools JSON
56-
profile diff <a.json> <b.json> [--limit N] Compare two exports`;
56+
profile diff <before.json> <after.json> [--limit N] [--threshold N] Compare two exports`;
5757
}
5858

5959
function parseArgs(argv: string[]): {
@@ -131,14 +131,27 @@ async function main(): Promise<void> {
131131
const fileA = command[2];
132132
const fileB = command[3];
133133
if (!fileA || !fileB) {
134-
console.error('Usage: devtools profile diff <before.json> <after.json> [--limit N]');
134+
console.error('Usage: devtools profile diff <before.json> <after.json> [--limit N] [--threshold N]');
135135
process.exit(1);
136136
}
137137
const { loadExportFile, diffProfiles } = await import('./profile-diff.js');
138-
const before = loadExportFile(resolve(fileA));
139-
const after = loadExportFile(resolve(fileB));
140-
const diff = diffProfiles(before, after);
138+
let before: ReturnType<typeof loadExportFile>;
139+
let after: ReturnType<typeof loadExportFile>;
140+
try {
141+
before = loadExportFile(resolve(fileA));
142+
} catch (e) {
143+
console.error(`Error reading ${fileA}: ${(e as Error).message}`);
144+
process.exit(1);
145+
}
146+
try {
147+
after = loadExportFile(resolve(fileB));
148+
} catch (e) {
149+
console.error(`Error reading ${fileB}: ${(e as Error).message}`);
150+
process.exit(1);
151+
}
141152
const limit = parseNumericFlag(flags, 'limit');
153+
const threshold = parseNumericFlag(flags, 'threshold');
154+
const diff = diffProfiles(before, after, threshold);
142155
console.log(formatProfileDiff(diff, limit));
143156
return;
144157
}

packages/agent-react-devtools/src/formatters.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ export function formatProfileDiff(diff: ProfileDiffResult, limit?: number): stri
374374

375375
if (diff.regressed.length === 0 && diff.improved.length === 0 && diff.added.length === 0 && diff.removed.length === 0) {
376376
lines.push('');
377-
lines.push('No significant changes (all within 5% threshold)');
377+
lines.push(`No significant changes (all within ${s.thresholdPct}% threshold)`);
378378
}
379379

380380
return lines.join('\n');

packages/agent-react-devtools/src/profile-diff.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface ProfileDiffResult {
2929
totalCommitsAfter: number;
3030
totalDurationBefore: number;
3131
totalDurationAfter: number;
32+
thresholdPct: number;
3233
};
3334
}
3435

@@ -57,6 +58,8 @@ export function extractStats(data: ProfilingDataExport): Map<string, ComponentSt
5758

5859
for (const root of data.dataForRoots) {
5960
for (const commit of root.commitData) {
61+
const selfMap = new Map(commit.fiberSelfDurations);
62+
6063
for (const [id, duration] of commit.fiberActualDurations) {
6164
const name = nameMap.get(id);
6265
if (!name) continue;
@@ -70,10 +73,9 @@ export function extractStats(data: ProfilingDataExport): Map<string, ComponentSt
7073
entry.count++;
7174
if (duration > entry.maxActual) entry.maxActual = duration;
7275

73-
// Find matching self duration
74-
const selfEntry = commit.fiberSelfDurations.find(([sid]) => sid === id);
75-
if (selfEntry) {
76-
entry.totalSelf += selfEntry[1];
76+
const selfDuration = selfMap.get(id);
77+
if (selfDuration !== undefined) {
78+
entry.totalSelf += selfDuration;
7779
}
7880
}
7981
}
@@ -111,11 +113,12 @@ function getTotalCommits(data: ProfilingDataExport): number {
111113
return total;
112114
}
113115

114-
const THRESHOLD_PCT = 5;
116+
const DEFAULT_THRESHOLD_PCT = 5;
115117

116118
export function diffProfiles(
117119
before: ProfilingDataExport,
118120
after: ProfilingDataExport,
121+
thresholdPct = DEFAULT_THRESHOLD_PCT,
119122
): ProfileDiffResult {
120123
const statsBefore = extractStats(before);
121124
const statsAfter = extractStats(after);
@@ -168,9 +171,9 @@ export function diffProfiles(
168171
renderCountDelta: a.renderCount - b.renderCount,
169172
};
170173

171-
if (pct !== null && pct > THRESHOLD_PCT) {
174+
if (pct !== null && pct > thresholdPct) {
172175
regressed.push(entry);
173-
} else if (pct !== null && pct < -THRESHOLD_PCT) {
176+
} else if (pct !== null && pct < -thresholdPct) {
174177
improved.push(entry);
175178
}
176179
// within threshold = unchanged, skip
@@ -193,6 +196,7 @@ export function diffProfiles(
193196
totalCommitsAfter: getTotalCommits(after),
194197
totalDurationBefore: getTotalDuration(before),
195198
totalDurationAfter: getTotalDuration(after),
199+
thresholdPct,
196200
},
197201
};
198202
}

0 commit comments

Comments
 (0)