From 04c7e0b2bf3e51f2d1f674196071a05d5ef2db22 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 29 Jan 2025 02:34:10 +0100 Subject: [PATCH 1/4] initial seed propagation impl --- dart/lib/src/sentry_baggage.dart | 19 ++++++++++++-- dart/lib/src/sentry_trace_context_header.dart | 7 +++++- dart/lib/src/sentry_tracer.dart | 1 + dart/lib/src/sentry_traces_sampler.dart | 25 +++++++++++-------- .../src/sentry_traces_sampling_decision.dart | 2 ++ dart/lib/src/sentry_transaction_context.dart | 4 ++- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/dart/lib/src/sentry_baggage.dart b/dart/lib/src/sentry_baggage.dart index a8b38e375f..a3000c6792 100644 --- a/dart/lib/src/sentry_baggage.dart +++ b/dart/lib/src/sentry_baggage.dart @@ -1,11 +1,13 @@ import 'package:meta/meta.dart'; -import 'scope.dart'; -import 'protocol.dart'; +import 'protocol.dart'; +import 'scope.dart'; import 'sentry_options.dart'; class SentryBaggage { static const String _sampleRateKeyName = 'sentry-sample_rate'; + static const String _sampleRandKeyName = 'sentry-sample_rand'; + static const int _maxChars = 8192; static const int _maxListMember = 64; @@ -194,6 +196,10 @@ class SentryBaggage { set(_sampleRateKeyName, value); } + void setSampleRand(String value) { + set(_sampleRandKeyName, value); + } + void setSampled(String value) { set('sentry-sampled', value); } @@ -207,6 +213,15 @@ class SentryBaggage { return double.tryParse(sampleRate); } + double? getSampleRand() { + final sampleRand = get(_sampleRandKeyName); + if (sampleRand == null) { + return null; + } + + return double.tryParse(sampleRand); + } + void setReplayId(String value) => set('sentry-replay_id', value); SentryId? getReplayId() { diff --git a/dart/lib/src/sentry_trace_context_header.dart b/dart/lib/src/sentry_trace_context_header.dart index e17b2b91f8..9435e752b4 100644 --- a/dart/lib/src/sentry_trace_context_header.dart +++ b/dart/lib/src/sentry_trace_context_header.dart @@ -1,7 +1,7 @@ import 'package:meta/meta.dart'; -import 'protocol/sentry_id.dart'; import 'protocol/access_aware_map.dart'; +import 'protocol/sentry_id.dart'; import 'sentry_baggage.dart'; import 'sentry_options.dart'; @@ -15,6 +15,7 @@ class SentryTraceContextHeader { this.userSegment, this.transaction, this.sampleRate, + this.sampleRand, this.sampled, this.unknown, this.replayId, @@ -30,6 +31,7 @@ class SentryTraceContextHeader { final String? userSegment; final String? transaction; final String? sampleRate; + final String? sampleRand; final String? sampled; @internal @@ -102,6 +104,9 @@ class SentryTraceContextHeader { if (sampleRate != null) { baggage.setSampleRate(sampleRate!); } + if (sampleRand != null) { + baggage.setSampleRate(sampleRand!); + } if (sampled != null) { baggage.setSampled(sampled!); } diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index 05a1fd87d0..a465def396 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -385,6 +385,7 @@ class SentryTracer extends ISentrySpan { transaction: _isHighQualityTransactionName(transactionNameSource) ? name : null, sampleRate: _sampleRateToString(_rootSpan.samplingDecision?.sampleRate), + sampleRand: _sampleRateToString(_rootSpan.samplingDecision?.sampleRand), sampled: _rootSpan.samplingDecision?.sampled.toString(), ); diff --git a/dart/lib/src/sentry_traces_sampler.dart b/dart/lib/src/sentry_traces_sampler.dart index b842514481..0596992c02 100644 --- a/dart/lib/src/sentry_traces_sampler.dart +++ b/dart/lib/src/sentry_traces_sampler.dart @@ -33,10 +33,7 @@ class SentryTracesSampler { try { final result = tracesSampler(samplingContext); if (result != null) { - return SentryTracesSamplingDecision( - _sample(result), - sampleRate: result, - ); + return _sample2(result); } } catch (exception, stackTrace) { _options.logger( @@ -64,10 +61,7 @@ class SentryTracesSampler { double? optionsOrDefaultRate = optionsRate ?? defaultRate; if (optionsOrDefaultRate != null) { - return SentryTracesSamplingDecision( - _sample(optionsOrDefaultRate), - sampleRate: optionsOrDefaultRate, - ); + return _sample2(optionsOrDefaultRate); } return SentryTracesSamplingDecision(false); @@ -78,8 +72,19 @@ class SentryTracesSampler { if (optionsRate == null || !tracesSamplingDecision.sampled) { return false; } - return _sample(optionsRate); + return _shouldSample(optionsRate); } - bool _sample(double result) => !(result < _random.nextDouble()); + SentryTracesSamplingDecision _sample2(double sampleRate) { + final sampleRand = _random.nextDouble(); + return SentryTracesSamplingDecision( + _shouldSample(sampleRate, sampleRand: sampleRand), + sampleRate: sampleRate, + sampleRand: sampleRand); + } + + bool _shouldSample(double sampleRate, {double? sampleRand}) { + final rand = sampleRand ?? _random.nextDouble(); + return rand <= sampleRate; + } } diff --git a/dart/lib/src/sentry_traces_sampling_decision.dart b/dart/lib/src/sentry_traces_sampling_decision.dart index 90161515cf..802d27f832 100644 --- a/dart/lib/src/sentry_traces_sampling_decision.dart +++ b/dart/lib/src/sentry_traces_sampling_decision.dart @@ -2,8 +2,10 @@ class SentryTracesSamplingDecision { SentryTracesSamplingDecision( this.sampled, { this.sampleRate, + this.sampleRand, }); final bool sampled; final double? sampleRate; + final double? sampleRand; } diff --git a/dart/lib/src/sentry_transaction_context.dart b/dart/lib/src/sentry_transaction_context.dart index 32ab0324b7..b677cb1f8a 100644 --- a/dart/lib/src/sentry_transaction_context.dart +++ b/dart/lib/src/sentry_transaction_context.dart @@ -1,8 +1,8 @@ import 'package:meta/meta.dart'; -import 'sentry_trace_origins.dart'; import 'protocol.dart'; import 'sentry_baggage.dart'; +import 'sentry_trace_origins.dart'; import 'tracing.dart'; @immutable @@ -35,6 +35,7 @@ class SentryTransactionContext extends SentrySpanContext { SentryBaggage? baggage, }) { final sampleRate = baggage?.getSampleRate(); + final sampleRand = baggage?.getSampleRand(); return SentryTransactionContext( name, operation, @@ -44,6 +45,7 @@ class SentryTransactionContext extends SentrySpanContext { ? SentryTracesSamplingDecision( traceHeader.sampled!, sampleRate: sampleRate, + sampleRand: sampleRand, ) : null, transactionNameSource: From ca2bc55dc9be99978f43271e6049e2044b58b668 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 29 Jan 2025 03:38:56 +0100 Subject: [PATCH 2/4] update --- dart/lib/src/sentry_trace_context_header.dart | 3 ++- dart/lib/src/sentry_tracer.dart | 9 ++++++++- dart/lib/src/sentry_traces_sampler.dart | 6 +++--- dart/lib/src/sentry_transaction_context.dart | 4 ++-- dart/lib/src/utils/tracing_utils.dart | 7 +++++++ dart/test/protocol/sentry_baggage_header_test.dart | 2 ++ dart/test/sentry_tracer_test.dart | 2 ++ 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/dart/lib/src/sentry_trace_context_header.dart b/dart/lib/src/sentry_trace_context_header.dart index 9435e752b4..c80aae9fea 100644 --- a/dart/lib/src/sentry_trace_context_header.dart +++ b/dart/lib/src/sentry_trace_context_header.dart @@ -105,7 +105,7 @@ class SentryTraceContextHeader { baggage.setSampleRate(sampleRate!); } if (sampleRand != null) { - baggage.setSampleRate(sampleRand!); + baggage.setSampleRand(sampleRand!); } if (sampled != null) { baggage.setSampled(sampled!); @@ -118,6 +118,7 @@ class SentryTraceContextHeader { factory SentryTraceContextHeader.fromBaggage(SentryBaggage baggage) { return SentryTraceContextHeader( + // TODO: implement and use proper get methods here SentryId.fromId(baggage.get('sentry-trace_id').toString()), baggage.get('sentry-public_key').toString(), release: baggage.get('sentry-release'), diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index a465def396..9dc6cb929f 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -385,7 +385,7 @@ class SentryTracer extends ISentrySpan { transaction: _isHighQualityTransactionName(transactionNameSource) ? name : null, sampleRate: _sampleRateToString(_rootSpan.samplingDecision?.sampleRate), - sampleRand: _sampleRateToString(_rootSpan.samplingDecision?.sampleRand), + sampleRand: _sampleRandToString(_rootSpan.samplingDecision?.sampleRand), sampled: _rootSpan.samplingDecision?.sampled.toString(), ); @@ -399,6 +399,13 @@ class SentryTracer extends ISentrySpan { return sampleRate != null ? SampleRateFormat().format(sampleRate) : null; } + String? _sampleRandToString(double? sampleRand) { + if (!isValidSampleRand(sampleRand)) { + return null; + } + return sampleRand != null ? SampleRateFormat().format(sampleRand) : null; + } + bool _isHighQualityTransactionName(SentryTransactionNameSource source) { return source != SentryTransactionNameSource.url; } diff --git a/dart/lib/src/sentry_traces_sampler.dart b/dart/lib/src/sentry_traces_sampler.dart index 0596992c02..7b8861f730 100644 --- a/dart/lib/src/sentry_traces_sampler.dart +++ b/dart/lib/src/sentry_traces_sampler.dart @@ -33,7 +33,7 @@ class SentryTracesSampler { try { final result = tracesSampler(samplingContext); if (result != null) { - return _sample2(result); + return _decideSampling(result); } } catch (exception, stackTrace) { _options.logger( @@ -61,7 +61,7 @@ class SentryTracesSampler { double? optionsOrDefaultRate = optionsRate ?? defaultRate; if (optionsOrDefaultRate != null) { - return _sample2(optionsOrDefaultRate); + return _decideSampling(optionsOrDefaultRate); } return SentryTracesSamplingDecision(false); @@ -75,7 +75,7 @@ class SentryTracesSampler { return _shouldSample(optionsRate); } - SentryTracesSamplingDecision _sample2(double sampleRate) { + SentryTracesSamplingDecision _decideSampling(double sampleRate) { final sampleRand = _random.nextDouble(); return SentryTracesSamplingDecision( _shouldSample(sampleRate, sampleRand: sampleRand), diff --git a/dart/lib/src/sentry_transaction_context.dart b/dart/lib/src/sentry_transaction_context.dart index b677cb1f8a..9caa170385 100644 --- a/dart/lib/src/sentry_transaction_context.dart +++ b/dart/lib/src/sentry_transaction_context.dart @@ -8,20 +8,20 @@ import 'tracing.dart'; @immutable class SentryTransactionContext extends SentrySpanContext { final String name; - final SentryTracesSamplingDecision? parentSamplingDecision; final SentryTransactionNameSource? transactionNameSource; final SentryTracesSamplingDecision? samplingDecision; + final SentryTracesSamplingDecision? parentSamplingDecision; SentryTransactionContext( this.name, String operation, { super.description, - this.parentSamplingDecision, super.traceId, super.spanId, super.parentSpanId, this.transactionNameSource, this.samplingDecision, + this.parentSamplingDecision, super.origin, }) : super( operation: operation, diff --git a/dart/lib/src/utils/tracing_utils.dart b/dart/lib/src/utils/tracing_utils.dart index 6198062ddc..5a14311d1c 100644 --- a/dart/lib/src/utils/tracing_utils.dart +++ b/dart/lib/src/utils/tracing_utils.dart @@ -80,3 +80,10 @@ bool isValidSampleRate(double? sampleRate) { } return !sampleRate.isNaN && sampleRate >= 0.0 && sampleRate <= 1.0; } + +bool isValidSampleRand(double? sampleRand) { + if (sampleRand == null) { + return false; + } + return !sampleRand.isNaN && sampleRand >= 0.0 && sampleRand < 1.0; +} diff --git a/dart/test/protocol/sentry_baggage_header_test.dart b/dart/test/protocol/sentry_baggage_header_test.dart index 910929776e..6d75d6dd15 100644 --- a/dart/test/protocol/sentry_baggage_header_test.dart +++ b/dart/test/protocol/sentry_baggage_header_test.dart @@ -21,6 +21,7 @@ void main() { baggage.setUserSegment('userSegment'); baggage.setTransaction('transaction'); baggage.setSampleRate('1.0'); + baggage.setSampleRand('0.4'); baggage.setSampled('false'); final replayId = SentryId.newId().toString(); baggage.setReplayId(replayId); @@ -37,6 +38,7 @@ void main() { 'sentry-user_segment=userSegment,' 'sentry-transaction=transaction,' 'sentry-sample_rate=1.0,' + 'sentry-sample_rand=0.4,' 'sentry-sampled=false,' 'sentry-replay_id=$replayId'); }); diff --git a/dart/test/sentry_tracer_test.dart b/dart/test/sentry_tracer_test.dart index 667e43b159..d5dde5ae98 100644 --- a/dart/test/sentry_tracer_test.dart +++ b/dart/test/sentry_tracer_test.dart @@ -487,6 +487,7 @@ void main() { SentryTracesSamplingDecision( true, sampleRate: 1.0, + sampleRand: 0.8, ); final _context = SentryTransactionContext( 'name', @@ -512,6 +513,7 @@ void main() { expect(newBaggage.get('sentry-user_segment'), 'segment'); expect(newBaggage.get('sentry-transaction'), 'name'); expect(newBaggage.get('sentry-sample_rate'), '1'); + expect(newBaggage.getSampleRand(), 0.8); expect(newBaggage.get('sentry-sampled'), 'true'); }); From f19679df6f728cd6244b212dbd057f0adb4c4075 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 29 Jan 2025 12:26:55 +0100 Subject: [PATCH 3/4] improve naming --- dart/lib/src/sentry_baggage.dart | 1 - dart/lib/src/sentry_traces_sampler.dart | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/dart/lib/src/sentry_baggage.dart b/dart/lib/src/sentry_baggage.dart index a3000c6792..8def45ca0f 100644 --- a/dart/lib/src/sentry_baggage.dart +++ b/dart/lib/src/sentry_baggage.dart @@ -1,5 +1,4 @@ import 'package:meta/meta.dart'; - import 'protocol.dart'; import 'scope.dart'; import 'sentry_options.dart'; diff --git a/dart/lib/src/sentry_traces_sampler.dart b/dart/lib/src/sentry_traces_sampler.dart index 7b8861f730..f668ad460f 100644 --- a/dart/lib/src/sentry_traces_sampler.dart +++ b/dart/lib/src/sentry_traces_sampler.dart @@ -31,9 +31,9 @@ class SentryTracesSampler { final tracesSampler = _options.tracesSampler; if (tracesSampler != null) { try { - final result = tracesSampler(samplingContext); - if (result != null) { - return _decideSampling(result); + final sampleRate = tracesSampler(samplingContext); + if (sampleRate != null) { + return _makeSampleDecision(sampleRate); } } catch (exception, stackTrace) { _options.logger( @@ -61,7 +61,7 @@ class SentryTracesSampler { double? optionsOrDefaultRate = optionsRate ?? defaultRate; if (optionsOrDefaultRate != null) { - return _decideSampling(optionsOrDefaultRate); + return _makeSampleDecision(optionsOrDefaultRate); } return SentryTracesSamplingDecision(false); @@ -72,18 +72,17 @@ class SentryTracesSampler { if (optionsRate == null || !tracesSamplingDecision.sampled) { return false; } - return _shouldSample(optionsRate); + return _isSampled(optionsRate); } - SentryTracesSamplingDecision _decideSampling(double sampleRate) { + SentryTracesSamplingDecision _makeSampleDecision(double sampleRate) { final sampleRand = _random.nextDouble(); - return SentryTracesSamplingDecision( - _shouldSample(sampleRate, sampleRand: sampleRand), - sampleRate: sampleRate, - sampleRand: sampleRand); + final sampled = _isSampled(sampleRate, sampleRand: sampleRand); + return SentryTracesSamplingDecision(sampled, + sampleRate: sampleRate, sampleRand: sampleRand); } - bool _shouldSample(double sampleRate, {double? sampleRand}) { + bool _isSampled(double sampleRate, {double? sampleRand}) { final rand = sampleRand ?? _random.nextDouble(); return rand <= sampleRate; } From 47e46d87c23665842fd5930a09225dc06ec3923f Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 30 Jan 2025 12:37:10 +0100 Subject: [PATCH 4/4] update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5483446c2c..a138dee07b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### Enhancements + +- Propagate sample seed in baggage header ([#2629](https://github.com/getsentry/sentry-dart/pull/2629)) + - Read more about the specs [here](https://develop.sentry.dev/sdk/telemetry/traces/#propagated-random-value) + ## 8.13.0-beta.3 ### Enhancements