Skip to content

Commit 82dd4c4

Browse files
committed
keyviz adapter: clamp rows to design cap + writes default + doc divergence
Round-3 review polish for PR #646 (Claude bot): - Default series swapped from Reads to Writes for KEYVIZ_SERIES_UNSPECIFIED to match design doc §4.1, which spec's writes as the default. The read-sampling path is intentionally Phase 2 (no reads will land in the matrix until then) so defaulting to a permanently-zero series was actively misleading. - Add keyVizRowBudgetCap = 1024 + clampRowBudget. Design §4.1 caps rows at 1024 to bound server work; pathological clients asking for more get the cap, not an error. Regression test TestGetKeyVizMatrixClampsRowsBudgetToCap stages 1029 rows and verifies the response carries 1024. - Document the design §5.5 divergence on applyKeyVizRowBudget: truncation is a Phase-1 simplification of the spec'd "lexicographic walk + greedy merge" so future contributors don't treat the truncation behavior as the contract. - Update TestGetKeyVizMatrixSeriesSelection's UNSPECIFIED case to expect Writes instead of Reads.
1 parent e4b11af commit 82dd4c4

2 files changed

Lines changed: 65 additions & 10 deletions

File tree

adapter/admin_grpc.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,24 @@ func (s *AdminServer) GetKeyVizMatrix(
535535
to := unixMsToTime(req.GetToUnixMs())
536536
cols := sampler.Snapshot(from, to)
537537
pickValue := matrixSeriesPicker(req.GetSeries())
538-
return matrixToProto(cols, pickValue, int(req.GetRows())), nil
538+
return matrixToProto(cols, pickValue, clampRowBudget(int(req.GetRows()))), nil
539+
}
540+
541+
// keyVizRowBudgetCap is the upper bound on the per-request rows
542+
// budget — design doc §4.1 caps rows at 1024 to bound server work
543+
// (sort + payload) for adversarial / over-large requests.
544+
const keyVizRowBudgetCap = 1024
545+
546+
// clampRowBudget enforces design §4.1's upper bound. A request of 0
547+
// (or negative) means "no cap" and is preserved; anything past the
548+
// cap is silently clamped — clients asking for more rows than the
549+
// server is willing to render get the most rows the server will
550+
// render, not an error.
551+
func clampRowBudget(requested int) int {
552+
if requested > keyVizRowBudgetCap {
553+
return keyVizRowBudgetCap
554+
}
555+
return requested
539556
}
540557

541558
// unixMsToTime converts a Unix-millisecond timestamp into a time.Time,
@@ -549,21 +566,22 @@ func unixMsToTime(ms int64) time.Time {
549566
}
550567

551568
// matrixSeriesPicker returns a callback that extracts the requested
552-
// counter from a MatrixRow. KEYVIZ_SERIES_UNSPECIFIED (and READS)
553-
// fall through to Reads so a default-valued request still returns
554-
// something useful.
569+
// counter from a MatrixRow. KEYVIZ_SERIES_UNSPECIFIED falls through
570+
// to Writes per design doc §4.1 — write traffic is the primary
571+
// signal the heatmap is built around, and the read path is wired in
572+
// a follow-up phase.
555573
func matrixSeriesPicker(series pb.KeyVizSeries) func(keyviz.MatrixRow) uint64 {
556574
switch series {
557-
case pb.KeyVizSeries_KEYVIZ_SERIES_WRITES:
558-
return func(r keyviz.MatrixRow) uint64 { return r.Writes }
575+
case pb.KeyVizSeries_KEYVIZ_SERIES_READS:
576+
return func(r keyviz.MatrixRow) uint64 { return r.Reads }
559577
case pb.KeyVizSeries_KEYVIZ_SERIES_READ_BYTES:
560578
return func(r keyviz.MatrixRow) uint64 { return r.ReadBytes }
561579
case pb.KeyVizSeries_KEYVIZ_SERIES_WRITE_BYTES:
562580
return func(r keyviz.MatrixRow) uint64 { return r.WriteBytes }
563-
case pb.KeyVizSeries_KEYVIZ_SERIES_UNSPECIFIED, pb.KeyVizSeries_KEYVIZ_SERIES_READS:
564-
return func(r keyviz.MatrixRow) uint64 { return r.Reads }
581+
case pb.KeyVizSeries_KEYVIZ_SERIES_UNSPECIFIED, pb.KeyVizSeries_KEYVIZ_SERIES_WRITES:
582+
return func(r keyviz.MatrixRow) uint64 { return r.Writes }
565583
default:
566-
return func(r keyviz.MatrixRow) uint64 { return r.Reads }
584+
return func(r keyviz.MatrixRow) uint64 { return r.Writes }
567585
}
568586
}
569587

