You are reading documentation for bouine v0.4.x — not the latest version. View latest →

Prometheus metrics

All metrics are exposed at GET /metrics on the admin port (default :9000) in Prometheus text format. No authentication is required to scrape this endpoint.

Traffic (RED)

MetricLabelsDescription
bouine_requests_totalmethod, status, cache_result, source, routeTotal requests processed. This is the primary RED counter.
bouine_request_duration_secondsmethod, status, cache_result, source, routeRequest latency histogram. Includes native histogram buckets for higher-resolution percentiles. Carries Prometheus exemplars linking high-latency observations to a trace ID when tracing is enabled.
bouine_response_bytes_totalmethod, cache_result, source, routeTotal bytes written in responses.

cache_result values: HIT, MISS, STALE, REVALIDATED, BYPASS.

source values: hot (hot in-memory tier), warm (warm disk-backed tier), peer (cluster peer via peer-fetch), origin (fetched from upstream, including errors and write-through proxy).

route label: the name field of the matched route config entry. Falls back to host:path_prefix when name is empty, or _default for unmatched requests.

Hit ratio (PromQL):

sum(rate(bouine_requests_total{cache_result="HIT"}[1m]))
/
sum(rate(bouine_requests_total[1m]))

Error rate (PromQL):

sum(rate(bouine_requests_total{status=~"5.."}[1m]))
/
sum(rate(bouine_requests_total[1m]))

Hot-tier cache storage

MetricTypeDescription
bouine_hot_store_bytesgaugeCurrent bytes used by the hot in-memory tier (body + per-entry overhead).
bouine_hot_store_entriesgaugeNumber of objects currently stored in the hot tier.
bouine_hot_store_evictions_totalcounterTotal objects evicted by SIEVE since boot. Rising rate indicates cache churn.
bouine_vary_cap_hits_totalcounterVary-variant insertions rejected because MaxVariants (64) was exceeded.

Cache utilisation (PromQL):

bouine_hot_store_bytes / <hot_max_bytes_from_config>

Note. There is no bouine_hot_store_max_bytes metric — the configured maximum is a static config value, not a gauge. Use bouine_hot_store_bytes against the known hot_max_bytes config value for utilisation calculations.

Warm-tier storage

MetricTypeDescription
bouine_warm_store_bytesgaugeCurrent bytes used by the warm disk-backed tier.
bouine_warm_store_entriesgaugeNumber of objects currently stored in the warm tier.
bouine_warm_store_self_heals_totalcounterWarm-tier self-heal events (segment recovery).
bouine_wal_dropped_entries_totalcounterWAL entries dropped due to pressure (async fsync batching).
bouine_wal_last_sync_timestamp_secondsgaugeTimestamp of the last WAL fsync.

Security

MetricTypeDescription
bouine_http_smuggling_rejected_totalcounterHTTP/1.1 requests rejected by the fast-path parser’s smuggling detection.

Cluster

MetricLabelsAvailable in
bouine_cluster_mode_infomodeall
bouine_peer_fetch_hits_totalstrong
bouine_peer_fetch_misses_totalstrong
bouine_peer_fetch_hop_limit_hits_totalstrong
bouine_peer_fetch_duration_secondsstrong
bouine_cluster_invalidations_http_totaltypestrong
bouine_cluster_invalidations_gossip_totaltypeall
bouine_cluster_broadcast_failures_totaltype, reasonstrong
bouine_cluster_gossip_drops_totalall

Startup

MetricTypeLabelsDescription
bouine_startup_phasegaugephaseCurrent startup phase (WAL replay, ring build, etc.). 1 = active, 0 = complete.
bouine_startup_condition_readygaugeconditionReadiness condition status during startup.
bouine_startup_duration_secondshistogramTotal startup duration.

Cloudflare propagation

MetricLabelsDescription
bouine_cloudflare_purge_totaloperation, statusCF Cache API calls by type and outcome
bouine_cloudflare_purge_duration_secondsoperationLatency of CF API calls
bouine_cloudflare_purge_skipped_totalreasonInvalidations not forwarded to CF

