Skip to content

Add snapshot phase timing display to dashboard UI - #257

Merged
cnlangzi merged 2 commits into
mainfrom
fix/snapshot-timing-ui-display
May 13, 2026
Merged

Add snapshot phase timing display to dashboard UI#257
cnlangzi merged 2 commits into
mainfrom
fix/snapshot-timing-ui-display

Conversation

@cnlangzi

Copy link
Copy Markdown
Owner

Fixes: #256

What Changed

Backend (internal/snapshot/capturer.go, cmd/app/server.go):

  • Added global ReadMs/BuildMs/TransformMs/WriteMs fields to Progress struct
  • Progress() method now computes sum of per-table timings as global values
  • API computes read_rows_per_sec = ReadRows * 1000 / ReadMs
  • API serializes per-table read_ms/build_ms/transform_ms/write_ms fields

Frontend (cmd/app/dashboard/pages/snapshot.html):

  • Added global four-phase summary cards displaying total timing
  • Per-table rows now show R:/B:/T:/W: inline timing labels
  • fmtMs(0) renders as , fmtMs(340) as 340ms, fmtMs(1200) as 1.2s

Why Changed

Users could not see phase-level timing breakdown to identify bottlenecks. Snapshot performance optimization was impossible without visibility. This fix completes the data chain from backend to UI, providing Read/Build/Transform/Write timing per table and globally.

How to Test

  1. API verification:

    • Call snapshot progress API and confirm per-table objects include read_ms/build_ms/transform_ms/write_ms
    • Confirm top-level response includes global timing fields and read_rows_per_sec
  2. Dashboard verification:

    • Navigate to snapshot dashboard
    • Verify four global cards display formatted timing values ( for null/zero)
    • Verify Read card shows rows/s throughput
    • Verify per-table rows display R:, B:, T:, W: labels with timing values inline
  3. Format verification:

    • fmtMs(0)
    • fmtMs(340)340ms
    • fmtMs(1200)1.2s

Files Modified

  • cmd/app/server.go
  • internal/snapshot/capturer.go
  • cmd/app/dashboard/pages/snapshot.html

Add real-time phase timing metrics to snapshot dashboard.

- Add timing card panel showing Read, Build, Transform, Write durations and rows/sec
- Add timing labels to individual table progress rows
- Include per-phase timing data (read_ms, build_ms, transform_ms, write_ms) in status API response
- Calculate and display global read rows per second metric
- Add fmtMs() helper for formatting milliseconds to human-readable values
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 945a50dd-2c03-4bce-aba8-b0f6a593b3f2

📥 Commits

Reviewing files that changed from the base of the PR and between 3fb64d7 and dc7ce73.

📒 Files selected for processing (1)
  • cmd/app/dashboard/pages/snapshot.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/app/dashboard/pages/snapshot.html

Walkthrough

The PR extends the snapshot status API to include per-table and global read/build/transform/write millisecond totals and read_rows_per_sec. The dashboard HTML adds a hidden Phase Timing panel, a showTimingPanel() helper, and fmtMs(ms). updateRunningUI() now populates global timing and throughput fields from the API, and updateTableProgress() adds per-table inline R/B/T/W timing labels formatted by fmtMs.

Poem

"I'm a rabbit with a stopwatch, hopping light and spry,
I count each Read and Build and Transform as they fly,
W for write, R/B/T in a row,
Milliseconds now glow — watch the dashboard show,
⏱️🥕"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add snapshot phase timing display to dashboard UI' accurately summarizes the main change—adding timing visualization to the snapshot dashboard frontend.
Description check ✅ Passed The description clearly relates to the changeset, detailing backend additions (timing fields in Progress struct, API serialization) and frontend changes (global summary cards, per-table timing labels) with testing steps.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #256: backend adds global timing fields to Progress struct with summation logic, API serializes per-table and global timings with read_rows_per_sec computation, and frontend renders global cards and per-table labels with correct fmtMs formatting.
Out of Scope Changes check ✅ Passed All changes are scoped to the linked issue #256: timing visibility additions. No unrelated modifications to CDC phases, per-table throughput calculations, or error handling are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In renderSnapshotStatus, the running progress percentage no longer clamps with Math.min(100, ...), so if read_rows overshoots total_rows the bar and label can exceed 100%; consider retaining the clamp to avoid odd UI states when counts are approximate.
  • Since the backend description mentions global ReadMs/BuildMs/TransformMs/WriteMs fields on Progress, you may want to reuse those instead of recomputing acc.read/build/transform/write in handleSnapshotStatus to avoid divergence between two sources of truth.
  • The per-table timing label (R:/B:/T:/W:) is appended as a single monospaced span before the mini progress bar; consider wrapping it in its own flex container or allowing it to wrap on narrow screens to avoid truncating long table names in the main label.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `renderSnapshotStatus`, the running progress percentage no longer clamps with `Math.min(100, ...)`, so if `read_rows` overshoots `total_rows` the bar and label can exceed 100%; consider retaining the clamp to avoid odd UI states when counts are approximate.
