RockfishRockfish  /  Schema → Data OpenTelemetry · traces · metrics · logs

Schema-based observability data

OpenTelemetry, synthesized from a schema.

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.

2.2M
spans across 159,948 traces, from 80,000 requests
3
signals: traces, metrics, and logs, mutually consistent
13
services emitting telemetry (a 15-node call graph; valkey and postgresql are datastores)
100%
of validation checks pass, at full scale

What is OpenTelemetry?

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.

traces

Where time goes

A request's path across services as a tree of timed spans, with parent and child causality and per-span attributes.

metrics

The aggregate

Numeric measurements over time: request rate, error rate, and latency percentiles (the RED signals), plus histograms.

logs

The detail

Structured event records, each carrying the trace and span ids that tie the line back to the request that produced it.

The flow, end to end

schema → platform → five tables → validate → visualize

01 · describe

Call graph

Services, the calls between them, and a planted incident, in one Python schema.

02 · generate

Rockfish

Submit as a workflow; the platform generates five correlated tables.

03 · check

Validate

A thorough battery over every row: structure, distributions, reconciliation.

04 · export

OTLP

Stream traces/metrics/logs through the OpenTelemetry Collector.

05 · view

Grafana

Tempo, Prometheus, and Loki. The incident, visible across all three.

The topology

the OpenTelemetry Astronomy Shop, as a call graph

entry   payment (incident)   datastore / queue.  grpc/http calls solid, async queue dashed.

Astronomy Shop service call graph

Modeled on the Astronomy Shop

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.

Five correlated tables

one schema, a complete OTLP-shaped signal set

TableRows (at 80k requests)What it is
trace80,000One row per request: a 128-bit hex trace id (the driver).
span2,201,842Nested SERVER/CLIENT/INTERNAL spans, db/cache/queue leaves, async CONSUMER spans, with k8s resource + semantic-convention attributes.
span_event25,747Exception events (exception.type/message/stacktrace) on failed spans.
span_metric1,573Per-service RED metrics in 30s buckets: calls, errors, p50/p95/p99, and an OTLP histogram.
log_record913,409INFO/WARN/ERROR logs correlated to spans by trace_id/span_id.
consistent by construction

The three signals agree

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.

production-shaped

Real OTel semantics

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.

The planted incident

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.

payment: the source

2.5%62.5%error rate
33 ms249 msp95 latency

frontend: the blast radius

1.2%8.6%error rate
311 ms461 msp95 latency

Change anything in the schema

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:

# In schema_definition.py, change a service's failure rate via its error_rate (here, payment fails 2%).
SpanService("payment", 2.0, 0.6, error_rate=0.02)

# In schema_definition.py, change the incident: target service, how much slower (latency_mult), how many errors (error_rate).
SpanIncident("payment", INCIDENT_START, INCIDENT_END, latency_mult=7.0, error_rate=0.6)

# In constants.py, change when the incident starts or how long the window runs:
INCIDENT_START = WINDOW_START + 1800 * NS # fires 30 minutes into the 1-hour window

Validated, thoroughly

every hard check passes over all 2.2M spans

See it in the stack

the three signals, correlated, in Grafana + Tempo + Prometheus + Loki

Grafana Golden Signals dashboard: the payment brownout spikes error rate and p95 latency, and the request rate shows wave-shaped traffic
Golden Signals (Prometheus). The payment brownout spikes error rate to ~70% and p95 latency on frontend and checkout, then clears; it propagates to frontend at a partial rate. Request rate per service and the correlated error logs sit below.
Tempo trace waterfall of a single 45-span checkout request
A distributed trace (Tempo). One request, 45 spans: frontend fans out to product-catalog, recommendation, and the checkout path, with nested per-span timings down the tree.
Loki logs with an expanded record showing its trace_id and span_id fields
Correlated logs (Loki). Every record carries its trace_id and span_id, so a log line links straight to its trace in Tempo.

It answers the real questions

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 questionQuery (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, timesource 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.

See it on your systems

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.

Talk to Rockfish →

full size