Metrics¶
The service exposes Prometheus metrics on /metrics at
http.address. Two metric families
cooperate to produce the surface operators care about:
oxfeed_feed_*— service-level state: which version is live, how big the feed is, whether in-memory pruning is active.oxfeed_source_command_*— pipeline metrics from the command source layer: how often the command runs, what exit codes it returns, how long it takes, when feed and codes content changes.
The default Go runtime and process collectors from the standard
Prometheus client are also exposed (go_*, process_*). They are not
listed here.
Label cardinality watch-out¶
Most metrics are unlabelled. Two carry labels:
oxfeed_source_command_exec_exitcode_total— labelled by exit code. Cardinality is bounded by how many distinct exit codes the source command actually returns; in practice you see0,11, and at worst a handful of error codes.oxfeed_source_command_exec_error_message_total— labelled by the raw Go error string. This is the one to watch. Transient errors with embedded addresses, timestamps, or process IDs can push the label set unbounded over time. If you ingest these metrics long-term, drop theerrorlabel at scrape time or alert on its cardinality.
Is the command running?¶
These metrics answer "is the source command being executed, and does it succeed?".
| Metric | Type | Meaning |
|---|---|---|
oxfeed_source_command_exec_total |
Counter | Total invocations of the command since startup. |
oxfeed_source_command_exec_success_last_time_seconds |
Gauge | Unix timestamp of the last successful run (including exit 11). |
oxfeed_source_command_exec_error_total |
Counter | Runs that failed: any exit code other than 0 or 11, plus output-parsing failures and timeouts. |
oxfeed_source_command_exec_exitcode_total |
Counter | Per-exit-code counter of non-zero exits. |
oxfeed_source_command_exec_error_message_total |
Counter | Per-error-message counter. High cardinality — see the watch-out above. |
Exit code 11 is the "not modified" signal documented under
Architecture → Exit codes; it dominates
the exit-code counter when the command supports --wait and the
source data is quiet. Alert on the error rate, not on the raw exit
code counter.
Alert on staleness with the success timestamp:
# No successful command run in the last 30 minutes.
time() - oxfeed_source_command_exec_success_last_time_seconds > 1800
Are updates landing?¶
| Metric | Type | Meaning |
|---|---|---|
oxfeed_source_command_feed_change_total |
Counter | Feed CSV content changed (hash differs from the previous accepted run). |
oxfeed_source_command_feed_change_last_time_seconds |
Gauge | Unix timestamp of the last feed content change. |
oxfeed_source_command_codes_change_total |
Counter | feed-codes.json content changed. |
oxfeed_source_command_codes_change_last_time_seconds |
Gauge | Unix timestamp of the last codes change. |
These count content changes, not command invocations. A command that
runs every five seconds against unchanged data will move
oxfeed_source_command_exec_success_last_time_seconds on every run
but leave the change counters flat.
A long-quiet feed is normal for low-churn sources (admin overrides rarely change). To detect a truly stuck pipeline, combine this with the exec success metric: the command running successfully but the content never changing is the expected steady state.
What is the feed state?¶
| Metric | Type | Meaning |
|---|---|---|
oxfeed_feed_current_version |
Gauge | Latest version written (snapshot or delta). Monotonically increasing per instance. |
oxfeed_feed_current_version_entries |
Gauge | Number of domain entries in the latest snapshot written. NaN before the first snapshot. |
oxfeed_feed_size_bytes |
Gauge | Total bytes occupied by snapshots, deltas, and feed-codes.json under feed.root. |
The size gauge is the easiest signal for a runaway feed or a stuck
expiry policy. If it grows monotonically over weeks the
feed.expiry rules are too
permissive, or if feed.prune_enabled is off the in-memory state may
be growing alongside it.
How fast is the command?¶
| Metric | Type | Meaning |
|---|---|---|
oxfeed_source_command_exec_duration_seconds |
Histogram | Wall-clock duration of each command run. Includes time spent inside --wait if the command supports it. |
oxfeed_source_command_update_channel_blocked_seconds |
Histogram | Time the source spent blocked publishing an update to the feed writer. |
Long tails on exec_duration_seconds are usually the command waiting
for source data to change (with --wait), not a slow source. To
distinguish, also look at feed_change_total: high duration + low
change rate is normal idle behaviour, high duration + high change
rate means the command itself is slow.
update_channel_blocked_seconds rising signals that the feed writer
is the bottleneck — usually storage backend slowness or an unusually
large delta to compress and ship.
PromQL for command duration p95:
Is pruning recovering memory?¶
These metrics are only meaningful when
feed.prune_enabled is
true.
| Metric | Type | Meaning |
|---|---|---|
oxfeed_feed_prune_call_total |
Counter | Times the in-memory feed state was compacted. |
oxfeed_feed_prune_saved_bytes_total |
Counter | Bytes freed by those compactions, cumulative. |
A prune_call_total rate close to zero on a long-running instance with
pruning enabled means the mutation-based trigger never fires (the feed
is small or stable) and the time-based trigger is doing the work.
prune_saved_bytes_total not increasing means there is no stringstore
fragmentation to recover, and pruning can be turned back off.