Metrics
The :runtime module exports metrics in Prometheus text format on GET /metrics. Internally it's a Micrometer PrometheusMeterRegistry — the engine reports events to a Micrometer instrumentation callback, the runtime binds JVM and Kafka-client metrics to the same registry, and the handler serves the registry's scrape output. Point your existing Prometheus stack at the endpoint and the StoatFlow meters appear alongside everything else.
Enable and scrape
Metrics are enabled by default and live under runtime.metrics:
runtime:
http:
enabled: true
port: 8080 # default
metrics:
enabled: true # default
prefix: stoatflow # default — prefix on all metric names
recording-level: info # info | debug | trace (default: info)
bind-jvm-metrics: true # default — JVM memory/GC/thread binders
common-tags: # extra tags applied to every meter
environment: production
service: word-count
Scrape it from the shell:
curl -s localhost:8080/metrics | head -40
A minimal Prometheus scrape config:
scrape_configs:
- job_name: stoatflow
metrics_path: /metrics
static_configs:
- targets: ["my-app:8080"]
The endpoint returns the standard exposition format, so any Prometheus-compatible collector (Prometheus, Grafana Agent, OpenTelemetry Collector with the Prometheus receiver, the VictoriaMetrics agent) scrapes it without extra translation. On Kubernetes, add prometheus.io/scrape / prometheus.io/path pod annotations or a ServiceMonitor pointing at the HTTP port — see Observability.
runtime.metrics.enabled: false) un-registers the /metrics endpoint — it returns 404 — and switches the engine to no-op instrumentation, so there's no measurement overhead when metrics are off.Recording levels
recording-level controls how much detail the engine reports, mirroring Kafka Streams' levels. A higher level is a superset of the ones below it.
| Level | What it adds | Use |
|---|---|---|
info | Essential operational meters — throughput, barrier/commit latency, watermark progress, restoration, consumer/producer, errors. | Production default. |
debug | Per-store, per-partition, per-processor detail and dispatch internals. Higher cardinality. | Targeted troubleshooting. |
trace | Deepest internals; highest overhead. | Development only. |
Tags like lane_id, store_name, topic, and partition multiply series count. On large topologies, keep production at info and switch to debug only while investigating.
Naming and tags
All StoatFlow meters use a dotted name with the configured prefix (stoatflow by default). The Prometheus registry rewrites dots to underscores and appends the conventional suffix (_total for counters; _seconds_count/_seconds_sum/_seconds_max for timers). So stoatflow.barrier.completed.total is scraped as stoatflow_barrier_completed_total, and the timer stoatflow.barrier.commit.latency produces stoatflow_barrier_commit_latency_seconds_* series. The latency timers publish client-side percentiles as {quantile="0.5|0.95|0.99"} labels on the base _seconds name rather than _bucket histograms, so use rate(_sum)/rate(_count) (or the {quantile=...} series) — histogram_quantile() over _bucket won't match.
Two application tags are present on StoatFlow meters:
| Tag | Source | Notes |
|---|---|---|
application | Registry-level common tag | Carries application-id; applied to every meter, including JVM and Kafka-client metrics. |
application_id | Per-meter tag | Applied by the StoatFlow instrumentation callback. |
Anything you add under common-tags (for example environment, region, service) is also applied to every meter — handy for selecting one deployment in a shared Prometheus. Per-meter tags vary by group: lane_id, store_name, topic, partition, error_type, trigger, type, result, and others are listed with each meter below.
{subtopology}_{lane} — e.g. 0_0, 0_15, 1_7. Aggregate across a sub-topology in PromQL with a regex matcher: sum(stoatflow_lane_records_processed_total{lane_id=~"1_.*"}).Key meters
The dotted names below are the canonical metric IDs the engine emits. Remember the Prometheus rewrite (. → _, plus the type suffix) when writing queries. This is a working set, not the exhaustive list — the full catalogue is on the metrics reference, and your live scrape stays authoritative for exact tag sets.
Throughput and end-to-end latency
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.lane.records.processed.total | Counter | info | lane_id | Records processed by a lane. |
stoatflow.lane.bytes.processed.total | Counter | info | lane_id | Bytes processed by a lane. |
stoatflow.lane.process.latency | Timer | info | lane_id | Per-record processing latency. |
stoatflow.lane.queue.size | Gauge | info | lane_id | Current lane queue depth (backpressure signal). |
stoatflow.lane.queue.capacity | Gauge | info | lane_id | Configured lane queue capacity. |
stoatflow.lane.queue.full.total | Counter | info | lane_id | Times a lane queue was full (backpressure events). |
stoatflow.e2e.latency | Timer | info | — | Per-record end-to-end latency, source timestamp to processing completion (analogous to KS record-e2e-latency). |
# Total processing throughput (records/sec)
sum(rate(stoatflow_lane_records_processed_total[1m]))
# Average per-record processing latency (this timer emits _sum/_count/_max, not _bucket)
sum(rate(stoatflow_lane_process_latency_seconds_sum[5m])) / sum(rate(stoatflow_lane_process_latency_seconds_count[5m]))
Barrier commits
Commit barriers are StoatFlow's exactly-once mechanism (see Exactly-once). These meters track barrier lifecycle and commit-phase latency. The latency timers publish client-side P50/P95/P99 percentiles.
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.barrier.initiated.total | Counter | info | — | Barriers initiated. |
stoatflow.barrier.completed.total | Counter | info | — | Barriers successfully completed. |
stoatflow.barrier.failed.total | Counter | info | error_type | Barrier failures, by error type. |
stoatflow.barrier.in.flight | Gauge | info | — | Currently pending barriers. |
stoatflow.barrier.latency | Timer | info | — | Initiation to completion. |
stoatflow.barrier.alignment.latency | Timer | info | — | Time for all lanes to receive the barrier. |
stoatflow.barrier.commit.latency | Timer | info | — | Commit latency (producer drain + Kafka TX + state flush). Comparable to KS commit-latency. |
stoatflow.barrier.commit.changelog.latency | Timer | info | — | Aggregate changelog serialization+send latency per commit epoch. |
stoatflow.barrier.commit.state.flush.latency | Timer | info | — | Aggregate state-store flush latency per commit epoch. |
stoatflow.barrier.commit.producer.flush.latency | Timer | info | — | Pre-commit producer flush latency. |
stoatflow.barrier.commit.send.offsets.latency | Timer | info | — | sendOffsetsToTransaction RPC latency. |
stoatflow.barrier.records.committed.total | Counter | info | — | Records committed across barriers. |
stoatflow.barrier.bytes.committed.total | Counter | info | — | Bytes committed across barriers. |
stoatflow.barrier.trigger.total | Counter | info | trigger | Barrier creation count by trigger — TIME, RECORD_COUNT, MEMORY_PRESSURE, CACHE_PRESSURE (state-cache cap forced an early commit). |
stoatflow.transaction.commit.total | Counter | info | — | Kafka transactions committed. |
stoatflow.transaction.abort.total | Counter | info | — | Kafka transactions aborted. |
stoatflow.transaction.commit.latency | Timer | info | — | Transaction commit latency. |
# Commit rate vs. failure rate
rate(stoatflow_barrier_completed_total[1m])
rate(stoatflow_barrier_failed_total[5m])
stoatflow.barrier.interval.target.ms, stoatflow.barrier.epoch.max.records, and related) report what the engine is currently doing so you can see the cadence on a dashboard. The bound knobs themselves are documented under Tuning and the configuration reference.Watermarks
Event-time progress for windowing and late-event detection (see Event time and watermarks).
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.watermark.current | Gauge | info | — | Current global watermark (epoch ms). |
stoatflow.watermark.lag.ms | Gauge | info | — | Wall-clock time minus watermark (event-time lag). |
stoatflow.watermark.advance.total | Counter | info | — | Watermark advancement events. |
stoatflow.watermark.late.records.total | Counter | debug | topic, partition | Records arriving after the watermark. |
# Alert when the watermark stalls while the app is running (RUNNING == 4)
delta(stoatflow_watermark_current[5m]) == 0 and stoatflow_client_state == 4
Changelog writes and state restoration
Changelog write counters track state durability traffic; restoration meters track recovery progress on startup (see State stores and State and thread safety).
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.store.changelog.records.written.total | Counter | info | store_name | Changelog records written. |
stoatflow.store.changelog.bytes.written.total | Counter | info | store_name | Changelog bytes written. |
stoatflow.restoration.in.progress | Gauge | info | — | 1 while restoration is running, 0 otherwise. |
stoatflow.restoration.stores.total | Gauge | info | — | Total stores to restore. |
stoatflow.restoration.stores.completed | Gauge | info | — | Stores that have finished restoring. |
stoatflow.restoration.records.restored.total | Counter | info | store_name | Records restored, per store. |
stoatflow.restoration.bytes.restored.total | Counter | info | store_name | Bytes restored, per store. |
stoatflow.restoration.duration.seconds | Timer | info | store_name | Per-store restoration duration. |
# Restoration progress as a fraction of stores completed
stoatflow_restoration_stores_completed / stoatflow_restoration_stores_total
Consumer, producer, and dispatch
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.consumer.records.consumed.total | Counter | info | topic, partition | Records consumed. |
stoatflow.consumer.bytes.consumed.total | Counter | info | topic, partition | Bytes consumed. |
stoatflow.consumer.lag.records | Gauge | info | topic, partition | Consumer lag, in records. |
stoatflow.consumer.poll.latency | Timer | info | — | Consumer poll latency. |
stoatflow.consumer.assigned.partitions | Gauge | info | — | Number of assigned partitions. |
stoatflow.producer.records.produced.total | Counter | info | topic | Records produced. |
stoatflow.producer.bytes.produced.total | Counter | info | topic | Bytes produced. |
stoatflow.dispatcher.dispatch.latency | Timer | info | — | Per-batch dispatch latency. |
# Worst-case consumer lag across partitions
max(stoatflow_consumer_lag_records)
Errors
Error classification for alerting (see Error handling).
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.error.total | Counter | info | error_type, topic | Errors by type (and topic where applicable). |
stoatflow.error.typed.total | Counter | info | category, error_type | Categorised errors (e.g. deserialization, processing, production). |
stoatflow.queue.offer.failure.total | Counter | info | queue_type | Internal queue offer rejections. |
Client state
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.client.state | Gauge | info | — | Application state ordinal: CREATED=0, STARTING=1, VALIDATING_STATE=2, RESTORING=3, RUNNING=4, DRAINING=5, PAUSED=6, STOPPING=7, STOPPED=8, ERROR=9. |
stoatflow.lanes | Gauge | info | — | Configured lane count. |
stoatflow.client.lane.alive | Gauge | info | — | Currently running lanes. |
stoatflow.client.uptime.seconds | Gauge | info | — | Seconds since the client was created. |
stoatflow.engine.restart.total | Counter | info | disposition, target, trigger | Successful in-place engine restarts (fault recovery via REPLACE_THREAD, HA demote-in-place). |
High availability
Bound only when hot standby is enabled (ha.mode != off).
| Metric | Type | Level | Tags | Meaning |
|---|---|---|---|---|
stoatflow.ha.role | Gauge | info | — | 1 when this pod is the active, 0 when standby. |
stoatflow.ha.standby.replication.lag.records | Gauge | info | — | Standby replication lag in records (0 when active or caught up). |
stoatflow.ha.standby.replication.lag.ms | Gauge | info | — | Standby replication lag in milliseconds. |
stoatflow.ha.peer.freshness.ms | Gauge | info | — | Time since the freshest observed peer's last heartbeat. |
stoatflow.ha.peers | Gauge | info | — | Number of observed HA peers. |
stoatflow.ha.redundancy.ready.standbys | Gauge | info | — | Caught-up (READY_STANDBY) standby count, including self. |
stoatflow.ha.redundancy.below.desired | Gauge | info | — | 1 when the active has fewer ready standby spares than desired-standbys. |
stoatflow.ha.token.epoch | Gauge | info | — | Current promotion-token epoch (increments once per election). |
stoatflow.ha.election.outcome | Counter | info | outcome | Cumulative election outcomes (won / reclaimed / stood_down_fresh / stood_down_lost). |
stoatflow.ha.election.latency.ms | Gauge | info | — | Last claim→ACTIVE election latency in ms (-1 until the first promotion). |
stoatflow.ha.restart.required | Gauge | info | — | 1 when this pod requires a restart (e.g. source-topic partition count changed). |
License metrics
When a license is configured, the runtime binds license meters to the same registry (see License configuration). These use the <prefix>.license prefix (default stoatflow.license, tracking runtime.metrics.prefix) and carry the registry common tags (application, plus your common-tags). They're exported even when recording-level is info.
| Metric | Type | Tags | Meaning |
|---|---|---|---|
stoatflow.license.valid | Gauge | tier | 1 when operating under a VALID or GRACE_PERIOD license, else 0. |
stoatflow.license.expiry_timestamp_seconds | Gauge | — | Unix epoch seconds of the entitlement deadline. |
stoatflow.license.days_remaining | Gauge | — | Whole days until expiry; negative within the post-expiry grace window. |
stoatflow.license.grace_remaining_seconds | Gauge | — | Seconds left in the offline-grace budget; 0 when exhausted. |
stoatflow.license.heartbeat_consecutive_failures | Gauge | — | Heartbeat ticks failed since the last success; resets to 0 on success. |
stoatflow.license.heartbeat_last_success_timestamp_seconds | Gauge | — | Unix epoch seconds of the most recent successful heartbeat. |
stoatflow.license.validations_total | Counter | result | Cold-start validation outcomes, by result label. |
stoatflow.license.heartbeats_total | Counter | result | Heartbeat tick outcomes, by result label. |
# Alert when the license is not valid, or when expiry is within 14 days
stoatflow_license_valid == 0
stoatflow_license_days_remaining < 14
/metrics only from your in-cluster monitoring stack, not through internet-facing ingress. See Kubernetes.JVM and Kafka-client metrics
With bind-jvm-metrics: true (the default), the runtime binds Micrometer's standard JVM binders to the registry, so the usual jvm_* and system_* series appear on the same endpoint:
jvm_memory_used_bytes/jvm_memory_max_bytes(heap and non-heap)jvm_gc_pause_secondsand related GC metricsjvm_threads_live_threads,jvm_threads_states_threadsjvm_classes_loaded_classessystem_cpu_usage,process_cpu_usage,system_load_average_1m
These carry the registry common tags (application + your common-tags) but not application_id. Native Kafka client metrics are also surfaced through Micrometer, so producer/consumer client internals appear alongside the StoatFlow meters.
Dashboards and alerts
A starting overview combines throughput, latency, backpressure, and commit health:
# Throughput
sum(rate(stoatflow_lane_records_processed_total[1m]))
# Backpressure — total queued vs. capacity
sum(stoatflow_lane_queue_size) / sum(stoatflow_lane_queue_capacity)
# Commit health
rate(stoatflow_barrier_completed_total[1m])
rate(stoatflow_barrier_failed_total[5m])
# Event-time lag
stoatflow_watermark_lag_ms
Useful alert conditions:
| Alert | Condition | Severity |
|---|---|---|
| Barrier failures | rate(stoatflow_barrier_failed_total[5m]) > 0 | Critical |
| License invalid | stoatflow_license_valid == 0 | Critical |
| High consumer lag | max(stoatflow_consumer_lag_records) > 100000 | Warning |
| Watermark stall | delta(stoatflow_watermark_current[5m]) == 0 and stoatflow_client_state == 4 | Warning |
| Backpressure | sum(stoatflow_lane_queue_size) > 0.8 * sum(stoatflow_lane_queue_capacity) | Warning |
Next steps
- Observability — wiring
/metrics, logs, and probes into a monitoring stack. - Health checks — the readiness and liveness signals that complement metrics.
- Tuning — which bound knobs the self-tuning cadence respects.
- The REST API — the other operational endpoints on the same HTTP server.
Health checks
Liveness and readiness probes on the StoatFlow runtime — the built-in indicators, what flips each, and how they behave during state restoration.
Pause and resume
Pause and resume record processing at runtime with POST /pause and /unpause — for maintenance windows and downstream backpressure, without restarting the instance.