1use std::collections::{HashMap, VecDeque};
16use std::sync::Arc;
17
18use crate::{
19 event::{attributes_size_hint, now_unix_nanos},
20 scope::ScopeState,
21 Attribute, AttributeValue,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub(crate) enum SpanKind {
27 Internal,
29 Client,
31}
32
33#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum SpanOutcome {
39 Ok,
41 Error,
43 Cancelled,
45}
46
47impl SpanOutcome {
48 pub(crate) fn as_str(self) -> &'static str {
49 match self {
50 SpanOutcome::Ok => "ok",
51 SpanOutcome::Error => "error",
52 SpanOutcome::Cancelled => "cancelled",
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq)]
61pub(crate) struct SpanEvent {
62 pub name: String,
63 pub time_ns: u64,
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub(crate) struct SpanRecord {
69 pub span_id: u64,
70 pub parent_span_id: Option<u64>,
71 pub name: String,
72 pub kind: SpanKind,
73 pub start_ns: u64,
74 pub end_ns: u64,
75 pub outcome: SpanOutcome,
76 pub error_type: Option<String>,
77 pub attributes: Vec<Attribute>,
78 pub dropped_attributes: u32,
80 pub events: Vec<SpanEvent>,
81 pub dropped_events: u32,
83 pub session: Arc<ScopeState>,
85 pub route: Option<String>,
87}
88
89const SPAN_OVERHEAD_BYTES: usize = 94;
92const SPAN_EVENT_OVERHEAD_BYTES: usize = 13;
94
95const MAX_EVENTS_PER_SPAN: usize = 128;
97const MAX_ATTRIBUTES_PER_SPAN: usize = 128;
99const MAX_OPEN_SPANS: usize = 256;
101
102pub(crate) struct Spans {
108 open: HashMap<u64, SpanRecord>,
109 open_order: VecDeque<u64>,
111 finished: VecDeque<SpanRecord>,
112 finished_capacity: usize,
113 sessions: HashMap<u64, Arc<ScopeState>>,
117 session_order: VecDeque<u64>,
120 next_id: u64,
121 dropped: u64,
122 warned: Warned,
123 revoked: Arc<std::sync::atomic::AtomicBool>,
125}
126
127#[derive(Default)]
130struct Warned {
131 full: bool,
133 attributes: bool,
134 events: bool,
135}
136
137fn warn_once(warned: &mut bool, warning: std::fmt::Arguments<'_>) {
139 if !*warned {
140 *warned = true;
141 log::warn!("{warning}");
142 }
143}
144
145const REMEMBERED_SPANS: usize = 1024;
149
150impl Spans {
151 pub fn new(finished_capacity: usize) -> Self {
154 Self {
155 open: HashMap::new(),
156 open_order: VecDeque::new(),
157 finished: VecDeque::new(),
158 finished_capacity: finished_capacity.max(1),
159 sessions: HashMap::new(),
160 session_order: VecDeque::new(),
161 next_id: (rand::random::<u64>() >> 1) | 1,
165 dropped: 0,
166 warned: Warned::default(),
167 revoked: Arc::default(),
168 }
169 }
170
171 pub fn with_consent(mut self, revoked: Arc<std::sync::atomic::AtomicBool>) -> Self {
173 self.revoked = revoked;
174 self
175 }
176
177 fn revoked(&self) -> bool {
178 self.revoked.load(std::sync::atomic::Ordering::SeqCst)
179 }
180
181 pub fn begin_in(
184 &mut self,
185 name: &str,
186 kind: SpanKind,
187 parent: Option<u64>,
188 session: Arc<ScopeState>,
189 ) -> u64 {
190 if self.revoked() {
191 return 0;
192 }
193 let id = self.next_id;
194 self.next_id = ((self.next_id + 1) & (u64::MAX >> 1)).max(1);
195 if self.open.len() >= MAX_OPEN_SPANS {
196 if let Some(oldest) = self.open_order.pop_front() {
197 self.open.remove(&oldest);
198 self.retire(oldest);
199 self.count_drop("open", MAX_OPEN_SPANS);
200 }
201 }
202 self.sessions.insert(id, session.clone());
203 let record = SpanRecord {
204 span_id: id,
205 parent_span_id: parent.filter(|p| *p != 0),
206 name: name.to_owned(),
207 kind,
208 start_ns: now_unix_nanos(),
209 end_ns: 0,
210 outcome: SpanOutcome::Ok,
211 error_type: None,
212 attributes: Vec::new(),
213 dropped_attributes: 0,
214 events: Vec::new(),
215 dropped_events: 0,
216 session,
217 route: None,
218 };
219 self.open.insert(id, record);
220 self.open_order.push_back(id);
221 id
222 }
223
224 pub fn scope_of(&self, id: u64) -> Option<Arc<ScopeState>> {
227 self.sessions.get(&id).cloned()
228 }
229
230 #[cfg(test)]
231 pub fn begin(&mut self, name: &str, kind: SpanKind, parent: Option<u64>) -> u64 {
232 self.begin_in(name, kind, parent, ScopeState::new())
233 }
234
235 #[cfg(test)]
236 pub fn open_count(&self) -> usize {
237 self.open.len()
238 }
239
240 pub fn any_open(&self, names: &[&str]) -> bool {
243 self.open.values().any(|span| names.contains(&span.name.as_str()))
244 }
245
246 pub fn add_event(&mut self, id: u64, name: &str) {
251 if self.revoked() {
252 return;
253 }
254 let Some(span) = self.open.get_mut(&id) else { return };
255 if span.events.len() >= MAX_EVENTS_PER_SPAN {
256 span.dropped_events = span.dropped_events.saturating_add(1);
257 warn_once(
258 &mut self.warned.events,
259 format_args!(
260 "{} over {MAX_EVENTS_PER_SPAN} checkpoints: dropping the rest",
261 span.name
262 ),
263 );
264 return;
265 }
266 span.events.push(SpanEvent { name: name.to_owned(), time_ns: now_unix_nanos() });
267 }
268
269 pub fn end(
278 &mut self,
279 id: u64,
280 outcome: SpanOutcome,
281 error_type: Option<String>,
282 mut attributes: Vec<Attribute>,
283 ) {
284 if self.revoked() {
285 return;
286 }
287 let Some(mut span) = self.open.remove(&id) else { return };
288 self.open_order.retain(|open| *open != id);
289 span.end_ns = now_unix_nanos().max(span.start_ns);
290 span.outcome = outcome;
291 span.error_type = error_type;
292 span.session.snapshot_custom(&mut attributes);
293 span.route = span.session.route();
294 let excess = attributes.len().saturating_sub(MAX_ATTRIBUTES_PER_SPAN);
295 if excess > 0 {
296 attributes.truncate(MAX_ATTRIBUTES_PER_SPAN);
297 span.dropped_attributes = u32::try_from(excess).unwrap_or(u32::MAX);
298 warn_once(
299 &mut self.warned.attributes,
300 format_args!(
301 "{} over {MAX_ATTRIBUTES_PER_SPAN} attributes: dropping {excess}",
302 span.name
303 ),
304 );
305 }
306 span.session.decorate(&mut attributes);
307 span.attributes = attributes;
308 self.retire(id);
309 if self.finished.len() >= self.finished_capacity {
310 self.finished.pop_front();
311 self.count_drop("finished", self.finished_capacity);
312 }
313 self.finished.push_back(span);
314 }
315
316 fn retire(&mut self, id: u64) {
319 self.session_order.push_back(id);
320 if self.session_order.len() > REMEMBERED_SPANS {
321 if let Some(old) = self.session_order.pop_front() {
322 self.sessions.remove(&old);
323 }
324 }
325 }
326
327 fn count_drop(&mut self, buffer: &str, capacity: usize) {
329 self.dropped += 1;
330 warn_once(
331 &mut self.warned.full,
332 format_args!("{buffer} spans full ({capacity}): dropping the oldest"),
333 );
334 }
335
336 pub fn drain(&mut self, max: usize, max_bytes: usize) -> Vec<SpanRecord> {
340 self.warned = Warned::default();
341 let (mut n, mut bytes) = (0, 0);
342 for span in self.finished.iter().take(max) {
343 let size = span.size_hint();
344 if n > 0 && bytes + size > max_bytes {
345 break;
346 }
347 bytes += size;
348 n += 1;
349 }
350 self.finished.drain(..n).collect()
351 }
352
353 pub fn clear(&mut self) -> u64 {
355 let n = self.open.len() + self.finished.len();
356 self.open.clear();
357 self.open_order.clear();
358 self.finished.clear();
359 self.sessions.clear();
360 self.session_order.clear();
361 self.warned = Warned::default();
362 n as u64
363 }
364
365 pub fn take_dropped(&mut self) -> u64 {
366 std::mem::take(&mut self.dropped)
367 }
368}
369
370impl SpanRecord {
371 pub(crate) fn size_hint(&self) -> usize {
373 SPAN_OVERHEAD_BYTES
374 + self.name.len()
375 + self.error_type.as_ref().map_or(0, String::len)
376 + attributes_size_hint(&self.attributes)
377 + self.events.iter().map(|e| SPAN_EVENT_OVERHEAD_BYTES + e.name.len()).sum::<usize>()
378 }
379
380 pub(crate) fn outcome_attributes(&self) -> Vec<Attribute> {
383 let mut attributes = vec![Attribute::new("lk.outcome", self.outcome.as_str())];
384 if let (SpanOutcome::Error, Some(error_type)) = (self.outcome, &self.error_type) {
385 attributes.push(Attribute::new("error.type", AttributeValue::Str(error_type.clone())));
386 }
387 attributes
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn spans_open_record_events_and_finish_in_order() {
397 let mut spans = Spans::new(8);
398 let parent = spans.begin("lk.connect", SpanKind::Client, None);
399 let child = spans.begin("lk.publish", SpanKind::Internal, Some(parent));
400 spans.add_event(parent, "ws_open");
401 spans.end(child, SpanOutcome::Cancelled, None, vec![]);
402 spans.end(
403 parent,
404 SpanOutcome::Error,
405 Some("timeout".into()),
406 vec![Attribute::new("lk.connect.attempt", 1i64)],
407 );
408 spans.end(parent, SpanOutcome::Ok, None, vec![]); let finished = spans.drain(10, usize::MAX);
411 assert_eq!(finished.len(), 2);
412 assert_eq!(finished[0].name, "lk.publish");
413 assert_eq!(finished[0].parent_span_id, Some(parent));
414 assert_eq!(finished[0].outcome, SpanOutcome::Cancelled);
415 assert_eq!(finished[1].events[0].name, "ws_open");
416 assert_eq!(finished[1].error_type.as_deref(), Some("timeout"));
417 assert!(finished[1].end_ns >= finished[1].start_ns);
418 assert_eq!(spans.take_dropped(), 0);
419 }
420
421 #[test]
422 fn open_spans_keep_their_session_however_many_spans_end_after_them() {
423 let mut spans = Spans::new(REMEMBERED_SPANS + 1);
424 let room = ScopeState::new();
425 let connect = spans.begin_in("lk.connect", SpanKind::Client, None, room.clone());
426 let first = spans.begin("lk.publish", SpanKind::Internal, None);
427 spans.end(first, SpanOutcome::Ok, None, vec![]);
428 for _ in 0..REMEMBERED_SPANS {
429 let id = spans.begin("lk.publish", SpanKind::Internal, None);
430 spans.end(id, SpanOutcome::Ok, None, vec![]);
431 }
432 assert!(spans.scope_of(first).is_none(), "ended spans age out");
433 assert_eq!(spans.scope_of(connect), Some(room.clone()));
434 spans.end(connect, SpanOutcome::Error, Some("timeout".into()), vec![]);
435 assert_eq!(spans.scope_of(connect), Some(room), "and stays resolvable once it ended");
436 assert_eq!(spans.sessions.len(), REMEMBERED_SPANS);
437 }
438
439 #[test]
440 fn abandoned_spans_are_counted_warned_once_and_age_out() {
441 crate::test_log::capture();
442 let mut spans = Spans::new(8);
443 let ids: Vec<_> = (0..MAX_OPEN_SPANS + REMEMBERED_SPANS + 1)
444 .map(|_| spans.begin("lk.publish", SpanKind::Internal, None))
445 .collect();
446 assert_eq!(spans.open_count(), MAX_OPEN_SPANS);
447 assert_eq!(spans.take_dropped(), REMEMBERED_SPANS as u64 + 1);
448 let warning = "open spans full (256): dropping the oldest";
449 assert_eq!(crate::test_log::take(), [warning], "the first drop logged, the rest counted");
450 assert!(spans.scope_of(ids[0]).is_none());
451 assert_eq!(spans.sessions.len(), MAX_OPEN_SPANS + REMEMBERED_SPANS);
452 spans.drain(10, usize::MAX);
453 spans.begin("lk.publish", SpanKind::Internal, None);
454 assert_eq!(crate::test_log::take(), [warning], "a drain starts a new episode");
455 }
456
457 #[test]
458 fn zero_finished_capacity_keeps_one_span_instead_of_panicking() {
459 let mut spans = Spans::new(0);
460 for _ in 0..2 {
461 let id = spans.begin("lk.publish", SpanKind::Internal, None);
462 spans.end(id, SpanOutcome::Ok, None, vec![]);
463 }
464 assert_eq!(spans.drain(10, usize::MAX).len(), 1);
465 assert_eq!(spans.take_dropped(), 1);
466 }
467
468 #[test]
469 fn clear_starts_a_new_warning_episode() {
470 let mut spans = Spans::new(1);
471 for _ in 0..2 {
472 let id = spans.begin("lk.publish", SpanKind::Internal, None);
473 spans.end(id, SpanOutcome::Ok, None, vec![]);
474 }
475 assert!(spans.warned.full);
476 assert_eq!(spans.clear(), 1);
477 assert!(!spans.warned.full);
478 }
479
480 fn encoded(spans: &mut Spans) -> Vec<crate::proto::opentelemetry::proto::trace::v1::Span> {
482 use crate::proto::opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest;
483 use prost::Message;
484
485 let bytes = crate::otlp::encode_spans(&[], &[], spans.drain(10, usize::MAX));
486 let mut decoded = ExportTraceServiceRequest::decode(&bytes[..]).expect("valid OTLP");
487 std::mem::take(&mut decoded.resource_spans[0].scope_spans[0].spans)
488 }
489
490 #[test]
491 fn attributes_over_the_cap_are_counted_warned_once_and_spare_the_sessions() {
492 crate::test_log::capture();
493 let mut spans = Spans::new(8);
494 let session = ScopeState::new();
495 session.set_attribute("lk.room.sid", Some("RM_1".into()));
496 assert!(session.set_custom("app.call_id", Some("c1".into())));
497 let overflow = |spans: &mut Spans| {
498 let id = spans.begin_in("lk.publish", SpanKind::Internal, None, session.clone());
499 let own = (0..MAX_ATTRIBUTES_PER_SPAN)
500 .map(|i| Attribute::new(format!("lk.publish.k{i}"), i as i64))
501 .collect();
502 spans.end(id, SpanOutcome::Ok, None, own);
503 };
504 overflow(&mut spans);
505 overflow(&mut spans);
506 let warning = "lk.publish over 128 attributes: dropping 1";
507 assert_eq!(crate::test_log::take(), [warning], "two overflows, one warning");
508 for span in encoded(&mut spans) {
509 assert_eq!(span.dropped_attributes_count, 1, "the app's call id");
510 let keys: Vec<_> = span.attributes.iter().map(|kv| kv.key.as_str()).collect();
511 assert!(!keys.contains(&"app.call_id"), "the app's own go first");
512 for key in ["lk.publish.k127", "lk.room.sid", "session.id", "lk.outcome"] {
513 assert!(keys.contains(&key), "{key} survives the cap");
514 }
515 }
516 overflow(&mut spans);
517 assert_eq!(crate::test_log::take(), [warning], "a drain starts a new episode");
518 assert_eq!(spans.take_dropped(), 0, "no span was lost");
519 }
520
521 #[test]
522 fn an_attribute_overflow_does_not_silence_a_full_buffer() {
523 crate::test_log::capture();
524 let mut spans = Spans::new(1);
525 let first = spans.begin("lk.publish", SpanKind::Internal, None);
526 spans.end(first, SpanOutcome::Ok, None, vec![]);
527 let id = spans.begin("lk.connect", SpanKind::Client, None);
528 let own = (0..=MAX_ATTRIBUTES_PER_SPAN).map(|i| Attribute::new(format!("k{i}"), 1i64));
529 spans.end(id, SpanOutcome::Ok, None, own.collect());
530 assert_eq!(
531 crate::test_log::take(),
532 [
533 "lk.connect over 128 attributes: dropping 1",
534 "finished spans full (1): dropping the oldest"
535 ],
536 "one `end`, two limits, two warnings"
537 );
538 }
539
540 #[test]
541 fn checkpoints_over_the_cap_are_counted_and_warned_once() {
542 crate::test_log::capture();
543 let mut spans = Spans::new(8);
544 let overflow = |spans: &mut Spans| {
545 let id = spans.begin("lk.connect", SpanKind::Client, None);
546 for i in 0..=MAX_EVENTS_PER_SPAN {
547 spans.add_event(id, &format!("step {i}"));
548 }
549 spans.end(id, SpanOutcome::Ok, None, vec![]);
550 };
551 overflow(&mut spans);
552 overflow(&mut spans);
553 let warning = "lk.connect over 128 checkpoints: dropping the rest";
554 assert_eq!(crate::test_log::take(), [warning], "two overflows, one warning");
555 for span in encoded(&mut spans) {
556 assert_eq!(span.dropped_events_count, 1);
557 assert_eq!(span.events.len(), MAX_EVENTS_PER_SPAN);
558 assert_eq!(span.events.last().map(|e| e.name.as_str()), Some("step 127"));
559 }
560 overflow(&mut spans);
561 assert_eq!(crate::test_log::take(), [warning], "a drain starts a new episode");
562 }
563
564 #[test]
565 fn finished_spans_are_bounded() {
566 let mut spans = Spans::new(1);
567 for _ in 0..2 {
568 let id = spans.begin("lk.publish", SpanKind::Internal, None);
569 spans.end(id, SpanOutcome::Ok, None, vec![]);
570 }
571 assert_eq!(spans.drain(10, usize::MAX).len(), 1);
572 assert_eq!(spans.take_dropped(), 1);
573 }
574}