pub struct Sampler { /* private fields */ }Expand description
A wait-free, lossy circular buffer for capturing telemetry samples.
Sampler provides a high-throughput mechanism for recording numeric data
in environments with massive write contention. It prioritizes progress
for producers (threads recording data) over absolute data retention.
§Concurrency Design
The buffer is wait-free. It uses a single atomic [head] pointer
to reserve slots in the samples vector. Multiple producers can
concurrently claim indices and store values without blocking or
needing to coordinate beyond a single fetch_add.
§Lossy Buffer Mechanics
Once the buffer reaches its capacity ($2^n$), the head index wraps
around, and new samples overwrite the oldest data. This ensures
memory usage remains constant regardless of the volume of events.
§Significant Implementation Details
- Masking: Indexing uses bitwise
& maskinstead of the remainder operator%. This requires the internal capacity to be a power of two, a constraint enforced during initialization. - Zero-Value Sentinels: During aggregation ([
write_samples]), values of0are ignored. This identifies uninitialized slots or intentionally skipped samples. - Read Consistency: Snapshotting iterates over the buffer with
Relaxedloads. While a snapshot is being taken, producers may be overwriting values, making the snapshot an “eventually consistent” view of the buffer’s state.
Implementations§
Source§impl Sampler
impl Sampler
Sourcepub fn new(capacity: usize) -> Self
pub fn new(capacity: usize) -> Self
Creates a new Sampler with a capacity rounded to the nearest power of two.
The actual capacity is $2^n$, ensuring that bitwise masking can be used for fast index calculation.
Sourcepub fn record(&self, data: u64)
pub fn record(&self, data: u64)
Records a value into the circular buffer using a bit-packed generation tag.
This operation is wait-free, ensuring that high-frequency writers are never
blocked by concurrent readers or other writers. It uniquely tags each sample
with a sequence_id (lap count) to prevent “ghost reads”—where stale data
from a previous rotation of the buffer is incorrectly included in a new snapshot.
§Bit-Packing Layout
The 64-bit atomic slot is partitioned to allow single-word updates:
- Bits 63–48 (16 bits):
sequence_id. Acts as a filter to validate data “freshness.” - Bits 47–0 (48 bits):
data. Supports measurements up to $2^{48} - 1$ (e.g., $\approx 281$ TB or 281 trillion nanoseconds).
§Performance & Safety
- Efficiency: Uses a bitwise mask (
head & mask) for indexing, avoiding expensive integer division. This requires the buffer size to be a power of two. - Memory Ordering: Uses
Relaxedfor the index increment to minimize cache-line contention, whileAcquireon thesequence_idensures the writer is synchronized with the current global lap.
§Examples
Since the value is masked by SAMPLER_VALUE_MASK, any bits higher than 47
provided in the value argument will be truncated to ensure the sequence_id
remains uncorrupted.
Sourcepub fn write_samples(&self, histogram: &mut Histogram<u64>)
pub fn write_samples(&self, histogram: &mut Histogram<u64>)
Aggregates samples from the buffer that match the generation active at the start of the call.
§Ghost Data Prevention
This method implements a generation-flip snapshot. By atomically incrementing
the sequence_id at the entry point, it captures the ID of the just-completed
generation. During iteration, it filters the buffer to prevent:
- Stale Data (Ghosts): Samples with a tag smaller than
sequency_idare ignored. - Future Data: Samples with a tag larger than
sequency_id(from writers that started after this read began) trigger an earlybreak.
§Concurrency & Performance
- Wait-Free: Writers are never blocked. They simply begin tagging new samples with the next generation ID while this reader processes the previous one.
- Early Exit: The
breakcondition optimizes for cases where writers rapidly overtake the reader, preventing unnecessary iteration over “future” slots. - Memory Ordering: Uses
Releaseon the increment to ensure subsequent writes in the next generation are ordered after this snapshot’s boundary.
§Panics
Does not panic, though it assumes the buffer is not so large that the reader
cannot complete a pass before the 16-bit sequence_id wraps around.