# Metrics
Metrics are optional and sit outside the core receive API.
## Enabling Metrics
```bash
cargo run --features metrics --bin mcrx_recv -- 239.1.2.3 5000
```
## Model
The metrics system is split into three layers:
### Snapshot
A snapshot is a point-in-time view.
Counter fields in a snapshot are cumulative.
For `ContextMetricsSnapshot`, packet, byte, would-block, receive-error, join,
leave, and batch counters are true context-lifetime totals. They are not
recomputed from the currently active subscriptions, and they do not decrease
when a subscription is removed.
Gauge-like fields in a snapshot reflect current state only:
- `active_subscriptions`
- `joined_subscriptions`
### Delta
A delta is computed between two snapshots of the same metric type.
Delta fields represent only the change over the sampled interval:
- packets received during the interval
- bytes received during the interval
- receive errors during the interval
- joins, leaves, and public batch receive calls during the interval
### Sampler
A sampler stores the previous snapshot and computes deltas across repeated
samples.
The first call returns `None` because a delta requires two snapshots.
## Cumulative Totals
At the context level, these snapshot fields are cumulative totals:
- `total_packets_received`
- `total_bytes_received`
- `total_would_block_count`
- `total_receive_errors`
- `total_join_count`
- `total_leave_count`
- `batch_calls`
- `batch_packets_received`
`batch_calls` counts public calls to `Context::try_recv_batch_*()` and
`Context::try_recv_all_*()`. It does not count the internal empty probe that
`try_recv_all_*()` uses to detect that a drain is complete.
At the subscription level, the per-subscription snapshot counters remain
cumulative for the lifetime of that subscription object.
## Rates
Delta types expose average interval rates such as:
- `packets_per_sec()`
- `bytes_per_sec()`
- `would_block_per_sec()`
- `receive_errors_per_sec()`
These are computed from delta counters divided by the sampled interval.
## CLI Integration
`mcrx_recv` can periodically emit:
- terminal summaries
- JSONL file output
The reusable single-header JSONL helper is also exported as
`mcrx_core::jsonl` when the `metrics` feature is enabled.
Configured via:
- `MCRX_METRICS_SUMMARY_SECS`
- `MCRX_METRICS_SUMMARY_FILE`
- `MCRX_METRICS_NODE_ID`
- `MCRX_METRICS_FLAGS_JSON`
## JSONL Schema
The library helper lives in `mcrx_core::jsonl` and provides:
- `MetricsJsonlOutputConfig`
- `HEIMDALL_JSONL_SCHEMA`
- `NETWORK_ARTIFACT_TYPE`
- `HARDWARE_ARTIFACT_TYPE`
- `infer_node_id_from_path()`
- `header_json()`
- `append_jsonl_sample_row()`
Metrics JSONL files use a single-header format:
1. The first non-empty line is a header object.
2. Every later non-empty line is one compact sample row.
3. Header metadata is not repeated on sample rows.
Network output uses `artifact_type = "mcrx-network"`.
When hardware metrics are enabled, the sibling `*_hardware` file uses
`artifact_type = "process-hardware"`.
### Header Row
The header row contains:
- `schema = "heimdall-jsonl-v1"`
- `artifact_type`
- `node_id`
- `producer`
- `created_at`
- `flags`
`producer` is currently `mcrx-core/mcrx_recv`.
`node_id` is resolved in this order:
1. `MCRX_METRICS_NODE_ID`, if set
2. the output file's parent directory name
3. the output file stem
`flags` is a free-form JSON object. `mcrx_recv` populates it with receiver
context such as:
- `transport`
- `role`
- `multicast_group`
- `multicast_port`
- `multicast_interface`
- `join_mode`
- `source_filter`
- `source_addr` when present
- `batch_receive_enabled`
Additional caller-provided flag fields can be merged in with
`MCRX_METRICS_FLAGS_JSON`.
### Sample Rows
The network sample rows keep the existing numeric metric fields and use explicit
names for counter totals and interval deltas.
Packet and byte fields:
- `packets_received_total`
- `bytes_received_total`
- `packets_received_delta`
- `bytes_received_delta`
- `packets_per_sec`
- `bytes_per_sec`
Other counter-style fields follow the same explicit pattern where emitted:
- `would_block_count_total`
- `would_block_count_delta`
- `receive_errors_total`
- `receive_errors_delta`
- `join_count_total`
- `join_count_delta`
- `leave_count_total`
- `leave_count_delta`
- `batch_calls_total`
- `batch_calls_delta`
- `batch_packets_received_total`
- `batch_packets_received_delta`
Current-state gauge fields remain:
- `active_subscriptions`
- `joined_subscriptions`
The header metadata fields are intentionally not repeated on sample rows:
- `schema`
- `artifact_type`
- `node_id`
- `producer`
- `flags`
Hardware sample rows keep their existing compact numeric fields such as:
- `cpu_user_secs`
- `cpu_system_secs`
- `cpu_total_secs`
- `cpu_util_percent`
- `rss_bytes`
- `virtual_memory_bytes`
- `thread_count`
- `open_fds`
- `page_faults_minor`
- `page_faults_major`
- `ctx_switches_voluntary`
- `ctx_switches_involuntary`
## Breaking Schema Change
There is no backward-compatibility layer for the older repeated-metadata JSONL
shape.
Consumers should expect:
- one header object at the top of each file
- compact sample rows afterward
- explicit `*_total` and `*_delta` network counter fields