@@ -608,6 +626,14 @@ func matrixToProto(cols []keyviz.MatrixColumn, pick func(keyviz.MatrixRow) uint6
608626
// applyKeyVizRowBudget caps rows to budget by total activity per row
609627
// (sum of per-column values), preserving the top-N rows. budget <= 0
610628
// means "no cap."
629+
//
630+
// NOTE: design doc §5.5 specifies a "lexicographic walk + greedy
631+
// merge of low-activity adjacent ranges" algorithm — we simplify to
632+
// activity-descending truncation for Phase 1 because it covers the
633+
// common UI need (highlight hotspots) without needing the synthetic
634+
// virtual-bucket plumbing the merge requires. Phase 2 should swap
635+
// this for the spec'd merge so low-activity ranges become coarse
636+
// aggregates instead of being silently dropped.
611637
func applyKeyVizRowBudget(rows []*pb.KeyVizRow, budget int) []*pb.KeyVizRow {
612638
if budget <= 0 || len(rows) <= budget {
613639
return rows

adapter/admin_grpc_keyviz_test.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func TestGetKeyVizMatrixSeriesSelection(t *testing.T) {
126126
series pb.KeyVizSeries
127127
want uint64
128128
}{
129-
{"unspecified defaults to reads", pb.KeyVizSeries_KEYVIZ_SERIES_UNSPECIFIED, 11},
129+
{"unspecified defaults to writes", pb.KeyVizSeries_KEYVIZ_SERIES_UNSPECIFIED, 22},
130130
{"reads", pb.KeyVizSeries_KEYVIZ_SERIES_READS, 11},
131131
{"writes", pb.KeyVizSeries_KEYVIZ_SERIES_WRITES, 22},
132132
{"read_bytes", pb.KeyVizSeries_KEYVIZ_SERIES_READ_BYTES, 333},
@@ -214,6 +214,35 @@ func TestGetKeyVizMatrixSurfacesRouteCountTruncation(t *testing.T) {
214214
require.Equal(t, []uint64{2, 3}, r.RouteIds)
215215
}
216216

217+
// TestGetKeyVizMatrixClampsRowsBudgetToCap pins design §4.1's
218+
// upper-bound: rows requests above the keyVizRowBudgetCap are
219+
// silently clamped down to the cap so a pathological client cannot
220+
// force the server to materialise an unbounded payload.
221+
func TestGetKeyVizMatrixClampsRowsBudgetToCap(t *testing.T) {
222+
t.Parallel()
223+
rows := make([]keyviz.MatrixRow, keyVizRowBudgetCap+5)
224+
for i := range rows {
225+
idx := uint64(i + 1) //nolint:gosec // i is bounded by keyVizRowBudgetCap+5
226+
rows[i] = keyviz.MatrixRow{
227+
RouteID: idx,
228+
Start: []byte{byte(i / 256), byte(i % 256)},
229+
End: []byte{byte((i + 1) / 256), byte((i + 1) % 256)},
230+
Writes: idx,
231+
}
232+
}
233+
srv := newAdminServerWithFakeSampler(t, []keyviz.MatrixColumn{{
234+
At: time.Unix(1_700_000_000, 0),
235+
Rows: rows,
236+
}})
237+
238+
resp, err := srv.GetKeyVizMatrix(context.Background(), &pb.GetKeyVizMatrixRequest{
239+
Series: pb.KeyVizSeries_KEYVIZ_SERIES_WRITES,
240+
Rows: uint32(keyVizRowBudgetCap + 1000),
241+
})
242+
require.NoError(t, err)
243+
require.Len(t, resp.Rows, keyVizRowBudgetCap, "rows must be clamped to keyVizRowBudgetCap")
244+
}
245+
217246
// TestGetKeyVizMatrixHonorsRowsBudget pins Codex round-1 P1 on
218247
// PR #646: a request with rows=N must return at most N rows. We
219248
// stage 4 routes with distinct activity totals and request rows=2;

0 commit comments

Comments
 (0)