Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion .github/workflows/deploy-perf-graph.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ name: Deploy Performance Graph

on:
workflow_run:
workflows: ["Picofuzz Tests"]
# "Picofuzz Full Chain" runs on its own schedule and uploads the
# picofuzz-csv-full_chain artifact; listing it here redeploys the page
# same-day when a fresh full-chain result lands.
workflows: ["Picofuzz Tests", "Picofuzz Full Chain"]
types: [completed]
branches: [main]
workflow_dispatch:
Expand All @@ -16,6 +19,8 @@ permissions:
contents: read
pages: write
id-token: write
# Read the Actions API to resolve the latest "Picofuzz Full Chain" run.
actions: read

concurrency:
group: "pages"
Expand Down Expand Up @@ -50,6 +55,47 @@ jobs:
run-id: ${{ github.event.workflow_run.id || inputs.workflow_run_id }}
continue-on-error: true

# The full-chain CSV lives in the separate "Picofuzz Full Chain" workflow,
# so the triggering run above never contains it. Resolve that workflow's
# latest successful run and pull its artifact regardless of what triggered
# this deploy, so the full-chain chart stays fresh on every deploy.
- name: Get latest Picofuzz Full Chain run ID
id: full-chain-run
run: |
api() {
curl -sS -H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" "$1"
}
# `// empty` turns a missing/null match into an empty string so an
# unresolved run never leaks a literal "null" into the output.
WORKFLOW_ID=$(api "https://api.github.com/repos/$GH_REPO/actions/workflows" \
| jq -r '.workflows[]? | select(.name == "Picofuzz Full Chain") | .id // empty') || true
RUN_ID=""
if [ -n "$WORKFLOW_ID" ]; then
RUN_ID=$(api "https://api.github.com/repos/$GH_REPO/actions/workflows/$WORKFLOW_ID/runs?branch=main&status=success&per_page=1" \
| jq -r '.workflow_runs[0].id // empty') || true
fi
if [ -z "$RUN_ID" ]; then
# Deliberately non-fatal: the full-chain chart falls back to the
# committed/seeded full_chain.csv rather than blocking the deploy of
# the other charts. The warning surfaces the degradation in the log.
echo "::warning::Could not resolve a successful 'Picofuzz Full Chain' run; the full-chain chart will use the committed/seeded full_chain.csv."
fi
echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}

- name: Download full-chain CSV artifact
if: steps.full-chain-run.outputs.run-id != ''
uses: actions/download-artifact@v8
with:
name: picofuzz-csv-full_chain
path: ./perf-graph/csv-artifacts
github-token: ${{ secrets.ARTIFACTS_PAT }}
run-id: ${{ steps.full-chain-run.outputs.run-id }}
continue-on-error: true

- name: Compile merge script
run: |
npx tsc scripts/merge-csvs.ts --outDir ./scripts-dist --esModuleInterop --module commonjs --target es2020 --moduleResolution node
Expand Down
63 changes: 63 additions & 0 deletions docs/superpowers/specs/2026-07-13-full-chain-perf-graph-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Add full-chain (100k block) import stats to the perf graph

## Goal

Show the picofuzz **full-chain** (100k-block) import stats on the performance
stats page (`perf-graph/`, deployed to GitHub Pages as "typeberry import
stats"), alongside the existing per-scenario charts (`conformance`, `fallback`,
`safrole`, `storage`, `storage_light`).

## Context

- The perf page renders one `<Chart>` per scenario. Each chart loads a CSV from
`perf-graph/public/<name>.csv` with the 20-field stats format
(`projectName,date,count,sum,mean,median,min,max,range,stdDeviation,variance,p1,p5,p10,p25,p50,p75,p90,p95,p99`).
- The **Picofuzz Full Chain** workflow (`.github/workflows/picofuzz-full.yml` →
`tests/picofuzz/full_chain.test.ts`) already emits `full_chain.csv` in exactly
this format, uploaded as artifact `picofuzz-csv-full_chain`. Each run chains
the previous run's CSV, so the latest artifact holds the full accumulated
history (~10 rows as of 2026-07-13, versions v0.9.x–v0.11.x, `count=100051`).
- The deploy (`deploy-perf-graph.yml`) is triggered by the **Picofuzz Tests**
workflow and downloads `picofuzz-csv-*` artifacts *from that triggering run
only*. The full-chain artifact lives in a **different** workflow's runs, so it
is not currently picked up.

## Design

### 1. Frontend (`perf-graph/`)
- Add an optional `title?: string` prop to `Chart` so the heading can read
"Full chain import (100k blocks)" instead of the raw `name`. Existing charts
fall back to `name` (unchanged behaviour).
- Render a dedicated `<Chart name="full_chain" title="..." />` in its own
section in `App.tsx` (mirroring the `conformance-section`), conceptually
separate from the per-scenario fuzzers.
- No changes to data parsing or the global version filter — `full_chain` shares
the `v0.x.x` version namespace and plugs into the existing filter as-is.

### 2. Deploy wiring (`deploy-perf-graph.yml`)
- Add a step that resolves the latest **successful** "Picofuzz Full Chain" run
via the GitHub API and downloads its `picofuzz-csv-full_chain` artifact into
`./csv-artifacts/` (`continue-on-error`, mirroring the existing download).
`merge-csvs.ts` globs whatever CSVs exist, so `full_chain.csv` is merged with
no merge-script change.
- Add "Picofuzz Full Chain" to the `workflow_run.workflows` trigger list so a
fresh full-chain result redeploys the page the same day. (Deploy runs on
`ubuntu-latest`, not the shared self-hosted runner — no runner contention.)