- Since the backend description mentions global `ReadMs/BuildMs/TransformMs/WriteMs` fields on `Progress`, you may want to reuse those instead of recomputing `acc.read/build/transform/write` in `handleSnapshotStatus` to avoid divergence between two sources of truth.
- The per-table timing label (`R:/B:/T:/W:`) is appended as a single monospaced span before the mini progress bar; consider wrapping it in its own flex container or allowing it to wrap on narrow screens to avoid truncating long table names in the main label.

## Individual Comments

### Comment 1
<location path="cmd/app/dashboard/pages/snapshot.html" line_range="343-345" />
<code_context>
     document.getElementById('run-current-table').textContent = data.current_table || '—';

-    var pct = totalRows > 0 ? Math.min(100, Math.round((readRows / totalRows) * 100)) : 0;
+    var pct = totalRows > 0 ? Math.round((readRows / totalRows) * 100) : 0;
     document.getElementById('run-progress-bar').style.width = pct + '%';
     document.getElementById('run-pct').textContent = pct + '%';

+    document.getElementById('run-read-ms').textContent = fmtMs(data.read_ms);
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider capping progress percentage at 100% to avoid layout issues.

Removing the previous `Math.min(100, ...)` means `pct` can now exceed 100%. If `readRows` surpasses `totalRows` (e.g., due to estimates or rounding), the bar width can overflow its container and cause visual glitches. Unless >100% is intentional, please clamp the width (and optionally the displayed percentage) while still tracking the raw value internally if required.
</issue_to_address>

### Comment 2
<location path="cmd/app/dashboard/pages/snapshot.html" line_range="348-351" />
<code_context>
+    document.getElementById('run-build-ms').textContent = fmtMs(data.build_ms);
+    document.getElementById('run-transform-ms').textContent = fmtMs(data.transform_ms);
+    document.getElementById('run-write-ms').textContent = fmtMs(data.write_ms);
+    document.getElementById('run-rows-per-sec').textContent = data.read_rows_per_sec ? data.read_rows_per_sec.toFixed(1) + ' r/s' : '—';
+
     var errEl = document.getElementById('run-error');
</code_context>
<issue_to_address>
**suggestion:** Avoid treating 0 rows/s as missing data when displaying throughput.

Because `0` is falsy, this will display `` when throughput is legitimately `0` (or very small values rounding to `0.0`). It would be better to check specifically for `null`/`undefined`, e.g. `data.read_rows_per_sec != null`, before formatting the value as `x.x r/s`.

```suggestion
    document.getElementById('run-build-ms').textContent = fmtMs(data.build_ms);
    document.getElementById('run-transform-ms').textContent = fmtMs(data.transform_ms);
    document.getElementById('run-write-ms').textContent = fmtMs(data.write_ms);
    document.getElementById('run-rows-per-sec').textContent =
        data.read_rows_per_sec != null ? data.read_rows_per_sec.toFixed(1) + ' r/s' : '—';
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread cmd/app/dashboard/pages/snapshot.html Outdated
Comment thread cmd/app/dashboard/pages/snapshot.html Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
cmd/app/server.go (1)

1708-1719: 💤 Low value

Consider naming the accumulator struct for clarity.

The anonymous struct works correctly and the division-by-zero guard is present. For readability, consider giving it a short name like totals or globalTiming.

♻️ Optional naming improvement
-	// Accumulate global timing totals.
-	var acc struct{ read, build, transform, write int64 }
-	for _, tp := range progress.Tables {
-		acc.read += tp.ReadMs
-		acc.build += tp.BuildMs
-		acc.transform += tp.TransformMs
-		acc.write += tp.WriteMs
-	}
+	// Accumulate global timing totals.
+	var totals struct{ read, build, transform, write int64 }
+	for _, tp := range progress.Tables {
+		totals.read += tp.ReadMs
+		totals.build += tp.BuildMs
+		totals.transform += tp.TransformMs
+		totals.write += tp.WriteMs
+	}
 	readRowsPerSec := float64(0)
-	if acc.read > 0 {
-		readRowsPerSec = float64(progress.ReadRows) * 1000 / float64(acc.read)
+	if totals.read > 0 {
+		readRowsPerSec = float64(progress.ReadRows) * 1000 / float64(totals.read)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/app/server.go` around lines 1708 - 1719, Rename the anonymous accumulator
variable `acc` to a clearer named struct (e.g., `totals` or `globalTiming`) to
improve readability; update its declaration (var acc struct{...}) and all
subsequent uses (`acc.read`, `acc.build`, `acc.transform`, `acc.write`) in the
loop that iterates `for _, tp := range progress.Tables { ... }` and in the `if
acc.read > 0 { ... }` guard so the new name is used consistently when computing
`readRowsPerSec` from `progress.ReadRows`.
cmd/app/dashboard/pages/snapshot.html (1)

