Schema-based observability data
Describe a microservice call graph once. Rockfish generates a complete, correlated OpenTelemetry signal set (distributed traces, RED metrics, and logs) that is structurally sound, statistically realistic, and carries no real production traffic. Then watch it light up a live Grafana + Tempo + Prometheus + Loki stack.
the open standard for traces, metrics, and logs
OpenTelemetry (OTel) is the vendor-neutral CNCF standard for instrumenting software to emit telemetry. Applications produce three correlated signals under shared semantic conventions, so any backend (Grafana, Tempo, Prometheus, Loki, Jaeger, and others) can ingest them without custom glue. This dataset is a complete, correlated set of all three.
A request's path across services as a tree of timed spans, with parent and child causality and per-span attributes.
Numeric measurements over time: request rate, error rate, and latency percentiles (the RED signals), plus histograms.
Structured event records, each carrying the trace and span ids that tie the line back to the request that produced it.
schema → platform → five tables → validate → visualize
Services, the calls between them, and a planted incident, in one Python schema.
Submit as a workflow; the platform generates five correlated tables.
A thorough battery over every row: structure, distributions, reconciliation.
Stream traces/metrics/logs through the OpenTelemetry Collector.
Tempo, Prometheus, and Loki. The incident, visible across all three.
the OpenTelemetry Astronomy Shop, as a call graph
■ entry ■ payment (incident) ■ datastore / queue. grpc/http calls solid, async queue dashed.
a recognizable reference application
The topology mirrors the OpenTelemetry Astronomy Shop, OTel's official reference microservice application and a familiar landmark across the observability community. It is a representative model of it, not a 1:1 reproduction. The Rockfish generator itself is domain-neutral: the shop's vocabulary (the routes it serves, its datastore tables, its gRPC namespace) lives entirely in the schema, so you can point the same pattern at your own services and swap in your own vocabulary without any engine changes.
one schema, a complete OTLP-shaped signal set
| Table | Rows (at 80k requests) | What it is |
|---|---|---|
trace | 80,000 | One row per request: a 128-bit hex trace id (the driver). |
span | 2,201,842 | Nested SERVER/CLIENT/INTERNAL spans, db/cache/queue leaves, async CONSUMER spans, with k8s resource + semantic-convention attributes. |
span_event | 25,747 | Exception events (exception.type/message/stacktrace) on failed spans. |
span_metric | 1,573 | Per-service RED metrics in 30s buckets: calls, errors, p50/p95/p99, and an OTLP histogram. |
log_record | 913,409 | INFO/WARN/ERROR logs correlated to spans by trace_id/span_id. |
Metrics are aggregated from the spans; error logs correspond exactly to failed spans; exception events land only on those spans. One incident, one signature, three signals.
Correct attributes per span kind, k8s pods/nodes/UUID instance ids, a Zipf end-user distribution, OTLP explicit-bucket histograms, and async producer→consumer span links.
a payment brownout, visible across every signal
A ten-minute payment degradation, thirty minutes into the window. It spikes payment's own error rate and latency, and propagates upstream to frontend at a realistic partial rate, exactly what an SRE would triage.
the whole dataset is plain, editable Python
Want a different story? Every knob is a value you can edit: the services
and the incident live in schema_definition.py, the timing in
constants.py. Change one, rerun, and traces, metrics, and logs all move
together. For example:
every hard check passes over all 2.2M spans
the three signals, correlated, in Grafana + Tempo + Prometheus + Loki
trace_id and span_id, so a log line links straight to its trace in Tempo.the on-call tasks a practitioner actually runs
Realistic is not enough; the data has to be queryable. These are the
concrete tasks an SRE runs during the incident, the same questions Grafana's
o11y-bench poses,
each answered straight from the dataset in real PromQL, LogQL, and TraceQL.
task_coverage.py runs them and checks every answer.
| The question | Query (PromQL / LogQL / TraceQL) | Answer, from the data |
|---|---|---|
| Which service is failing? | topk(1, error_rate by service) | payment, 62.5% error rate (next 32.4%) |
| Is payment burning its SLO? | otel_demo_error_rate{service_name="payment"} | 2.5% → 62.5% (a 25x burn) |
| Is there an error burst in the logs? | count_over_time({...} | severity_text=`ERROR`) | 14x denser inside the incident window |
| Which requests actually failed? | { service.name = "payment" && status = error } | 5,543 traces with a failed payment span |
| Root cause and blast radius? | correlate(metrics, logs, traces) by service, time | source payment (63%), hits frontend (8.6%), one window, all three signals |
Every question here is answerable because the incident lives in the schema. Model more scenarios there, and the data answers more of them.
the next step
This is one worked example. Rockfish generates schema-accurate, fully synthetic data (distributed traces, metrics, and logs here; tabular, relational, and time-series elsewhere) from a description of your own systems, carrying no real production data. To see it on your schema, get in touch with the Rockfish team.