OpenPay (1 crate, ~2940 LOC, 40 tests)
op-statements Statement generation + reconciliation + fee accounting.
Render targets: PDF (template), CSV, JSON, 94-char
NACHA fixed-width. ISO 20022 camt.053 with full
BkToCstmrStmt envelope (GrpHdr/Stmt/Bal/Ntry). BAI2
with 01/02/03/16/49/98/99 records. MT940/MT942
SWIFT with :20:/:25:/:28C:/:60F:/:61:/:86:/:62F:
+ :90C:/:90D: counters. FeeBucket (interchange/scheme/
acquirer/fx/settlement-network/bnpl) + FeeSchedule
with bps + flat rules. Cadence (daily/weekly/monthly/
custom). Two-tier Reconciler (external_id → window
heuristic).
Smart Byte (1 crate, 3032 LOC, 39 tests)
smart-byte-activitypub ActivityPub W3C Rec (2018). AS2 vocab + Public
addressing; Actor (Person/Application/Group/
Organization/Service) with publicKey + shared-inbox;
Object (Note/Article/Image/Video/Tombstone) with
Mastodon extensions (sensitive, CW summary,
conversation, blurhash); 12 Activity types
(Create/Update/Delete/Follow/Accept/Reject/Announce/
Like/Undo/Move/Block/Flag) with side-effect rules;
OrderedCollection + Page; Webfinger (RFC 7033);
HTTP Signatures (cavage draft-12 + RFC 9421);
SHA-256 Digest; Ed25519 sign/verify.
EOC (1 crate, 2759 LOC, 20 tests)
eoc-observability OpenTelemetry-compatible tracing + metrics + logs.
W3C tracecontext + baggage. Span / SpanKind / Status.
Samplers: AlwaysOn/Off/RatioBased/ParentBased. Sync
BatchSpanProcessor + Exporter trait. Counter / Gauge /
Histogram. Prometheus text exposition (HELP/TYPE/
_bucket/_sum/_count/+Inf). StatsD / DogStatsD lines.
GenAI semconv (gen_ai.{operation,system,request.*,
response.*,usage.*}) + EOC extensions (eoc.joules.*,
eoc.co2e_grams, eoc.cost.usd). USD + gCO2e attribution
with PUE. LangSmith + LangFuse export adapters.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
199 lines
5.1 KiB
Rust
199 lines
5.1 KiB
Rust
//! Sampling strategies.
|
|
//!
|
|
//! Implements the OTel-defined samplers:
|
|
//!
|
|
//! - [`AlwaysOnSampler`] — always returns `RecordAndSample`.
|
|
//! - [`AlwaysOffSampler`] — always returns `Drop`.
|
|
//! - [`TraceIdRatioBased`] — deterministic ratio based on trace_id high bytes.
|
|
//! - [`ParentBased`] — defers to the remote-parent decision when present.
|
|
|
|
use crate::context::{SpanContext, TraceId};
|
|
|
|
/// Result of a sampling decision.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum SamplingDecision {
|
|
/// Drop the span entirely.
|
|
Drop,
|
|
/// Record the span but do not propagate the sampled flag.
|
|
RecordOnly,
|
|
/// Record and propagate the sampled flag.
|
|
RecordAndSample,
|
|
}
|
|
|
|
impl SamplingDecision {
|
|
/// True if the span should be recorded at all.
|
|
pub fn is_recorded(&self) -> bool {
|
|
matches!(self, SamplingDecision::RecordOnly | SamplingDecision::RecordAndSample)
|
|
}
|
|
|
|
/// True if the sampled flag should be propagated.
|
|
pub fn is_sampled(&self) -> bool {
|
|
matches!(self, SamplingDecision::RecordAndSample)
|
|
}
|
|
}
|
|
|
|
/// Sampler trait. Implementations are `Send + Sync`.
|
|
pub trait Sampler: Send + Sync + std::fmt::Debug {
|
|
/// Decide whether to sample a span with this trace_id and parent context.
|
|
fn should_sample(
|
|
&self,
|
|
parent: Option<&SpanContext>,
|
|
trace_id: &TraceId,
|
|
name: &str,
|
|
) -> SamplingDecision;
|
|
|
|
/// Human-readable description for diagnostics.
|
|
fn description(&self) -> String;
|
|
}
|
|
|
|
/// Always sample.
|
|
#[derive(Debug, Default)]
|
|
pub struct AlwaysOnSampler;
|
|
|
|
impl Sampler for AlwaysOnSampler {
|
|
fn should_sample(
|
|
&self,
|
|
_parent: Option<&SpanContext>,
|
|
_trace_id: &TraceId,
|
|
_name: &str,
|
|
) -> SamplingDecision {
|
|
SamplingDecision::RecordAndSample
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
"AlwaysOnSampler".to_string()
|
|
}
|
|
}
|
|
|
|
/// Never sample.
|
|
#[derive(Debug, Default)]
|
|
pub struct AlwaysOffSampler;
|
|
|
|
impl Sampler for AlwaysOffSampler {
|
|
fn should_sample(
|
|
&self,
|
|
_parent: Option<&SpanContext>,
|
|
_trace_id: &TraceId,
|
|
_name: &str,
|
|
) -> SamplingDecision {
|
|
SamplingDecision::Drop
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
"AlwaysOffSampler".to_string()
|
|
}
|
|
}
|
|
|
|
/// Deterministic ratio sampler: keeps `ratio` fraction of trace_ids.
|
|
///
|
|
/// Uses the high 64 bits of the trace_id, interpreted big-endian.
|
|
#[derive(Debug)]
|
|
pub struct TraceIdRatioBased {
|
|
ratio: f64,
|
|
threshold: u64,
|
|
}
|
|
|
|
impl TraceIdRatioBased {
|
|
/// Build a sampler that keeps `ratio` of traces.
|
|
///
|
|
/// `ratio` is clamped to `[0.0, 1.0]`.
|
|
pub fn new(ratio: f64) -> Self {
|
|
let clamped = ratio.clamp(0.0, 1.0);
|
|
// Map [0,1] into u64 range. 1.0 -> u64::MAX so every id passes.
|
|
let threshold = if clamped >= 1.0 {
|
|
u64::MAX
|
|
} else if clamped <= 0.0 {
|
|
0
|
|
} else {
|
|
(clamped * (u64::MAX as f64)) as u64
|
|
};
|
|
Self {
|
|
ratio: clamped,
|
|
threshold,
|
|
}
|
|
}
|
|
|
|
/// The clamped ratio in `[0.0, 1.0]`.
|
|
pub fn ratio(&self) -> f64 {
|
|
self.ratio
|
|
}
|
|
|
|
/// The u64 threshold value used for comparison.
|
|
pub fn threshold(&self) -> u64 {
|
|
self.threshold
|
|
}
|
|
}
|
|
|
|
impl Sampler for TraceIdRatioBased {
|
|
fn should_sample(
|
|
&self,
|
|
_parent: Option<&SpanContext>,
|
|
trace_id: &TraceId,
|
|
_name: &str,
|
|
) -> SamplingDecision {
|
|
if self.threshold == 0 {
|
|
return SamplingDecision::Drop;
|
|
}
|
|
if self.threshold == u64::MAX {
|
|
return SamplingDecision::RecordAndSample;
|
|
}
|
|
// Big-endian load of the high 8 bytes.
|
|
let hi = u64::from_be_bytes([
|
|
trace_id.0[0],
|
|
trace_id.0[1],
|
|
trace_id.0[2],
|
|
trace_id.0[3],
|
|
trace_id.0[4],
|
|
trace_id.0[5],
|
|
trace_id.0[6],
|
|
trace_id.0[7],
|
|
]);
|
|
if hi < self.threshold {
|
|
SamplingDecision::RecordAndSample
|
|
} else {
|
|
SamplingDecision::Drop
|
|
}
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
format!("TraceIdRatioBased({:.6})", self.ratio)
|
|
}
|
|
}
|
|
|
|
/// Parent-based sampler: respect the parent's sampled flag if present,
|
|
/// otherwise consult a `root` sampler.
|
|
#[derive(Debug)]
|
|
pub struct ParentBased {
|
|
root: Box<dyn Sampler>,
|
|
}
|
|
|
|
impl ParentBased {
|
|
/// Wrap a root sampler used for spans with no parent.
|
|
pub fn new(root: Box<dyn Sampler>) -> Self {
|
|
Self { root }
|
|
}
|
|
}
|
|
|
|
impl Sampler for ParentBased {
|
|
fn should_sample(
|
|
&self,
|
|
parent: Option<&SpanContext>,
|
|
trace_id: &TraceId,
|
|
name: &str,
|
|
) -> SamplingDecision {
|
|
match parent {
|
|
Some(p) if p.is_valid() => {
|
|
if p.flags.is_sampled() {
|
|
SamplingDecision::RecordAndSample
|
|
} else {
|
|
SamplingDecision::Drop
|
|
}
|
|
}
|
|
_ => self.root.should_sample(parent, trace_id, name),
|
|
}
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
format!("ParentBased({})", self.root.description())
|
|
}
|
|
}
|