@@ -156,6 +156,13 @@ impl TransactionContext {
156
156
}
157
157
}
158
158
159
+ /// A function to be run for each new transaction, to determine the rate at which
160
+ /// it should be sampled.
161
+ ///
162
+ /// This function may choose to respect the sampling of the parent transaction (`ctx.sampled`)
163
+ /// or ignore it.
164
+ pub type TracesSampler = dyn Fn ( & TransactionContext ) -> f32 + Send + Sync ;
165
+
159
166
// global API types:
160
167
161
168
/// A wrapper that groups a [`Transaction`] and a [`Span`] together.
@@ -279,6 +286,37 @@ pub(crate) struct TransactionInner {
279
286
280
287
type TransactionArc = Arc < Mutex < TransactionInner > > ;
281
288
289
+ /// Functional implementation of how a new transation's sample rate is chosen.
290
+ ///
291
+ /// Split out from `Client.is_transaction_sampled` for testing.
292
+ #[ cfg( feature = "client" ) ]
293
+ fn transaction_sample_rate (
294
+ traces_sampler : Option < & TracesSampler > ,
295
+ ctx : & TransactionContext ,
296
+ traces_sample_rate : f32 ,
297
+ ) -> f32 {
298
+ match ( traces_sampler, traces_sample_rate) {
299
+ ( Some ( traces_sampler) , _) => traces_sampler ( ctx) ,
300
+ ( None , traces_sample_rate) => ctx
301
+ . sampled
302
+ . map ( |sampled| if sampled { 1.0 } else { 0.0 } )
303
+ . unwrap_or ( traces_sample_rate) ,
304
+ }
305
+ }
306
+
307
+ /// Determine whether the new transaction should be sampled.
308
+ #[ cfg( feature = "client" ) ]
309
+ impl Client {
310
+ fn is_transaction_sampled ( & self , ctx : & TransactionContext ) -> bool {
311
+ let client_options = self . options ( ) ;
312
+ self . sample_should_send ( transaction_sample_rate (
313
+ client_options. traces_sampler . as_deref ( ) ,
314
+ ctx,
315
+ client_options. traces_sample_rate ,
316
+ ) )
317
+ }
318
+ }
319
+
282
320
/// A running Performance Monitoring Transaction.
283
321
///
284
322
/// The transaction needs to be explicitly finished via [`Transaction::finish`],
@@ -292,18 +330,9 @@ pub struct Transaction {
292
330
impl Transaction {
293
331
#[ cfg( feature = "client" ) ]
294
332
fn new ( mut client : Option < Arc < Client > > , ctx : TransactionContext ) -> Self {
295
- let context = protocol:: TraceContext {
296
- trace_id : ctx. trace_id ,
297
- parent_span_id : ctx. parent_span_id ,
298
- op : Some ( ctx. op ) ,
299
- ..Default :: default ( )
300
- } ;
301
-
302
333
let ( sampled, mut transaction) = match client. as_ref ( ) {
303
334
Some ( client) => (
304
- ctx. sampled . unwrap_or_else ( || {
305
- client. sample_should_send ( client. options ( ) . traces_sample_rate )
306
- } ) ,
335
+ client. is_transaction_sampled ( & ctx) ,
307
336
Some ( protocol:: Transaction {
308
337
name : Some ( ctx. name ) ,
309
338
#[ cfg( all( feature = "profiling" , target_family = "unix" ) ) ]
@@ -314,6 +343,13 @@ impl Transaction {
314
343
None => ( ctx. sampled . unwrap_or ( false ) , None ) ,
315
344
} ;
316
345
346
+ let context = protocol:: TraceContext {
347
+ trace_id : ctx. trace_id ,
348
+ parent_span_id : ctx. parent_span_id ,
349
+ op : Some ( ctx. op ) ,
350
+ ..Default :: default ( )
351
+ } ;
352
+
317
353
// throw away the transaction here, which means there is nothing to send
318
354
// on `finish`.
319
355
if !sampled {
@@ -679,4 +715,36 @@ mod tests {
679
715
assert_eq ! ( & parsed. 0 . to_string( ) , "09e04486820349518ac7b5d2adbf6ba5" ) ;
680
716
assert_eq ! ( parsed. 2 , Some ( true ) ) ;
681
717
}
718
+
719
+ #[ cfg( feature = "client" ) ]
720
+ #[ test]
721
+ fn compute_transaction_sample_rate ( ) {
722
+ // Global rate used as fallback.
723
+ let ctx = TransactionContext :: new ( "noop" , "noop" ) ;
724
+ assert_eq ! ( transaction_sample_rate( None , & ctx, 0.3 ) , 0.3 ) ;
725
+ assert_eq ! ( transaction_sample_rate( None , & ctx, 0.7 ) , 0.7 ) ;
726
+
727
+ // If only global rate, setting sampled overrides it
728
+ let mut ctx = TransactionContext :: new ( "noop" , "noop" ) ;
729
+ ctx. set_sampled ( true ) ;
730
+ assert_eq ! ( transaction_sample_rate( None , & ctx, 0.3 ) , 1.0 ) ;
731
+ ctx. set_sampled ( false ) ;
732
+ assert_eq ! ( transaction_sample_rate( None , & ctx, 0.3 ) , 0.0 ) ;
733
+
734
+ // If given, sampler function overrides everything else.
735
+ let mut ctx = TransactionContext :: new ( "noop" , "noop" ) ;
736
+ assert_eq ! ( transaction_sample_rate( Some ( & |_| { 0.7 } ) , & ctx, 0.3 ) , 0.7 ) ;
737
+ ctx. set_sampled ( false ) ;
738
+ assert_eq ! ( transaction_sample_rate( Some ( & |_| { 0.7 } ) , & ctx, 0.3 ) , 0.7 ) ;
739
+ // But the sampler may choose to inspect parent sampling
740
+ let sampler = |ctx : & TransactionContext | match ctx. sampled {
741
+ Some ( true ) => 0.8 ,
742
+ Some ( false ) => 0.4 ,
743
+ None => 0.6 ,
744
+ } ;
745
+ ctx. set_sampled ( true ) ;
746
+ assert_eq ! ( transaction_sample_rate( Some ( & sampler) , & ctx, 0.3 ) , 0.8 ) ;
747
+ ctx. set_sampled ( None ) ;
748
+ assert_eq ! ( transaction_sample_rate( Some ( & sampler) , & ctx, 0.3 ) , 0.6 ) ;
749
+ }
682
750
}
0 commit comments