Prometheus Metrics¶
Circuit Breaker exposes an inventory metrics endpoint in the Prometheus text exposition format. You can scrape it directly from Prometheus, Grafana Alloy, the OpenTelemetry Collector, or any compatible agent.
For v1.0 release candidates, this metrics schema is beta unless a later contract promotes it. Do not treat metric names, labels, or cardinality as a stable public API before the 1.0 compatibility policy says otherwise.
Endpoint¶
GET /api/v1/metrics/metrics
Returns text/plain; version=0.0.4; charset=utf-8 — the standard Prometheus scrape format.
Note: The endpoint is intentionally placed under
/api/v1/(not at the root/metrics) to avoid conflicts with Circuit Breaker's frontend catch-all route.The path really does end in
/metrics/metrics: the metrics router is mounted at the/api/v1/metricsprefix and declares its route as/metrics.GET /api/v1/metricsanswers404.
Authentication¶
Authentication is always required — there is no unauthenticated mode for this endpoint. Provide a
valid Authorization: Bearer <token> header. Without a valid token the endpoint returns HTTP 401.
Generating a token for Prometheus¶
Create a service account token as an admin:
curl -X POST https://your-server/api/v1/auth/service-account \
-H "Authorization: Bearer <your-admin-jwt>" \
-H "Content-Type: application/json" \
-d '{"label": "prometheus", "scopes": ["read:*"]}'
The response contains the token. It defaults to a one-year lifetime (expires_at accepts an ISO
datetime to shorten it) and can be revoked later from the API token list. A session JWT copied from
the browser also works, but expires with the session.
CB_API_TOKEN is deprecated and is not a working scrape credential: a request presenting it as a
bearer token is rejected with 401 unless CB_LEGACY_AUTH=true is set on the backend. It is retained only for
backward compatibility and has no scheduled removal release — use a service account instead.
Prometheus Scrape Configuration¶
scrape_configs:
- job_name: 'circuit-breaker'
static_configs:
- targets: ['your-server:80']
metrics_path: '/api/v1/metrics/metrics'
authorization:
credentials: '<your-service-account-token>'
Use the port you published the app on: CB_PORT (default 80) for HTTP or CB_PORT_HTTPS
(default 443) for HTTPS. 8080 and 8443 are the container-internal ports and are not reachable
from outside the container.
Full Metrics Reference¶
App Metadata¶
| Metric | Type | Labels | Description |
|---|---|---|---|
circuitbreaker_info |
Info | version |
Circuit Breaker application version |
Inventory Counts¶
| Metric | Type | Labels | Description |
|---|---|---|---|
circuitbreaker_hardware_total |
Gauge | — | Total hardware nodes in inventory |
circuitbreaker_compute_units_total |
Gauge | kind |
Compute units grouped by kind (vm, container) |
circuitbreaker_services_total |
Gauge | — | Total services in inventory |
circuitbreaker_services_by_status_total |
Gauge | status |
Services grouped by operational status. One series per status: running, stopped, degraded, maintenance (zero-filled) |
circuitbreaker_storage_items_total |
Gauge | kind |
Storage items grouped by kind (disk, pool, dataset, share) |
circuitbreaker_storage_capacity_gb_total |
Gauge | — | Sum of all configured storage capacity in GB |
circuitbreaker_storage_used_gb_total |
Gauge | — | Sum of all reported storage usage in GB |
circuitbreaker_networks_total |
Gauge | — | Total network segments |
circuitbreaker_hardware_clusters_total |
Gauge | — | Total hardware clusters defined |
circuitbreaker_external_nodes_total |
Gauge | provider, kind |
External nodes grouped by provider and kind |
circuitbreaker_misc_items_total |
Gauge | — | Total miscellaneous items |
circuitbreaker_docs_total |
Gauge | — | Total documentation entries |
circuitbreaker_users_total |
Gauge | — | Total registered users |
circuitbreaker_tags_total |
Gauge | — | Total unique tags |
circuitbreaker_service_dependencies_total |
Gauge | — | Total service-to-service dependency edges |
circuitbreaker_audit_log_entries_total |
Gauge | level, category |
Audit log entries grouped by level and category |
Per-Resource State¶
These follow the kube-state-metrics enum state pattern: one series is emitted per {resource × possible_state} combination. The active state receives value 1, all inactive states receive 0. This makes alerting on specific states straightforward.
| Metric | Type | Labels | Description |
|---|---|---|---|
circuitbreaker_service_status |
Gauge | name, slug, environment, status |
Current status of each service. One series per service per status (running, stopped, degraded, maintenance); value is 1 for the active status, 0 for all others |
circuitbreaker_hardware_memory_configured_gb |
Gauge | name, role |
Configured memory per hardware node in GB (omitted if not set) |
circuitbreaker_compute_unit_memory_configured_mb |
Gauge | name, kind |
Configured memory per compute unit in MB (omitted if not set) |
circuitbreaker_compute_unit_cpu_cores_configured |
Gauge | name, kind |
Configured CPU cores per compute unit (omitted if not set) |
circuitbreaker_storage_capacity_gb |
Gauge | name, kind |
Configured capacity per storage item in GB (omitted if not set) |
circuitbreaker_storage_used_gb |
Gauge | name, kind |
Reported used space per storage item in GB (omitted if not set) |
Service-objective series¶
The tables above are point-in-time gauges rebuilt from the database on every scrape. These are process-lifetime counters and gauges, held in a separate registry so a scrape does not reset them, and appended to the same exposition. They are the measurement source behind the 1.0.0 service objectives.
| Metric | Type | Labels | Description |
|---|---|---|---|
circuitbreaker_http_requests_total |
Counter | method, route, status_class |
HTTP requests served. The availability indicator |
circuitbreaker_http_request_duration_seconds |
Histogram | method, route |
Request duration. Buckets are 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0 — 0.5 is a bucket edge because the latency objective is stated at p95 < 500 ms |
circuitbreaker_health_state |
Gauge | state |
One-hot: 1 for the current state, 0 for the others. Every state (starting, ready, degraded, not_ready, stopping) is always present, so an alert fires on a value rather than on a missing label |
circuitbreaker_write_admission_rejections_total |
Counter | health |
Mutating requests refused because readiness could not serve them safely, by the state that refused |
circuitbreaker_background_job_runs_total |
Counter | job, outcome |
Background job executions. The skipped_not_owner outcome makes single-owner execution observable rather than merely asserted |
circuitbreaker_process_uptime_seconds |
Gauge | — | Seconds since this process finished importing |
Cardinality is bounded by construction. route is the route template the request matched
(/api/v1/hardware/{hardware_id}), never the raw path — which would carry ids and grow without
limit. A request that matched no route collapses to the single value unmatched. Status is bucketed
into a class rather than exposed per code.
Example PromQL Queries¶
# Services currently stopped or degraded
circuitbreaker_service_status{status=~"stopped|degraded"} == 1
# Count of running services
sum(circuitbreaker_service_status{status="running"} == 1)
# Service count by environment
sum by (environment) (circuitbreaker_service_status{status="running"} == 1)
# Storage utilization ratio per item (requires both capacity and usage to be set)
circuitbreaker_storage_used_gb / circuitbreaker_storage_capacity_gb
# Overall storage fill percentage
sum(circuitbreaker_storage_used_gb_total) / sum(circuitbreaker_storage_capacity_gb_total) * 100
# Total configured RAM across all hardware nodes (GB)
sum(circuitbreaker_hardware_memory_configured_gb)
# Total configured RAM across all compute units (GB, converted from MB)
sum(circuitbreaker_compute_unit_memory_configured_mb) / 1024
# Compute units per kind
circuitbreaker_compute_units_total
# Services without a "running" status (potentially unhealthy)
circuitbreaker_services_total - sum(circuitbreaker_service_status{status="running"} == 1)
Grafana Panel Suggestions¶
| Panel | PromQL |
|---|---|
| Service status overview (stat panel) | sum by (status) (circuitbreaker_service_status == 1) |
| Degraded/stopped services (alert panel) | sum(circuitbreaker_service_status{status=~"stopped\|degraded"} == 1) |
| Storage utilization (gauge panel) | sum(circuitbreaker_storage_used_gb_total) / sum(circuitbreaker_storage_capacity_gb_total) * 100 |
| Inventory totals (stat panels) | circuitbreaker_hardware_total, circuitbreaker_services_total, circuitbreaker_compute_units_total |
| RAM by host (bar chart) | circuitbreaker_hardware_memory_configured_gb |
| Storage by item (bar chart) | circuitbreaker_storage_capacity_gb |
Implementation Notes¶
- No global state: A fresh
CollectorRegistryis created per scrape request. There are no background threads or process-level metric accumulators. Every scrape reflects the current database state. - DB-backed, not push-based: Metrics are queried on demand from the Circuit Breaker PostgreSQL database. They represent point-in-time inventory values, not counters that accumulate over time.
- Null safety: Per-resource metrics (memory, CPU, capacity, usage) are only emitted for resources where the value is explicitly configured. Resources with null values are silently omitted to avoid misleading zero values.
- OpenAPI schema: The endpoint is excluded from the Circuit Breaker OpenAPI/Swagger UI (
include_in_schema=False) because it returns plain text, not JSON.