1use crate::collector::CentralCollector;
10use crate::primitives::sync::atomic::{AtomicU64, Ordering};
11use crate::primitives::sync::{Arc, Mutex, Weak};
12use dial9_trace_format::encoder::{Encoder, FxHashMap};
13use dial9_trace_format::{InternedStackFrames, InternedString};
14use std::panic::Location;
15use std::time::Duration;
16
17pub struct ThreadLocalEncoder<'a> {
27 encoder: &'a mut Encoder<Vec<u8>>,
28 location_cache: &'a mut FxHashMap<&'static Location<'static>, String>,
29 events_written: &'a mut usize,
33}
34
35impl std::fmt::Debug for ThreadLocalEncoder<'_> {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("ThreadLocalEncoder").finish_non_exhaustive()
38 }
39}
40
41impl ThreadLocalEncoder<'_> {
42 pub fn intern_string(&mut self, s: &str) -> InternedString {
48 self.encoder.intern_string_infallible(s)
49 }
50
51 pub fn intern_stack_frames(&mut self, frames: &[u64]) -> InternedStackFrames {
57 self.encoder.intern_stack_frames_infallible(frames)
58 }
59
60 pub fn encode(&mut self, event: &impl dial9_trace_format::TraceEvent) {
62 self.encoder.write_infallible(event);
63 *self.events_written += 1;
64 }
65
66 #[doc(hidden)]
83 #[must_use = "a validation failure means the event was dropped"]
87 pub fn write_event(
88 &mut self,
89 schema: &dial9_trace_format::encoder::Schema,
90 timestamp_ns: u64,
91 values: &[dial9_trace_format::types::FieldValue],
92 ) -> std::io::Result<()> {
93 self.encoder.write_event(schema, timestamp_ns, values)?;
94 *self.events_written += 1;
95 Ok(())
96 }
97
98 #[doc(hidden)]
100 pub fn intern_location(&mut self, location: &'static Location<'static>) -> InternedString {
101 let s = self
102 .location_cache
103 .entry(location)
104 .or_insert_with(|| location.to_string());
105 self.encoder.intern_string_infallible(s)
106 }
107}
108
109pub trait Encodable {
155 fn encode(&self, encoder: &mut ThreadLocalEncoder<'_>);
162}
163
164impl<T: dial9_trace_format::TraceEvent> Encodable for T {
165 fn encode(&self, encoder: &mut ThreadLocalEncoder<'_>) {
166 encoder.encode(self);
167 }
168}
169
170#[derive(Clone)]
176pub(crate) struct FlushEpoch(Arc<AtomicU64>);
177
178impl FlushEpoch {
179 pub(crate) fn new() -> Self {
180 Self(Arc::new(AtomicU64::new(0)))
181 }
182
183 pub(crate) fn store(&self, epoch: u64) {
184 self.0.store(epoch, Ordering::Relaxed);
185 }
186
187 pub(crate) fn load(&self) -> u64 {
188 self.0.load(Ordering::Relaxed)
189 }
190}
191
192const DEFAULT_BATCH_SIZE: usize = 1023 * 1024;
194
195pub(crate) struct ThreadLocalBuffer {
196 encoder: Encoder<Vec<u8>>,
197 event_count: usize,
198 batch_size: usize,
199 collector: Option<Arc<CentralCollector>>,
200 location_cache: FxHashMap<&'static Location<'static>, String>,
204 pub(crate) flush_epoch: FlushEpoch,
207}
208
209impl Default for ThreadLocalBuffer {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl ThreadLocalBuffer {
216 fn new() -> Self {
217 Self::with_batch_size(DEFAULT_BATCH_SIZE)
218 }
219
220 fn with_batch_size(batch_size: usize) -> Self {
221 Self {
222 encoder: Encoder::new_to(Vec::with_capacity(batch_size + 1024))
224 .expect("Vec::write_all cannot fail"),
225 event_count: 0,
226 batch_size,
227 collector: None,
228 location_cache: FxHashMap::default(),
229 flush_epoch: FlushEpoch::new(),
230 }
231 }
232
233 fn set_collector(&mut self, collector: &Arc<CentralCollector>) -> bool {
237 if self.collector.is_none() {
238 self.collector = Some(Arc::clone(collector));
239 return true;
240 }
241 false
242 }
243
244 fn thread_local_encoder(&mut self) -> ThreadLocalEncoder<'_> {
245 ThreadLocalEncoder {
246 encoder: &mut self.encoder,
247 location_cache: &mut self.location_cache,
248 events_written: &mut self.event_count,
249 }
250 }
251
252 #[cfg_attr(not(feature = "test-util"), allow(dead_code))]
254 fn record_encodable(&mut self, event: &dyn Encodable) {
255 event.encode(&mut self.thread_local_encoder());
256 }
257
258 fn should_flush(&self) -> bool {
259 self.encoder.bytes_written() as usize >= self.batch_size
260 }
261
262 pub(crate) fn flush(&mut self) -> crate::collector::Batch {
263 let event_count = self.event_count as u64;
264 let encoded_bytes = self
265 .encoder
266 .reset_to_infallible(Vec::with_capacity(self.batch_size));
267 self.event_count = 0;
268 crate::collector::Batch::new(encoded_bytes, event_count)
269 }
270
271 pub(crate) fn has_pending_events(&self) -> bool {
272 self.event_count > 0
273 }
274}
275
276crate::test_util_pub! {
277fn encode_single(event: &dyn Encodable) -> Vec<u8> {
279 let mut buf = ThreadLocalBuffer::with_batch_size(1024);
280 buf.record_encodable(event);
281 buf.flush().into_encoded_bytes()
282}
283}
284
285impl Drop for ThreadLocalBuffer {
286 fn drop(&mut self) {
287 if self.event_count > 0 {
288 if let Some(collector) = self.collector.take() {
289 collector.accept_flush(self.flush());
290 } else {
291 crate::rate_limit::rate_limited!(Duration::from_secs(60), {
292 tracing::warn!(
293 "dial9-tokio-telemetry: dropping {} unflushed events (no collector registered on this thread)",
294 self.event_count
295 );
296 });
297 }
298 }
299 }
300}
301
302pub(crate) struct TlBufferHandle {
305 pub(crate) buffer: Weak<Mutex<ThreadLocalBuffer>>,
306 pub(crate) flush_epoch: FlushEpoch,
307}
308
309crate::primitives::thread_local! {
310 static BUFFER: Arc<Mutex<ThreadLocalBuffer>> = Arc::new(Mutex::new(ThreadLocalBuffer::new()));
311}
312
313pub(crate) fn drain_to_collector(collector: &CentralCollector) {
316 BUFFER.with(|buf| {
317 let mut buf = match buf.lock() {
318 Ok(guard) => guard,
319 Err(_) => {
320 crate::rate_limit::rate_limited!(Duration::from_secs(60), {
321 tracing::error!("dial9: thread-local buffer mutex poisoned in drain_to_collector; skipping drain");
322 });
323 return;
324 }
325 };
326 if buf.event_count > 0 {
327 collector.accept_flush(buf.flush());
328 }
329 });
330}
331
332pub(crate) fn record_encodable_event(
333 event: &dyn Encodable,
334 collector: &Arc<CentralCollector>,
335 drain_epoch: &AtomicU64,
336) -> Option<TlBufferHandle> {
337 with_encoder(|enc| event.encode(enc), collector, drain_epoch)
338}
339
340pub(crate) fn with_encoder(
341 f: impl FnOnce(&mut ThreadLocalEncoder<'_>),
342 collector: &Arc<CentralCollector>,
343 drain_epoch: &AtomicU64,
344) -> Option<TlBufferHandle> {
345 BUFFER.with(|arc| {
346 let mut buf = match arc.lock() {
347 Ok(guard) => guard,
348 Err(_) => {
349 crate::rate_limit::rate_limited!(Duration::from_secs(60), {
350 tracing::error!("dial9: thread-local buffer mutex poisoned in with_encoder; dropping events for this thread");
351 });
352 return None;
353 }
354 };
355 let first_call = buf.set_collector(collector);
356 f(&mut buf.thread_local_encoder());
357 let current_epoch = drain_epoch.load(Ordering::Relaxed);
358 if buf.should_flush() || buf.flush_epoch.load() < current_epoch {
359 collector.accept_flush(buf.flush());
360 buf.flush_epoch.store(current_epoch);
361 }
362 if first_call {
363 Some(TlBufferHandle {
364 buffer: Arc::downgrade(arc),
365 flush_epoch: buf.flush_epoch.clone(),
366 })
367 } else {
368 None
369 }
370 })
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 fn sample_event() -> crate::format::ClockSyncEvent {
378 crate::format::ClockSyncEvent {
379 timestamp_ns: 1000,
380 realtime_ns: 2000,
381 }
382 }
383
384 #[derive(dial9_trace_format::TraceEvent)]
385 struct BorrowedEvent<'a> {
386 #[traceevent(timestamp)]
387 timestamp_ns: u64,
388 value: &'a str,
389 }
390
391 #[test]
392 fn test_buffer_creation() {
393 let buffer = ThreadLocalBuffer::new();
394 assert_eq!(buffer.event_count, 0);
395 assert_eq!(buffer.batch_size, DEFAULT_BATCH_SIZE);
396 }
397
398 #[test]
399 fn test_record_event() {
400 let mut buffer = ThreadLocalBuffer::new();
401 buffer.record_encodable(&sample_event());
402 assert_eq!(buffer.event_count, 1);
403 assert!(buffer.encoder.bytes_written() > 0);
404 }
405
406 #[test]
407 fn borrowed_trace_event_is_encodable() {
408 let value = String::from("borrowed");
409 let event = BorrowedEvent {
410 timestamp_ns: 1000,
411 value: &value,
412 };
413 let mut buffer = ThreadLocalBuffer::new();
414 buffer.record_encodable(&event);
415 assert_eq!(buffer.event_count, 1);
416 assert!(buffer.encoder.bytes_written() > 0);
417 }
418
419 #[test]
420 fn test_should_flush_respects_batch_size() {
421 let mut buffer = ThreadLocalBuffer::with_batch_size(1);
423 assert!(!buffer.should_flush());
424 buffer.record_encodable(&sample_event());
425 assert!(buffer.should_flush());
426 }
427
428 #[test]
429 fn test_should_flush_default_batch_size() {
430 let mut buffer = ThreadLocalBuffer::new();
431 assert!(!buffer.should_flush());
432 buffer.record_encodable(&sample_event());
433 assert!(!buffer.should_flush());
435 }
436
437 #[test]
438 fn test_flush() {
439 let mut buffer = ThreadLocalBuffer::new();
440 buffer.record_encodable(&sample_event());
441 let batch = buffer.flush();
442 assert!(!batch.encoded_bytes().is_empty());
443 assert_eq!(buffer.event_count, 0);
444 }
445
446 #[test]
447 fn test_flush_epoch_store_load() {
448 let epoch = FlushEpoch::new();
449 assert_eq!(epoch.load(), 0);
450 epoch.store(42);
451 assert_eq!(epoch.load(), 42);
452 }
453
454 #[test]
455 fn test_flush_epoch_shared_across_threads() {
456 let epoch = FlushEpoch::new();
457 let epoch_clone = epoch.clone();
458 let handle = std::thread::spawn(move || {
459 epoch_clone.store(7);
460 });
461 handle.join().unwrap();
462 assert_eq!(epoch.load(), 7);
463 }
464
465 #[test]
466 fn test_flush_epoch_stamped_on_self_flush() {
467 let collector = Arc::new(CentralCollector::new());
468 let drain_epoch = AtomicU64::new(5);
469 let mut buffer = ThreadLocalBuffer::with_batch_size(1);
473 buffer.set_collector(&collector);
474 buffer.record_encodable(&sample_event());
475 assert!(buffer.should_flush());
476 buffer
477 .flush_epoch
478 .store(drain_epoch.load(Ordering::Relaxed));
479 collector.accept_flush(buffer.flush());
480 assert_eq!(buffer.flush_epoch.load(), 5);
481 }
482
483 #[test]
484 fn test_mutex_accessible_from_another_thread() {
485 let buf = Arc::new(Mutex::new(ThreadLocalBuffer::new()));
486 let buf_clone = Arc::clone(&buf);
487 let handle = std::thread::spawn(move || {
489 let mut guard = buf_clone.lock().unwrap();
490 guard.record_encodable(&sample_event());
491 assert_eq!(guard.event_count, 1);
492 });
493 handle.join().unwrap();
494 let guard = buf.lock().unwrap();
496 assert_eq!(guard.event_count, 1);
497 }
498}