Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Metrics: functional counters without a server

camayoc is a CLI toolkit. Every entry point in the justfile is python3 scripts/<x>.py, nothing listens, and the service name it would answer on returns 404 because there is no service behind it. That is not a gap to be closed by adding a daemon — it is what camayoc is.

So the work reports itself as it happens, to a Pushgateway. This is creel's resolution to the same problem (creel/docs/metrics.md), and the shape aegis-wou8k asks for.

What is exported

jobpushedcarries
camayoc_producerevery run, unconditionallycamayoc_producer_run_timestamp_seconds, camayoc_producer_duration_seconds, camayoc_producer_exit_status
camayoconly on a successful runthe functional samples below
metricmoves when
camayoc_ingest_commits_scanned{adapter}an ingest reads more history
camayoc_ingest_items_linked{adapter}commits carry work-item ids
camayoc_ingest_commits_unlinked{adapter}they do not — the denominator
camayoc_ingest_edges_written{adapter}modifies edges are produced

Why two groups

A gap in a pushed series has two causes that are identical at the gateway:

the producer DIED          vs      the producer RAN and had nothing to say

camayoc_producer going stale means nothing is running it. camayoc going stale while the producer group stays fresh means camayoc ran and did no work. Collapse them into one job and a pipeline can go quiet while everything reports healthy — which is the up=1-while-dead class this exists to close, and which this fleet has been bitten by (a watcher sat up=1 for three hours observing a store nobody was writing to).

Each adapter (and optional instance) has its own grouping key. Pushes use PUT to replace that complete group: a metric name omitted from the next successful run disappears. POST would retain that retired name indefinitely. The producer and sample jobs are separate, so updating one leaves the other untouched. A failed run updates only the producer group and retains the last good samples.

Why these counters and not up

up 1 says a scrape succeeded. It is true of a tool that has done nothing for a week. A functional counter moves when real work happens and stays put when it does not, so the difference between two readings IS the work. The numbers above are the ingest's own totals — the same ones it prints for a human — pushed unchanged. Counting separately from the work lets the count drift from it, and then the metric is measuring the metric.

Running it

CAMAYOC_METRICS_PUSHGATEWAY=http://user@host[:port] \
CAMAYOC_METRICS_PASSWORD_FILE=/path/to/password-file \
  python3 scripts/ingest_git_provenance.py <repo> --project <prefix>

Unset the variable and nothing is pushed — the ingest is unaffected and says metrics: CAMAYOC_METRICS_PUSHGATEWAY is unset on stderr. That direction is deliberate: the ingest is the point and the metric is the observation of it, so a missing gateway must never fail a run. It is never SILENT about it either, because a metrics pipeline that quietly does nothing is the thing being guarded against.

The password file takes precedence over an inline URL password. An unreadable or empty configured file refuses the push without falling back to the inline credential. The URL must include a username when using a password file. Without the file setting, inline credentials and unauthenticated gateways still work. The credential arrives at run time and is never written to the repo.

Verified

2026-09-04, two runs against a live gateway, same command, different --limit:

run 1run 2
camayoc_ingest_commits_scanned540
camayoc_ingest_items_linked115
camayoc_ingest_edges_written276

read back from Prometheus, not from the pushing script:

camayoc_ingest_commits_scanned{adapter="git_provenance", job="camayoc"} => 40
camayoc_producer_exit_status{adapter="git_provenance", job="camayoc_producer"} => 0

A moving value read from the scrape target is the claim; a push returning 200 is not, and neither is a gauge that is always 0.

2026-09-11, isolated adapter groups on a live gateway: PUT of A+B followed by A left A present and B absent. The POST control retained B after the same pair of pushes. The sibling producer group's scraped lines stayed byte-identical in both arms. Both probe groups were deleted and read back as absent. The PUT arm used push() with a password file and a username-only gateway URL.