Skip to content

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:

cb.SetState(metrics.FromResilienceState(int(breaker.State())))

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:

register db instruments: duplicate metrics collector registration attempted

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.