Telemetry signals
Prometheus series, OTLP log records and their attributes, and example records. See Monitor transfers and device reports for how to read them.
Prometheus metrics
IRIS serves Prometheus on :9101 /metrics and exports the same values as OTLP metrics. Every family carries {image, info_hash} labels only; per-device and per-peer detail is in OTLP log records.
| Prometheus family | OTLP metric | Unit | Attributes |
|---|---|---|---|
iris_transfer_devices |
iris.transfer.devices |
{device} |
image attrs |
iris_transfer_throughput_bytes_per_second |
iris.transfer.throughput |
By/s |
image attrs + network.io.direction = receive | transmit |
iris_transfer_progress_ratio |
iris.transfer.progress |
1 |
image attrs |
iris_transfer_zero_receive_devices |
iris.transfer.zero_receive_devices |
{device} |
image attrs |
iris_transfer_freshness_age_seconds |
iris.transfer.freshness_age |
s |
image attrs |
iris_stream_devices |
iris.stream.devices |
{device} |
image attrs + sampling_class = good | constrained |
iris_seeder_torrent_upload_length_bytes |
iris.seeder.torrent.upload_length |
By |
image attrs |
iris_seeder_torrent_upload_bytes_per_second |
iris.seeder.torrent.upload_rate |
By/s |
image attrs |
iris_legacy_announce_participants |
iris.legacy.announce_participants |
{participant} |
none |
iris_telemetry_samples_rejected_total |
iris.telemetry.samples.rejected |
{sample} |
(counter; no _total on the OTLP wire) |
iris_telemetry_export_failures_total |
iris.telemetry.export.failures |
{error} |
signal = logs | metrics |
iris_telemetry_export_dropped_total |
iris.telemetry.export.dropped |
{record} |
signal = logs | metrics |
iris_telemetry_export_last_success_seconds |
Prometheus only | export health also on authenticated /status |
|
iris_peer_policy_revision |
iris.peer.policy.revision |
1 |
none |
iris_peer_enforcement_applied_revision |
iris.peer.enforcement.applied_revision |
1 |
none |
iris_peer_enforcement_desired_ips |
iris.peer.enforcement.desired_ips |
{ip} |
none |
iris_peer_enforcement_health |
iris.peer.enforcement.health |
1 |
none |
iris_transfer_lifecycle_plans |
iris.transfer.lifecycle.plans |
{plan} |
none |
iris_transfer_lifecycle_plan_cap |
iris.transfer.lifecycle.plan_cap |
{plan} |
none |
iris_transfer_lifecycle_awaiting_report |
iris.transfer.lifecycle.awaiting_report |
{plan} |
none |
iris_transfer_lifecycle_unconfirmed |
iris.transfer.lifecycle.unconfirmed |
{event} |
none |
iris_transfer_lifecycle_dropped_unemitted_total |
iris.transfer.lifecycle.dropped_unemitted |
{plan} |
(counter) |
iris_transfer_lifecycle_live_evicted_total |
iris.transfer.lifecycle.live_evicted |
{plan} |
(counter) |
iris_transfer_lifecycle_retired_undelivered_total |
iris.transfer.lifecycle.retired_undelivered |
{event} |
(counter) |
iris_transfer_lifecycle_promoted_recovered_total |
iris.transfer.lifecycle.promoted_recovered |
{plan} |
(counter) |
iris_origin_sent_bytes_total |
(Prometheus only) | By |
image, info_hash |
iris_peer_attributed_bytes_total |
(Prometheus only) | By |
image, info_hash |
iris_peer_unattributed_bytes_total |
(Prometheus only) | By |
image, info_hash |
iris_swarm_peers_attributed |
(Prometheus only) | {peer} |
image, info_hash |
iris_swarm_peers_saturated |
(Prometheus only) | 1 |
image, info_hash |
iris_instruction_certificate_days_to_expiry, iris_instruction_keylist_age_days |
(Prometheus only) | d |
none |
iris_instruction_roots_attested_180d |
(Prometheus only) | {root} |
none |
iris_instruction_root_ceremony_overdue |
(Prometheus only) | 1 |
none |
iris_instruction_root_quorum_degraded |
(Prometheus only) | 1 |
none |
Throughput and progress are omitted for an image with no fresh device, rather than published as zero; freshness age is exported alongside. The iris_transfer_lifecycle_* family is omitted whole when the plan store is unreadable. The five iris_instruction_* families report on the two offline root keys: certificate and keylist age in days, roots attested within 180 days, and two status gauges (root_ceremony_overdue reads 0 on schedule, 1 due, 2 overdue). A field the server cannot read is omitted.
Traced and untraced origin bytes
The origin seeder knows exactly how many bytes it sent; which device received them is sampled from aria2's per-connection counters.
| Series | Type | Meaning |
|---|---|---|
iris_origin_sent_bytes_total |
counter | Bytes the origin seeder uploaded for this torrent. |
iris_peer_attributed_bytes_total |
counter | Of those, bytes traced to a device. |
iris_peer_unattributed_bytes_total |
gauge | The untraced residue: max(0, origin - traced). Graph the value; never apply rate() to it. |
iris_swarm_peers_attributed |
gauge | Peer edges with a nonzero traced total right now, not an accumulation. |
iris_swarm_peers_saturated |
gauge | 1 when the per-torrent peer cap refused new peers. |
A peer that connects, takes bytes and disconnects between two samples is counted as untraced, not lost. Metric names use attributed; the matching OTLP attribute is iris.peer.attribution, covered under iris.device.peer_transfer_record below.
Prometheus labels never carry per-peer or per-device identity, because that would put unbounded peer identity into label cardinality. That detail is available only in the OTLP log records below.
OTLP log records
Terminal per-device reports, tracker lifecycle events, peer-policy operations, and measured peer rates and byte totals flow as OTLP logs. Event identity is the top-level eventName field. These are the otel.log.name values IRIS emits:
| Record name | Source | Nature |
|---|---|---|
iris.device.transfer.report |
Device agent | Terminal per-device transfer report. |
iris.device.report |
Device agent | Reduced projection of the report. |
iris.tracker.peer |
Tracker | Tracker peer lifecycle: announce, join, leave. |
iris.peer.policy |
Tracker | Peer-policy revision applied and enforcement outcome. |
iris.swarm.peer_rate |
Server-side peer ledger | Sampled per-connection send rate. |
iris.transfer.lifecycle |
Tracker | Plan lifecycle: assignment recorded, seeding confirmed. |
iris.swarm.peer_bytes |
Server-side peer ledger | Sampled estimate of one edge's bytes. |
iris.device.peer_transfer_record |
Device-side completion hook | Device-measured exact bytes received from one peer. |
Int64 attributes ride the wire as strings
Byte attributes are int64 and travel as JSON strings in OTLP. A query that sums them must coerce to a number first.
Never sum the two peer record names together
iris.swarm.peer_bytes and iris.device.peer_transfer_record describe the same bytes from opposite ends of the wire, one sampled, one exact. A query that sums both counts every transfer twice. Pick one name per panel; prefer iris.device.peer_transfer_record where you need a number you can defend.
iris.device.transfer.report
| Attribute | Meaning |
|---|---|
device.id |
The reporting device. |
iris.image.id |
The image the report is about. |
iris.transfer.id |
The transfer this report describes. |
iris.report.event |
The report's terminal outcome. |
iris.transfer.content_sha256.state |
The device's own hash check result. |
iris.transfer.ios_copy_verify.state |
The result of copying the file into place. |
iris.transfer.completed_content_bytes |
Bytes present in the staged file. |
iris.transfer.peers_total |
Distinct peer count, saturating at the device's 512-IP tracking cap. |
iris.device.observed_at |
Device-side observation time; use this for when the measurement was made. |
network.peer.address |
A flat string array of the observed peer addresses. |
iris.transfer.bytes_from_all_senders_total |
The device's own total, including the origin. |
iris.transfer.bytes_from_origin_total, iris.transfer.bytes_from_devices_total, iris.transfer.bytes_from_unknown_total |
The classified parts: from the origin seeder, from other devices, unattributable. |
iris.transfer.bytes_unattributed_omitted |
Bytes dropped with the peer rows capped by iris.transfer.peer_records.rows_omitted. Keep it in its own bucket; never redistribute it among the origin, device, or unknown totals above. |
iris.transfer.peer_records.rows_omitted, iris.transfer.bytes_from_all_senders_omitted |
Peer rows dropped at a cap, and the bytes dropped with them. |
iris.transfer.peer_records.capture_complete |
false when the peer capture itself was lossy. |
iris.download.started_at, iris.download.completed_at, iris.download.duration_seconds |
Elapsed download time, from the optional v2 report download block. A resume retains the original start when known. Already-present images, missing hooks, and a transfer whose start was not recorded are excluded from the average rather than reported as zero. |
The transfer record is a floor, not a census
The device only counts peers aria2 still has an open connection to when the last piece lands. bytes_from_all_senders_total is a lower bound, not an exact match for completed_content_bytes. A transfer with no usable snapshot carries no peer-transfer-record attributes at all.
The iris.device.report record is a reduced projection: device.id, iris.image.id, iris.report.event, iris.transfer.peers_total, network.peer.address. Use iris.device.transfer.report for completed bytes and verification fields; counting both counts one delivery twice.
iris.tracker.peer
| Attribute | Meaning |
|---|---|
iris.principal |
Who the record is from: a device, the server's seeder, or a legacy device. |
iris.torrent.info_hash |
The torrent the peer joined. |
iris.peer.role |
The BitTorrent role: seeder or leecher. |
network.peer.address |
The peer's address. |
iris.device.role |
The peer's role membership from the loaded policy, present only for an authenticated device. Not the same vocabulary as iris.peer.role. |
iris.peer.policy
| Attribute | Meaning |
|---|---|
iris.policy.revision |
The policy revision the operation concerns. |
iris.policy.action |
The action taken. |
iris.enforcement.state |
The enforcement outcome. |
iris.enforcement.applied_revision |
The revision actually applied. |
iris.enforcement.desired_ip_count |
How many addresses the desired state names. |
This record is count-only: it never carries role membership lists, raw rules, addresses, or origin-QoS option values.
See Roles and sharing-policy API for the fields behind these attributes.
iris.swarm.peer_rate
| Attribute | Meaning |
|---|---|
iris.principal |
Who the sending peer is: a device, the server's seeder, or a legacy device. |
iris.image.id |
The image being transferred. |
iris.torrent.info_hash |
The torrent. |
network.peer.address, network.peer.port |
The peer's address and port. |
iris.transfer.peer_send_bps |
Sampled per-connection send rate. |
iris.torrent.left |
Bytes the peer still needs. |
iris.peer.role |
seeder or leecher. |
iris.device.role |
The peer's role membership, present only for an authenticated device. |
iris.transfer.lifecycle
A plan is the decision that one device should hold one image. It carries two ids for the life of that transfer:
| Attribute | Meaning |
|---|---|
iris.plan.id |
Identifies the decision. Stable across both events of one plan. Group on this id. |
iris.transfer.id |
Identifies the transfer the device performs for that plan; the device stamps the same value on its own iris.device.transfer.report. Join the two record families on this id. |
Two event values, in the order they occur:
event |
Meaning |
|---|---|
planned |
The assignment was recorded. Emitted once per plan. |
seeding_started |
The server confirmed the device holds verified content and is seeding it. Emitted at most once per plan. |
A planned record carries: event, iris.transfer.id, iris.plan.id, iris.device.id, device.id (both spellings, so either join works), iris.image.id, iris.torrent.info_hash, iris.transfer.planned_at. A seeding_started record repeats every planned attribute and adds iris.transfer.seeding_started_at, iris.transfer.checksum_verified_at, iris.transfer.report_received_at, iris.transfer.tracker_seeder_at, iris.device.observed_at, iris.device.report_created_at and, on a recovered promotion only, iris.transfer.recovered_promotion. See Monitor transfers and device reports for what these events prove.
A missing value is absent, never an empty string or a default: a plan minted before its torrent existed carries no iris.torrent.info_hash.
A planned event
The attributes array of a planned record (eventName is iris.transfer.lifecycle, severityText is INFO):
[
{ "key": "otel.log.name", "value": { "stringValue": "iris.transfer.lifecycle" } },
{ "key": "iris.telemetry.schema.version", "value": { "intValue": "2" } },
{ "key": "event", "value": { "stringValue": "planned" } },
{ "key": "iris.transfer.id", "value": { "stringValue": "4d1a6e0c73b94f2ea85d10c6b7f3928a" } },
{ "key": "iris.plan.id", "value": { "stringValue": "9f2c4b7a1d8e4f60b3a25c9107de4412" } },
{ "key": "iris.device.id", "value": { "stringValue": "203.0.113.3" } },
{ "key": "device.id", "value": { "stringValue": "203.0.113.3" } },
{ "key": "iris.image.id", "value": { "stringValue": "cat9k_iosxe.26.01.01" } },
{ "key": "iris.torrent.info_hash", "value": { "stringValue": "c8f4e2a1b09d7635fe1428a0d5c93b7614e0af82" } },
{ "key": "iris.transfer.planned_at", "value": { "stringValue": "2026-09-02T12:43:11.482Z" } },
{ "key": "event.id", "value": { "stringValue": "9f2c4b7a1d8e4f60b3a25c9107de4412.planned" } }
]
A seeding_started event
The same plan, ten minutes later. It repeats every planned attribute, including iris.transfer.planned_at, and adds:
[
{ "key": "event", "value": { "stringValue": "seeding_started" } },
{ "key": "iris.transfer.seeding_started_at", "value": { "stringValue": "2026-09-02T12:53:44.117Z" } },
{ "key": "iris.transfer.checksum_verified_at", "value": { "stringValue": "2026-09-02T12:53:44.117Z" } },
{ "key": "iris.transfer.report_received_at", "value": { "stringValue": "2026-09-02T12:53:44.117Z" } },
{ "key": "iris.transfer.tracker_seeder_at", "value": { "stringValue": "2026-09-02T12:48:30.905Z" } },
{ "key": "iris.device.observed_at", "value": { "doubleValue": 1788353602.0 } },
{ "key": "iris.device.report_created_at", "value": { "doubleValue": 1788353604.4 } },
{ "key": "event.id", "value": { "stringValue": "9f2c4b7a1d8e4f60b3a25c9107de4412.seeding_started" } }
]
iris.transfer.planned_at, iris.transfer.seeding_started_at, iris.transfer.checksum_verified_at, iris.transfer.report_received_at and iris.transfer.tracker_seeder_at are RFC 3339 strings with a fixed shape:
2026-09-02T12:43:11.482Z
UTC, always; exactly three fractional digits (...:11.000Z, never ...:11Z); a literal Z, never +00:00. The matching Splunk pattern is %Y-%m-%dT%H:%M:%S.%N%Z; these are string attributes, not int64. iris.device.observed_at and iris.device.report_created_at are float epochs on the device's own clock, not RFC 3339 strings. Show them, but never subtract a server instant from one as though the clocks agreed.
iris.swarm.peer_bytes
| Attribute | Meaning |
|---|---|
iris.image.id, iris.image.name |
The image and its catalog filename at export time. |
iris.torrent.info_hash |
The torrent. |
network.peer.address |
The peer edge this row measures. |
device.id, iris.peer.device.id, iris.peer.device_id |
The receiving device, and the sending device, when known. |
iris.peer.attribution |
origin, device, or unknown. |
iris.transfer.session_bytes_from_peer |
The sampled estimate of bytes this edge carried. |
iris.transfer_record.capture_complete |
false when the sampled capture was incomplete. |
iris.device.role |
The peer's role membership, present only for an authenticated device. |
iris.peer.tls.configured_mode, iris.peer.tls.runtime_mode, iris.peer.tls.runtime_source, iris.peer.tls.reported_at |
An authenticated device's own reported TLS policy, not proof of one connection's negotiated cipher. |
iris.device.peer_transfer_record
Emitted once per peer per completed transfer, from a device-side completion hook reading aria2's per-peer counters when the last piece lands.
| Attribute | Meaning |
|---|---|
device.id |
The receiving device. |
iris.image.id, iris.image.name |
The image and its catalog filename. |
iris.transfer.id |
The transfer this row belongs to. |
network.peer.address, network.peer.port |
The peer's address and port. |
iris.transfer.session_bytes_from_peer, iris.transfer.session_bytes_to_peer |
Bytes received from, and sent to, that peer in this session. |
iris.peer.attribution |
origin, device, or unknown. |
iris.peer.device.id, iris.peer.device_id |
The sender: a device id, origin, or absent for an unknown sender. |
iris.peer.has_complete_file, iris.transfer_record.capture_complete |
Whether that peer already held the whole file, and whether the capture itself was lossy. has_complete_file does not identify the origin: it is aria2's seeder flag, true for any peer holding the complete file, including every device that finished early in a multi-device wave. It answers complete-vs-partial, not origin identity. |
The server classifies and pins each row's attribution once, at first export; see Data formats and states. A row the server did not resolve stays unknown rather than being folded into device.
The telemetry_observation payload
While a transfer is active, a device with streaming enabled embeds one telemetry_observation payload in its heartbeat, bounded at 8 KB. Only schema v == 2 is accepted; a malformed field is dropped without failing the heartbeat.
| Field | Meaning |
|---|---|
v |
Payload schema version (currently 2). |
obs_state |
observed, not_due, paused, disabled, not_active, or rpc_unavailable. Only observed carries transfer fields. |
observed_at |
Device-side observation time. |
transfer_id / image_id |
The transfer and the device's policy-assigned image, validated server-side. |
sample_seq |
Monotonic per-transfer sequence, checkpointed before the POST. |
sampling_class |
good or constrained. |
aria |
receive_bps, send_bps, completed_content_bytes, total_content_bytes, connections, status. |
peer_connections |
Up to 32 rows: ip, port, send_bps, receive_bps, peer_client_name, progress. Extra rows are truncated and flagged, not rejected. |
Announce counters are the device's own
The announce counters, including bytes uploaded and bytes downloaded, are device-authored: the device reports them and the tracker records what it was told.
Event identity
The device creates a report_id, frozen before its first POST. A retry after a crash sends the same report bytes and identifier, so the server stores it once. The exported event.id preserves that identity for downstream deduplication. Use the device observation timestamp for when the measurement was made, not the record's ingest timestamp. See Monitor transfers and device reports for how delivery stays consistent across a restart.
Reading iris_legacy_announce_participants
iris_legacy_announce_participants counts authenticated peers with no attributed device or seeder identity. A rotated-out seeder credential stays valid for IRIS_SEEDER_PREV_TTL; after that, its peer drops out of this gauge. Check iris_tracker_announces_refused_total and iris_tracker_announces_refused_expired_total alongside it, comparing changes over the same window.