diff --git a/docs/code/api.md b/docs/code/api.md index e07cbdb..0c424af 100644 --- a/docs/code/api.md +++ b/docs/code/api.md @@ -104,7 +104,8 @@ published to. Flows own the namespace; everything else is a client of it. |---|---|---| | `POST` | `/runs/flows/{name}` | queue one run — `{"params": {...}, "seed": 7, "draft": false, "no_cache": false}` | | `POST` | `/runs/flows/{name}/sweep` | queue many, sharing a `group_id` | -| `GET` | `/runs` | the queryable history: `?flow=`, `?status=`, `?group=`, `?digest=`, `?limit=` | +| `GET` | `/runs` | the queryable history: `?flow=`, `?status=`, `?group=`, `?digest=`, `?limit=`, `?offset=` | +| `GET` | `/runs/overview` | one row per flow that has runs, with how many are running or queued | | `GET` | `/runs/{id}` | one run in full: params, result, per-node record, artifacts | | `POST` | `/runs/{id}/cancel` | stop it | | `GET` | `/runs/{id}/metrics?name=&stride=` | one metric's series, in step order; every series of the run without `name` | @@ -141,6 +142,7 @@ code normally reaches these through `fluksio.save_artifact` / | `GET` | `/dashboards/` | every dashboard, without its contents | | `GET` | `/dashboards/{name}?draft=true` | the published document, or the editor's copy | | `POST` | `/dashboards/{name}` | create one | +| `POST` | `/dashboards/from-flow/{flow}` | build `{flow}_results` from a batch flow's declared ports — a chart per streamed metric, a stat per output. 409 if it already exists | | `PUT` | `/dashboards/{name}` | save a draft | | `POST` | `/dashboards/{name}/publish` · `/discard` · `/rename` | | | `GET` `PUT` | `/panels/` | which device shows which dashboards | diff --git a/docs/concepts/runs.md b/docs/concepts/runs.md index 3fa0c00..57ab0b7 100644 --- a/docs/concepts/runs.md +++ b/docs/concepts/runs.md @@ -286,6 +286,56 @@ acknowledgement was late is not recovery; instead a running run refreshes a lease, and one whose lease goes stale is marked `abandoned` — which is what a run whose engine was killed mid-training becomes. +## Looking at what ran + +The **Runs** screen is the experiment log: every run newest-first, filtered by +flow, by status, or down to one sweep. A sweep is worth filtering to — the +table then draws a column per parameter that actually varied, which is what +makes fifty runs of one flow readable. + +Tick two or more and their curves go side by side. That comparison is the +address, so a link to it is a link someone else can open. + +One run in full is params, the per-node record with its logs and traceback, +the artifacts it made, its metrics and its result. + +### A dashboard, read against runs + +A run records values under the same names a dashboard binds to — a run of +`study` writes `study.loss` — so a dashboard is already a way of looking at +one. **Open in dashboard** from a run or a comparison opens any dashboard with +`?runs=a,b,c` on it, and the widgets resolve from those runs instead of from +the live engine: each chart draws a line per run, the tiles that show one +number show the first, and the controls go quiet because there is nothing left +to publish to. + +Nothing about the dashboard is specific to runs. The page built to watch a +training run happen is the page that shows the finished ones. + +If a flow has no dashboard yet, the same menu offers to build one from what +the flow declares — a chart per streaming port, a tile per output: + +``` +POST /api/v1/dashboards/from-flow/study +``` + +It publishes `study_results` and is an ordinary dashboard afterwards; editing +it is how it stops being generic. + +### A tile that always shows the last few + +The other direction is a chart pinned to runs rather than a page opened +against them, which is what a panel over a bench wants. Set a chart's source +to **Runs**, name the metric, and pick either the latest N of a flow, one +sweep, or specific runs. It re-reads on its own and whenever a run finishes. + +### When a run draws nothing + +A node restored from the [stage cache](#stage-caching) replays no emissions — +a cache hit returns what the node returned, not what it emitted on the way. So +a run that reused an earlier one has a result and an empty curve, and the +chart says so rather than looking broken. + ## What this costs, compared The repository ships a benchmark that measures submitting a run against a diff --git a/docs/getting-started/data-science.md b/docs/getting-started/data-science.md index d64af60..b177cbf 100644 --- a/docs/getting-started/data-science.md +++ b/docs/getting-started/data-science.md @@ -485,6 +485,21 @@ curl -s "$FLUKSIO/runs/series/compare?ids=$A,$B,$C&metric=train.loss" \ which answers in exactly the shape a chart widget draws. +## Looking at them in the portal + +None of the above needs a browser, but there is one. **Runs** is the table of +everything that has run: filter it to a flow, to a status, or to one sweep — +where it grows a column per parameter that actually varied — and tick two runs +to put their curves side by side. + +A run records its metrics under the same names a dashboard binds to, so a +dashboard is another way of reading one. **Open in dashboard** carries the +runs you picked along in the URL, and every widget then answers for those runs +instead of for the live engine. If the flow has no dashboard yet, the same +menu builds one from the ports the flow declares. + +See [Runs → Looking at what ran](../concepts/runs.md#looking-at-what-ran). + ## What sync does, at the API level Nothing here is privileged: `sync` is a client, and a flow is a document you diff --git a/frontend/scripts/capture-screenshots.mjs b/frontend/scripts/capture-screenshots.mjs index b0d09c6..1666609 100644 --- a/frontend/scripts/capture-screenshots.mjs +++ b/frontend/scripts/capture-screenshots.mjs @@ -152,6 +152,12 @@ async function captureRuns(page, dir) { await page.screenshot({ path: `${dir}/app-run-context.png` }) } + // The dashboard that pins the last few runs, drawn live rather than in a + // context: the other half of the same idea. + await page.goto(`${APP_URL}/view/demo_training`, { waitUntil: "networkidle" }) + await page.waitForTimeout(2500) + await page.screenshot({ path: `${dir}/app-runs-pinned.png` }) + await page.goto(`${APP_URL}/runs`, { waitUntil: "networkidle" }) await page.getByTestId("run-link").first().click() await page.waitForURL(/\/runs\/.+/, { timeout: 15000 })