This document describes the server-side telemetry collector, its runtime configuration, and the HTTP API exposed by sdrd.
The telemetry system is intended for debugging and performance analysis of the SDR pipeline, especially around source cadence, extraction, DSP timing, boundary artifacts, queue pressure, and other runtime anomalies.
The telemetry layer gives you three different views of runtime state:
It is designed to be lightweight in normal operation and more detailed when heavy_enabled is turned on.
All telemetry endpoints live under:
/api/debug/telemetry/live/api/debug/telemetry/history/api/debug/telemetry/events/api/debug/telemetry/configResponses are JSON.
Telemetry metrics are stored in three logical groups:
A historical metric sample is returned as:
{
"ts": "2026-03-25T12:00:00Z",
"name": "stage.extract_stream.duration_ms",
"type": "distribution",
"value": 4.83,
"tags": {
"stage": "extract_stream",
"signal_id": "1"
}
}
Telemetry events are structured anomaly/debug records:
{
"id": 123,
"ts": "2026-03-25T12:00:02Z",
"name": "demod_boundary",
"level": "warn",
"message": "boundary discontinuity detected",
"tags": {
"signal_id": "1",
"stage": "demod"
},
"fields": {
"d2": 0.3358,
"index": 25
}
}
Tags are string key/value metadata used for filtering and correlation.
Common tag keys already supported by the HTTP layer:
signal_idsession_idstagetrace_idcomponentYou can also filter on arbitrary tags via tag_<key>=<value> query parameters.
GET /api/debug/telemetry/liveReturns a live snapshot of the in-memory collector state.
{
"now": "2026-03-25T12:00:05Z",
"started_at": "2026-03-25T11:52:10Z",
"uptime_ms": 472500,
"config": {
"enabled": true,
"heavy_enabled": false,
"heavy_sample_every": 12,
"metric_sample_every": 2,
"metric_history_max": 12000,
"event_history_max": 4000,
"retention": 900000000000,
"persist_enabled": false,
"persist_dir": "debug/telemetry",
"rotate_mb": 16,
"keep_files": 8
},
"counters": [
{
"name": "source.resets",
"value": 1,
"tags": {
"component": "source"
}
}
],
"gauges": [
{
"name": "source.buffer_samples",
"value": 304128,
"tags": {
"component": "source"
}
}
],
"distributions": [
{
"name": "dsp.frame.duration_ms",
"count": 96,
"min": 82.5,
"max": 212.4,
"mean": 104.8,
"last": 98.3,
"p95": 149.2,
"tags": {
"stage": "dsp"
}
}
],
"recent_events": [],
"status": {
"source_state": "running"
}
}
counters, gauges, and distributions are sorted by metric name.recent_events contains the most recent in-memory event slice.status is optional and contains arbitrary runtime status published by code using SetStatus(...).*.duration_ms distributions.GET /api/debug/telemetry/historyReturns historical metric samples from in-memory history and, optionally, persisted JSONL files.
{
"items": [
{
"ts": "2026-03-25T12:00:01Z",
"name": "stage.extract_stream.duration_ms",
"type": "distribution",
"value": 5.2,
"tags": {
"stage": "extract_stream",
"signal_id": "2"
}
}
],
"count": 1
}
sinceuntilAccepted formats:
Examples:
?since=1711368000?since=1711368000123?since=2026-03-25T12:00:00Zlimit
name=<exact_metric_name>prefix=<metric_name_prefix>Examples:
?name=source.read.duration_ms?prefix=stage.?prefix=iq.extract.Special convenience query params map directly to tag filters:
signal_idsession_idstagetrace_idcomponentArbitrary tag filters:
tag_<key>=<value>Examples:
?signal_id=1?stage=extract_stream?tag_path=gpu?tag_zone=broadcastinclude_persisted=true|false
trueWhen enabled and persistence is active, the server reads matching data from rotated JSONL telemetry files in addition to in-memory history.
limit is hit, the most recent matching items are retained.Get all stage timing since a specific start:
/api/debug/telemetry/history?since=2026-03-25T12:00:00Z&prefix=stage.
Get extraction metrics for a single signal:
/api/debug/telemetry/history?since=2026-03-25T12:00:00Z&prefix=extract.&signal_id=2
Get source cadence metrics only from in-memory history:
/api/debug/telemetry/history?prefix=source.&include_persisted=false
GET /api/debug/telemetry/eventsReturns historical telemetry events from memory and, optionally, persisted storage.
{
"items": [
{
"id": 991,
"ts": "2026-03-25T12:00:03Z",
"name": "source_reset",
"level": "warn",
"message": "source reader reset observed",
"tags": {
"component": "source"
},
"fields": {
"reason": "short_read"
}
}
],
"count": 1
}
All history filters are also supported here, plus:
level=<debug|info|warn|error|...>Examples:
?since=2026-03-25T12:00:00Z&level=warn?prefix=audio.&signal_id=1?name=demod_boundary&signal_id=1name, prefix, level, time range, and tags.level matching is case-insensitive.Get warnings during a reproduction run:
/api/debug/telemetry/events?since=2026-03-25T12:00:00Z&level=warn
Get boundary-related events for one signal:
/api/debug/telemetry/events?since=2026-03-25T12:00:00Z&signal_id=1&prefix=demod_
GET /api/debug/telemetry/configReturns both:
debug.telemetry{
"collector": {
"enabled": true,
"heavy_enabled": false,
"heavy_sample_every": 12,
"metric_sample_every": 2,
"metric_history_max": 12000,
"event_history_max": 4000,
"retention": 900000000000,
"persist_enabled": false,
"persist_dir": "debug/telemetry",
"rotate_mb": 16,
"keep_files": 8
},
"config": {
"enabled": true,
"heavy_enabled": false,
"heavy_sample_every": 12,
"metric_sample_every": 2,
"metric_history_max": 12000,
"event_history_max": 4000,
"retention_seconds": 900,
"persist_enabled": false,
"persist_dir": "debug/telemetry",
"rotate_mb": 16,
"keep_files": 8
}
}
collector.retention is a Go duration serialized in nanoseconds.config.retention_seconds is the config-facing field used by YAML and the POST update API.If you are writing tooling, prefer config.retention_seconds for human-facing config edits.
POST /api/debug/telemetry/configUpdates telemetry settings at runtime and writes them back via the autosave config path.
All fields are optional. Only provided fields are changed.
{
"enabled": true,
"heavy_enabled": true,
"heavy_sample_every": 8,
"metric_sample_every": 1,
"metric_history_max": 20000,
"event_history_max": 6000,
"retention_seconds": 1800,
"persist_enabled": true,
"persist_dir": "debug/telemetry",
"rotate_mb": 32,
"keep_files": 12
}
{
"ok": true,
"collector": {
"enabled": true,
"heavy_enabled": true,
"heavy_sample_every": 8,
"metric_sample_every": 1,
"metric_history_max": 20000,
"event_history_max": 6000,
"retention": 1800000000000,
"persist_enabled": true,
"persist_dir": "debug/telemetry",
"rotate_mb": 32,
"keep_files": 12
},
"config": {
"enabled": true,
"heavy_enabled": true,
"heavy_sample_every": 8,
"metric_sample_every": 1,
"metric_history_max": 20000,
"event_history_max": 6000,
"retention_seconds": 1800,
"persist_enabled": true,
"persist_dir": "debug/telemetry",
"rotate_mb": 32,
"keep_files": 12
}
}
A POST updates:
config.Save(...)That means these updates are runtime-effective immediately and also survive restarts through autosave, unless manually reverted.
400 Bad Request400 Bad Request503 Service Unavailabledebug.telemetry)Telemetry config lives under:
debug:
telemetry:
enabled: true
heavy_enabled: false
heavy_sample_every: 12
metric_sample_every: 2
metric_history_max: 12000
event_history_max: 4000
retention_seconds: 900
persist_enabled: false
persist_dir: debug/telemetry
rotate_mb: 16
keep_files: 8
enabledMaster on/off switch for telemetry collection.
If false:
heavy_enabledEnables more expensive / more detailed telemetry paths that should not be left on permanently unless needed.
Use this for deep extractor/IQ/boundary debugging.
heavy_sample_everySampling cadence for heavy telemetry.
1 means every eligible heavy samplemetric_sample_everySampling cadence for normal historical metric point storage.
Collector summaries still update live, but historical storage becomes less dense when this value is greater than 1.
metric_history_maxMaximum number of in-memory historical metric samples retained.
event_history_maxMaximum number of in-memory telemetry events retained.
retention_secondsTime-based in-memory retention window.
Older in-memory metrics/events are trimmed once they fall outside this retention period.
persist_enabledWhen enabled, telemetry metrics/events are also appended to rotated JSONL files.
persist_dirDirectory where rotated telemetry JSONL files are written.
Default:
debug/telemetryrotate_mbApproximate JSONL file rotation threshold in megabytes.
keep_filesHow many rotated telemetry files to retain in persist_dir.
Older files beyond this count are pruned.
The query endpoints can read from both:
This means a request may return data older than current in-memory retention if:
persist_enabled=true, andinclude_persisted=trueNot every observation necessarily becomes a historical metric point.
The collector:
metric_sample_everySo the live snapshot and historical series density are intentionally different.
Distribution values in the live snapshot include:
countminmaxmeanlastp95The p95 estimate is based on the collector's bounded rolling sample buffer, not an unbounded full-history quantile computation.
The collector's retention field is a Go duration. In JSON this appears as an integer nanosecond count.
This is expected.
Use:
enabled=trueheavy_enabled=falsepersist_enabled=false or true if you want an archiveThen query:
/api/debug/telemetry/live/api/debug/telemetry/history?prefix=stage./api/debug/telemetry/events?level=warnSuggested settings:
enabled=trueheavy_enabled=falsepersist_enabled=truemetric_sample_everyThen:
stage.*, streamer.*, and source.* historyTemporarily enable:
heavy_enabled=trueheavy_sample_every > 1 unless you really need every samplepersist_enabled=trueThen inspect:
iq.*extract.*audio.*signal_id or session_idTurn heavy telemetry back off once done.
curl http://localhost:8080/api/debug/telemetry/live
curl "http://localhost:8080/api/debug/telemetry/history?since=2026-03-25T12:00:00Z&prefix=stage."
curl "http://localhost:8080/api/debug/telemetry/history?prefix=source.&signal_id=1"
curl "http://localhost:8080/api/debug/telemetry/events?since=2026-03-25T12:00:00Z&level=warn"
curl "http://localhost:8080/api/debug/telemetry/events?tag_zone=broadcast"
curl -X POST http://localhost:8080/api/debug/telemetry/config \
-H "Content-Type: application/json" \
-d '{
"heavy_enabled": true,
"heavy_sample_every": 8,
"persist_enabled": true
}'
README.md - high-level project overview and endpoint summarydocs/telemetry-debug-runbook.md - quick operational runbook for short debug sessionsinternal/telemetry/telemetry.go - collector implementation detailscmd/sdrd/http_handlers.go - HTTP wiring for telemetry endpoints