ProofedMesh/services/attest-reconciler/internal/metrics/metrics.go
CTO 2e44147a71
All checks were successful
validate / JSON Schema (ajv) (push) Successful in 19s
validate / Go (vet + test) (push) Successful in 19s
validate / Rego (opa fmt + opa test) (push) Successful in 14s
services: add attest-reconciler Go service (periodic worker re-signs per_diem_rates rows without rate_change_attestation_ref via Signer+TLog, 11/11 go tests pass, go vet clean)
- services/attest-reconciler/ as its own go.mod (admin-api internal/ is not importable; shared code gets extracted when a 3rd consumer appears).
- Scheduler accepts "@every <dur>" or a bare Go duration; context-cancellation stops the loop cleanly.
- Store client defines PendingRateStore + Postgres skeleton for SELECT ... WHERE rate_change_attestation_ref IS NULL.
- Orchestrator + HTTPSigner + tlog.HTTPSubmitter mirror admin-api byte-for-byte so the TLog idempotency key collides on resubmits (kein Doppelsignieren).
- Prometheus text-format /metrics endpoint, dependency-free: attest_reconciler_dirty_rows_total, _reconciliation_attempts_total, _reconciliation_failures_total, _idempotent_noop_total.
- Tests with fake store + fake signer + fake submitter cover: happy path, already-attested rows are NOT re-signed, TLog idempotency across ticks, TLog-down failure path, pre-cancelled ctx.
- Top-level README reflects the new service; CI auto-discovers services/*/go.mod so no workflow change needed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:06:36 +00:00

74 lines
3.1 KiB
Go

// Package metrics implements the three Prometheus counters the reconciler
// exposes, without pulling the prometheus/client_golang module. The /metrics
// handler prints the standard text exposition format.
package metrics
import (
"fmt"
"io"
"net/http"
"sync/atomic"
)
// Registry holds the reconciler's counters. Use New() to construct and share
// one instance across the scheduler and the HTTP handler.
type Registry struct {
dirtyRows atomic.Int64
reconciliationAttempts atomic.Int64
reconciliationFailures atomic.Int64
reconciliationNoopHits atomic.Int64
}
func New() *Registry { return &Registry{} }
// SetDirtyRows sets the current pending-row count. Called at the top of each
// tick, before any attempts land.
func (r *Registry) SetDirtyRows(n int64) { r.dirtyRows.Store(n) }
func (r *Registry) IncAttempts() { r.reconciliationAttempts.Add(1) }
func (r *Registry) IncFailures() { r.reconciliationFailures.Add(1) }
func (r *Registry) IncIdempotentNoopHits() { r.reconciliationNoopHits.Add(1) }
// Snapshot returns a point-in-time copy; useful for tests.
type Snapshot struct {
DirtyRows int64
ReconciliationAttempts int64
ReconciliationFailures int64
IdempotentNoopHits int64
}
func (r *Registry) Snapshot() Snapshot {
return Snapshot{
DirtyRows: r.dirtyRows.Load(),
ReconciliationAttempts: r.reconciliationAttempts.Load(),
ReconciliationFailures: r.reconciliationFailures.Load(),
IdempotentNoopHits: r.reconciliationNoopHits.Load(),
}
}
// Handler returns an http.HandlerFunc that writes the Prometheus text format.
func (r *Registry) Handler() http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
r.write(w)
}
}
func (r *Registry) write(w io.Writer) {
s := r.Snapshot()
fmt.Fprintln(w, "# HELP attest_reconciler_dirty_rows_total Current count of per_diem_rates rows missing an attestation reference (gauge-style, overwritten each tick).")
fmt.Fprintln(w, "# TYPE attest_reconciler_dirty_rows_total gauge")
fmt.Fprintf(w, "attest_reconciler_dirty_rows_total %d\n", s.DirtyRows)
fmt.Fprintln(w, "# HELP attest_reconciler_reconciliation_attempts_total Total pending rows the reconciler attempted to re-sign.")
fmt.Fprintln(w, "# TYPE attest_reconciler_reconciliation_attempts_total counter")
fmt.Fprintf(w, "attest_reconciler_reconciliation_attempts_total %d\n", s.ReconciliationAttempts)
fmt.Fprintln(w, "# HELP attest_reconciler_reconciliation_failures_total Total pending-row attempts that returned an error from the signer or transparency log.")
fmt.Fprintln(w, "# TYPE attest_reconciler_reconciliation_failures_total counter")
fmt.Fprintf(w, "attest_reconciler_reconciliation_failures_total %d\n", s.ReconciliationFailures)
fmt.Fprintln(w, "# HELP attest_reconciler_idempotent_noop_total Rows the reconciler re-signed where the TLog already had the entry (safe no-op; proves the idempotency contract works).")
fmt.Fprintln(w, "# TYPE attest_reconciler_idempotent_noop_total counter")
fmt.Fprintf(w, "attest_reconciler_idempotent_noop_total %d\n", s.IdempotentNoopHits)
}