409-413: 💤 Low value

Consider more explicit input validation in fmtMs.

The function works correctly for expected inputs (non-negative milliseconds). The if (!ms) check is loose and catches 0, null, undefined, false, NaN, and empty string. For clarity and robustness, consider explicit checks.

♻️ Optional validation improvement
 function fmtMs(ms) {
-    if (!ms) return '—';
+    if (ms == null || ms === 0) return '—';
+    if (ms < 0) return '—';
     if (ms < 1000) return ms + 'ms';
     return (ms / 1000).toFixed(1) + 's';
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/app/dashboard/pages/snapshot.html` around lines 409 - 413, The fmtMs
function uses a loose if (!ms) which treats 0 and other falsy values as missing;
update fmtMs to explicitly handle null/undefined and invalid numbers: first
check if ms == null (null or undefined) and return '—', then validate
Number.isFinite(ms) and that ms >= 0 (return '—' for NaN, non-numeric or
negative), and only then format (so 0 returns "0ms"); refer to the fmtMs
function to implement these explicit checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/app/dashboard/pages/snapshot.html`:
- Around line 409-413: The fmtMs function uses a loose if (!ms) which treats 0
and other falsy values as missing; update fmtMs to explicitly handle
null/undefined and invalid numbers: first check if ms == null (null or
undefined) and return '—', then validate Number.isFinite(ms) and that ms >= 0
(return '—' for NaN, non-numeric or negative), and only then format (so 0
returns "0ms"); refer to the fmtMs function to implement these explicit checks.

In `@cmd/app/server.go`:
- Around line 1708-1719: Rename the anonymous accumulator variable `acc` to a
clearer named struct (e.g., `totals` or `globalTiming`) to improve readability;
update its declaration (var acc struct{...}) and all subsequent uses
(`acc.read`, `acc.build`, `acc.transform`, `acc.write`) in the loop that
iterates `for _, tp := range progress.Tables { ... }` and in the `if acc.read >
0 { ... }` guard so the new name is used consistently when computing
`readRowsPerSec` from `progress.ReadRows`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 85ceea18-5c57-421b-8ec0-49bbfaa488b6

📥 Commits

Reviewing files that changed from the base of the PR and between 94dcd97 and 3fb64d7.

📒 Files selected for processing (2)
  • cmd/app/dashboard/pages/snapshot.html
  • cmd/app/server.go

- Add timing card panel showing Read/Build/Transform/Write durations and rows/s
- Add timing labels to individual table progress rows
- Per-phase timing data (read_ms, build_ms, transform_ms, write_ms) in status API
- Calculate and display global read rows per second metric
- fmtMs() helper for formatting milliseconds to human-readable values
- Clamp progress percentage at 100% to avoid overflow
- Fix rows/s display to treat 0 as valid (not missing) data
- Timing label wraps on narrow screens
@cnlangzi
cnlangzi force-pushed the fix/snapshot-timing-ui-display branch from 3fb64d7 to dc7ce73 Compare May 13, 2026 02:54

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmd/app/dashboard/pages/snapshot.html">

<violation number="1" location="cmd/app/dashboard/pages/snapshot.html:343">
P2: Progress percentage is no longer clamped, so the bar can exceed 100% when read rows surpass total rows.</violation>

<violation number="2" location="cmd/app/dashboard/pages/snapshot.html:351">
P3: Throughput display treats `0` as missing data, showing `—` instead of `0.0 r/s`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread cmd/app/dashboard/pages/snapshot.html Outdated
Comment thread cmd/app/dashboard/pages/snapshot.html Outdated
@cnlangzi
cnlangzi merged commit 6f18403 into main May 13, 2026
4 checks passed
@cnlangzi
cnlangzi deleted the fix/snapshot-timing-ui-display branch May 13, 2026 02:56
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.

Snapshot Timing UI Display

1 participant