### 3. Seed data
- Commit the current 10-row history as `perf-graph/public/full_chain.csv`,
consistent with the already-committed `fallback.csv` etc. The artifact has
90-day retention, so the repo copy is the long-term history base that merge
builds on.

## Testing
- `npm run build` in `perf-graph/` with the seeded CSV → confirms the chart
parses and renders.
- Run `merge-csvs.ts` against a `csv-artifacts/full_chain.csv` → confirms merge
picks it up and dedups by `(version,date)`.
- The API-fetch workflow step is validated on CI; it is a mirror of the existing
working `download-artifact` step.

## Out of scope
- The `docker-imports-full` wall-clock flow (a separate, larger project that
would require inventing a new metric and chart type).
10 changes: 10 additions & 0 deletions perf-graph/public/full_chain.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@typeberry/jam@0.9.0,2026-06-16T07:28:19.966Z,100051,4790105856529,47876641,28905899,16855002,700703887,683848885,52256091.9481506,2730699145693569.5,18885295,19473106,19930784,21015813,28905899,47870597,96054433,141666936,286217487
@typeberry/jam@0.9.0,2026-06-18T07:28:21.018Z,100051,4957784766408,49552575,29134841,17018907,662111429,645092522,55770054.75181068,3110299007019961,18859130,19465771,19927588,21049540,29134841,49382048,98487349,157368761,309381738
@typeberry/jam@0.9.0,2026-06-20T09:45:45.121Z,100051,3516809212390,35150165,20563935,16524960,384789703,368264743,30374531.204473626,922612145891542,18260394,18515054,18755790,19473141,20563935,37488725,82425168,94828281,157006429
@typeberry/jam@0.9.0,2026-06-22T07:06:22.423Z,100051,3500254338023,34984701,20514358,16552233,347354243,330802010,30010855.351568308,900651438932756.1,18220969,18478231,18713155,19431780,20514358,37474878,81917061,94350250,155123723
@typeberry/jam@0.9.0,2026-06-24T10:26:13.814Z,100051,4310867096850,43086696,21867130,16289417,810852253,794562836,50439780.53285954,2544171460203036,18256682,18618980,19002622,19824827,21867130,43011481,90847850,116208467,255919777
@typeberry/jam@0.9.0,2026-06-30T04:08:38.579Z,100051,1241505857755,12408730,9785486,7808668,104821456,97012788,7015151.04717442,49212344214672.36,8528131,8641408,8729413,9085894,9785486,10837625,22948025,24185105,37853214
@typeberry/jam@0.10.0,2026-07-06T04:08:08.340Z,100051,1242978710607,12423451,9790703,7785109,120715306,112930197,7084975.035878926,50196871259027.586,8529025,8649336,8740835,9104514,9790703,10834002,22941240,24173155,38048515
@typeberry/jam@0.11.0,2026-07-09T04:13:43.918Z,100051,1581702477171,15808962,12483046,7921865,120118024,112196159,7368162.178071096,54289813882357.4,8779230,8983839,9343906,11239972,12483046,20418041,23897959,26315611,38511206
@typeberry/jam@0.11.0,2026-07-11T04:02:17.053Z,100051,1262166483792,12615231,9888228,7841012,122789744,114948732,7159809.516851619,51262872317599.016,8595961,8722028,8825248,9250151,9888228,11042736,23025870,24275169,37581002
@typeberry/jam@0.11.0,2026-07-13T04:07:06.120Z,100051,1248889065451,12482524,9838582,7756324,134940690,127184366,7111531.756449001,50573883922982.61,8533966,8666980,8769605,9161550,9838582,10935234,22954161,24267366,38035163
4 changes: 4 additions & 0 deletions perf-graph/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@
.conformance-section {
margin-bottom: 25px;
}

.full-chain-section {
margin-bottom: 25px;
}
9 changes: 9 additions & 0 deletions perf-graph/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ function App() {
<Chart name="conformance" visibleVersions={visibleVersions} onVersionsFound={handleVersionsFound} />
</div>

<div className="full-chain-section">
<Chart
name="full_chain"
title="Full chain import (100k blocks)"
visibleVersions={visibleVersions}
onVersionsFound={handleVersionsFound}
/>
</div>

<div className="app">
<Chart name="fallback" visibleVersions={visibleVersions} onVersionsFound={handleVersionsFound} />
<Chart name="safrole" visibleVersions={visibleVersions} onVersionsFound={handleVersionsFound} />
Expand Down
5 changes: 3 additions & 2 deletions perf-graph/src/components/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ function getMinorVersion(fullVersion: string): string {

interface ChartProps {
name: string;
title?: string;
visibleVersions: Set<string>;
onVersionsFound: (versions: string[]) => void;
}

export function Chart({ name, visibleVersions, onVersionsFound }: ChartProps) {
export function Chart({ name, title, visibleVersions, onVersionsFound }: ChartProps) {
const [data, setData] = useState<PerformanceData[]>([]);
const [allData, setAllData] = useState<PerformanceData[]>([]);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -174,7 +175,7 @@ export function Chart({ name, visibleVersions, onVersionsFound }: ChartProps) {

return (
<div style={{ padding: "20px" }}>
<h3>{name}</h3>
<h3>{title ?? name}</h3>
<div style={{ marginBottom: "30px" }}>
<h4>Block import time ({UNIT})</h4>
<ResponsiveContainer width="100%" height={400}>
Expand Down