You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit db708a8
Browse filesBrowse the repository at this point in the historyBrowse files
fix: Color service map nodes by absolute error-rate thresholds (#3125)
## Why
Service map nodes were always some shade of red, and you couldn't tell a service with zero errors from one with a trace of them.
Two causes, both in `getNodeColors`:
1. **The ramp was max-normalized.** `intensity = value / max` meant the darkest node was always whichever service had the highest error rate *on the current graph* — whether that was 0.3% or 60%. A perfectly healthy map painted its best-of-a-good-bunch service full dark red.
2. **Zero sat at the bottom of the red ramp,** rendering `hsl(0 35% 90%)` — a pale pink. A service at 5% of the graph max landed at `hsl(0 37% 88%)`, a 2-point lightness difference on a 32px circle. Not perceptible.
Worst case: when no service has errors at all, `metricMax.errorRate === 0`, so every node renders identically pale pink — indistinguishable from "everyone has a trace of errors."
This is out of step with how other tools handle it. Datadog and New Relic leave healthy nodes neutral and only color on an absolute threshold breach (New Relic explicitly won't draw green for an operational node). AWS X-Ray and Grafana's Tempo service graph use a proportional donut. Nobody asks users to read error rate off a lightness ramp.
## What changed
Error rate now maps to four **absolute** states:
| Error rate | Fill |
|---|---|
| `0` | `hsl(220 8% 82%)` — neutral cool grey |
| `0 – 1%` | `hsl(0 48% 73%)` |
| `1 – 5%` | `hsl(0 61% 56%)` |
| `≥ 5%` | `hsl(0 72% 42%)` |
- The neutral is a **hue shift, not desaturation**. At 32px a cool grey reads as a different kind of thing; a washed-out red just reads as a little red.
- The three red stops come from the existing ramp, so no new hexes enter the palette.
- 5% is the threshold `ServiceMapTooltip` already used for its alarm color, now exported as `ERROR_RATE_HIGH` and shared, so the fill and the hover text can't drift apart.
- **Latency and throughput are unchanged** — they keep the max-normalized ramp. Neither has a universal threshold to anchor to.
- The legend gradient emits hard stops for error rate (four discrete steps rather than a blend, since a continuous bar would imply precision the bucketing doesn't have), and labels the top of the scale `≥5%` instead of the observed max.
No changes to `ServiceMapNode`, `ServiceMap.tsx`, or the SCSS — the node already just consumes `{ backgroundColor, borderColor }`.
## Trade-off
With hard buckets a service at 4.9% and one at 60% paint identically. A proportional donut would keep that distinction, but it's a much larger change (and a four-way success/4xx/5xx/throttle split isn't available to us — the source schema only has `statusCodeExpression`, and OTel's `StatusCode` is just `Ok`/`Error`/`Unset`). The tooltip shows the exact percentage on hover, so the detail is recoverable.
Thresholds are hardcoded as exported constants. A service on a 99.9% SLO and a batch job have different notions of "elevated" — if that comes up, per-source overrides are the natural extension.
## Testing
`packages/app/src/components/ServiceMap/__tests__/utils.test.ts` — existing ramp tests re-pointed at `throughput` (still max-normalized), plus a new block covering the absolute buckets: zero renders neutral, buckets ignore `max`, thresholds are inclusive lower bounds, and the neutral state still draws a darker border.
Full app suite green (223 suites / 3664 tests), `make ci-lint` clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
0 commit comments