Skip to content
cronpad
Observability / Metrics

Metrics

cronpad’s metrics use OpenTelemetry names (dot-separated). The Prometheus exporter converts them to Prometheus conventions: dots become underscores and the unit becomes a suffix, so cronpad.run.start_lag (milliseconds) is exposed as cronpad_run_start_lag_milliseconds_bucket, _sum and _count. Labels are deliberately low-cardinality: there is never a per-job or per-workspace label. Per-job numbers are in the dashboard and the stats API.

Runs

MetricTypeLabelsDescription
cronpad.run.start_laghistogram (ms)sink, triggerScheduled time → first attempt started. The SLA number.
cronpad.run.queue_waithistogram (ms)Claimed → started by a worker.
cronpad.run.sink_durationhistogram (ms)sink, outcomeDuration of each attempt.
cronpad.run.e2ehistogram (ms)sink, statusScheduled time → final attempt finished, including retries.
cronpad.runscountersink, statusRuns reaching a final or deferred state: succeeded, failed, deferred, skipped, cancelled.
cronpad.runs.sla_breachedcountersinkRuns whose start lag exceeded their SLA threshold.
cronpad.attemptscountersink, outcomeAttempts by success, retryable, fatal, timeout.
cronpad.duplicates_suppressedcounterRedeliveries of already-finished runs that were skipped.

Histogram buckets (ms): 1, 5, 10, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000. cronpad.results.batch_size instead uses command-count buckets 1, 10, 50, 100, 250, 500. Observable queue gauges are sampled every five seconds.

Scheduler

MetricTypeLabelsDescription
cronpad.dispatcher.concurrency_deferredcounterClaims deferred 250ms by workspace concurrency caps; no workspace label.
cronpad.dispatcher.claimedcounterOccurrences claimed.
cronpad.dispatcher.tick_durationhistogram (ms)Time per claim-loop iteration.
cronpad.dispatcher.backpressurecounterIterations where all workers were busy. Rising = add workers.
cronpad.dispatcher.lock_heldgauge (0/1)Whether this process is the active dispatcher.
cronpad.queue.due_sizegaugeEntries in the due queue (future and due).
cronpad.queue.inflight_sizegaugeEntries currently leased.
cronpad.queue.oldest_due_agegauge (ms)How long the oldest due-but-unclaimed entry has waited. The best single “falling behind” signal.
cronpad.leases.expiredcounterLeases that expired and were redelivered. Should be 0.
cronpad.leases.lostcounterWorkers that found their lease taken while still running.
cronpad.reconciler.repairedcounterkindQueue entries restored from the database (lost_member, lost_retry, lost_inflight, full_sweep).
cronpad.reconciler.backloggedcounterkindOverdue jobs that were simply waiting in the queue (load, not loss).

Storage and background work

MetricTypeLabelsDescription
cronpad.results.batch_sizehistogramdbWrites per database transaction.
cronpad.results.flush_durationhistogram (ms)dbTime per batched transaction.
cronpad.janitor.deletedcountertableRows removed by retention.
cronpad.janitor.durationhistogram (ms)Time per pruning pass.
cronpad.queue.errorscounteropQueue file failures (claim, lease renewal, lock). Called cronpad.redis.errors before Redis was removed.
cronpad.db.errorscounteropDatabase errors.

Jobs and alerts

MetricTypeLabelsDescription
cronpad.jobsgaugestateJobs by state.
cronpad.jobs.auto_disabledcounterJobs disabled for consecutive failures.
cronpad.alerts.sentcounterchannel, statusAlert deliveries by channel kind and result.

Suggested alerts

groups:
  - name: cronpad
    rules:
      - alert: CronpadFallingBehind
        expr: cronpad_queue_oldest_due_age_milliseconds > 30000
        for: 2m
      - alert: CronpadWorkersDying
        expr: increase(cronpad_leases_expired_total[10m]) > 0
      - alert: CronpadSLABreaches
        expr: sum(rate(cronpad_runs_sla_breached_total[15m])) / sum(rate(cronpad_runs_total[15m])) > 0.01
        for: 15m
      - alert: CronpadNotDispatching
        expr: max(cronpad_dispatcher_lock_held) == 0
        for: 1m

The shipped alert rules also include CronpadNotReady, using a blackbox-exporter probe of /readyz with job label cronpad-readyz.