11//! `ThreadLocalBuffer` is the entrypoint for almost all dial9 events
22//!
3- //! The TL buffer is created lazily the first time an event is sent. Events are buffered into a fixed-size Vec (currently 1024 items)
4- //! before being flushed to the central collector.
3+ //! The TL buffer is created lazily the first time an event is sent. Events are encoded directly
4+ //! into a thread-local `Encoder<Vec<u8>>` and flushed to the central collector when the encoded
5+ //! batch reaches the configured batch size (default 1 MB).
56use crate :: telemetry:: collector:: CentralCollector ;
67use crate :: telemetry:: events:: RawEvent ;
8+ use crate :: telemetry:: format:: * ;
9+ use dial9_trace_format:: InternedString ;
10+ use dial9_trace_format:: encoder:: Encoder ;
711use std:: cell:: RefCell ;
12+ use std:: collections:: HashMap ;
13+ use std:: panic:: Location ;
814use std:: sync:: Arc ;
915
10- const BUFFER_CAPACITY : usize = 1024 ;
16+ /// Default maximum encoded batch size before flushing (64KB).
17+ const DEFAULT_BATCH_SIZE : usize = 63 * 1024 ;
1118
1219pub ( crate ) struct ThreadLocalBuffer {
13- pub ( crate ) events : Vec < RawEvent > ,
20+ encoder : Encoder < Vec < u8 > > ,
21+ event_count : usize ,
22+ batch_size : usize ,
1423 collector : Option < Arc < CentralCollector > > ,
24+ location_cache : HashMap < & ' static Location < ' static > , String > ,
1525}
1626
1727impl Default for ThreadLocalBuffer {
@@ -22,9 +32,19 @@ impl Default for ThreadLocalBuffer {
2232
2333impl ThreadLocalBuffer {
2434 fn new ( ) -> Self {
35+ Self :: with_batch_size ( DEFAULT_BATCH_SIZE )
36+ }
37+
38+ fn with_batch_size ( batch_size : usize ) -> Self {
2539 Self {
26- events : Vec :: with_capacity ( BUFFER_CAPACITY ) ,
40+ // make the Vec 1KB bigger to reduce the risk of reallocating
41+ // TODO: harden this code, we should ensure we never have to re-allocate this buffer.
42+ encoder : Encoder :: new_to ( Vec :: with_capacity ( batch_size + 1024 ) )
43+ . expect ( "Vec::write_all cannot fail" ) ,
44+ event_count : 0 ,
45+ batch_size,
2746 collector : None ,
47+ location_cache : HashMap :: new ( ) ,
2848 }
2949 }
3050
@@ -36,28 +56,143 @@ impl ThreadLocalBuffer {
3656 }
3757 }
3858
59+ // todo: this is now really "encode tokio event"
60+ fn encode_event ( & mut self , event : & RawEvent ) {
61+ match event {
62+ RawEvent :: PollStart {
63+ timestamp_nanos,
64+ worker_id,
65+ worker_local_queue_depth,
66+ task_id,
67+ location,
68+ } => {
69+ let spawn_loc = self . intern_location ( location) ;
70+ self . encoder . write_infallible ( & PollStartEvent {
71+ timestamp_ns : * timestamp_nanos,
72+ worker_id : * worker_id,
73+ local_queue : * worker_local_queue_depth as u8 ,
74+ task_id : * task_id,
75+ spawn_loc,
76+ } ) ;
77+ }
78+ RawEvent :: PollEnd {
79+ timestamp_nanos,
80+ worker_id,
81+ } => self . encoder . write_infallible ( & PollEndEvent {
82+ timestamp_ns : * timestamp_nanos,
83+ worker_id : * worker_id,
84+ } ) ,
85+ RawEvent :: WorkerPark {
86+ timestamp_nanos,
87+ worker_id,
88+ worker_local_queue_depth,
89+ cpu_time_nanos,
90+ } => self . encoder . write_infallible ( & WorkerParkEvent {
91+ timestamp_ns : * timestamp_nanos,
92+ worker_id : * worker_id,
93+ local_queue : * worker_local_queue_depth as u8 ,
94+ cpu_time_ns : * cpu_time_nanos,
95+ } ) ,
96+ RawEvent :: WorkerUnpark {
97+ timestamp_nanos,
98+ worker_id,
99+ worker_local_queue_depth,
100+ cpu_time_nanos,
101+ sched_wait_delta_nanos,
102+ } => self . encoder . write_infallible ( & WorkerUnparkEvent {
103+ timestamp_ns : * timestamp_nanos,
104+ worker_id : * worker_id,
105+ local_queue : * worker_local_queue_depth as u8 ,
106+ cpu_time_ns : * cpu_time_nanos,
107+ sched_wait_ns : * sched_wait_delta_nanos,
108+ } ) ,
109+ RawEvent :: QueueSample {
110+ timestamp_nanos,
111+ global_queue_depth,
112+ } => self . encoder . write_infallible ( & QueueSampleEvent {
113+ timestamp_ns : * timestamp_nanos,
114+ global_queue : * global_queue_depth as u8 ,
115+ } ) ,
116+ RawEvent :: TaskSpawn {
117+ timestamp_nanos,
118+ task_id,
119+ location,
120+ } => {
121+ let spawn_loc = self . intern_location ( location) ;
122+ self . encoder . write_infallible ( & TaskSpawnEvent {
123+ timestamp_ns : * timestamp_nanos,
124+ task_id : * task_id,
125+ spawn_loc,
126+ } ) ;
127+ }
128+ RawEvent :: TaskTerminate {
129+ timestamp_nanos,
130+ task_id,
131+ } => self . encoder . write_infallible ( & TaskTerminateEvent {
132+ timestamp_ns : * timestamp_nanos,
133+ task_id : * task_id,
134+ } ) ,
135+ RawEvent :: WakeEvent {
136+ timestamp_nanos,
137+ waker_task_id,
138+ woken_task_id,
139+ target_worker,
140+ } => self . encoder . write_infallible ( & WakeEventEvent {
141+ timestamp_ns : * timestamp_nanos,
142+ waker_task_id : * waker_task_id,
143+ woken_task_id : * woken_task_id,
144+ target_worker : * target_worker,
145+ } ) ,
146+ RawEvent :: CpuSample ( data) => {
147+ let thread_name = match & data. thread_name {
148+ Some ( name) => self . encoder . intern_string_infallible ( name. as_str ( ) ) ,
149+ None => self . encoder . intern_string_infallible ( "<no thread name>" ) ,
150+ } ;
151+ self . encoder . write_infallible ( & CpuSampleEvent {
152+ timestamp_ns : data. timestamp_nanos ,
153+ worker_id : data. worker_id ,
154+ tid : data. tid ,
155+ source : data. source ,
156+ thread_name,
157+ callchain : dial9_trace_format:: StackFrames ( data. callchain . clone ( ) ) ,
158+ } ) ;
159+ }
160+ }
161+ }
162+
163+ fn intern_location ( & mut self , location : & ' static Location < ' static > ) -> InternedString {
164+ let s = self
165+ . location_cache
166+ . entry ( location)
167+ . or_insert_with ( || location. to_string ( ) ) ;
168+ self . encoder . intern_string_infallible ( s)
169+ }
170+
39171 fn record_event ( & mut self , event : RawEvent ) {
40- self . events . push ( event) ;
172+ self . encode_event ( & event) ;
173+ self . event_count += 1 ;
41174 }
42175
43176 fn should_flush ( & self ) -> bool {
44- self . events . len ( ) >= BUFFER_CAPACITY
177+ self . encoder . bytes_written ( ) as usize >= self . batch_size
45178 }
46179
47- fn flush ( & mut self ) -> Vec < RawEvent > {
48- std:: mem:: replace ( & mut self . events , Vec :: with_capacity ( BUFFER_CAPACITY ) )
180+ fn flush ( & mut self ) -> crate :: telemetry:: collector:: Batch {
181+ let encoded_bytes = self . encoder . reset_to ( Vec :: with_capacity ( self . batch_size ) ) ;
182+ self . event_count = 0 ;
183+ crate :: telemetry:: collector:: Batch { encoded_bytes }
49184 }
50185}
51186
52187impl Drop for ThreadLocalBuffer {
53188 fn drop ( & mut self ) {
54- if ! self . events . is_empty ( ) {
189+ if self . event_count > 0 {
55190 if let Some ( collector) = self . collector . take ( ) {
56- collector. accept_flush ( std :: mem :: take ( & mut self . events ) ) ;
191+ collector. accept_flush ( self . flush ( ) ) ;
57192 } else {
58193 tracing:: warn!(
59194 "dial9-tokio-telemetry: dropping {} unflushed events (no collector registered on this thread)" ,
60- self . events . len ( )
195+ self . event_count
61196 ) ;
62197 }
63198 }
@@ -88,9 +223,8 @@ pub(crate) fn record_event(event: RawEvent, collector: &Arc<CentralCollector>) {
88223pub ( crate ) fn drain_to_collector ( collector : & CentralCollector ) {
89224 BUFFER . with ( |buf| {
90225 let mut buf = buf. borrow_mut ( ) ;
91- let events = buf. flush ( ) ;
92- if !events. is_empty ( ) {
93- collector. accept_flush ( events) ;
226+ if buf. event_count > 0 {
227+ collector. accept_flush ( buf. flush ( ) ) ;
94228 }
95229 } ) ;
96230}
@@ -108,34 +242,42 @@ mod tests {
108242 #[ test]
109243 fn test_buffer_creation ( ) {
110244 let buffer = ThreadLocalBuffer :: new ( ) ;
111- assert_eq ! ( buffer. events . len ( ) , 0 ) ;
112- assert_eq ! ( buffer. events . capacity ( ) , BUFFER_CAPACITY ) ;
245+ assert_eq ! ( buffer. event_count , 0 ) ;
246+ assert_eq ! ( buffer. batch_size , DEFAULT_BATCH_SIZE ) ;
113247 }
114248
115249 #[ test]
116250 fn test_record_event ( ) {
117251 let mut buffer = ThreadLocalBuffer :: new ( ) ;
118252 buffer. record_event ( poll_end_event ( ) ) ;
119- assert_eq ! ( buffer. events. len( ) , 1 ) ;
253+ assert_eq ! ( buffer. event_count, 1 ) ;
254+ assert ! ( buffer. encoder. bytes_written( ) > 0 ) ;
120255 }
121256
122257 #[ test]
123- fn test_should_flush ( ) {
124- let mut buffer = ThreadLocalBuffer :: new ( ) ;
258+ fn test_should_flush_respects_batch_size ( ) {
259+ // Use a tiny batch size so a single event triggers flush.
260+ let mut buffer = ThreadLocalBuffer :: with_batch_size ( 1 ) ;
125261 assert ! ( !buffer. should_flush( ) ) ;
126- for _ in 0 ..BUFFER_CAPACITY {
127- buffer. record_event ( poll_end_event ( ) ) ;
128- }
262+ buffer. record_event ( poll_end_event ( ) ) ;
129263 assert ! ( buffer. should_flush( ) ) ;
130264 }
131265
266+ #[ test]
267+ fn test_should_flush_default_batch_size ( ) {
268+ let mut buffer = ThreadLocalBuffer :: new ( ) ;
269+ assert ! ( !buffer. should_flush( ) ) ;
270+ buffer. record_event ( poll_end_event ( ) ) ;
271+ // A single small event should not exceed 1 MB.
272+ assert ! ( !buffer. should_flush( ) ) ;
273+ }
274+
132275 #[ test]
133276 fn test_flush ( ) {
134277 let mut buffer = ThreadLocalBuffer :: new ( ) ;
135278 buffer. record_event ( poll_end_event ( ) ) ;
136- let flushed = buffer. flush ( ) ;
137- assert_eq ! ( flushed. len( ) , 1 ) ;
138- assert_eq ! ( buffer. events. len( ) , 0 ) ;
139- assert_eq ! ( buffer. events. capacity( ) , BUFFER_CAPACITY ) ;
279+ let batch = buffer. flush ( ) ;
280+ assert ! ( !batch. encoded_bytes. is_empty( ) ) ;
281+ assert_eq ! ( buffer. event_count, 0 ) ;
140282 }
141283}
0 commit comments