Skip to content

feat(app): role-local measured energy axes + per-worker power drilldown | 前端:新增角色级实测能耗坐标轴与每 worker 功耗下钻 - #908

Closed
edwingao28 wants to merge 4 commits into
klaud/powerx-02-certified-viewfrom
klaud/powerx-10-workers-ui
Closed

feat(app): role-local measured energy axes + per-worker power drilldown | 前端:新增角色级实测能耗坐标轴与每 worker 功耗下钻#908
edwingao28 wants to merge 4 commits into
klaud/powerx-02-certified-viewfrom
klaud/powerx-10-workers-ui

Conversation

@edwingao28

@edwingao28 edwingao28 commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Important

STACKED PR — base branch is klaud/powerx-02-certified-view (#907). That PR must merge first; this diff contains only the PLAN-10 deltas on top of it. Do not squash-merge this PR into master before #907 lands.

What (PLAN-10 / gap G15)

Three connected gaps left measured-power data invisible or untested in the app frontend:

  1. workers[] was plumbed end-to-end but nothing rendered it. The type, transform (benchmark-transform.ts), and API docs all carry per-worker power, yet no tooltip/dialog/page consumed it.
  2. Role-local energy had no y-axis. prefill_joules_per_input_token / decode_joules_per_output_token are populated whenever power_valid !== 0, but users could only plot whole-deployment J/tok and role watts — not the role energy efficiency that separates prefill-bound from decode-bound disagg deployments.
  3. No frontend test covered AgentX + measured power. All transform power tests used single_turn; both power cypress specs were fixed-seq only.

Changes

Two new registry axes (Measured Energy group)

  • y_measuredPrefillJPerInputTokenMeasured Prefill Joules per Input Token / 每输入 token 实测 Prefill 能耗
  • y_measuredDecodeJPerOutputTokenMeasured Decode Joules per Output Token / 每输出 token 实测 Decode 能耗
  • Both polarity: 'lower' (Pareto lower_right on interactivity / lower_left on e2e derives automatically), bilingual labels/titles, entries in METRIC_EXPLANATIONS with the certification-tier note, and membership in MEASURED_ENERGY_METRIC_CONFIG_KEYS — so the PLAN-02 tier ring / tooltip tier line / footer key apply to them too.
  • buildMeasuredPowerChartFields maps the two role scalars into {y, roof} chart fields; tokenMetricTypeForConfigKey classifies them input/output by substring (asserted in tests).
  • Coverage filtering is generic: series without role energy (single-node aggregated, legacy rows) drop off the chart and out of the rendered legend when the axis is selected — the rendered legend is metric-aware (hwTypesWithData). The switch is non-destructive: the selection universe (selectableHwTypes) never intersects metric coverage, so switching back to an axis both series carry restores the series with no legend re-selection (asserted in the e2e spec).

WorkerPower drilldown in the pinned chart tooltip

  • New generateWorkerPowerHTML(d, isPinned, locale) in tooltipUtils.ts, rendered by all three generators (official, overlay, GPU-compare) only when pinned and workers is non-empty.
  • Per row: role[worker_idx] · N chips · W, plus temp (avg/peak°C), util %, mem GiB, and hosts when present. Capped at 8 rows with a bilingual +N more workers line. role and hosts are HTML-escaped (they arrive from artifact JSONB).
  • avg_mem_used_mb is read as MiB (nvidia-smi convention) → /1024 GiB; commented in code, to be confirmed against PLAN-09's producer.

Tests

  • Transform unit tests: benchmark_type='agentic_traces' with the full measured-power payload (pass-through incl. workers) and the power_valid: 0 scrub mirror.
  • chart-utils.test.ts: emission / absence / zero-preservation for the two new chart fields.
  • measured-power-direction.test.ts: Pareto corners + bilingual labels for both new config keys.
  • metric-registry.test.ts: token-type classification + share-link round-trip; the pre-existing registry⇄groups parity test enforces group membership.
  • tooltip-utils.test.ts: worker table across all three generators — pinned/unpinned, empty/absent, 8-row cap, ZH strings, HTML escaping.
  • New e2e spec cypress/e2e/agentic-measured-power.cy.ts (spec-scoped intercepts, synthetic fixtures via new measuredPowerMetrics() / syntheticWorkers() helpers in agentic-fixtures.ts): both axes visible under Measured Energy, aggregate series coverage-filtered off chart and legend with a non-destructive switch back, worker table on a pinned agentic point, graceful absence on a workers-less point. Registered in timings.json for cypress-split.

Data caveat

Production workers[] is currently empty for AgentX runs — producer-side emission lands with PLAN-09 (InferenceX repo). The UI renders gracefully with workers absent/empty (verified by unit tests and the e2e spec's aggregate series); the synthetic cypress fixtures verify the drilldown ahead of real data. Fixed-seq multinode disagg points already carry real workers today.

Backward compatibility

  • Legacy rows (no workers, no role energy, no power_valid) render exactly as before — the untouched pre-existing transform/chart/tooltip tests stay green.
  • New share-link metric keys degrade safely on old deploys via resolveMetricConfigKey (fall back to the default axis); old links are unaffected.
  • No schema, API, or stored-state migration; a single revert restores prior behavior.

Review findings & resolutions

An independent two-lens review of the initial push found:

  1. Blocker (confirmed, fixed in 703aaae0): the e2e spec's first test asserted the legend still contains B300 after selecting the prefill-energy axis, but the rendered legend is metric-aware (hwTypesWithData, InferenceContext.tsx) and intentionally drops coverage-filtered series — the assertion failed deterministically. Fixed to assert not.contain.text('B300'), and the test now also proves the switch is non-destructive by re-selecting Measured Joules per Output Token and asserting the b300 dots and legend entry return.
  2. Minor (confirmed, resolved): the spec had been pushed unexecuted. It has now been run locally (portable Node 22.14.0 + E2E_FIXTURES=1 dev server): 3/3 passing, plus adjacent regression specs measured-power-overlay / yaxis-metrics-render / ttft-x-axis-toggle / certified-power-filter69/69 passing.
  3. Nit (resolved): stray Next.js-generated packages/app/AGENTS.md / CLAUDE.md (dev-server byproducts, regenerated on every next dev) removed from the worktree; never part of this diff.
  4. Nit (documented): the avg_mem_used_mb → GiB /1024 conversion assumes MiB semantics (nvidia-smi convention); noted in code for re-check when PLAN-09's producer lands.
  5. Minor (no change): the spec's exact dot-count assertion (have.length, 3) is stricter than sibling specs' at.least. Kept: the fixture injects exactly 3 disagg rows, hidden points are hidden via opacity (not DOM removal), and the assertion passed locally on the first attempt; relax only if CI proves it flaky.

Validation

  • bun run typecheckbun run lintbun run fmt
  • packages/app full unit suite: 251 files / 4499 tests passed ✅ (targeted power/registry/tooltip suites: 456 tests ✅)
  • packages/db (596 tests) and packages/constants (47 tests) ✅ (packages/mcp src/server.test.ts fails identically on the base branch — pre-existing environment issue, untouched here)
  • Cypress (local, Node 22.14.0 + E2E_FIXTURES=1): new agentic-measured-power.cy.ts 3/3 ✅; adjacent measured-power-overlay + yaxis-metrics-render + ttft-x-axis-toggle + certified-power-filter 69/69 ✅. CI re-verifies via tests-e2e.yml (specPattern + cypress-split, timings.json entry added).
  • Manual smoke screenshots to be attached from the PR preview once CI e2e is green.

🤖 Generated with Claude Code


Note

Low Risk
Frontend-only visualization and test additions; worker tooltip data is escaped before HTML interpolation and invalid power rows continue to scrub telemetry unchanged.

Overview
Adds two Measured Energy y-axesMeasured Prefill Joules per Input Token and Measured Decode Joules per Output Token — wired through the metric registry, bilingual explanations, chart field mapping from prefill_joules_per_input_token / decode_joules_per_output_token, and existing coverage filtering so aggregate rows without role energy drop off those axes.

Pinned chart tooltips now show a Measured Worker Power section when workers[] is present: per-worker watts (and optional temp, util, memory, hosts), capped at eight rows with overflow text, HTML-escaped role/host strings, and EN/ZH copy across official, overlay, and GPU-compare tooltip generators; hover and empty/missing workers stay unchanged.

Test coverage expands with transform/chart/registry/tooltip unit tests, Cypress helpers (measuredPowerMetrics, syntheticWorkers), and a new AgentX e2e spec that intercepts synthetic agentic benchmarks to assert axis visibility, legend filtering, worker drilldown, and graceful absence without workers.

Reviewed by Cursor Bugbot for commit 703aaae. Bugbot is set up for automated code reviews on this repo. Configure here.

Wenyao Gao and others added 3 commits August 27, 2026 12:00
…rilldown | 推理图表:新增角色级实测能耗坐标轴与每 worker 功耗下钻

Add y_measuredPrefillJPerInputToken and y_measuredDecodeJPerOutputToken to
the Measured Energy group (bilingual labels, lower polarity, explanations),
map prefill_joules_per_input_token / decode_joules_per_output_token into
chart fields, and render a pinned-only per-worker power table (role, chips,
watts, optional temp/util/mem/hosts, 8-row cap, HTML-escaped JSONB strings)
in all three tooltip generators.

在“实测能耗”分组新增每输入 token 实测 Prefill 能耗与每输出 token 实测
Decode 能耗两个 Y 轴(双语标签、越低越好、附说明),将角色级能耗字段映射
为图表字段,并在三种工具提示中新增仅固定时显示的每 worker 功耗表
(角色、芯片数、功耗,可选温度/利用率/显存/主机,上限 8 行,HTML 转义)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…worker drilldown | 测试:覆盖 AgentX 实测功耗、角色级能耗坐标轴与 worker 下钻

Agentic-traces transform pass-through and power_valid=0 scrub (including
workers), role-energy chart-field emission/absence/zero-preservation,
Pareto corners and bilingual labels for the two new axes, input/output
token classification and share-link round-trip, and the pinned worker
table across all three tooltip generators (cap, ZH, HTML escaping).

覆盖 agentic_traces 变换透传与 power_valid=0 清除(含 workers)、角色级
能耗图表字段的生成/缺省/零值保留、两个新坐标轴的帕累托方向与双语标签、
输入/输出 token 分类与分享链接解析回环,以及三种工具提示中固定状态下
的 worker 表(行数上限、中文、HTML 转义)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…使用合成 worker 数据的 AgentX 实测功耗

Spec-scoped intercepts inject a disagg agentic series (role energy +
workers[]) and an aggregate series (whole-run metrics, workers: null):
the two role-energy axes render under Measured Energy, the aggregate
series coverage-filters off while the legend stays intact, the pinned
tooltip shows the worker table, and a workers-less point stays clean.

通过 spec 级拦截注入分离式 agentic 序列(角色级能耗 + workers[])与
聚合序列(全程指标、workers 为 null):两个角色级能耗坐标轴显示在
“实测能耗”分组下,聚合序列因无该指标从图中过滤但图例保持完整,
固定的工具提示显示 worker 表,无 workers 的点保持正常。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edwingao28
edwingao28 requested a review from adibarra as a code owner August 27, 2026 19:11
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview Aug 27, 2026 7:43pm

Request Review

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 3m 55s —— View job


Review: role-local measured energy axes + per-worker power drilldown

  • Read the diff against origin/klaud/powerx-02-certified-view
  • Review new registry axes + chart-field mapping
  • Review generateWorkerPowerHTML tooltip drilldown (XSS/escaping, edge cases)
  • Review tests + fixtures
  • Check CI status
  • Runtime verification (localhost)

🟢 LGTM — no blocking issues found.

Clean, well-scoped stacked PR. The implementation is consistent with the surrounding PLAN-02 measured-power machinery, and I verified the changed behavior at runtime.

Correctness spot-checks (all pass):

  • tokenMetricTypeForConfigKey classifies y_measuredPrefillJPerInputToken → input and y_measuredDecodeJPerOutputToken → output correctly (substring order handles both).
  • Group membership is derived, not duplicated — METRIC_CONTROL_GROUPS's Measured Energy entry references MEASURED_ENERGY_METRIC_CONFIG_KEYS directly, so the two new keys can't drift.
  • Field names in generateWorkerPowerHTML (role, worker_idx, num_gpus, avg_power_w, avg_temp_c, peak_temp_c, avg_util_pct, avg_mem_used_mb, hosts) all match the WorkerPower type in benchmark-mapper.ts.
  • Role energy in buildMeasuredPowerChartFields is correctly not re-gated on power_valid — the transform (benchmark-transform.ts:237-241) already scrubs prefill_joules_per_input_token/decode_joules_per_output_token on !measuredPowerValid, and workers (line 253) is likewise scrubbed + Array.isArray-guarded, so null on production AgentX rows collapses to undefined and the tooltip's Array.isArray(d.workers) check no-ops.

Security: The only untrusted string fields (role, hosts, sourced from artifact JSONB) are HTML-escaped via escapeHtml; the remaining interpolated worker fields are number-typed through the transform, so no XSS surface. Good.

Interpolation sync: None of the four TS interpolation functions changed, so no iso_interactivity.py update is required.

Runtime (localhost, Playwright):

  • Both axes appear under Measured Energy in the y-axis selector (Measured Prefill Joules per Input Token, Measured Decode Joules per Output Token).
  • Selecting the Prefill axis on the real DB (no role-local energy for AgentX yet) coverage-filters to zero points and shows the graceful "No data available" state — no crash, 0 console errors. This matches the PR's data caveat; the synthetic cypress fixtures cover the populated path.

Tests: Comprehensive — transform pass-through + power_valid:0 scrub, chart-field emission/absence/zero-preservation, Pareto-corner direction, token-type + share-link round-trip, tooltip worker table across all three generators (pinned/unpinned, empty/absent, 8-row cap, ZH strings, HTML escaping), and the new e2e spec (both axes + coverage filtering + worker drilldown + graceful absence). Coverage requirement satisfied.

Chinese copy: The new zh strings (axis labels/titles, METRIC_EXPLANATIONS, WORKER_POWER_STRINGS.zh) are accurate and natural; Prefill/Decode/Worker correctly kept in English as established terms. No blocking or advisory findings.

Note (non-blocking, not new to this PR): the e2e/lint/unit workflows aren't in this PR's check list (likely because the base is a stacked non-master branch). Cursor Bugbot passed; make sure the full test/lint gates run before the eventual merge to master.
· klaud/powerx-10-workers-uiklaud/powerx-02-certified-view

…n-destructive axis switch | 端到端测试修复:断言覆盖过滤会移出图例,并验证坐标轴切换可无损恢复

The rendered legend is metric-aware (hwTypesWithData), so a series without
the selected role-energy metric drops out of the legend too — the previous
assertion expected it to stay and failed deterministically. Re-selecting a
whole-run measured axis now proves the selection universe survives the
switch. Also documents the MiB reading of avg_mem_used_mb in the worker
tooltip.

图例渲染依据所选指标(hwTypesWithData),缺少角色级能耗指标的系列会同时从图例中
移除,原断言必然失败。现改为断言其不在图例中,并通过重新选择全局实测坐标轴验证
选择集在切换后无损恢复。同时为 worker 工具提示中 avg_mem_used_mb 的 MiB 语义
补充说明。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 2m 47s —— View job


Re-review: role-local measured energy axes + per-worker power drilldown

  • Read the diff against origin/klaud/powerx-02-certified-view
  • Review new registry axes + chart-field mapping
  • Review generateWorkerPowerHTML tooltip (XSS/escaping, edge cases)
  • Review tests + fixtures
  • Check CI status
  • Runtime verification (localhost)

🟢 Re-review: LGTM — no blocking issues found.

Only one commit is new since my last review (ffcc972): 703aaae0, which strengthens the e2e spec so it asserts the coverage-filtered aggregate series also leaves the legend (not just the chart) and that switching back to a whole-run axis restores it non-destructively. That's a test-only improvement and the assertions are correct — hwTypesWithData is metric-aware, and the selection universe never intersects metric coverage, so the round-trip holds.

Everything from the prior pass still stands:

  • Registry / chart fields: both new keys carry polarity: 'lower', bilingual labels/titles, METRIC_EXPLANATIONS entries (measuredRoleJoulesPerToken references valid MEASURED_PHASE_ZH / MEASURED_TIER_NOTE_* constants), and derived — not duplicated — membership in MEASURED_ENERGY_METRIC_CONFIG_KEYS. buildMeasuredPowerChartFields correctly does not re-gate role energy on power_valid because benchmark-transform.ts:237-241 already scrubs prefill_joules_per_input_token / decode_joules_per_output_token (and workers at line 253) on !measuredPowerValid.
  • Tooltip drilldown: all WorkerPower field accesses (role, worker_idx, num_gpus, avg_power_w, avg_temp_c, peak_temp_c, avg_util_pct, avg_mem_used_mb, hosts) match the type in benchmark-mapper.ts. Wired into all three generators (official / overlay / GPU-compare) with isPinned passed through; Array.isArray + length guard means absent/empty workers no-ops.
  • Security: only untrusted JSONB strings (role, hosts) are interpolated, and both go through escapeHtml; every other field is number-typed through the transform. No XSS surface.
  • Interpolation sync: none of the four TS interpolation functions changed → no iso_interactivity.py update required.
  • Tests: comprehensive (transform pass-through + power_valid:0 scrub, chart-field emission/absence/zero-preservation, Pareto-corner direction, token-type + share-link round-trip, tooltip worker table across all three generators incl. 8-row cap / ZH / HTML-escaping, and the new e2e spec).
  • Chinese copy: the new zh strings (axis labels/titles, METRIC_EXPLANATIONS, WORKER_POWER_STRINGS.zh) are accurate and natural; Prefill/Decode/Worker correctly kept in English. No advisory findings.

Runtime (localhost, Playwright): Both axes appear under Measured Energy (Measured Prefill Joules per Input Token, Measured Decode Joules per Output Token). Selecting the Prefill axis on the real DB (no role-local energy for the current selection) coverage-filters to zero points and shows the graceful "No data available" state — no crash, 0 console errors — matching the PR's data caveat; the synthetic cypress fixtures cover the populated drilldown path.

Note (non-blocking, unchanged): only the Claude workflow runs on this PR (stacked non-master base), so the tests-unit / tests-e2e / lint gates aren't in this PR's check list. Make sure the full test/lint gates run before the eventual merge to master.
· klaud/powerx-10-workers-uiklaud/powerx-02-certified-view

@edwingao28

Copy link
Copy Markdown
Collaborator Author

Superseded by #935 (branch renamed to feat/worker-power-drilldown); identical diff.

@edwingao28 edwingao28 closed this Aug 28, 2026
@edwingao28
edwingao28 deleted the klaud/powerx-10-workers-ui branch August 28, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant