Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ export default function App() {
aria-busy={disconnected}
>
<StalePanel reason={staleKpi}>
<KpiRow metrics={data.metrics} summary={data.summary} />
<KpiRow
metrics={data.metrics}
summary={data.summary}
metricsEnabled={platform?.metricsEnabled ?? null}
tracesEnabled={platform?.tracesEnabled ?? null}
/>
</StalePanel>

<div className="grid grid-cols-1 gap-3.5 lg:grid-cols-[1.9fr_1fr]">
Expand Down
67 changes: 62 additions & 5 deletions src/components/kpi-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,61 @@ function Tile({
)
}

/**
* Why the cumulative totals are unknown, or `null` when the numbers can be shown.
*
* Requests and error rate are sums over `metrics`, and an empty array sums to zero
* whether or not anything was recorded. A platform with per-module metrics off sends
* exactly that, so these tiles read "0 requests, 0.00%" over live traffic — the
* module table already knew better from the same ack, and the tiles did not ask.
*
* The order is the module table's (`emptyMetrics`), for the same reason:
* `metricsEnabled` is the effective value, so recording off reports metrics off too,
* and recording is the cause worth naming. Rows that did arrive are always shown —
* the setting is live and may have changed since the ack, but the rows are data.
*
* Not replaced with `summary.count`: that is capped by the ring `capacity`, so it
* would read lower the busier the platform got. A dash is the honest answer.
*/
function totalsUnknown(
metrics: ModuleMetricsSnapshot[],
metricsEnabled: boolean | null,
tracesEnabled: boolean | null,
): string | null {
if (metrics.length > 0) return null
if (tracesEnabled === false) return 'recording off'
if (metricsEnabled === false) return 'metrics off'
if (metricsEnabled === true) return null // on and idle: zero is the true count
return 'not reported'
}

/**
* Header KPIs. Requests and error rate are the platform's all-time cumulative
* aggregates (from `metrics`); the sub-line under each is the recent-window trend
* from the `summary` SSE event (windowed count / error rate / p95 vs the previous
* window). When `summary` is absent or a trend field is null, the sub-line renders
* a muted placeholder rather than a made-up delta.
* a muted placeholder rather than a made-up delta. When the platform is not
* aggregating per-module metrics the totals do not exist, and the tiles say why
* instead of showing zero (`totalsUnknown`).
*
* Latency p95 is the exception: it is `summary`'s windowed p95, the same number the
* latency chart plots, so the tile reads as the chart's newest point. It used to be
* the worst per-module p95 from `metrics`, which sat beside a chart drawn from a
* third source and agreed with neither. Until a `summary` arrives it shows a dash —
* the per-module figure is a different measurement, not a stand-in for this one.
*/
export function KpiRow({ metrics, summary }: { metrics: ModuleMetricsSnapshot[]; summary: TraceSummary | null }) {
export function KpiRow({
metrics,
summary,
metricsEnabled = null,
tracesEnabled = null,
}: {
metrics: ModuleMetricsSnapshot[]
summary: TraceSummary | null
metricsEnabled?: boolean | null
tracesEnabled?: boolean | null
}) {
const unknown = totalsUnknown(metrics, metricsEnabled, tracesEnabled)
const total = metrics.reduce((a, m) => a + m.count, 0)
const errors = metrics.reduce((a, m) => a + m.errorCount, 0)
const errorRate = total ? errors / total : 0
Expand All @@ -103,7 +144,13 @@ export function KpiRow({ metrics, summary }: { metrics: ModuleMetricsSnapshot[];

return (
<div className="grid grid-cols-2 gap-3.5 lg:grid-cols-4">
<Tile label="Requests · total" value={num(total)}>
{/* The reason sits where the unit would, beside the dash: the sub-line keeps
saying what it always says, and the tile stays one line tall. */}
<Tile
label="Requests · total"
value={unknown ? '—' : num(total)}
unit={unknown ? `\u00a0${unknown}` : undefined}
>
{summary?.requestsDeltaPct != null ? (
<Delta dir={dirOf(summary.requestsDeltaPct)} tone="neutral">
{Math.abs(summary.requestsDeltaPct * 100).toFixed(0)}% vs prev
Expand All @@ -113,13 +160,23 @@ export function KpiRow({ metrics, summary }: { metrics: ModuleMetricsSnapshot[];
)}
</Tile>

<Tile label="Error rate" value={(errorRate * 100).toFixed(2)} unit="%" valueClass="text-crit">
<Tile
label="Error rate"
value={unknown ? '—' : (errorRate * 100).toFixed(2)}
unit={unknown ? `\u00a0${unknown}` : '%'}
valueClass={unknown ? undefined : 'text-crit'}
>
{summary?.errorRateDeltaPp != null ? (
<Delta dir={dirOf(summary.errorRateDeltaPp)} tone={badWhenUp(summary.errorRateDeltaPp)}>
{Math.abs(summary.errorRateDeltaPp).toFixed(2)}pp · {num(summary.errorCount)} err
</Delta>
) : summary ? (
<Muted>{win} · {num(summary.errorCount)} err</Muted>
) : unknown ? (
// The error count is the same sum as the rate above: no rows, no count.
<Muted>{win} · no summary yet</Muted>
) : (
<Muted>{win} · {num(summary?.errorCount ?? errors)} err</Muted>
<Muted>{win} · {num(errors)} err</Muted>
)}
</Tile>

Expand Down
119 changes: 119 additions & 0 deletions src/components/kpi-totals.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { act, cleanup, render, renderHook, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { KpiRow } from '@/components/kpi-row'
import { useConsoleData } from '@/hooks/use-console-data'
import type { ModuleMetricsSnapshot, TraceSummary } from '@/lib/types'
import { GO_CONNECT, PROTEAN_CONNECT, SPRING_CONNECT } from '@/test/stream-fixtures'
import { FakeEventSource, installFakeEventSource, parseStream, replay } from '@/test/sse-stream'

/**
* The request and error-rate tiles are sums over `metrics`. An empty array sums to
* zero whether or not anything was recorded, and a platform with per-module metrics
* off sends exactly that — measured live on 2026-10-12: quickstart under a
* 3,000-request burst, and the header read "Requests · total 0" and "0.00%".
*
* The ack already says which case it is, and the module table already reads it.
* These scenarios hold the tiles to the same reading, and replay real connect
* sequences so the ack is the one a platform actually sends.
*/

afterEach(cleanup)

function value(label: string): string {
return screen.getByText(label).nextElementSibling!.textContent!
}

function subline(label: string): string {
return screen.getByText(label).parentElement!.lastElementChild!.textContent!
}

const row = (count: number, errorCount: number): ModuleMetricsSnapshot => ({
moduleId: 'greeter',
count,
errorCount,
errorRate: count ? errorCount / count : 0,
p50LatencyMs: 1,
p95LatencyMs: 2,
p99LatencyMs: 3,
maxLatencyMs: 4,
lastSeenEpochMillis: 1_790_000_000_000,
})

describe('on a live connect sequence', () => {
let restoreEventSource: () => void
beforeEach(() => {
restoreEventSource = installFakeEventSource()
})
afterEach(() => restoreEventSource())

function connect(body: string) {
const { result } = renderHook(() => useConsoleData())
act(() => {
const es = FakeEventSource.current()
es.emit('open')
replay(es, body)
})
const { data, platform } = result.current
render(
<KpiRow
metrics={data!.metrics}
summary={data!.summary}
metricsEnabled={platform?.metricsEnabled ?? null}
tracesEnabled={platform?.tracesEnabled ?? null}
/>,
)
}

it('says metrics are off, not that nothing happened, on a platform that turned them off', () => {
// 🔴 The protean capture: `metricsEnabled: false`, `metrics: []`, and a summary
// counting traffic. The tiles used to print a confident zero over it.
connect(PROTEAN_CONNECT)
expect(value('Requests · total')).toMatch(/^—\smetrics\soff$/)
expect(value('Error rate')).toMatch(/^—\smetrics\soff$/)
// The windowed trend is a different measurement, and it is still there.
expect(subline('Requests · total')).toContain('vs prev')
})

it('says it was not told, on a platform that sends no ack and no rows', () => {
connect(SPRING_CONNECT)
expect(value('Requests · total')).toMatch(/^—\snot\sreported$/)
})

it('shows the totals a platform did send', () => {
// Go sends module rows (and an older `hello`, not `ready`): the rows are data.
connect(GO_CONNECT)
const rows = JSON.parse(parseStream(GO_CONNECT).find((f) => f.event === 'metrics')!.data) as ModuleMetricsSnapshot[]
expect(value('Requests · total')).toBe(String(rows.reduce((a, r) => a + r.count, 0)))
expect(value('Requests · total')).not.toMatch(/off|not reported/)
})
})

describe('the reading, case by case', () => {
const summary = JSON.parse(parseStream(PROTEAN_CONNECT).find((f) => f.event === 'summary')!.data) as TraceSummary

it('names recording before metrics, as the module table does', () => {
// `metricsEnabled` is the effective value, so recording off reports metrics off
// too; "metrics off" would point at the wrong switch.
render(<KpiRow metrics={[]} summary={summary} metricsEnabled={false} tracesEnabled={false} />)
expect(value('Requests · total')).toMatch(/^—\srecording\soff$/)
})

it('keeps a true zero: metrics on and no traffic yet', () => {
render(<KpiRow metrics={[]} summary={summary} metricsEnabled tracesEnabled />)
expect(value('Requests · total')).toBe('0')
expect(value('Error rate')).toBe('0.00%')
})

it('shows rows that arrived even if the ack said off', () => {
// The setting is live: it can be switched on after the ack. Rows are data.
render(<KpiRow metrics={[row(40, 4)]} summary={summary} metricsEnabled={false} tracesEnabled />)
expect(value('Requests · total')).toBe('40')
expect(value('Error rate')).toBe('10.00%')
})

it('does not invent an error count when there is no summary either', () => {
render(<KpiRow metrics={[]} summary={null} metricsEnabled={false} tracesEnabled />)
expect(value('Error rate')).toMatch(/^—\smetrics\soff$/)
expect(subline('Error rate')).not.toMatch(/\d+ err/)
})
})
Loading