Refresh before expiry

These metrics are emitted when refresh_before_expiry is enabled on at least one route. All carry a route label matching the route name.

MetricTypeLabelsDescription
bouine_refresh_totalcounterroute, resultBackground refresh fetches by result (304, 200, error, persist_cycle).
bouine_refresh_errors_totalcounterroute, error_typeFailed background refresh fetches by error type.
bouine_refresh_skips_totalcounterroute, reasonSkipped background refreshes by reason (not_found, stale, semaphore_full, rate_limited, not_registered, bad_url, below_min_hits, negative).
bouine_refresh_in_flightgaugerouteCurrent in-flight background refresh goroutines.
bouine_refresh_scheduledgaugerouteEntries currently in the refresh scheduler heap.
bouine_refresh_registry_sizegaugerouteEntries currently in the refresh registry.

Refresh rate (PromQL):

sum(rate(bouine_refresh_total[1m])) by (route)

Refresh skip ratio (PromQL):

sum(rate(bouine_refresh_skips_total{reason="below_min_hits"}[5m]))
/
sum(rate(bouine_refresh_total[5m]))

A high below_min_hits skip ratio indicates the popularity gate (refresh_min_hits) is filtering out many objects — adjust the threshold if too many popular objects are expiring.

Go runtime

The standard go_* and process_* metrics from the Prometheus Go client are automatically included. The most operationally relevant:

MetricWhy it matters
go_gc_duration_seconds{quantile="1"}Worst-case GC stop-the-world pause. If this approaches your HIT p99, raise GOMEMLIMIT. See Troubleshooting → GC pauses.
process_resident_memory_bytesActual RSS. Compare with go_gc_gomemlimit_bytes.
go_gc_gomemlimit_bytesConfigured GOMEMLIMIT. Set to ~85 % of resources.limits.memory.

Access logs

bouine emits a structured JSON access log line to stdout for every request.

{
  "time":         "2026-06-07T12:00:00Z",
  "level":        "INFO",
  "msg":          "access",
  "method":       "GET",
  "host":         "example.com",
  "path":         "/posts/hello/",
  "proto":        "HTTP/1.1",
  "status":       200,
  "bytes_out":    15234,
  "dur_ms":       1,
  "cache_status": "HIT",
  "route":        "/",
  "remote":       "10.42.0.1:54321"
}

⚠️ Access log sampling — use Prometheus for throughput

200 OK responses are sampled at 1:100. All other status codes (errors, redirects, unusual 2xx) are always logged.

This is intentional: at high RPS the log write would otherwise dominate the hot path. The trade-off is that you cannot compute accurate hit ratio or request rate from logs alone. Use bouine_requests_total in Prometheus for throughput and cache result distribution. Use access logs for error diagnosis and per-request debugging.

Rate accuracy from logs:  ❌ (1:100 sampling for 200s)
Error investigation:      ✅ (all non-200 always logged)
Slow request debugging:   ✅ (dur_ms present on sampled 200s)

Distributed tracing (OpenTelemetry)

When tracing.endpoint is set, bouine exports OTLP/HTTP spans to any OpenTelemetry-compatible backend (Grafana Tempo, Jaeger, Honeycomb, etc.).

Endpoint format

tracing.endpoint accepts either a bare host:port string or a full URL with http:// / https:// scheme. The scheme prefix is stripped automatically; WithInsecure() is used for plain HTTP.

# canonical form (host:port)
tracing:
  endpoint: "otel-collector.monitoring.svc.cluster.local:4318"
  service_name: "bouine"
  sampling_rate: 0.1

# also accepted (scheme is stripped)
tracing:
  endpoint: "http://otel-collector.monitoring.svc.cluster.local:4318"

Leave endpoint empty (the default) to disable tracing at zero overhead.

Trace structure

Each request produces a nested span tree:

