Skip to content

Monitoring and metrics

Jarvis exposes a Prometheus-compatible /metrics endpoint so the alerting stack it fronts can also monitor Jarvis itself. The endpoint is public (like /health) — it bypasses JARVIS_AUTH_MODE=full_protect — and exposes only aggregate counts and configured cluster names, never alert names, labels, or annotations.

Monitoring Jarvis closes an easy blind spot: the UI can remain reachable while an Alertmanager member is no longer being polled, a PostgreSQL follower has a stale snapshot, or retention sweeps have stopped. The metrics below cover HTTP requests, WebSockets, upstream polling, alert lifecycle events, leader election, snapshot freshness, and retention. There are currently no dedicated database, authentication, fanout, or history-write-error metrics.

Scrape configuration

Plain prometheus.yml configuration:

yaml
scrape_configs:
  - job_name: jarvis
    static_configs:
      - targets: ["jarvis:8080"]

For Kubernetes, the Helm chart supports Prometheus Operator discovery or pod annotations:

yaml
# Prometheus Operator (requires monitoring.coreos.com/v1 CRDs)
metrics:
  serviceMonitor:
    enabled: true
    labels:
      release: kube-prometheus-stack

# Alternatively, annotation-based scraping
metrics:
  podAnnotations: true

See the Helm values reference for all related values.

Scrape-time gauges

Computed from the in-memory alert store, the WebSocket hub, and the poller's own state at every scrape — never from a counter, so they can't drift out of sync with reality.

MetricLabelsMeaning
jarvis_build_infoversionAlways 1; join on version to track which build is running
jarvis_alertscluster, stateCurrent alert count by state (active, suppressed, unprocessed, resolved)
jarvis_alerts_by_severitycluster, severityCurrent alert count by severity label (none when unset)
jarvis_alertmanager_upcluster, member1 if the last poll of that Alertmanager HA member succeeded, else 0. Single-member clusters emit their one member; a cluster stays healthy overall as long as at least one member is up
jarvis_ws_clientsNumber of currently connected WebSocket clients
jarvis_clusters_configuredNumber of configured Alertmanager clusters
jarvis_leader1 if this pod currently holds Alertmanager-polling/history-write leadership, else 0. Always 1 on SQLite (single replica by design)
jarvis_snapshot_stale1 if a follower's consumed poll snapshot is older than 3× JARVIS_POLL_INTERVAL (a missed/delayed pg_notify and periodic resync both not having landed yet), else 0. Always 0 while leader or on SQLite

Event counters

MetricLabelsIncremented when
jarvis_poll_cycles_totalclusterEvery poll attempt of a cluster
jarvis_poll_errors_totalcluster, endpoint (alerts/silences)A poll of a cluster's alerts or silences endpoint fails
jarvis_poll_duration_secondsHistogram of the full poll cycle across all clusters, including DB persistence — use this to see whether Jarvis overall is keeping up with its poll interval
jarvis_cluster_fetch_duration_secondscluster, memberHistogram of a single Alertmanager HA member's response time (alerts or silences) — use this to find which member is slow
jarvis_alert_events_totalcluster, status (firing/suppressed/expired/resolved)A genuine alert lifecycle transition is recorded
jarvis_ws_broadcasts_totaltypeA WebSocket event is broadcast to clients
jarvis_http_requests_totalmethod, path, statusEvery HTTP request (labeled by route pattern, not raw URL)
jarvis_http_request_duration_secondsmethod, path, statusHistogram of HTTP request duration
jarvis_retention_sweeps_totalA data-retention sweep completes (see docs/retention.md). Only increments while retention is enabled — stays 0 forever on a default install
jarvis_retention_deleted_rows_totaltableRows deleted by the retention sweeper, by table (alert_events, alert_claims, alert_comments, silence_events, alert_fingerprints)
jarvis_retention_sweep_duration_secondsHistogram of a full sweep's duration across all tables

Runtime metrics (go_*, process_*) are included via the standard Prometheus Go/process collectors.

Example PromQL

promql
# Jarvis lost contact to a cluster
jarvis_alertmanager_up == 0

# Poll error rate
rate(jarvis_poll_errors_total[5m]) > 0

# Alert volume by cluster and state
sum by (cluster, state) (jarvis_alerts)

# Which Alertmanager member is slow
histogram_quantile(0.95, sum by (le, cluster, member) (rate(jarvis_cluster_fetch_duration_seconds_bucket[5m])))

# API latency p95
histogram_quantile(0.95, sum by (le, path) (rate(jarvis_http_request_duration_seconds_bucket[5m])))

Prometheus alert rules

The following starting point covers the signals Jarvis actually exports. Adjust job, durations, and thresholds to match your scrape configuration and poll interval. The leader rules intentionally aggregate across pods; checking jarvis_leader == 1 per series cannot detect zero leaders or split brain.

yaml
groups:
  - name: jarvis
    rules:
      - alert: JarvisNoLeader
        expr: sum(jarvis_leader{job="jarvis"}) == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: No Jarvis pod is the polling leader

      - alert: JarvisMultipleLeaders
        expr: sum(jarvis_leader{job="jarvis"}) > 1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: More than one Jarvis pod reports leadership

      - alert: JarvisAlertmanagerClusterDown
        expr: max by (cluster) (jarvis_alertmanager_up{job="jarvis"}) == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: Jarvis cannot reach any member of Alertmanager cluster {{ $labels.cluster }}

      - alert: JarvisSnapshotStale
        expr: max by (instance) (jarvis_snapshot_stale{job="jarvis"}) == 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: Jarvis follower {{ $labels.instance }} has a stale snapshot

      - alert: JarvisPollErrors
        expr: sum by (cluster) (rate(jarvis_poll_errors_total{job="jarvis"}[5m])) > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: Jarvis polling cluster {{ $labels.cluster }} is failing

      # Enable only when retention is configured. Set the window comfortably
      # above JARVIS_RETENTION_SWEEP_INTERVAL (12h by default).
      - alert: JarvisRetentionSweepMissing
        expr: sum(increase(jarvis_retention_sweeps_total{job="jarvis"}[25h])) < 1
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: Jarvis retention has not completed a sweep in 25 hours

jarvis_alertmanager_up is per member, so max by (cluster) alerts only when every member of a cluster is down. JarvisRetentionSweepMissing cannot infer whether retention is enabled; omit that rule on installations where it is disabled. Prometheus should also alert on the Jarvis scrape target itself being absent, because an absent target produces no jarvis_* series for these rules to evaluate.

Released under the Apache 2.0 License. Jarvis is not affiliated with Prometheus or Alertmanager.