feat: add trend visualization with sparklines to perf report#9939
feat: add trend visualization with sparklines to perf report#9939christian-byrne wants to merge 2 commits intomainfrom
Conversation
🎨 Storybook: ✅ Built — View Storybook |
🎭 Playwright: ✅ 560 passed, 0 failed · 5 flaky📊 Browser Reports
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds trend analysis (sparklines, trend direction/arrows, latest values) to perf reports and updates the GitHub Actions workflow to use a frontend setup, download perf history from the Changes
Sequence DiagramsequenceDiagram
participant GHA as GitHub Actions
participant History as Perf History
participant Report as perf-report.ts
participant Stats as perf-stats.ts
participant Output as Report Output
GHA->>History: fetch/download `perf-data` branch archive
activate History
History-->>GHA: historical reports archive
deactivate History
GHA->>Report: run `pnpm exec tsx scripts/perf-report.ts` with history
activate Report
Report->>Report: getHistoricalTimeSeries() → timeSeries per test/metric
Report->>Stats: sparkline(timeSeries)
activate Stats
Stats-->>Report: sparkline string
deactivate Stats
Report->>Stats: trendDirection(timeSeries)
activate Stats
Stats-->>Report: 'rising' | 'falling' | 'stable'
deactivate Stats
Report->>Stats: trendArrow(direction)
activate Stats
Stats-->>Report: emoji arrow
deactivate Stats
Report->>Output: render Trend section (if ≥3 points) with sparkline, arrow, latest value
deactivate Report
Output-->>GHA: perf report artifact with trends
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
📦 Bundle: 4.99 MB gzip 🟢 -101 BDetailsSummary
Category Glance App Entry Points — 21.7 kB (baseline 21.7 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.08 MB (baseline 1.08 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 75.3 kB (baseline 75.3 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed Panels & Settings — 460 kB (baseline 460 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed User & Accounts — 16.6 kB (baseline 16.6 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed Editors & Dialogs — 81.8 kB (baseline 81.8 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 59 kB (baseline 59 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed Data & Services — 3.17 MB (baseline 3.17 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 15 added / 15 removed Utilities & Hooks — 68.9 kB (baseline 68.9 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 12 added / 12 removed Vendor & Third-Party — 9.78 MB (baseline 9.78 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Other — 8.21 MB (baseline 8.21 MB) • ⚪ 0 BBundles that do not match a named category
Status: 52 added / 52 removed |
⚡ Performance Report
All metrics
Historical variance (last 2 runs)
Raw data{
"timestamp": "2026-03-15T10:43:15.060Z",
"gitSha": "3ba1b519aa4e425ffd5dcb2219c141fd1c0d56dc",
"branch": "feat/perf-trend-visualization",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2059.3709999999987,
"styleRecalcs": 12,
"styleRecalcDurationMs": 11.26,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 357.009,
"heapDeltaBytes": 436916,
"domNodes": 24,
"jsHeapTotalBytes": 23330816,
"scriptDurationMs": 21.296000000000003,
"eventListeners": 6
},
{
"name": "canvas-idle",
"durationMs": 2026.0189999999625,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.71,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 362.85599999999994,
"heapDeltaBytes": 1380668,
"domNodes": 24,
"jsHeapTotalBytes": 18350080,
"scriptDurationMs": 22.169,
"eventListeners": 6
},
{
"name": "canvas-idle",
"durationMs": 2052.696000000026,
"styleRecalcs": 14,
"styleRecalcDurationMs": 15.097,
"layouts": 1,
"layoutDurationMs": 0.2050000000000001,
"taskDurationMs": 376.158,
"heapDeltaBytes": 1439064,
"domNodes": 68,
"jsHeapTotalBytes": 16777216,
"scriptDurationMs": 21.823999999999998,
"eventListeners": 23
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2007.4400000000026,
"styleRecalcs": 85,
"styleRecalcDurationMs": 52.051,
"layouts": 12,
"layoutDurationMs": 3.574,
"taskDurationMs": 1006.2060000000001,
"heapDeltaBytes": -2972412,
"domNodes": 70,
"jsHeapTotalBytes": 16515072,
"scriptDurationMs": 148.439,
"eventListeners": 6
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1816.0160000000474,
"styleRecalcs": 75,
"styleRecalcDurationMs": 37.934,
"layouts": 12,
"layoutDurationMs": 3.655,
"taskDurationMs": 744.761,
"heapDeltaBytes": -3402340,
"domNodes": 59,
"jsHeapTotalBytes": 18874368,
"scriptDurationMs": 130.214,
"eventListeners": 4
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1744.0129999999954,
"styleRecalcs": 73,
"styleRecalcDurationMs": 34.095,
"layouts": 12,
"layoutDurationMs": 3.342,
"taskDurationMs": 722.7979999999999,
"heapDeltaBytes": -3455072,
"domNodes": 56,
"jsHeapTotalBytes": 19136512,
"scriptDurationMs": 127.916,
"eventListeners": 4
},
{
"name": "dom-widget-clipping",
"durationMs": 545.439000000016,
"styleRecalcs": 14,
"styleRecalcDurationMs": 11.164000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 341.245,
"heapDeltaBytes": 11768676,
"domNodes": 23,
"jsHeapTotalBytes": 17825792,
"scriptDurationMs": 64.68499999999999,
"eventListeners": 2
},
{
"name": "dom-widget-clipping",
"durationMs": 578.661000000011,
"styleRecalcs": 14,
"styleRecalcDurationMs": 10.278,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 358.886,
"heapDeltaBytes": 12732068,
"domNodes": 23,
"jsHeapTotalBytes": 16252928,
"scriptDurationMs": 65.84700000000001,
"eventListeners": 2
},
{
"name": "dom-widget-clipping",
"durationMs": 580.3150000000414,
"styleRecalcs": 14,
"styleRecalcDurationMs": 10.092,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 339.344,
"heapDeltaBytes": 12905032,
"domNodes": 24,
"jsHeapTotalBytes": 14417920,
"scriptDurationMs": 63.164,
"eventListeners": 2
},
{
"name": "large-graph-idle",
"durationMs": 2007.1950000000243,
"styleRecalcs": 14,
"styleRecalcDurationMs": 14.176,
"layouts": 1,
"layoutDurationMs": 0.2270000000000002,
"taskDurationMs": 500.1720000000001,
"heapDeltaBytes": 17537228,
"domNodes": 68,
"jsHeapTotalBytes": 9437184,
"scriptDurationMs": 91.834,
"eventListeners": 23
},
{
"name": "large-graph-idle",
"durationMs": 2025.6909999999948,
"styleRecalcs": 14,
"styleRecalcDurationMs": 13.652999999999999,
"layouts": 1,
"layoutDurationMs": 0.23700000000000002,
"taskDurationMs": 497.131,
"heapDeltaBytes": -9163828,
"domNodes": 68,
"jsHeapTotalBytes": 8626176,
"scriptDurationMs": 89.88499999999999,
"eventListeners": 23
},
{
"name": "large-graph-idle",
"durationMs": 2031.2829999999735,
"styleRecalcs": 14,
"styleRecalcDurationMs": 13.262,
"layouts": 1,
"layoutDurationMs": 0.1929999999999998,
"taskDurationMs": 494.3070000000001,
"heapDeltaBytes": 17205488,
"domNodes": 69,
"jsHeapTotalBytes": 8626176,
"scriptDurationMs": 89.576,
"eventListeners": 21
},
{
"name": "large-graph-pan",
"durationMs": 2095.702999999986,
"styleRecalcs": 70,
"styleRecalcDurationMs": 15.991999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1085.134,
"heapDeltaBytes": 3622568,
"domNodes": 20,
"jsHeapTotalBytes": 9498624,
"scriptDurationMs": 451.89,
"eventListeners": 6
},
{
"name": "large-graph-pan",
"durationMs": 2091.980000000035,
"styleRecalcs": 70,
"styleRecalcDurationMs": 16.532999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1010.4090000000001,
"heapDeltaBytes": 1539328,
"domNodes": 20,
"jsHeapTotalBytes": 8445952,
"scriptDurationMs": 390.1329999999999,
"eventListeners": 6
},
{
"name": "large-graph-pan",
"durationMs": 2103.11200000001,
"styleRecalcs": 71,
"styleRecalcDurationMs": 18.294000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1017.898,
"heapDeltaBytes": 3825736,
"domNodes": 24,
"jsHeapTotalBytes": 9236480,
"scriptDurationMs": 386.355,
"eventListeners": 6
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 565.9089999999765,
"styleRecalcs": 49,
"styleRecalcDurationMs": 12.906,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 357.12300000000005,
"heapDeltaBytes": 12652224,
"domNodes": 23,
"jsHeapTotalBytes": 16252928,
"scriptDurationMs": 120.51799999999999,
"eventListeners": 8
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 600.4790000000071,
"styleRecalcs": 48,
"styleRecalcDurationMs": 11.827,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 354.521,
"heapDeltaBytes": 6112460,
"domNodes": 22,
"jsHeapTotalBytes": 12058624,
"scriptDurationMs": 121.89500000000001,
"eventListeners": 8
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 578.4290000000283,
"styleRecalcs": 49,
"styleRecalcDurationMs": 14.988,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 368.788,
"heapDeltaBytes": 12850380,
"domNodes": 22,
"jsHeapTotalBytes": 13631488,
"scriptDurationMs": 123.221,
"eventListeners": 8
},
{
"name": "subgraph-idle",
"durationMs": 2002.699000000007,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.846999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 337.30499999999995,
"heapDeltaBytes": 717244,
"domNodes": 24,
"jsHeapTotalBytes": 17825792,
"scriptDurationMs": 18.211,
"eventListeners": 6
},
{
"name": "subgraph-idle",
"durationMs": 2002.4839999999813,
"styleRecalcs": 12,
"styleRecalcDurationMs": 11.513999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 365.579,
"heapDeltaBytes": -25276,
"domNodes": 24,
"jsHeapTotalBytes": 19136512,
"scriptDurationMs": 20.136000000000003,
"eventListeners": 6
},
{
"name": "subgraph-idle",
"durationMs": 2021.8899999999849,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.592999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 339.05400000000003,
"heapDeltaBytes": 344984,
"domNodes": 24,
"jsHeapTotalBytes": 18350080,
"scriptDurationMs": 17.400000000000002,
"eventListeners": 6
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1708.328999999992,
"styleRecalcs": 79,
"styleRecalcDurationMs": 41.367000000000004,
"layouts": 17,
"layoutDurationMs": 4.915,
"taskDurationMs": 689.3720000000001,
"heapDeltaBytes": -7013676,
"domNodes": 107,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 97.93499999999999,
"eventListeners": 21
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1689.1710000000444,
"styleRecalcs": 77,
"styleRecalcDurationMs": 36.982,
"layouts": 16,
"layoutDurationMs": 4.2700000000000005,
"taskDurationMs": 637.381,
"heapDeltaBytes": -7615956,
"domNodes": 64,
"jsHeapTotalBytes": 18612224,
"scriptDurationMs": 93.329,
"eventListeners": 4
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1991.4549999999736,
"styleRecalcs": 86,
"styleRecalcDurationMs": 48.105999999999995,
"layouts": 17,
"layoutDurationMs": 4.428999999999999,
"taskDurationMs": 895.992,
"heapDeltaBytes": -6718368,
"domNodes": 118,
"jsHeapTotalBytes": 16515072,
"scriptDurationMs": 96.64299999999999,
"eventListeners": 23
}
]
} |
- Add sparkline(), trendDirection(), trendArrow() to perf-stats.ts - Add collapsible 'Trend' section to perf-report.ts showing ASCII sparklines and directional arrows for each metric over last N commits on main - Add historical data download step to pr-perf-report.yaml from perf-data orphan branch - Switch pr-perf-report.yaml to setup-frontend action and pnpm exec - Add tests for all new functions (sparkline, trendDirection, trendArrow)
4068979 to
b2d31dc
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/perf-stats.ts (1)
86-104: Consider the zero-baseline edge case.When
firstMean === 0butsecondMean > 0, the function returns'stable'to avoid division by zero. This is a safe fallback, but it masks what could be a significant upward trend (e.g., a metric that was previously zero and is now non-zero).If this scenario is unlikely for the performance metrics being tracked, the current behavior is acceptable. Otherwise, consider treating it as
'rising'whensecondMean > 0andfirstMean === 0.Optional: Handle zero-baseline as rising
- if (firstMean === 0) return 'stable' + if (firstMean === 0) return secondMean > 0 ? 'rising' : 'stable'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/perf-stats.ts` around lines 86 - 104, The current trendDirection function returns 'stable' when firstMean === 0 to avoid division by zero, which hides cases where secondMean > 0; update trendDirection to explicitly handle the zero-baseline: if firstMean === 0 and secondMean > 0 return 'rising' (and if both are 0 keep 'stable'), otherwise compute changePct as before using ((secondMean - firstMean) / firstMean) * 100 to determine 'rising'/'falling'/'stable' while avoiding division by zero; refer to the function trendDirection and the variables firstMean, secondMean, and changePct when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@scripts/perf-stats.ts`:
- Around line 86-104: The current trendDirection function returns 'stable' when
firstMean === 0 to avoid division by zero, which hides cases where secondMean >
0; update trendDirection to explicitly handle the zero-baseline: if firstMean
=== 0 and secondMean > 0 return 'rising' (and if both are 0 keep 'stable'),
otherwise compute changePct as before using ((secondMean - firstMean) /
firstMean) * 100 to determine 'rising'/'falling'/'stable' while avoiding
division by zero; refer to the function trendDirection and the variables
firstMean, secondMean, and changePct when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: daf48065-805c-4f22-b1c5-8ad9d68733b1
📒 Files selected for processing (4)
.github/workflows/pr-perf-report.yamlscripts/perf-report.tsscripts/perf-stats.test.tsscripts/perf-stats.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- scripts/perf-stats.test.ts
Summary
Add historical trend visualization (ASCII sparklines + directional arrows) to the performance PR report, showing how each metric has moved over recent commits on main.
Changes
sparkline(),trendDirection(),trendArrow()functions inperf-stats.ts. New collapsible "Trend" section in the perf report showing per-metric sparklines, direction indicators, and latest values. CI workflow updated to download historical data from theperf-dataorphan branch and switched tosetup-frontendaction withpnpm exec tsx.Review Focus
trendDirection()uses a split-half mean comparison with ±10% threshold — review whether this sensitivity is appropriategit archivestep inpr-perf-report.yamlis idempotent and fails silently if no perf-history data exists yet on the perf-data branch┆Issue is synchronized with this Notion page by Unito