Tutorial¶
This tutorial picks up where Getting Started left off. You have Sonda installed and have run your first metric and log. Now let's explore the full range of generators, encoders, sinks, and advanced features.
What you need:
- Sonda installed (Getting Started covers installation)
- Docker and Docker Compose (needed for The Server API and Pushing to a Backend sections only)
Let's start with the different value shapes Sonda can produce.
Generators¶
A metric that always outputs zero isn't very useful for testing. Generators let you shape the values Sonda emits -- smooth waves for latency simulation, random noise for jitter, or exact sequences to trigger alert thresholds.
Sonda ships eight generators:
| Generator | Description | Best for |
|---|---|---|
constant |
Fixed value every tick | Up/down indicators, baselines |
sine |
Smooth sinusoidal wave | CPU, latency, cyclical load |
sawtooth |
Linear ramp, resets at period | Queue depth, buffer fill |
uniform |
Random value in [min, max] | Jitter, noisy signals |
sequence |
Cycles through an explicit list | Alert threshold testing |
step |
Monotonic counter with optional wrap | rate() and increase() testing |
spike |
Baseline with periodic spikes | Anomaly detection, alert thresholds |
csv_replay |
Replays values from a CSV file | Reproducing real incidents |
YAML-only generators
Sequence, step, spike, and csv_replay require a scenario file -- they have no CLI flag equivalents.
All other generators are available via --value-mode.
constant¶
The default generator. Set the value with --value:
sine¶
Produces a smooth wave defined by amplitude, offset (midpoint), and period:
sonda metrics --name cpu_usage --rate 2 --duration 10s \
--value-mode sine --amplitude 40 --offset 50 --period-secs 30
This oscillates between 10 and 90, centered on 50, completing one cycle every 30 seconds.
Sine wave math
The formula is: value = offset + amplitude * sin(2 * pi * elapsed / period).
At t=0 the value equals offset. It peaks at offset + amplitude after one quarter period.
sequence¶
For testing alert thresholds, you often need values that cross a specific boundary at a specific time. Sequence gives you that exact control:
name: cpu_spike_test
rate: 1
duration: 80s
generator:
type: sequence
values: [10, 10, 10, 10, 10, 95, 95, 95, 95, 95, 10, 10, 10, 10, 10, 10]
repeat: true
labels:
instance: server-01
job: node
encoder:
type: prometheus_text
sink:
type: stdout
More generators: sawtooth, uniform, step, csv_replay
sawtooth -- A linear ramp from 0 to 1 that resets every period. Useful for simulating queue fill and drain cycles:
uniform -- Random values drawn uniformly between --min and --max. Pass --seed 42
for deterministic replay:
step -- A monotonic counter that increments by step_size each tick. Set max to
simulate counter resets, perfect for testing rate() and increase():
csv_replay -- Replays recorded values from a CSV file. Point it at real incident data to reproduce production behavior:
name: cpu_replay
rate: 1
duration: 60s
generator:
type: csv_replay
file: examples/sample-cpu-values.csv
column: 1
has_header: true
repeat: true
labels:
instance: prod-server-42
job: node
encoder:
type: prometheus_text
sink:
type: stdout
For multi-column CSV files, use columns instead of column to emit multiple metrics
from a single scenario — see Multi-column replay.
For full generator configuration details, see Generators.
Add realism with jitter
Real metrics are never perfectly smooth. Add jitter to any generator to introduce
deterministic uniform noise:
This adds noise in the range [-3.0, +3.0] to every value. Set jitter_seed for
reproducible noise across runs. See Generators - Jitter
for details.
You've seen what values Sonda can generate. Next, let's look at how those values get formatted on the wire.
Encoders¶
Your monitoring backend expects data in a specific wire format. Sonda can speak all of them.
The same metric looks different in each format:
Feature-gated encoders
The remote_write encoder produces Prometheus remote write protobuf format. It requires
the remote-write feature flag when building from source (cargo build --features remote-write).
Pre-built binaries and Docker images include it by default.
The otlp encoder produces OTLP protobuf format for metrics and logs. It requires
the otlp feature flag (cargo build --features otlp). Pre-built binaries and Docker
images do not include this feature -- you must build from source.
See Encoders for details.
With the right format chosen, the next question is: where should the data go?
Sinks¶
So far everything has gone to stdout. In production testing, you need data flowing to real backends -- over HTTP, TCP, or directly into Kafka or Loki.
Sonda supports nine sinks:
| Sink | Description | CLI flag |
|---|---|---|
stdout |
Print to standard output | (default) |
file |
Write to a file | --output path |
tcp |
Stream to a TCP listener | YAML only |
udp |
Send to a UDP endpoint | YAML only |
http_push |
POST batches to an HTTP endpoint | --sink http_push --endpoint <url> |
loki |
Push logs to Grafana Loki | --sink loki --endpoint <url> |
kafka |
Publish to a Kafka topic | --sink kafka --brokers <addr> --topic <t> |
remote_write |
Prometheus remote write protocol | --sink remote_write --endpoint <url> |
otlp_grpc |
OTLP/gRPC to an OpenTelemetry Collector | --sink otlp_grpc --endpoint <url> --signal-type <s> |
stdout (default)¶
No flags needed -- stdout is the default sink. Pipe output to any tool:
file¶
Write to a file with --output:
http_push¶
POST batched data to any HTTP endpoint. This is the most universal network sink -- it works with any backend that accepts HTTP imports. Use CLI flags for quick ad-hoc runs:
sonda metrics --name cpu --rate 10 --duration 30s \
--sink http_push --endpoint http://localhost:9090/api/v1/push \
--content-type "text/plain; version=0.0.4"
Or use a scenario file for full control (including custom headers):
sink:
type: http_push
url: "http://localhost:9090/api/v1/push"
content_type: "text/plain; version=0.0.4"
batch_size: 65536
The key sink fields are url, content_type, and batch_size (bytes buffered before each POST).
TCP sink setup
Stream metrics over TCP. Start a listener in another terminal:
Then run:
UDP sink setup
Send metrics over UDP. Start a listener in another terminal:
Then run:
loki¶
Push JSON logs to Grafana Loki. The fastest way is a single CLI command:
sonda logs --mode template --rate 10 --duration 30s \
--sink loki --endpoint http://localhost:3100 \
--label job=sonda --label env=dev
For richer logs with field pools and severity weights, use a scenario file:
Full Loki scenario file
name: app_logs_loki
rate: 10
duration: 60s
generator:
type: template
templates:
- message: "Request from {ip} to {endpoint}"
field_pools:
ip: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
endpoint: ["/api/v1/health", "/api/v1/metrics", "/api/v1/logs"]
severity_weights:
info: 0.7
warn: 0.2
error: 0.1
labels:
job: sonda
env: dev
encoder:
type: json_lines
sink:
type: loki
url: http://localhost:3100
batch_size: 50
kafka¶
Publish metrics to a Kafka topic. Use CLI flags for a quick test:
sonda metrics --name cpu --rate 10 --duration 30s \
--sink kafka --brokers 127.0.0.1:9092 --topic sonda-metrics
Or use a scenario file for full control:
Full Kafka scenario file
See examples/kafka-sink.yaml for the complete example with generator and encoder config.
otlp_grpc¶
Push metrics or logs to an OpenTelemetry Collector via gRPC. Use CLI flags:
sonda metrics --name cpu --rate 10 --duration 30s \
--encoder otlp \
--sink otlp_grpc --endpoint http://localhost:4317 --signal-type metrics
For logs, --signal-type defaults to logs automatically:
sonda logs --mode template --rate 10 --duration 30s \
--encoder otlp \
--sink otlp_grpc --endpoint http://localhost:4317
Or use a scenario file:
Full OTLP scenario file
Feature flag required
OTLP requires the otlp Cargo feature. Pre-built binaries do not include it --
build from source with cargo build --features otlp -p sonda.
See Sinks - otlp_grpc for the full configuration reference.
For full sink configuration details, see Sinks.
So far we've focused on metrics. Sonda also generates structured log events.
Generating Logs¶
Getting Started showed basic log generation. Sonda supports two log modes -- template for synthetic messages with randomized fields, and replay for re-emitting lines from an existing log file. Let's explore what each can do with YAML scenarios.
Template mode with field pools¶
The CLI --message flag supports template syntax, but placeholder tokens like {ip} render
as literal text. For dynamic log messages with randomized fields, use a YAML scenario:
The examples/log-template.yaml file defines multiple message templates, each with its own
pool of randomized field values and severity weights. See
Generators for the full template configuration reference.
Replay mode¶
Replay lines from an existing log file:
name: app_logs_replay
rate: 5
duration: 30s
generator:
type: replay
file: examples/sample-app.log
encoder:
type: json_lines
sink:
type: stdout
Lines are replayed in order and cycle back to the start when the file is exhausted.
Bring your own log file
The example uses examples/sample-app.log which ships with Sonda. To replay your
own logs, point file: at any text file -- one log line per line.
Syslog output¶
Combine template logs with the syslog encoder for RFC 5424 output:
Your metrics and logs are flowing, but real telemetry has irregularities. Let's add some.
Scheduling -- Gaps and Bursts¶
Real telemetry is messy -- networks drop packets, services restart, traffic spikes hit. Gaps and bursts let you inject those irregularities into your synthetic data, so you can test how your pipeline and alerts behave under imperfect conditions.
Gaps¶
Drop all events for a window within a recurring cycle. Useful for simulating network partitions or scrape failures:
This emits at 2/s, but goes silent for 3 seconds out of every 10-second cycle.
Bursts¶
Temporarily multiply the emission rate. Useful for load spikes or log storms:
sonda metrics --name req_rate --rate 10 --duration 20s \
--burst-every 10s --burst-for 2s --burst-multiplier 5
This emits at 10/s normally, but spikes to 50/s for 2 seconds every 10-second cycle.
YAML example with bursts:
name: cpu_burst
rate: 100
duration: 60s
generator:
type: sine
amplitude: 20.0
period_secs: 60
offset: 50.0
bursts:
every: 10s
for: 2s
multiplier: 5.0
labels:
host: web-01
zone: us-east-1
encoder:
type: prometheus_text
sink:
type: stdout
| Pattern | Real-world scenario |
|---|---|
| Gap 3s every 60s | Scrape target restarts |
| Gap 30s every 5m | Network partition |
| Burst 5x for 2s every 30s | Traffic spike |
| Burst 10x for 1s every 10s | Log storm during deploy |
Combine with generators
Gaps and bursts work with any generator. A sine wave with periodic gaps creates realistic "flapping service" patterns for alert testing.
Running one scenario at a time is great for exploration, but production systems emit multiple signals simultaneously.
Multi-Scenario Runs¶
Production systems emit multiple signals simultaneously. sonda run lets you orchestrate
several scenarios concurrently from a single YAML file, each on its own thread.
scenarios:
- signal_type: metrics
name: cpu_usage
rate: 100
duration: 30s
generator:
type: sine
amplitude: 50
period_secs: 60
offset: 50
encoder:
type: prometheus_text
sink:
type: stdout
- signal_type: logs
name: app_logs
rate: 10
duration: 30s
generator:
type: template
templates:
- message: "Request from {ip} to {endpoint}"
field_pools:
ip: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
endpoint: ["/api/v1/health", "/api/v1/metrics", "/api/v1/logs"]
severity_weights:
info: 0.7
warn: 0.2
error: 0.1
seed: 42
encoder:
type: json_lines
sink:
type: file
path: /tmp/sonda-logs.json
Each scenario runs on its own thread. Use different sinks per scenario to keep outputs separate.
Correlated metrics¶
Use phase_offset and clock_group to create time-shifted metrics that simulate compound
alert conditions:
scenarios:
- signal_type: metrics
name: cpu_usage
rate: 1
duration: 120s
phase_offset: "0s"
clock_group: alert-test
generator:
type: sequence
values: [20, 20, 20, 95, 95, 95, 95, 95, 20, 20]
repeat: true
labels:
instance: server-01
job: node
# ...
- signal_type: metrics
name: memory_usage_percent
rate: 1
duration: 120s
phase_offset: "3s"
clock_group: alert-test
generator:
type: sequence
values: [40, 40, 40, 88, 88, 88, 88, 88, 40, 40]
repeat: true
labels:
instance: server-01
job: node
# ...
Here is how the phase offset creates an overlapping window for compound alert testing:
t=0s cpu_usage starts (values: 20 -> 95)
t=3s memory_usage starts (3s phase offset, values: 40 -> 88)
t=5s Both above threshold compound alert fires (cpu > 90 AND memory > 85)
CPU spikes at t=0, memory follows 3 seconds later -- testing compound alert rules like
cpu > 90 AND memory > 85.
For more on alert testing patterns, see Alert Testing.
For long-running or programmatic use, Sonda includes an HTTP API.
The Server API¶
For long-running or programmatic use, Sonda includes an HTTP API that lets you submit, monitor, and stop scenarios without touching the CLI.
Start the server¶
Submit a scenario¶
curl -X POST \
-H "Content-Type: text/yaml" \
--data-binary @examples/simple-constant.yaml \
http://localhost:8080/scenarios
Using the scenario ID
The POST response includes an id field (a UUID). Use this ID in all subsequent
requests to check status, scrape metrics, or stop the scenario. The examples below
use <id> as a placeholder -- replace it with the actual UUID from your response.
You can also pipe through jq to extract it:
List scenarios¶
Get scenario details¶
Get live stats¶
Scrape metrics (Prometheus format)¶
Stop a scenario¶
Long-running scenarios¶
Omit duration to run indefinitely. Stop with DELETE when done:
name: continuous_cpu
rate: 10
generator:
type: sine
amplitude: 50.0
period_secs: 60
offset: 50.0
labels:
instance: api-server-01
job: sonda
encoder:
type: prometheus_text
sink:
type: stdout
# Start
curl -X POST -H "Content-Type: text/yaml" \
--data-binary @examples/long-running-metrics.yaml \
http://localhost:8080/scenarios
# Stop later
curl -X DELETE http://localhost:8080/scenarios/$ID
For the full API reference, see Server API.
The final step is getting your synthetic data into a real monitoring backend.
Pushing to a Backend¶
The final step is getting your synthetic data into a real monitoring backend for end-to-end validation. Sonda supports three approaches.
Complete backend examples
For complete Docker Compose setups with VictoriaMetrics, Prometheus, and Grafana, see Alert Testing and Docker Deployment. This section covers the Sonda-specific configuration.
1. HTTP Push (import API)¶
POST metrics directly to a backend's import endpoint. You can use CLI flags for quick ad-hoc runs without writing a scenario file:
sonda metrics --name cpu --rate 10 --duration 30s \
--sink http_push --endpoint http://victoriametrics:8428/api/v1/import/prometheus \
--content-type "text/plain"
Or use a scenario file for more control:
sink:
type: http_push
url: "http://victoriametrics:8428/api/v1/import/prometheus"
content_type: "text/plain"
batch_size: 65536
2. Remote Write¶
Use the Prometheus remote write protocol for native compatibility. CLI flags let you set up a quick ad-hoc run:
sonda metrics --name cpu --rate 10 --duration 30s \
--encoder remote_write \
--sink remote_write --endpoint http://localhost:8428/api/v1/write
Or use a scenario file:
encoder:
type: remote_write
sink:
type: remote_write
url: "http://localhost:8428/api/v1/write"
batch_size: 100
Compatible targets include VictoriaMetrics, Prometheus, Thanos Receive, and Cortex/Mimir.
3. Loki (logs)¶
Push logs directly to Grafana Loki:
sonda logs --mode template --rate 10 --duration 30s \
--sink loki --endpoint http://localhost:3100 \
--label app=myservice --label env=staging
Or use a scenario file for richer templates:
4. OTLP/gRPC (OpenTelemetry Collector)¶
Push directly to an OpenTelemetry Collector via gRPC. This requires building with --features otlp.
Use CLI flags for a quick test:
sonda metrics --name cpu --rate 10 --duration 30s \
--encoder otlp \
--sink otlp_grpc --endpoint http://localhost:4317 --signal-type metrics
Or use a scenario file:
encoder:
type: otlp
sink:
type: otlp_grpc
endpoint: "http://localhost:4317"
signal_type: metrics
The Collector routes data to any configured exporter (Prometheus, Jaeger, Loki, etc.), making this the most flexible backend integration option.
5. Scrape via sonda-server¶
Point Prometheus at sonda-server's metrics endpoint:
scrape_configs:
- job_name: sonda
static_configs:
- targets: ["sonda-server:8080"]
metrics_path: /scenarios/<id>/metrics
Scrape path
Replace <id> with the scenario ID returned by POST /scenarios. Each running
scenario exposes its own metrics endpoint.
Verify data arrived¶
# VictoriaMetrics
curl "http://localhost:8428/api/v1/query?query=sonda_http_request_duration_ms"
# Prometheus
curl "http://localhost:9090/api/v1/query?query=sonda_http_request_duration_ms"
Next Steps¶
Testing alert rules? Start with Alert Testing.
Validating a pipeline change? See Pipeline Validation.
Verifying recording rules? Check Recording Rules.
Running Sonda in production? See Docker Deployment or Server API.
Browsing ready-to-use scenarios? See Example Scenarios.