1
1
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2
2
// SPDX-License-Identifier: Apache-2.0
3
3
4
+ use hashbrown:: { hash_map, HashMap as BHashMap } ;
4
5
use std:: {
5
- collections:: { hash_map, HashMap } ,
6
+ collections:: HashMap ,
7
+ fmt:: Debug ,
6
8
str:: FromStr ,
7
9
sync:: { Arc , RwLock } ,
8
10
} ;
@@ -56,7 +58,7 @@ const EMPTY_PROPAGATION_DATA: TracePropagationData = TracePropagationData {
56
58
57
59
#[ derive( Debug ) ]
58
60
struct InnerTraceRegistry {
59
- registry : HashMap < [ u8 ; 16 ] , Trace > ,
61
+ registry : BHashMap < [ u8 ; 16 ] , Trace > ,
60
62
}
61
63
62
64
pub enum RegisterTracePropagationResult {
@@ -211,6 +213,12 @@ impl InnerTraceRegistry {
211
213
}
212
214
}
213
215
216
+ const TRACE_REGISTRY_SHARDS : usize = 64 ;
217
+
218
+ #[ repr( align( 128 ) ) ]
219
+ #[ derive( Debug , Clone ) ]
220
+ struct CachePadded < T > ( T ) ;
221
+
214
222
#[ derive( Clone , Debug ) ]
215
223
/// A registry of traces that are currently running
216
224
///
@@ -220,23 +228,32 @@ impl InnerTraceRegistry {
220
228
/// - The number of open spans in the trace
221
229
/// - The sampling decision of the trace
222
230
pub ( crate ) struct TraceRegistry {
223
- // TODO: The lock should probably sharded based on the hash of the trace id
224
- // so we reduce contention...
225
231
// Example:
226
232
// inner: Arc<[CacheAligned<RwLock<InnerTraceRegistry>>; N]>;
227
233
// to access a trace we do inner[hash(trace_id) % N].read()
228
- inner : Arc < RwLock < InnerTraceRegistry > > ,
234
+ inner : Arc < [ CachePadded < RwLock < InnerTraceRegistry > > ; TRACE_REGISTRY_SHARDS ] > ,
235
+ hasher : foldhash:: fast:: RandomState ,
229
236
}
230
237
231
238
impl TraceRegistry {
232
239
pub fn new ( ) -> Self {
233
240
Self {
234
- inner : Arc :: new ( RwLock :: new ( InnerTraceRegistry {
235
- registry : HashMap :: new ( ) ,
241
+ inner : Arc :: new ( std:: array:: from_fn ( |_| {
242
+ CachePadded ( RwLock :: new ( InnerTraceRegistry {
243
+ registry : BHashMap :: new ( ) ,
244
+ } ) )
236
245
} ) ) ,
246
+ hasher : foldhash:: fast:: RandomState :: default ( ) ,
237
247
}
238
248
}
239
249
250
+ fn get_shard ( & self , trace_id : [ u8 ; 16 ] ) -> & RwLock < InnerTraceRegistry > {
251
+ use std:: hash:: BuildHasher ;
252
+ let hash = self . hasher . hash_one ( u128:: from_ne_bytes ( trace_id) ) ;
253
+ let shard = hash as usize % TRACE_REGISTRY_SHARDS ;
254
+ & self . inner [ shard] . 0
255
+ }
256
+
240
257
/// Register the trace propagation data for a given trace ID
241
258
/// This increases the open span count for the trace by 1, but does not set the root span ID.
242
259
/// You will then need to call `register_local_root_span` to set the root span ID
@@ -249,7 +266,7 @@ impl TraceRegistry {
249
266
propagation_data : TracePropagationData ,
250
267
) -> RegisterTracePropagationResult {
251
268
let mut inner = self
252
- . inner
269
+ . get_shard ( trace_id )
253
270
. write ( )
254
271
. expect ( "Failed to acquire lock on trace registry" ) ;
255
272
inner. register_local_root_trace_propagation_data ( trace_id, propagation_data)
@@ -260,7 +277,7 @@ impl TraceRegistry {
260
277
/// If the trace is already registered, it will ignore the new root span ID and log a warning.
261
278
pub fn register_local_root_span ( & self , trace_id : [ u8 ; 16 ] , root_span_id : [ u8 ; 8 ] ) {
262
279
let mut inner = self
263
- . inner
280
+ . get_shard ( trace_id )
264
281
. write ( )
265
282
. expect ( "Failed to acquire lock on trace registry" ) ;
266
283
inner. register_local_root_span ( trace_id, root_span_id) ;
@@ -274,7 +291,7 @@ impl TraceRegistry {
274
291
propagation_data : TracePropagationData ,
275
292
) {
276
293
let mut inner = self
277
- . inner
294
+ . get_shard ( trace_id )
278
295
. write ( )
279
296
. expect ( "Failed to acquire lock on trace registry" ) ;
280
297
inner. register_span ( trace_id, span_id, propagation_data) ;
@@ -285,15 +302,15 @@ impl TraceRegistry {
285
302
/// flush
286
303
fn finish_span ( & self , trace_id : [ u8 ; 16 ] , span_data : SpanData ) -> Option < Trace > {
287
304
let mut inner = self
288
- . inner
305
+ . get_shard ( trace_id )
289
306
. write ( )
290
307
. expect ( "Failed to acquire lock on trace registry" ) ;
291
308
inner. finish_span ( trace_id, span_data)
292
309
}
293
310
294
311
pub fn get_trace_propagation_data ( & self , trace_id : [ u8 ; 16 ] ) -> TracePropagationData {
295
312
let inner = self
296
- . inner
313
+ . get_shard ( trace_id )
297
314
. read ( )
298
315
. expect ( "Failed to acquire lock on trace registry" ) ;
299
316
@@ -532,13 +549,22 @@ impl opentelemetry_sdk::trace::SpanProcessor for DatadogSpanProcessor {
532
549
533
550
#[ cfg( test) ]
534
551
mod tests {
535
- use std:: sync:: { Arc , RwLock } ;
536
-
537
- use dd_trace:: Config ;
552
+ use std:: {
553
+ collections:: HashMap ,
554
+ hint:: black_box,
555
+ sync:: { Arc , RwLock } ,
556
+ thread,
557
+ time:: Duration ,
558
+ } ;
559
+
560
+ use dd_trace:: {
561
+ sampling:: { mechanism, priority, SamplingDecision } ,
562
+ Config ,
563
+ } ;
538
564
use opentelemetry:: { Key , KeyValue , Value } ;
539
565
use opentelemetry_sdk:: { trace:: SpanProcessor , Resource } ;
540
566
541
- use crate :: span_processor:: { DatadogSpanProcessor , TraceRegistry } ;
567
+ use crate :: span_processor:: { DatadogSpanProcessor , TracePropagationData , TraceRegistry } ;
542
568
543
569
#[ test]
544
570
fn test_set_resource_from_empty_dd_config ( ) {
@@ -683,4 +709,103 @@ mod tests {
683
709
Some ( Value :: String ( "otel-service" . into( ) ) )
684
710
) ;
685
711
}
712
+
713
+ fn bench_trace_registry ( c : & mut criterion:: Criterion ) {
714
+ const ITERATIONS : u32 = 10000 ;
715
+ const NUM_TRACES : usize = ITERATIONS as usize / 20 ;
716
+ let mut group = c. benchmark_group ( "trace_registry_concurrent_access_threads" ) ;
717
+ group
718
+ . warm_up_time ( Duration :: from_millis ( 100 ) )
719
+ . measurement_time ( Duration :: from_millis ( 1000 ) ) ;
720
+
721
+ for concurrency in [ 1 , 2 , 4 , 8 , 16 , 32 ] {
722
+ group
723
+ . throughput ( criterion:: Throughput :: Elements (
724
+ ITERATIONS as u64 * concurrency,
725
+ ) )
726
+ . bench_function (
727
+ criterion:: BenchmarkId :: from_parameter ( concurrency) ,
728
+ move |g| {
729
+ let trace_ids: Vec < _ > = ( 0 ..concurrency)
730
+ . map ( |thread| {
731
+ std:: array:: from_fn :: < _ , NUM_TRACES , _ > ( |i| {
732
+ ( ( thread << 16 | i as u64 ) as u128 ) . to_be_bytes ( )
733
+ } )
734
+ } )
735
+ . collect ( ) ;
736
+ g. iter_batched_ref (
737
+ {
738
+ let trace_ids = trace_ids. clone ( ) ;
739
+ move || {
740
+ let tr: TraceRegistry = TraceRegistry :: new ( ) ;
741
+ for trace_id in trace_ids. iter ( ) . flatten ( ) {
742
+ tr. register_local_root_trace_propagation_data (
743
+ * trace_id,
744
+ TracePropagationData {
745
+ sampling_decision : SamplingDecision {
746
+ priority : Some ( priority:: AUTO_KEEP ) ,
747
+ mechanism : Some ( mechanism:: DEFAULT ) ,
748
+ } ,
749
+ origin : Some ( "rum" . to_string ( ) ) ,
750
+ tags : Some ( HashMap :: from_iter ( [ (
751
+ "dd.p.tid" . to_string ( ) ,
752
+ "foobar" . to_string ( ) ,
753
+ ) ] ) ) ,
754
+ } ,
755
+ ) ;
756
+ }
757
+ tr
758
+ }
759
+ } ,
760
+ move |tr| {
761
+ let tr = & * tr;
762
+ let trace_ids = & trace_ids;
763
+ thread:: scope ( move |s| {
764
+ for trace_id in trace_ids {
765
+ s. spawn ( move || {
766
+ for _ in 0 ..( ITERATIONS as usize / NUM_TRACES ) {
767
+ for trace_id in trace_id {
768
+ black_box ( tr. get_trace_propagation_data (
769
+ black_box ( * trace_id) ,
770
+ ) ) ;
771
+ }
772
+ }
773
+ } ) ;
774
+ }
775
+ } )
776
+ } ,
777
+ criterion:: BatchSize :: LargeInput ,
778
+ ) ;
779
+ } ,
780
+ ) ;
781
+ }
782
+ }
783
+
784
+ #[ test]
785
+ fn bench ( ) {
786
+ // Run with
787
+ // `cargo test --profile bench -- --nocapture bench -- <benchmark_filter>
788
+ // Collect cli arguments
789
+
790
+ // Interpret sequence of args `[ "...bench", "--", "[filter]" ]` as a trigger and extract
791
+ // `filter`
792
+ let filter = std:: env:: args ( )
793
+ . collect :: < Vec < _ > > ( )
794
+ . windows ( 3 )
795
+ . filter ( |p| p. len ( ) >= 2 && p[ 0 ] . ends_with ( "bench" ) && p[ 1 ] == "--" )
796
+ . map ( |s| s. get ( 2 ) . unwrap_or ( & "" . to_string ( ) ) . clone ( ) )
797
+ . next ( ) ;
798
+
799
+ let filter = match filter {
800
+ None => return ,
801
+ Some ( f) => f,
802
+ } ;
803
+
804
+ let mut criterion = criterion:: Criterion :: default ( )
805
+ . with_output_color ( true )
806
+ . with_filter ( & filter) ;
807
+ bench_trace_registry ( & mut criterion) ;
808
+
809
+ criterion. final_summary ( ) ;
810
+ }
686
811
}
0 commit comments