Surfaced by #43, which wired up the Prometheus exporter. Now that /metrics is actually served, it's visible that several metric families are created and exported but never written to — they scrape as 0 forever, which reads as "this node is idle" rather than "this isn't instrumented".
The registry and the exporter are both fine. The gap is that the write paths never call the record* methods.
Current state
Counted callers outside the metrics modules themselves:
| Family |
Registered? |
Recorded? |
Effect |
ServerMetrics (flo_commands_total, bytes, connections, uptime) |
n/a |
✅ recordCommand, recordBytesReceived/Sent, connectionOpened/Closed |
works |
ReplicationMetrics (flo_replication_*) |
n/a |
✅ wired in #16 |
works |
ProcessingMetrics (flo_processing_*) |
n/a |
⚠️ partial — recordProcessed (2), recordCheckpoint (1), recordError (1); recordDropped, recordSubmitted, recordFailed have none |
partly live |
ShardMetrics (flo_shard_*) |
sized by initShards (#43) |
❌ shardMetrics() has 0 callers |
all rows 0 |
StreamMetrics (flo_stream_*) |
✅ registerStream ×8 |
❌ recordAppend / recordRead 0 callers |
series appear, all values 0 |
QueueMetrics (flo_queue_*) |
✅ registerQueue ×15 |
❌ recordEnqueue / recordDequeue 0 callers |
queues appear, all values 0 |
WorkflowMetrics (flo_workflow_*) |
n/a |
❌ workflow/handler.zig never references metrics_registry at all |
all 0 |
TieredLogMetrics (flo_tiered_log_*) |
❌ registerTieredLog 0 callers |
❌ |
family never appears |
KVMetrics |
⚠️ registerKVNamespace ×1 |
❌ recordGet / recordSet 0 callers |
not emitted by exportPrometheus at all |
Why it matters
A scrape today shows flo_stream_append_records_total{topic="orders"} 0 on a stream that is actively taking writes. That's worse than the family being absent: a dashboard or alert built on it silently reads "no traffic". It's the same shape as the fabricated ingest_rate: 0 / retention: "7d" values fixed in #29 — a number that looks real and isn't.
What to do
Call the existing record* methods from the write paths. The handlers already hold metrics_registry (and registerStream/registerQueue are already called there), so most of this is a one-line call next to the existing register:
stream/handler.zig — recordAppend(records, bytes) on append, recordRead(...) on read/group-read; also the trim and consumer-group counters the family already defines.
queue/handler.zig — recordEnqueue / recordDequeue / ack / nack / DLQ / lease counters.
workflow/handler.zig — wire metrics_registry in (it has none today), then recordStarted / recordCompleted / recordFailed / recordSignalDelivered / recordStepExecuted.
node/shard.zig — take registry.shardMetrics(self.id) once at wire-up and record commands / bytes / connections / reactor loops / inbox depth per shard. Note the shard already records the global server.* counters, so this is about attribution, not new data.
processing/handler.zig — fill in recordSubmitted / recordFailed / recordDropped.
- Decide whether
KVMetrics and TieredLogMetrics should be exported at all; if yes, add them to exportPrometheus and call registerTieredLog. If not, delete them rather than leaving dead structs.
Acceptance
Assert real values, not presence. E.g. append 3 records, scrape, assert flo_stream_append_records_total{...} 3 — a test that only checks the metric name appears would pass today and is exactly what let this hide.
Until this lands, docs/deployment/clustering.mdx carries a note that these families export as 0; remove it when they're populated.
Surfaced by #43, which wired up the Prometheus exporter. Now that
/metricsis actually served, it's visible that several metric families are created and exported but never written to — they scrape as0forever, which reads as "this node is idle" rather than "this isn't instrumented".The registry and the exporter are both fine. The gap is that the write paths never call the
record*methods.Current state
Counted callers outside the metrics modules themselves:
ServerMetrics(flo_commands_total, bytes, connections, uptime)recordCommand,recordBytesReceived/Sent,connectionOpened/ClosedReplicationMetrics(flo_replication_*)ProcessingMetrics(flo_processing_*)recordProcessed(2),recordCheckpoint(1),recordError(1);recordDropped,recordSubmitted,recordFailedhave noneShardMetrics(flo_shard_*)initShards(#43)shardMetrics()has 0 callers0StreamMetrics(flo_stream_*)registerStream×8recordAppend/recordRead0 callers0QueueMetrics(flo_queue_*)registerQueue×15recordEnqueue/recordDequeue0 callers0WorkflowMetrics(flo_workflow_*)workflow/handler.zignever referencesmetrics_registryat all0TieredLogMetrics(flo_tiered_log_*)registerTieredLog0 callersKVMetricsregisterKVNamespace×1recordGet/recordSet0 callersexportPrometheusat allWhy it matters
A scrape today shows
flo_stream_append_records_total{topic="orders"} 0on a stream that is actively taking writes. That's worse than the family being absent: a dashboard or alert built on it silently reads "no traffic". It's the same shape as the fabricatedingest_rate: 0/retention: "7d"values fixed in #29 — a number that looks real and isn't.What to do
Call the existing
record*methods from the write paths. The handlers already holdmetrics_registry(andregisterStream/registerQueueare already called there), so most of this is a one-line call next to the existing register:stream/handler.zig—recordAppend(records, bytes)on append,recordRead(...)on read/group-read; also the trim and consumer-group counters the family already defines.queue/handler.zig—recordEnqueue/recordDequeue/ ack / nack / DLQ / lease counters.workflow/handler.zig— wiremetrics_registryin (it has none today), thenrecordStarted/recordCompleted/recordFailed/recordSignalDelivered/recordStepExecuted.node/shard.zig— takeregistry.shardMetrics(self.id)once at wire-up and record commands / bytes / connections / reactor loops / inbox depth per shard. Note the shard already records the globalserver.*counters, so this is about attribution, not new data.processing/handler.zig— fill inrecordSubmitted/recordFailed/recordDropped.KVMetricsandTieredLogMetricsshould be exported at all; if yes, add them toexportPrometheusand callregisterTieredLog. If not, delete them rather than leaving dead structs.Acceptance
Assert real values, not presence. E.g. append 3 records, scrape, assert
flo_stream_append_records_total{...} 3— a test that only checks the metric name appears would pass today and is exactly what let this hide.Until this lands,
docs/deployment/clustering.mdxcarries a note that these families export as0; remove it when they're populated.