keyviz admin: apply default rows budget when omitted (PR #660 follow-up)#672
keyviz admin: apply default rows budget when omitted (PR #660 follow-up)#672
Conversation
Codex round-3 P1 on PR #660: omitting ?rows= left the parsed budget at the zero value, and applyKeyVizRowBudget treats budget <= 0 as "no cap" — so a normal SPA poll without rows would receive the full route catalogue (potentially every MaxTrackedRoutes route) in a single payload, defeating the 1024-row resource guard the cap was supposed to provide. Pre-seed parseKeyVizParams.rows to keyVizRowBudgetCap so an omitted query parameter inherits the cap. setKeyVizRowsParam now also collapses an explicit rows=0 to the cap so callers cannot disable the budget by passing 0. Test TestKeyVizHandlerOmittedRowsAppliesDefaultCap stages keyVizRowBudgetCap+5 routes and confirms both the omitted and rows=0 forms truncate down to keyVizRowBudgetCap.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 57 minutes and 36 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request updates the KeyViz handler to enforce a row budget cap of 1024 for the 'rows' query parameter, ensuring that omitted or zero values default to this limit to prevent resource exhaustion. The changes include documentation updates, logic to apply the default cap in the parameter parser, and a new test case. Feedback focuses on a discrepancy where negative values currently trigger an error instead of defaulting to the cap as documented, and suggests improving test coverage and resource cleanup in the test suite.
| if err != nil || n < 0 { | ||
| return errors.New("rows must be a non-negative integer") | ||
| } | ||
| if n > keyVizRowBudgetCap { | ||
| if n == 0 || n > keyVizRowBudgetCap { | ||
| // Explicit 0 collapses to the cap (same as omitting the | ||
| // param) so callers can't disable the budget by passing 0. | ||
| n = keyVizRowBudgetCap | ||
| } |
There was a problem hiding this comment.
The implementation of setKeyVizRowsParam returns an error for negative values, which contradicts the updated documentation and the PR description stating that negative values should yield the default cap. Applying a fixed bound to this parameter prevents potential resource exhaustion from large or invalid requests.
| if err != nil || n < 0 { | |
| return errors.New("rows must be a non-negative integer") | |
| } | |
| if n > keyVizRowBudgetCap { | |
| if n == 0 || n > keyVizRowBudgetCap { | |
| // Explicit 0 collapses to the cap (same as omitting the | |
| // param) so callers can't disable the budget by passing 0. | |
| n = keyVizRowBudgetCap | |
| } | |
| if err != nil { | |
| return errors.New("rows must be an integer") | |
| } | |
| if n <= 0 || n > keyVizRowBudgetCap { | |
| // Explicit 0 or negative collapses to the cap (same as | |
| // omitting the param) so callers can't disable the budget. | |
| n = keyVizRowBudgetCap | |
| } |
References
- To prevent unbounded memory growth and potential OOM issues, apply a fixed bound to collections that can grow from external requests, such as pending configuration changes. Reject new requests when the bound is reached.
| for _, query := range []string{"", "?rows=0"} { | ||
| resp := keyVizGet(t, srv.URL+query) | ||
| require.Equal(t, http.StatusOK, resp.StatusCode) | ||
| var matrix KeyVizMatrix | ||
| require.NoError(t, json.NewDecoder(resp.Body).Decode(&matrix)) | ||
| resp.Body.Close() | ||
| require.Len(t, matrix.Rows, keyVizRowBudgetCap, | ||
| "omitted/0 rows must apply the default cap (query=%q)", query) | ||
| } |
There was a problem hiding this comment.
Update the test loop to include a negative rows value, ensuring it also correctly defaults to the cap as specified in the documentation. Additionally, ensure the error from Close() is not ignored to maintain visibility into resource cleanup issues.
| for _, query := range []string{"", "?rows=0"} { | |
| resp := keyVizGet(t, srv.URL+query) | |
| require.Equal(t, http.StatusOK, resp.StatusCode) | |
| var matrix KeyVizMatrix | |
| require.NoError(t, json.NewDecoder(resp.Body).Decode(&matrix)) | |
| resp.Body.Close() | |
| require.Len(t, matrix.Rows, keyVizRowBudgetCap, | |
| "omitted/0 rows must apply the default cap (query=%q)", query) | |
| } | |
| for _, query := range []string{"", "?rows=0", "?rows=-1"} { | |
| resp := keyVizGet(t, srv.URL+query) | |
| require.Equal(t, http.StatusOK, resp.StatusCode) | |
| var matrix KeyVizMatrix | |
| require.NoError(t, json.NewDecoder(resp.Body).Decode(&matrix)) | |
| require.NoError(t, resp.Body.Close()) | |
| require.Len(t, matrix.Rows, keyVizRowBudgetCap, | |
| "omitted/0/negative rows must apply the default cap (query=%q)", query) | |
| } |
References
- To prevent unbounded memory growth and potential OOM issues, apply a fixed bound to collections that can grow from external requests, such as pending configuration changes. Reject new requests when the bound is reached.
- Do not silently ignore errors from Close() methods on resources like network connections. Log them to ensure resource leaks or other cleanup problems are visible.
Round-1 review fixes for PR #672 (Gemini medium): - setKeyVizRowsParam previously rejected negative rows with a 400 invalid_query — but the godoc and PR description state that omitted / 0 / negative all yield the cap. Align the code with the contract: any non-positive (or above-cap) value collapses to keyVizRowBudgetCap. Caller can never disable or invert the budget. - Extend TestKeyVizHandlerOmittedRowsAppliesDefaultCap to include ?rows=-1 in the table so the negative branch is pinned alongside the omitted and 0 cases. - Test now asserts on resp.Body.Close()'s error so a future regression that breaks response-stream cleanup surfaces.
|
@claude review |
|
Claude finished @bootjp's task in 2m 38s —— View job Code Review
The fix is correct and the core logic is sound. The two Gemini suggestions (negative-rows-to-cap collapse, 1. Missing test for above-cap clamping — PR description references a test that doesn't existThe PR description says:
There is no Suggested addition to // above-cap is silently clamped down
resp := keyVizGet(t, srv.URL+"?rows=9999")
require.Equal(t, http.StatusOK, resp.StatusCode)
var m KeyVizMatrix
require.NoError(t, json.NewDecoder(resp.Body).Decode(&m))
require.NoError(t, resp.Body.Close())
require.Len(t, m.Rows, keyVizRowBudgetCap, "above-cap rows must clamp to cap")2.
|
Adds TestKeyVizHandlerClampsRowsBudgetToCap to lock the n>cap branch of setKeyVizRowsParam, and extracts stagedRowsForBudgetTest so the omitted and above-cap tests share the same fixture. The shared helper iterates with a uint64 counter and uses byte-shift key encoding, removing the //nolint:gosec at the previous int->uint64 conversion.
|
@claude review Round-2 follow-ups (b629f2b):
|
|
Claude finished @bootjp's task in 1m 53s —— View job Code Review (Round 2)
Both round-2 items are correctly addressed. Here's the detailed pass: Item 1 —
|
| Path | Before | After |
|---|---|---|
?rows= omitted |
rows=0 → no cap → all routes returned |
rows=1024 → cap applied |
?rows=0 |
rows=0 → no cap |
rows=1024 → cap applied |
?rows=-1 |
400 error | rows=1024 → cap applied |
?rows=9999 |
rows=1024 (clamped) |
rows=1024 (clamped) |
?rows=2 |
rows=2 |
rows=2 (unchanged) |
All branches are now covered by tests. Both the primary fix and the round-2 feedback have been addressed cleanly. LGTM — ready to merge.
## Summary Phase 2-B of the Key Visualizer design (`docs/admin_ui_key_visualizer_design.md`): integrate the heatmap into the existing `web/admin/` SPA rather than building a parallel dashboard. Doc + implementation in one PR (doc commit first, per CLAUDE.md design-doc-first workflow). - New page at `/keyviz` — canvas heatmap polling `/admin/api/v1/keyviz/matrix` with series picker (writes / reads / write_bytes / read_bytes), row-budget input (clamped at 1024), and off / 5 s / 30 s auto-refresh. - Cold cells (value 0) render as the page background, not a faint blue — spotting actually-cold routes stays the dominant visual signal. - Row-detail flyout on hover: bucket_id, start, end, aggregate, route_count, route_ids (with truncation indicator). `start` / `end` decode through a printable-or-hex preview so binary keys do not render as mojibake. - Dependency-free: hand-rolled five-stop colour ramp in `lib/colorRamp.ts`. No d3, no ECharts. Bundle grew from ~155 kB to ~208 kB (raw), 64 kB gzipped. Backend is unchanged. The handler at `/admin/api/v1/keyviz/matrix` and its sampler wiring already shipped under Phase 2-A (PRs #639 / #645 / #646 / #647 / #651 / #660 / #661 / #672). ## Design `docs/design/2026_04_27_proposed_keyviz_spa_integration.md` — proposed status, lands with this PR. Parent design §12 phase table is split into 2-A (server, shipped) / 2-B (SPA, this PR) / 2-C (cluster fan-out, open). ## Five-lens self-review 1. **Data loss** — n/a; SPA is read-only against an existing handler. 2. **Concurrency / distributed** — n/a; single browser tab polling a single handler. Sampler concurrency was already covered by Phase 2-A tests. 3. **Performance** — Canvas + per-cell `fillRect` runs under §10 budget at 1024 × 500. Auto-refresh defaults off; 5-second cadence is the lower bound (sampler flush is 1 s). 4. **Data consistency** — SPA renders whatever the handler returns; consistency guarantees come from the existing leader-issued counters in the sampler. 5. **Test coverage** — `tsc -b --noEmit` clean; `vite build` clean; `go build ./internal/admin/...` clean (embed glob unaffected); `go test ./internal/admin/...` clean. Manual verification documented in the design doc §5. ## Test plan - [x] `npm run lint` (`tsc -b --noEmit`) — clean - [x] `npm run build` (Vite) — clean, output goes to `internal/admin/dist` - [x] `go build ./internal/admin/...` — clean - [x] `go test ./internal/admin/...` — clean - [ ] Manual: `make run` + `make client`, navigate to `/keyviz`, see hot routes light up red within ~5 s of write traffic - [ ] Manual: series picker swaps the displayed counter; row-budget input clamps at 1024; auto-refresh polls without flicker ## Out of scope - **Cluster fan-out** — handler is currently node-local. Phase 2-C will add a cross-node admin RPC; this PR will pick up the aggregate view automatically once that ships. - **Drill-down per-route sparkline** — Phase 3. - **Routes / Raft Groups correlation** — Phase 1 SPA pages not yet built; correlation lands when those pages do. - **`localStorage` for series / rows / refresh** — punt to follow-up.
Summary
Follow-up to PR #660, which was merged at the round-2 commit before this Codex P1 fix propagated. The orphaned change:
Codex round-3 P1:
?rows=was optional and parsed to0when omitted;applyKeyVizRowBudgetthen treatsbudget <= 0as "no cap" and returns every tracked route in one payload — defeating the 1024-row resource guard the cap was supposed to provide. A normal SPA poll without?rows=would fall into this trap.parseKeyVizParamsnow pre-seedsrows = keyVizRowBudgetCapso an omitted query parameter inherits the cap.setKeyVizRowsParamcollapses an explicitrows=0to the cap so callers cannot disable the budget by passing 0.Test plan
TestKeyVizHandlerOmittedRowsAppliesDefaultCap— stageskeyVizRowBudgetCap+5routes and confirms both the omitted ("") and?rows=0forms truncate down tokeyVizRowBudgetCap.TestKeyVizHandlerHonorsRowsBudgetandTestKeyVizHandlerClampsRowsBudgetToCapstill pass — the change is additive at the default end.go test -race -count=1 ./internal/admin/...clean.golangci-lint run ./internal/admin/...clean.