Reusing your Kafka Streams dashboards
You arrive from Kafka Streams with an observability estate — Grafana dashboards, Prometheus recording rules, alerts — all built against Kafka Streams metric names. By default none of it lights up against StoatFlow: every StoatFlow meter lives under the stoatflow.* namespace with StoatFlow-native structure. This page turns on the opt-in Kafka Streams-compatible metrics mode, which emits KS-named, KS-shaped, KS-unit series derived from StoatFlow's own meters — so your existing dashboards work with little or no editing.
Turn it on
The mode is a config knob under runtime.metrics. Zero code changes.
runtime:
metrics:
enabled: true
naming: both # stoatflow | kafka-streams | both (default: stoatflow)
ks-compat:
shape: micrometer-binder # micrometer-binder | jmx-exporter (default: micrometer-binder)
sample-window-ms: 30000 # KS metrics.sample.window.ms parity
kafka-version: "4.1.0" # value of the synthetic kafka_version tag (micrometer-binder shape)
The three naming modes:
naming | /metrics contains | Use it when |
|---|---|---|
stoatflow | Native stoatflow.* only (default — today's behaviour) | You have no KS dashboards to reuse. |
both | Native + KS-named | Migration default. Your KS dashboards work and your StoatFlow dashboards keep working. |
kafka-streams | KS-named only; the mapped stoatflow.* families are hidden | A deliberate end state, or when scrape/ingest size matters. Note: StoatFlow's own reference dashboards and support runbooks key on stoatflow_* and won't work here. |
Start with both. Move to kafka-streams only once you're sure nothing you own reads the native names.
Which shape does your Prometheus have?
"The KS metric name" in Prometheus depends on how the KS app was scraped. StoatFlow renders either shape:
| Shape | Example series | This is what you have if… |
|---|---|---|
micrometer-binder | kafka_stream_thread_process_latency_avg | Your KS app used Micrometer / Spring Boot's KafkaStreamsMetrics binder. |
jmx-exporter | kafka_streams_stream_thread_metrics_process_rate | You scraped KS JMX via the Prometheus jmx_exporter (Strimzi / Confluent-style). |
Look at one panel query in your existing dashboard and match the prefix. (The jmx-exporter shape targets the common default-config output; heavily customised jmx rules files may need panel tweaks.)
The two edits that cover most dashboards
StoatFlow has no stream threads and no tasks — it runs one instance with virtual-thread lanes. The compat layer synthesises the KS identity tags, with two consequences worth knowing before you read a dashboard:
task-id is a lane, not a partition. Sums and aggregations (sum(...), rate(...)) match exactly. But a per-task drill-down enumerates lanes (0_5 is lane 5, not partition 5), and the lane count is not the partition count. Panels that break down by task show lanes.thread-id. StoatFlow collapses the whole engine into a single <application-id>-StreamThread-1. Per-thread breakdowns become one series, and any alert like kafka_stream_alive_stream_threads < N must have its threshold set to 1.Alert-edit checklist
-
alive-stream-threadsalerts → threshold1(single synthetic thread). -
failed-stream-threads→ maps from StoatFlow's engine-restart counter (areplace_threadrestart is the KS thread-replacement recovery event); keep the alert, it fires on real fault storms. - Per-
task-id/ per-thread-idbreakdown panels → read them as per-lane / single-series. - Thread
process-latency→ StoatFlow measures per record, KS per iteration-batch, so absolute values are smaller than a real KS app; keep the shape, re-baseline any hard thresholds. - Ratios (
process-ratio,active-process-ratio,punctuate-ratio) are not emitted (lane parallelism makes true utilization >1); remove or ignore those panels.
What maps, and how well
The full per-metric compatibility table is the canonical mapping in the engine; the tiers are:
- ✅ full — same meaning, same math (poll, store
range/all/flushlatency, topic consumed/produced totals, node e2e latency, …). - ⚠️ approximate — emitted, semantics differ, documented per row (thread
commit-latency= barrier commit;dropped-recordscovers null-key + join + late-window drops; per-lane task metrics; storeget/put/deletelatency; RocksDB rows). - ❌ not mappable — architecturally absent (no rebalancing, no enforced-processing, no iterator metrics); their absence is parity — a Micrometer-instrumented KS app skips the same non-numeric metrics.
State-store panels key on the scope-dependent tag (rocksdb-window-state-id, in-memory-session-state-id, …); StoatFlow synthesises the correct scope key per store, so those panels populate.
One thing to know: the native surface shifts slightly
With naming != stoatflow a compat filter tightens the mapped native timers' stoatflow_*_seconds_max to a ~30 s window (fresher than the default 2 min × 3) and adds a p0 quantile to stoatflow.e2e.latency (used to approximate record-e2e-latency-min). This is bounded and arguably an improvement, but it is an observable change to a few native _max series — expect them to be a little "twitchier".
Sizing
both mode roughly adds: thread ≈ 25 series; task ≈ 8-10 × lanes (64 lanes ≈ 600 series); store ≈ 10-14 × stores (plus ~44 × stores for RocksDB when statistics-enabled); topic ≈ 4 × topics; cache ≈ 3 × stores. Lane count dominates. At recording-level: debug with many lanes this is thousands of extra series — which is remote-write / ingest cost, not just scrape payload. The recording level gates the sources (see Metrics).
Next steps
- Metrics — the native
stoatflow.*catalogue and recording levels. - Observability — alerting and the Grafana view.
The migration tool
How to run stoatflow-migration-tool — the offline Kafka Streams changelog translator. Config reference, the plan / translate / seed-offsets / verify commands, the cutover runbook, and the rollback line.
Reference
Lookup material for StoatFlow — configuration keys, REST API, the Gradle plugin and Maven build references, the Kafka Streams compatibility matrix, the metrics catalogue, and the glossary.