-
-
Notifications
You must be signed in to change notification settings - Fork 226
Propagate Sampling Seed #3951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Propagate Sampling Seed #3951
Changes from 13 commits
02593ec
3e7034d
c70ca4e
0b22582
79ca7e6
6feb7b7
7ee8924
54d3293
e486d84
03d948f
724880d
be64839
8fea485
a363f00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| using Sentry.Internal; | ||
| using Sentry.Internal.Extensions; | ||
|
|
||
| namespace Sentry; | ||
|
|
@@ -24,27 +25,33 @@ private DynamicSamplingContext( | |
| string publicKey, | ||
| bool? sampled, | ||
| double? sampleRate = null, | ||
| double? sampleRand = null, | ||
| string? release = null, | ||
| string? environment = null, | ||
| string? transactionName = null) | ||
| { | ||
| // Validate and set required values | ||
| if (traceId == SentryId.Empty) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(traceId)); | ||
| throw new ArgumentOutOfRangeException(nameof(traceId), "cannot be empty"); | ||
| } | ||
|
|
||
| if (string.IsNullOrWhiteSpace(publicKey)) | ||
| { | ||
| throw new ArgumentException(default, nameof(publicKey)); | ||
| throw new ArgumentException("cannot be empty", nameof(publicKey)); | ||
| } | ||
|
|
||
| if (sampleRate is < 0.0 or > 1.0) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(sampleRate)); | ||
| throw new ArgumentOutOfRangeException(nameof(sampleRate), "Arg invalid if < 0.0 or > 1.0"); | ||
| } | ||
|
|
||
| var items = new Dictionary<string, string>(capacity: 7) | ||
| if (sampleRand is < 0.0 or >= 1.0) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(sampleRand), "Arg invalid if < 0.0 or >= 1.0"); | ||
| } | ||
|
|
||
| var items = new Dictionary<string, string>(capacity: 8) | ||
| { | ||
| ["trace_id"] = traceId.ToString(), | ||
| ["public_key"] = publicKey, | ||
|
|
@@ -61,6 +68,11 @@ private DynamicSamplingContext( | |
| items.Add("sample_rate", sampleRate.Value.ToString(CultureInfo.InvariantCulture)); | ||
| } | ||
|
|
||
| if (sampleRand is not null) | ||
| { | ||
| items.Add("sample_rand", sampleRand.Value.ToString("N4", CultureInfo.InvariantCulture)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we want this even if we have
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe so yes. The docs developer docs indicate:
Which we're doing... and then we propagate the |
||
| } | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(release)) | ||
| { | ||
| items.Add("release", release); | ||
|
|
@@ -99,7 +111,7 @@ private DynamicSamplingContext( | |
| return null; | ||
| } | ||
|
|
||
| if (items.TryGetValue("sampled", out var sampledString) && !bool.TryParse(sampledString, out _)) | ||
| if (items.TryGetValue("sampled", out var sampledString) && !bool.TryParse(sampledString, out var sampled)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
@@ -111,6 +123,27 @@ private DynamicSamplingContext( | |
| return null; | ||
| } | ||
|
|
||
| // See https://develop.sentry.dev/sdk/telemetry/traces/#propagated-random-value | ||
| if (items.TryGetValue("sample_rand", out var sampleRand)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. odd that we validate this to be within a range as a float but then we make it a string then try to parse it back out.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I follow. It may or may not be present as a string in the baggage header. If it's present then here we try to cast it to a float and ensure it's in the correct range. If it's present but malformed then we don't create the DSC from the baggage header. |
||
| { | ||
| if (!double.TryParse(sampleRand, NumberStyles.Float, CultureInfo.InvariantCulture, out var rand) || | ||
| rand is < 0.0 or >= 1.0) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. didn't we validate this above before adding it to
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is creating a DSC from the baggage header. The BaggageHeader is a pretty simple class that just pulls stuff directly off a certain HTTP header, without any strong opinions about what the items in that header should be. Other vendors might be putting things on that header so we just propagate it, pretty much verbatim (we might add things to it but we never remove things from it). But we can't control what will be on that baggage header in this SDK. A request could be made to our SDK with a malformed sentry-sample_rand item in the BaggageHeader. |
||
| { | ||
| return null; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| var rand = SampleRandHelper.GenerateSampleRand(traceId); | ||
| if (!string.IsNullOrEmpty(sampledString)) | ||
| { | ||
| // Ensure sample_rand is consistent with the sampling decision that has already been made | ||
| rand = bool.Parse(sampledString) | ||
| ? rand * rate // 0 <= sampleRand < rate | ||
| : rate + (1 - rate) * rand; // rate < sampleRand < 1 | ||
| } | ||
| items.Add("sample_rand", rand.ToString("N4", CultureInfo.InvariantCulture)); | ||
| } | ||
| return new DynamicSamplingContext(items); | ||
| } | ||
|
|
||
|
|
@@ -121,6 +154,7 @@ public static DynamicSamplingContext CreateFromTransaction(TransactionTracer tra | |
| var traceId = transaction.TraceId; | ||
| var sampled = transaction.IsSampled; | ||
| var sampleRate = transaction.SampleRate!.Value; | ||
| var sampleRand = transaction.SampleRand; | ||
| var transactionName = transaction.NameSource.IsHighQuality() ? transaction.Name : null; | ||
|
|
||
| // These two may not have been set yet on the transaction, but we can get them directly. | ||
|
|
@@ -132,6 +166,7 @@ public static DynamicSamplingContext CreateFromTransaction(TransactionTracer tra | |
| publicKey, | ||
| sampled, | ||
| sampleRate, | ||
| sampleRand, | ||
| release, | ||
| environment, | ||
| transactionName); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| namespace Sentry.Internal; | ||
|
|
||
| /// <summary> | ||
| /// FNV is a non-cryptographic hash. | ||
| /// | ||
| /// See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV_hash_parameters | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// We use a struct to avoid heap allocations. | ||
| /// </remarks> | ||
| internal struct FnvHash | ||
| { | ||
| public FnvHash() | ||
| { | ||
| } | ||
|
|
||
| private const int Offset = unchecked((int)2166136261); | ||
| private const int Prime = 16777619; | ||
|
|
||
| private int HashCode { get; set; } = Offset; | ||
|
|
||
| private void Combine(byte data) | ||
| { | ||
| unchecked | ||
| { | ||
| HashCode ^= data; | ||
| HashCode *= Prime; | ||
| } | ||
| } | ||
|
|
||
| private static int ComputeHash(byte[] data) | ||
| { | ||
| var result = new FnvHash(); | ||
| foreach (var b in data) | ||
| { | ||
| result.Combine(b); | ||
| } | ||
|
|
||
| return result.HashCode; | ||
| } | ||
|
|
||
| public static int ComputeHash(string data) => ComputeHash(Encoding.UTF8.GetBytes(data)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| namespace Sentry.Internal; | ||
|
|
||
| internal static class SampleRandHelper | ||
| { | ||
| internal static double GenerateSampleRand(string traceId) | ||
| => new Random(FnvHash.ComputeHash(traceId)).NextDouble(); | ||
|
|
||
| internal static bool IsSampled(double sampleRand, double rate) => rate switch | ||
| { | ||
| >= 1 => true, | ||
| <= 0 => false, | ||
| _ => sampleRand < rate | ||
| }; | ||
|
|
||
| } |
Uh oh!
There was an error while loading. Please reload this page.