Instrument resources (DB, cache, breakers, queues)¶
Databases, caches, circuit breakers and work queues are the resources whose
USE signals — Utilization, Saturation, Errors — tell you whether a service is
healthy under load. transport-metrics ships ready-made helpers for each, so you
instrument them without hand-rolling Prometheus. All register on the same registry
that serves /metrics, and each carries a constant identifying label.
Database¶
Query RED plus connection-pool USE:
db, err := m.NewDB("orders")
if err != nil { return err }
// Query RED — labelled by a bounded query name (never a raw SQL string):
err = db.Query(ctx, "get_order", func() error {
return sqldb.QueryRowContext(ctx, q, id).Scan(&o)
})
// Pool USE — scraped from database/sql on each collect:
_ = db.RegisterPool(func() metrics.PoolStats {
s := sqldb.Stats()
return metrics.PoolStats{
MaxOpen: s.MaxOpenConnections, Open: s.OpenConnections,
InUse: s.InUse, Idle: s.Idle,
WaitCount: s.WaitCount, WaitSeconds: s.WaitDuration.Seconds(),
}
})
Metrics (const label db="orders"): db_queries_total, db_query_errors_total,
db_query_duration_seconds, db_queries_in_flight (label query); and
db_pool_{max_open,open,in_use,idle}, db_pool_wait_count_total,
db_pool_wait_seconds_total.
Cache¶
c, _ := m.NewCache("sessions")
if hit { c.Hit() } else { c.Miss() }
c.Eviction(1)
c.SetSize(cache.Len())
c.SetCapacity(cache.Cap())
Metrics (const label cache="sessions"): cache_hits_total,
cache_misses_total, cache_evictions_total, cache_size, cache_capacity. Hit
ratio is rate(cache_hits_total[5m]) / (rate(cache_hits_total[5m]) + rate(cache_misses_total[5m])).
Circuit breaker¶
cb, _ := m.NewCircuitBreaker("payments")
cb.SetState(metrics.BreakerClosed) // 0 closed, 1 open, 2 half-open
cb.Success() // or cb.Failure()
cb.Trip() // increments trips and sets state open
cb.ShortCircuit() // a request rejected while open
The state values (BreakerClosed=0, BreakerOpen=1, BreakerHalfOpen=2) match
go/transit's resilience.State ordering. When mapping a state observed from a
resilience breaker, use metrics.FromResilienceState(int(state)) rather than a
bare numeric cast:
Metrics (const label name="payments"): circuit_breaker_state
(gauge 0=closed, 1=open, 2=half-open),
circuit_breaker_trips_total, circuit_breaker_short_circuits_total,
circuit_breaker_successes_total, circuit_breaker_failures_total.
Queue¶
q, _ := m.NewQueue("jobs")
q.Enqueue(); q.SetDepth(queue.Len())
// on dequeue:
q.Dequeue(); q.ObserveWait(waited)
q.ObserveProcessing(ctx, took) // exemplar-aware
Metrics (const label queue="jobs"): queue_depth, queue_enqueued_total,
queue_dequeued_total, queue_wait_seconds, queue_processing_seconds.
Options¶
Every helper (and NewOperations) takes the same HelperOption set:
| Option | Effect |
|---|---|
WithNamespace("app") |
prefix the metric names (app_cache_hits_total) |
WithBuckets(...) |
duration-histogram buckets (default prometheus.DefBuckets) |
WithConstLabels{...} |
extra constant labels, merged with the identifying label |
Multiple instances of the same resource type¶
Call the constructor once per resource (NewDB("orders"), NewDB("billing")) — the
identifying label distinguishes them and they share metric names. A repeated name is
a duplicate-registration error, returned rather than panicked:
A second call under the same name succeeds only if you also change the namespace
(WithNamespace), because that changes the metric names. Keep the names bounded, as
always — they are label values.
What the helpers will not do for you¶
SetSize, SetCapacity and SetDepth are values you push; nothing reads your cache
or queue, so a gauge you never set reads 0 rather than "unknown". The breaker
counters are likewise yours to call — the state gauge does not infer trips. The one
exception is DB.RegisterPool, whose callback is invoked on every scrape and does
read live state; keep it cheap for that reason.
There is no way to unregister a helper once created. See Limitations.