bouine.listener.http   (L1 — network accept + protocol detection)
  bouine.pipeline      (L2 — route matching, metrics, access log)
    bouine.cache       (L4 — RFC 9111 state machine)
      bouine.origin    (L5 — upstream fetch, miss/revalidate path only)

The bouine.origin span carries W3C TraceContext headers (traceparent, tracestate) injected into the upstream request, so the origin server can continue the trace if it also exports spans.

Exemplars

When tracing is active, bouine_request_duration_seconds observations carry a Prometheus exemplar with trace_id. In Grafana, click any histogram bar and select “Query with exemplar” to jump directly from a high-latency bucket to the matching trace in Tempo.


Admin API

EndpointMethodAuth requiredDescription
/healthzGETLiveness probe
/readyzGETReadiness probe; 503 during drain
/versionGETBinary version, commit, build date
/metricsGETPrometheus metrics
/v1/cluster/peersGETGossip member list
/v1/peer/fetchPOSTInternal: cluster peer-fetch RPC
/v1/purgePOSTExact URL purge
/v1/banPOSTPredicate ban
/v1/refreshPOSTSoft-purge (mark stale)
/v1/statsGETRuntime stats (store entries, ring info)
/v1/configGETRead-only view of running configuration
/v1/debug/cachecheck?url=...GETCache debug info for a URL
/debug/pprof/*GETGo pprof profiling endpoints (when admin.pprof_enabled)
/dashboard/GETsessionOperator web dashboard

Grafana dashboard

An official RED dashboard JSON is shipped in the bouine repository at deploy/grafana/bouine-red.json. Import it into Grafana via Dashboards → Import → Upload JSON file.

The dashboard covers five rows:

RowContents
RateRPS, hit ratio %, error rate %, active pod count, cluster mode, peer-fetch hit ratio
Errors5xx rate by route, error ratio by status, live error log stream
DurationHIT/MISS/REVALIDATED p50/p99/p999, p99 by route
Cache internalsResult mix, response throughput, peer-fetch hits/misses/latency
Go runtime / GCGC pause vs HIT p99, RSS vs GOMEMLIMIT, goroutines, heap

groups:
  - name: bouine
    rules:

    # ── Traffic ───────────────────────────────────────────────────────────
    - alert: BouineHighErrorRate
      expr: |
        sum(rate(bouine_requests_total{status=~"5.."}[5m]))
        /
        sum(rate(bouine_requests_total[5m])) > 0.01
      for: 5m
      labels: { severity: critical }
      annotations:
        summary: "bouine 5xx error rate > 1%"

    - alert: BouineHighStaleRate
      expr: |
        sum(rate(bouine_requests_total{cache_result="STALE"}[5m]))
        /
        sum(rate(bouine_requests_total[5m])) > 0.10
      for: 10m
      labels: { severity: warning }
      annotations:
        summary: "Stale serve ratio > 10% — possible origin outage"

    # ── Storage ───────────────────────────────────────────────────────────
    - alert: BouineHighEvictionRate
      expr: rate(bouine_hot_store_evictions_total[5m]) > 100
      for: 10m
      labels: { severity: warning }
      annotations:
        summary: "High SIEVE eviction rate — working set may exceed hot_max_bytes"

    # ── GC / Runtime ──────────────────────────────────────────────────────
    - alert: BouineGCPauseHigh
      expr: |
        max(rate(go_gc_duration_seconds_sum[1m])
            / rate(go_gc_duration_seconds_count[1m])) > 0.01
      for: 5m
      labels: { severity: warning }
      annotations:
        summary: "GC average pause > 10 ms — raise GOMEMLIMIT (see troubleshooting)"

    # ── Cluster ───────────────────────────────────────────────────────────
    - alert: BouineClusterModeMismatch
      expr: count(count by (mode) (bouine_cluster_mode_info == 1)) > 1
      for: 2m
      labels: { severity: critical }
      annotations:
        summary: "Pods running different cluster modes — configuration drift"