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, because 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.