use crate::runtime::resource_monitor::{
OverloadBrownoutLedger, OverloadBrownoutPhase, OverloadBrownoutReason,
};
use crossbeam_queue::ArrayQueue;
use parking_lot::Mutex;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
const DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH: usize = 255;
fn truncate_attribute_value(value: &str) -> String {
if value.len() <= DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH {
value.to_string()
} else {
let mut end = DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH;
while end > 0 && !value.is_char_boundary(end) {
end -= 1;
}
let mut result = String::with_capacity(end + 3); result.push_str(&value[..end]);
result.push('…'); result
}
}
#[derive(Debug, Clone)]
pub enum ExportError {
Transport(String),
InvalidData(String),
RateLimited,
Unavailable,
}
impl std::fmt::Display for ExportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Transport(msg) => write!(f, "transport error: {}", msg),
Self::InvalidData(msg) => write!(f, "invalid data: {}", msg),
Self::RateLimited => write!(f, "rate limited"),
Self::Unavailable => write!(f, "service unavailable"),
}
}
}
impl std::error::Error for ExportError {}
#[derive(Debug, Clone)]
pub struct LoadSheddingStats {
pub queue_depth: usize,
pub queue_capacity: usize,
pub dropped_batches: u64,
pub brownout_dropped_spans: u64,
pub retained_summary_spans: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OtlpBrownoutAction {
ExportAll,
DropLowPriority,
RetainSummaryOnly,
}
#[derive(Debug, Clone)]
struct OtlpBrownoutPolicyState {
action: OtlpBrownoutAction,
shared_phase: OverloadBrownoutPhase,
fallback_used: bool,
shared_reason_codes: Vec<OverloadBrownoutReason>,
}
impl Default for OtlpBrownoutPolicyState {
fn default() -> Self {
Self {
action: OtlpBrownoutAction::ExportAll,
shared_phase: OverloadBrownoutPhase::Normal,
fallback_used: false,
shared_reason_codes: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OtlpBrownoutPolicySnapshot {
pub action: OtlpBrownoutAction,
pub shared_phase: OverloadBrownoutPhase,
pub fallback_used: bool,
pub shared_reason_codes: Vec<OverloadBrownoutReason>,
pub brownout_dropped_spans: u64,
pub retained_summary_spans: u64,
pub queue_dropped_spans: u64,
}
pub const OTLP_TAIL_SAMPLING_SCOPE_CONTRACT_VERSION: &str = "otlp-tail-sampling-scope-contract-v1";
pub const OTLP_TAIL_SAMPLING_SCOPE_BEAD_ID: &str = "asupersync-7s6";
pub const OTLP_TAIL_SAMPLING_E2E_BEAD_ID: &str = "asupersync-uw9zg9";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OtlpTailSamplingSupportClass {
ExplicitlyUnsupported,
}
impl OtlpTailSamplingSupportClass {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ExplicitlyUnsupported => "explicitly_unsupported",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OtlpTailSamplingScope {
pub contract_version: &'static str,
pub bead_id: &'static str,
pub feeds_bead_id: &'static str,
pub support_class: OtlpTailSamplingSupportClass,
pub evidence_quality: &'static str,
pub verdict: &'static str,
pub production_supported: bool,
pub missing_surfaces: &'static [&'static str],
pub desired_semantics: &'static [&'static str],
}
impl OtlpTailSamplingScope {
#[must_use]
pub const fn support_class_str(&self) -> &'static str {
self.support_class.as_str()
}
}
#[must_use]
pub const fn otlp_tail_based_sampling_scope() -> OtlpTailSamplingScope {
OtlpTailSamplingScope {
contract_version: OTLP_TAIL_SAMPLING_SCOPE_CONTRACT_VERSION,
bead_id: OTLP_TAIL_SAMPLING_SCOPE_BEAD_ID,
feeds_bead_id: OTLP_TAIL_SAMPLING_E2E_BEAD_ID,
support_class: OtlpTailSamplingSupportClass::ExplicitlyUnsupported,
evidence_quality: "unsupported",
verdict: "unsupported",
production_supported: false,
missing_surfaces: &[
"trace-completion detector",
"bounded span buffer for deferred decisions",
"late sampling policy API",
"flush/shutdown behavior for undecided traces",
],
desired_semantics: &[
"policy match after trace completion",
"consistent decision across every span in a trace",
"bounded memory and trace-expiry behavior",
"no trace leaks on cancellation, flush, or shutdown",
],
}
}
#[derive(Debug)]
pub struct BoundedExportQueue<T> {
queue: ArrayQueue<T>,
capacity: usize,
dropped_count: AtomicU64,
}
impl<T> BoundedExportQueue<T> {
pub fn new(capacity: usize) -> Self {
Self {
queue: ArrayQueue::new(capacity),
capacity,
dropped_count: AtomicU64::new(0),
}
}
pub fn enqueue(&self, item: T) -> Option<T> {
let dropped = self.queue.force_push(item);
if dropped.is_some() {
self.dropped_count.fetch_add(1, Ordering::Relaxed);
}
dropped
}
pub fn dequeue(&self) -> Option<T> {
self.queue.pop()
}
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn dropped_count(&self) -> u64 {
self.dropped_count.load(Ordering::Relaxed)
}
}
#[derive(Debug, Clone)]
pub struct SpanBatch {
pub batch_id: u64,
pub spans: Vec<OtlpSpan>,
pub created_at: std::time::Instant,
}
#[derive(Debug, Clone)]
pub struct OtlpSpan {
pub span_id: String,
pub name: String,
pub start_time_unix_nano: u64,
pub end_time_unix_nano: u64,
pub attributes: Vec<(String, String)>,
pub trace_flags: Option<u8>,
}
impl OtlpSpan {
pub fn is_sampled(&self) -> bool {
match self.trace_flags {
Some(flags) => (flags & 0x01) != 0, None => true, }
}
pub fn new_with_flags(
span_id: String,
name: String,
start_time_unix_nano: u64,
end_time_unix_nano: u64,
attributes: Vec<(String, String)>,
trace_flags: u8,
) -> Self {
let truncated_attributes = attributes
.into_iter()
.map(|(key, value)| (key, truncate_attribute_value(&value)))
.collect();
Self {
span_id,
name,
start_time_unix_nano,
end_time_unix_nano,
attributes: truncated_attributes,
trace_flags: Some(trace_flags),
}
}
pub fn new(
span_id: String,
name: String,
start_time_unix_nano: u64,
end_time_unix_nano: u64,
attributes: Vec<(String, String)>,
) -> Self {
let truncated_attributes = attributes
.into_iter()
.map(|(key, value)| (key, truncate_attribute_value(&value)))
.collect();
Self {
span_id,
name,
start_time_unix_nano,
end_time_unix_nano,
attributes: truncated_attributes,
trace_flags: None, }
}
}
pub trait TraceExporter: Send + Sync + std::fmt::Debug {
fn export(&self, batch: &SpanBatch) -> Result<(), ExportError>;
fn flush(&self) -> Result<(), ExportError>;
}
#[derive(Debug)]
pub struct LoadSheddingTraceExporter {
inner: Box<dyn TraceExporter>,
export_queue: BoundedExportQueue<SpanBatch>,
batch_timeout: Duration,
dropped_spans_metric: Arc<AtomicU64>,
brownout_state: Mutex<OtlpBrownoutPolicyState>,
brownout_dropped_spans_metric: Arc<AtomicU64>,
retained_summary_spans_metric: Arc<AtomicU64>,
}
impl LoadSheddingTraceExporter {
#[must_use]
pub fn new(
inner: Box<dyn TraceExporter>,
batch_capacity: usize,
batch_timeout: Duration,
) -> Self {
Self {
inner,
export_queue: BoundedExportQueue::new(batch_capacity),
batch_timeout,
dropped_spans_metric: Arc::new(AtomicU64::new(0)),
brownout_state: Mutex::new(OtlpBrownoutPolicyState::default()),
brownout_dropped_spans_metric: Arc::new(AtomicU64::new(0)),
retained_summary_spans_metric: Arc::new(AtomicU64::new(0)),
}
}
#[must_use]
pub fn load_shedding_stats(&self) -> LoadSheddingStats {
LoadSheddingStats {
queue_depth: self.export_queue.len(),
queue_capacity: self.export_queue.capacity(),
dropped_batches: self.export_queue.dropped_count(),
brownout_dropped_spans: self.brownout_dropped_spans_count(),
retained_summary_spans: self.retained_summary_spans_count(),
}
}
#[must_use]
pub fn dropped_spans_count(&self) -> u64 {
self.dropped_spans_metric.load(Ordering::Relaxed)
}
#[must_use]
pub fn brownout_dropped_spans_count(&self) -> u64 {
self.brownout_dropped_spans_metric.load(Ordering::Relaxed)
}
#[must_use]
pub fn retained_summary_spans_count(&self) -> u64 {
self.retained_summary_spans_metric.load(Ordering::Relaxed)
}
pub fn update_brownout_policy(
&self,
brownout: Option<&OverloadBrownoutLedger>,
) -> OtlpBrownoutPolicySnapshot {
let mut state = self.brownout_state.lock();
match brownout {
Some(ledger) => {
state.action = Self::action_for_phase(ledger.phase);
state.shared_phase = ledger.phase;
state.fallback_used = ledger.fallback_used;
state.shared_reason_codes.clone_from(&ledger.reason_codes);
}
None => {
state.action = OtlpBrownoutAction::ExportAll;
state.shared_phase = OverloadBrownoutPhase::Normal;
state.fallback_used = true;
state.shared_reason_codes.clear();
}
}
drop(state);
self.brownout_policy_snapshot()
}
#[must_use]
pub fn brownout_policy_snapshot(&self) -> OtlpBrownoutPolicySnapshot {
let state = self.brownout_state.lock().clone();
OtlpBrownoutPolicySnapshot {
action: state.action,
shared_phase: state.shared_phase,
fallback_used: state.fallback_used,
shared_reason_codes: state.shared_reason_codes,
brownout_dropped_spans: self.brownout_dropped_spans_count(),
retained_summary_spans: self.retained_summary_spans_count(),
queue_dropped_spans: self.dropped_spans_count(),
}
}
fn action_for_phase(phase: OverloadBrownoutPhase) -> OtlpBrownoutAction {
match phase {
OverloadBrownoutPhase::Normal | OverloadBrownoutPhase::Observe => {
OtlpBrownoutAction::ExportAll
}
OverloadBrownoutPhase::Degrade | OverloadBrownoutPhase::Recovery => {
OtlpBrownoutAction::DropLowPriority
}
OverloadBrownoutPhase::ShedOptional => OtlpBrownoutAction::RetainSummaryOnly,
}
}
fn is_low_priority_span(span: &OtlpSpan) -> bool {
span.attributes.iter().any(|(key, value)| {
(key == "otlp.priority" || key == "observability.priority") && value == "low"
})
}
pub fn process_queue(&self) -> Result<usize, ExportError> {
let mut processed = 0;
let mut _total_spans_processed = 0;
while let Some(batch) = self.export_queue.dequeue() {
let batch_age = batch.created_at.elapsed();
if batch_age > Duration::from_secs(30) {
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::warn!(
target: "asupersync::observability::otlp_trace",
"Exporting stale span batch: age={}s, spans={}",
batch_age.as_secs(),
batch.spans.len()
);
}
self.inner.export(&batch)?;
processed += 1;
_total_spans_processed += batch.spans.len();
if batch.created_at.elapsed() > self.batch_timeout {
break;
}
}
#[cfg(feature = "tracing-integration")]
if processed > 0 {
crate::tracing_compat::trace!(
target: "asupersync::observability::otlp_trace",
"Processed {} span batches ({} spans)",
processed,
_total_spans_processed
);
}
Ok(processed)
}
}
impl TraceExporter for LoadSheddingTraceExporter {
fn export(&self, batch: &SpanBatch) -> Result<(), ExportError> {
let sampled_spans: Vec<OtlpSpan> = batch
.spans
.iter()
.filter(|span| span.is_sampled())
.cloned()
.collect();
let unsampled_count = batch.spans.len() - sampled_spans.len();
if unsampled_count > 0 {
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::debug!(
target: "asupersync::observability::otlp_trace",
"Head-based sampling: dropped {} unsampled spans (trace_flags=0), \
exporting {} sampled spans",
unsampled_count,
sampled_spans.len()
);
}
if sampled_spans.is_empty() {
return Ok(());
}
let brownout_state = self.brownout_state.lock().clone();
let mut sampled_spans = sampled_spans;
match brownout_state.action {
OtlpBrownoutAction::ExportAll => {}
OtlpBrownoutAction::DropLowPriority => {
let before = sampled_spans.len();
sampled_spans.retain(|span| !Self::is_low_priority_span(span));
let dropped = (before - sampled_spans.len()) as u64;
if dropped > 0 {
self.brownout_dropped_spans_metric
.fetch_add(dropped, Ordering::Relaxed);
}
if sampled_spans.is_empty() {
return Ok(());
}
}
OtlpBrownoutAction::RetainSummaryOnly => {
self.retained_summary_spans_metric
.fetch_add(sampled_spans.len() as u64, Ordering::Relaxed);
return Ok(());
}
}
let filtered_batch = SpanBatch {
batch_id: batch.batch_id,
spans: sampled_spans,
created_at: batch.created_at,
};
if let Some(dropped_batch) = self.export_queue.enqueue(filtered_batch) {
let dropped_spans = dropped_batch.spans.len() as u64;
self.dropped_spans_metric
.fetch_add(dropped_spans, Ordering::Relaxed);
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::warn!(
target: "asupersync::observability::otlp_trace",
"OTLP trace export queue full: dropped oldest span batch ({} spans). \
Queue capacity: {}, total dropped spans: {}",
dropped_spans,
self.export_queue.capacity(),
self.dropped_spans_count()
);
}
Ok(())
}
fn flush(&self) -> Result<(), ExportError> {
self.process_queue()?;
self.inner.flush()
}
}
impl Drop for LoadSheddingTraceExporter {
fn drop(&mut self) {
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
let queue_depth = self.export_queue.len();
if queue_depth == 0 {
return; }
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::info!(
target: "asupersync::observability::otlp_trace",
"OTLP exporter graceful shutdown: flushing {} pending batches (timeout: {:?})",
queue_depth,
SHUTDOWN_TIMEOUT
);
let flush_start = std::time::Instant::now();
let flush_result = loop {
if flush_start.elapsed() >= SHUTDOWN_TIMEOUT {
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::warn!(
target: "asupersync::observability::otlp_trace",
"OTLP exporter shutdown timeout ({:?}): abandoning {} pending batches to prevent deadlock",
SHUTDOWN_TIMEOUT,
self.export_queue.len()
);
break Err(ExportError::Transport("shutdown timeout".to_string()));
}
if let Some(batch) = self.export_queue.dequeue() {
match self.inner.export(&batch) {
Ok(()) => {
}
Err(_e) => {
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::warn!(
target: "asupersync::observability::otlp_trace",
"OTLP exporter shutdown: export failed for batch, continuing with remaining: {}",
_e
);
}
}
} else {
match self.inner.flush() {
Ok(()) => {
break Ok(());
}
Err(e) => {
break Err(e);
}
}
}
};
let _flush_duration = flush_start.elapsed();
let final_queue_depth = self.export_queue.len();
let _batches_flushed = queue_depth.saturating_sub(final_queue_depth);
match flush_result {
Ok(()) => {
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::info!(
target: "asupersync::observability::otlp_trace",
"OTLP exporter graceful shutdown completed: {} batches flushed in {:?}",
_batches_flushed,
_flush_duration
);
}
Err(_e) => {
#[cfg(feature = "tracing-integration")]
crate::tracing_compat::warn!(
target: "asupersync::observability::otlp_trace",
"OTLP exporter shutdown flush failed: {} (flushed {} of {} batches in {:?})",
_e,
_batches_flushed,
queue_depth,
_flush_duration
);
}
}
}
}
#[derive(Clone)]
pub struct InMemoryOtlpHttpExporter {
exported_batches: Arc<Mutex<Vec<SpanBatch>>>,
export_delay: Duration,
}
impl InMemoryOtlpHttpExporter {
#[must_use]
pub fn new(export_delay: Duration) -> Self {
Self {
exported_batches: Arc::new(Mutex::new(Vec::new())),
export_delay,
}
}
#[must_use]
pub fn exported_batches(&self) -> Vec<SpanBatch> {
self.exported_batches.lock().clone()
}
#[must_use]
pub fn exported_span_count(&self) -> usize {
self.exported_batches
.lock()
.iter()
.map(|batch| batch.spans.len())
.sum()
}
}
impl TraceExporter for InMemoryOtlpHttpExporter {
fn export(&self, batch: &SpanBatch) -> Result<(), ExportError> {
std::thread::sleep(self.export_delay);
self.exported_batches.lock().push(batch.clone());
Ok(())
}
fn flush(&self) -> Result<(), ExportError> {
Ok(())
}
}
impl std::fmt::Debug for InMemoryOtlpHttpExporter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InMemoryOtlpHttpExporter")
.field("export_delay", &self.export_delay)
.field(
"exported_batches_count",
&self.exported_batches.lock().len(),
)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
fn create_test_span(span_id: &str, name: &str) -> OtlpSpan {
OtlpSpan {
span_id: span_id.to_string(),
name: name.to_string(),
start_time_unix_nano: 1000000000,
end_time_unix_nano: 1000001000,
attributes: vec![("service".to_string(), "test".to_string())],
trace_flags: Some(0x01), }
}
fn create_test_batch(batch_id: u64, span_count: usize) -> SpanBatch {
let spans = (0..span_count)
.map(|i| create_test_span(&format!("span-{}-{}", batch_id, i), "test_operation"))
.collect();
SpanBatch {
batch_id,
spans,
created_at: Instant::now(),
}
}
#[test]
fn audit_otlp_trace_exporter_high_load_shedding() {
let memory_exporter = InMemoryOtlpHttpExporter::new(Duration::from_millis(1));
let queue_capacity = 3;
let batch_timeout = Duration::from_secs(1);
let exporter = LoadSheddingTraceExporter::new(
Box::new(memory_exporter.clone()),
queue_capacity,
batch_timeout,
);
let batch_size = 512;
let batches: Vec<SpanBatch> = (0..6).map(|i| create_test_batch(i, batch_size)).collect();
for batch in &batches {
let result = exporter.export(batch);
assert!(
result.is_ok(),
"export should succeed even during load shedding"
);
}
let stats = exporter.load_shedding_stats();
assert_eq!(stats.queue_capacity, 3, "queue capacity should be 3");
assert_eq!(stats.queue_depth, 3, "queue should be at capacity");
assert_eq!(
stats.dropped_batches, 3,
"should have dropped 3 oldest batches"
);
let expected_dropped_spans = 3 * batch_size as u64; assert_eq!(
exporter.dropped_spans_count(),
expected_dropped_spans,
"otel.exporter.dropped_spans must track total dropped spans"
);
let processed = exporter
.process_queue()
.expect("queue processing should succeed");
assert_eq!(processed, 3, "should process 3 remaining batches");
let exported = memory_exporter.exported_batches();
assert_eq!(exported.len(), 3, "should have exported 3 batches");
let exported_batch_ids: Vec<u64> = exported.iter().map(|b| b.batch_id).collect();
assert_eq!(
exported_batch_ids,
vec![3, 4, 5],
"should preserve NEWEST batches and drop oldest (0,1,2)"
);
println!("✅ OTLP TRACE EXPORTER LOAD SHEDDING AUDIT PASSED");
println!(" Queue capacity: {}", stats.queue_capacity);
println!(" Dropped batches: {}", stats.dropped_batches);
println!(" Dropped spans: {}", exporter.dropped_spans_count());
println!(" Preserved batches: {:?}", exported_batch_ids);
}
#[test]
fn audit_normal_operation_no_shedding() {
let memory_exporter = InMemoryOtlpHttpExporter::new(Duration::from_millis(1));
let exporter = LoadSheddingTraceExporter::new(
Box::new(memory_exporter.clone()),
10, Duration::from_secs(1),
);
for i in 0..5 {
let batch = create_test_batch(i, 100);
exporter.export(&batch).expect("export should succeed");
}
let stats = exporter.load_shedding_stats();
assert_eq!(stats.dropped_batches, 0, "no batches should be dropped");
assert_eq!(
exporter.dropped_spans_count(),
0,
"no spans should be dropped"
);
exporter
.process_queue()
.expect("queue processing should succeed");
let exported_spans = memory_exporter.exported_span_count();
assert_eq!(exported_spans, 500, "all 500 spans should be exported");
println!("✅ NORMAL OPERATION AUDIT PASSED - No load shedding");
}
#[test]
fn audit_fifo_order_preserved_during_shedding() {
let memory_exporter = InMemoryOtlpHttpExporter::new(Duration::from_millis(1));
let exporter = LoadSheddingTraceExporter::new(
Box::new(memory_exporter.clone()),
2, Duration::from_secs(1),
);
for i in 0..4 {
let batch = create_test_batch(i, 10);
exporter.export(&batch).expect("export should succeed");
}
exporter
.process_queue()
.expect("queue processing should succeed");
let exported = memory_exporter.exported_batches();
assert_eq!(exported.len(), 2, "should export 2 batches");
assert_eq!(exported[0].batch_id, 2, "first exported should be batch 2");
assert_eq!(exported[1].batch_id, 3, "second exported should be batch 3");
println!("✅ FIFO ORDER AUDIT PASSED - Correct processing order maintained");
}
#[test]
fn enqueue_does_not_panic_under_concurrent_overload() {
use std::sync::Arc;
use std::thread;
const CAPACITY: usize = 8;
const PRODUCERS: usize = 16;
const PER_PRODUCER: usize = 4096;
let queue: Arc<BoundedExportQueue<u64>> = Arc::new(BoundedExportQueue::new(CAPACITY));
let mut handles = Vec::with_capacity(PRODUCERS);
for producer in 0..PRODUCERS {
let queue = Arc::clone(&queue);
handles.push(thread::spawn(move || {
let base = (producer as u64) * (PER_PRODUCER as u64);
for offset in 0..PER_PRODUCER {
let _ = queue.enqueue(base + offset as u64);
}
}));
}
for handle in handles {
handle.join().expect("producer must not panic");
}
let dropped = queue.dropped_count();
let len = queue.len();
let total = (PRODUCERS * PER_PRODUCER) as u64;
assert_eq!(
dropped + len as u64,
total,
"every push must either land in the queue or be counted as dropped",
);
assert!(len <= CAPACITY, "queue must respect capacity");
assert_eq!(
dropped,
total - len as u64,
"dropped count must equal total minus retained",
);
}
}