Skip to content

Repository files navigation

Missing Is Not Zero Lab

CI

An interactive OpenTelemetry lab about the difference between a real zero, a historical value, and no current observation.

The default incident models an image-resize worker whose queue gauge stops reporting at second 197 while ingress continues at 50 items/second:

Strategy Dashboard value What it claims
Last-value fill 20 The 420-second-old observation is still current
Zero fill 0 The missing queue is empty
Absence-aware The current value is unknown
Cross-signal estimate 20,170 Ingress events and the stalled-drain boundary expose accumulating work

The first two charts look calm. Both manufacture meaning that the telemetry did not contain.

What makes this a telemetry.sh lab

The interface visualizes the moment observations stop, while the API emits the same incident as correlated OTLP-shaped signals:

  • Metrics contain the real gauge observations followed by an explicit data point with the No recorded value flag and no numeric field.
  • Logs record the worker stall, the staleness marker, and the freshness-budget alert.
  • Traces compare the four rendering strategies and identify which ones preserve missingness.
  • Independent ingress evidence produces a clearly labeled queue estimate rather than silently rewriting the missing gauge.
  • Every response proves that GNU Make executed the model and includes the model’s SHA-256.

Inspect the evidence:

curl 'http://localhost:8080/api/simulate'
curl 'http://localhost:8080/api/telemetry'
curl 'http://localhost:8080/healthz'

Run it

Requirements:

  • Node.js 24+
  • GNU Make
npm ci
npm start

Open http://localhost:8080.

Or use Docker:

docker compose up --build

The executable Make model

model/lab.mk is the source of truth. It clamps the experiment inputs, builds the observation timeline, computes the freshness boundary, and writes JSON:

make -s -f model/lab.mk model \
  WINDOW=600 \
  OBSERVATION_INTERVAL=30 \
  STALL=197 \
  STALE_AFTER=90 \
  LAST_QUEUE=20 \
  INGRESS_RATE=50

The last real observation is the scheduled observation strictly before the stall:

last_observation = floor((stall − 1) / interval) × interval
observation_age = window − last_observation

The cross-signal estimate assumes ingress remains measurable and the stalled worker stops draining:

estimated_queue =
  last_observed_queue + ingress_rate × (window − stall)

That estimate is not substituted into the original gauge. It is exported under a different metric name with its derivation made explicit.

API inputs

GET /api/simulate and GET /api/telemetry accept:

Query parameter Range Default Meaning
windowSeconds 120–1,800 600 Observation window
observationIntervalSeconds 5–120 30 Gauge reporting interval
stallSecond 1–window−1 197 Time the callback stops
staleAfterSeconds 5–600 90 Freshness budget
lastObservedQueue 0–1,000,000 20 Stable pre-stall queue
ingressRatePerSecond 1–10,000 50 Correlated incoming work

Example:

curl 'http://localhost:8080/api/simulate?windowSeconds=900&observationIntervalSeconds=60&stallSecond=311&staleAfterSeconds=120&ingressRatePerSecond=80'

Why missingness is part of the data model

The OpenTelemetry Metrics Data Model defines the No recorded value flag for an explicitly missing point. It says that a previously present timeseries was removed, should no longer be returned by queries, and that numeric fields on the flagged point should be ignored:

The OpenTelemetry supplementary guidance also describes staleness markers as a way to indicate the start of a stream gap:

Prometheus similarly removes marked-stale series from queries rather than treating them as zero:

Zero is a measurement. Missing is state about a measurement. They need different storage and query behavior.

Verify it

npm run check

The test suite executes the real Make model, checks the observation and freshness boundaries, verifies flagged OTLP points contain no number, validates all three signal types, and exercises the live HTTP endpoints. CI repeats those checks and builds the container image.

License

MIT

About

Interactive GNU Make lab showing why missing gauge observations are neither zero nor fresh last values.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages