Skip to content

Commit 735dd0d

Browse files
kalyanajTimothy Mothracijothomas
authored
An example / proof of concept for stratified sampling in OpenTelemetry.NET (#4208)
Co-authored-by: Timothy Mothra <[email protected]> Co-authored-by: Cijo Thomas <[email protected]>
1 parent 77cd1b0 commit 735dd0d

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

OpenTelemetry.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-console", "
249249
EndProject
250250
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-jaeger", "docs\trace\getting-started-jaeger\getting-started-jaeger.csproj", "{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}"
251251
EndProject
252+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stratified-sampling-example", "docs\trace\advanced\stratified-sampling-example\stratified-sampling-example.csproj", "{9C99621C-343E-479C-A943-332DB6129B71}"
253+
EndProject
252254
Global
253255
GlobalSection(SolutionConfigurationPlatforms) = preSolution
254256
Debug|Any CPU = Debug|Any CPU
@@ -523,6 +525,10 @@ Global
523525
{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
524526
{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
525527
{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}.Release|Any CPU.Build.0 = Release|Any CPU
528+
{9C99621C-343E-479C-A943-332DB6129B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
529+
{9C99621C-343E-479C-A943-332DB6129B71}.Debug|Any CPU.Build.0 = Debug|Any CPU
530+
{9C99621C-343E-479C-A943-332DB6129B71}.Release|Any CPU.ActiveCfg = Release|Any CPU
531+
{9C99621C-343E-479C-A943-332DB6129B71}.Release|Any CPU.Build.0 = Release|Any CPU
526532
EndGlobalSection
527533
GlobalSection(SolutionProperties) = preSolution
528534
HideSolutionNode = FALSE
@@ -562,6 +568,7 @@ Global
562568
{DEDE8442-03CA-48CF-99B9-EA224D89D148} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
563569
{EF4F6280-14D1-49D4-8095-1AC36E169AA8} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
564570
{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
571+
{9C99621C-343E-479C-A943-332DB6129B71} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
565572
EndGlobalSection
566573
GlobalSection(ExtensibilityGlobals) = postSolution
567574
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// <copyright file="Program.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System.Diagnostics;
18+
using OpenTelemetry;
19+
using OpenTelemetry.Trace;
20+
21+
namespace StratifiedSamplingByQueryTypeDemo;
22+
23+
internal class Program
24+
{
25+
private static readonly ActivitySource MyActivitySource = new("StratifiedSampling.POC");
26+
27+
public static void Main(string[] args)
28+
{
29+
// We wrap the stratified sampler within a parentbased sampler.
30+
// This is to enable downstream participants (i.e., the non-root spans) to have
31+
// the same consistent sampling decision as the root span (that uses the stratified sampler).
32+
// Such downstream participants may not have access to the same attributes that were used to
33+
// make the stratified sampling decision at the root.
34+
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
35+
.SetSampler(new ParentBasedSampler(new StratifiedSampler()))
36+
.AddSource("StratifiedSampling.POC")
37+
.AddConsoleExporter()
38+
.Build();
39+
40+
var random = new Random(2357);
41+
var tagsList = new List<KeyValuePair<string, object?>>(1);
42+
43+
// Generate some spans
44+
for (var i = 0; i < 20; i++)
45+
{
46+
// Simulate a mix of user-initiated (25%) and programmatic (75%) queries
47+
var randomValue = random.Next(4);
48+
switch (randomValue)
49+
{
50+
case 0:
51+
tagsList.Add(new KeyValuePair<string, object?>("queryType", "userInitiated"));
52+
break;
53+
default:
54+
tagsList.Add(new KeyValuePair<string, object?>("queryType", "programmatic"));
55+
break;
56+
}
57+
58+
// Note that the queryType attribute here is present as part of the tags list when the activity is started.
59+
// We are using this attribute value to achieve stratified sampling.
60+
using (var activity = MyActivitySource.StartActivity(ActivityKind.Internal, parentContext: default, tags: tagsList))
61+
{
62+
activity?.SetTag("foo", "bar");
63+
using (var activity2 = MyActivitySource.StartActivity(ActivityKind.Internal, parentContext: default, tags: tagsList))
64+
{
65+
activity2?.SetTag("foo", "child");
66+
}
67+
}
68+
69+
tagsList.Clear();
70+
}
71+
}
72+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Stratified Sampling: An Example
2+
3+
This example shows one possible way to achieve stratified sampling in
4+
OpenTelemetry.NET.
5+
6+
## What is stratified sampling?
7+
8+
Stratified sampling is a way to divide a population into mutually exclusive
9+
sub-populations or "strata". For example, the strata for a population of
10+
"queries" could be "user-initiated queries" and "programmatic queries". Each
11+
stratum is then sampled using a probabilistic sampling method. This ensures
12+
that all sub-populations are represented.
13+
14+
## How does this example do stratified sampling?
15+
16+
We achieve this by using a custom Sampler that internally holds two samplers.
17+
Based on the stratum, the appropriate sampler is invoked.
18+
19+
One prerequisite for this is that the tag (e.g. queryType) used for the
20+
stratified sampling decision must be provided as part of activity creation.
21+
22+
We use disproportionate stratified sampling (also known as "unequal probability
23+
sampling") here - i.e., the sample size of each sub-population is not
24+
proportionate to their occurrence in the overall population. In this example,
25+
we want to ensure that all user initiated queries are represented, so we use a
26+
100% sampling rate for it, while the sampling rate chosen for programmatic
27+
queries is much lower.
28+
29+
## What is an example output?
30+
31+
You should see the following output on the Console when you use "dotnet run" to
32+
run this application. This shows that the two sub-populations (strata) are being
33+
sampled independently.
34+
35+
```text
36+
StratifiedSampler handling userinitiated query
37+
Activity.TraceId: 1a122d63e5f8d32cb8ebd3e402eb5389
38+
Activity.SpanId: 83bdc6bbebea1df8
39+
Activity.TraceFlags: Recorded
40+
Activity.ParentSpanId: 1ddd00d845ad645e
41+
Activity.ActivitySourceName: StratifiedSampling.POC
42+
Activity.DisplayName: Main
43+
Activity.Kind: Internal
44+
Activity.StartTime: 2023-02-09T05:19:30.8156879Z
45+
Activity.Duration: 00:00:00.0008656
46+
Activity.Tags:
47+
queryType: userInitiated
48+
foo: child
49+
Resource associated with Activity:
50+
service.name: unknown_service:Examples.StratifiedSamplingByQueryType
51+
52+
Activity.TraceId: 1a122d63e5f8d32cb8ebd3e402eb5389
53+
Activity.SpanId: 1ddd00d845ad645e
54+
Activity.TraceFlags: Recorded
55+
Activity.ActivitySourceName: StratifiedSampling.POC
56+
Activity.DisplayName: Main
57+
Activity.Kind: Internal
58+
Activity.StartTime: 2023-02-09T05:19:30.8115186Z
59+
Activity.Duration: 00:00:00.0424036
60+
Activity.Tags:
61+
queryType: userInitiated
62+
foo: bar
63+
Resource associated with Activity:
64+
service.name: unknown_service:Examples.StratifiedSamplingByQueryType
65+
66+
StratifiedSampler handling programmatic query
67+
StratifiedSampler handling programmatic query
68+
StratifiedSampler handling programmatic query
69+
StratifiedSampler handling programmatic query
70+
Activity.TraceId: 03cddefbc0e0f61851135f814522a2df
71+
Activity.SpanId: 8d4fa3e27a12f666
72+
Activity.TraceFlags: Recorded
73+
Activity.ParentSpanId: 8c46e4dc6d0f418c
74+
Activity.ActivitySourceName: StratifiedSampling.POC
75+
Activity.DisplayName: Main
76+
Activity.Kind: Internal
77+
Activity.StartTime: 2023-02-09T05:19:30.8553756Z
78+
Activity.Duration: 00:00:00.0000019
79+
Activity.Tags:
80+
queryType: programmatic
81+
foo: child
82+
Resource associated with Activity:
83+
service.name: unknown_service:Examples.StratifiedSamplingByQueryType
84+
85+
StratifiedSampler handling programmatic query
86+
StratifiedSampler handling programmatic query
87+
StratifiedSampler handling programmatic query
88+
StratifiedSampler handling programmatic query
89+
StratifiedSampler handling programmatic query
90+
StratifiedSampler handling userinitiated query
91+
Activity.TraceId: 8a5894524f1bea2a7bd8271fef9ec22d
92+
Activity.SpanId: 94b5b004287bd678
93+
Activity.TraceFlags: Recorded
94+
Activity.ParentSpanId: 99600e9fe011c1cc
95+
Activity.ActivitySourceName: StratifiedSampling.POC
96+
Activity.DisplayName: Main
97+
Activity.Kind: Internal
98+
Activity.StartTime: 2023-02-09T05:19:30.9660777Z
99+
Activity.Duration: 00:00:00.0000005
100+
Activity.Tags:
101+
queryType: userInitiated
102+
foo: child
103+
Resource associated with Activity:
104+
service.name: unknown_service:Examples.StratifiedSamplingByQueryType
105+
```
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// <copyright file="StratifiedSampler.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using OpenTelemetry.Trace;
18+
19+
namespace StratifiedSamplingByQueryTypeDemo;
20+
21+
internal class StratifiedSampler : Sampler
22+
{
23+
// For this POC, we have two groups.
24+
// 0 is the group corresponding to user-initiated queries where we want a 100% sampling rate.
25+
// 1 is the group corresponding to programmatic queries where we want a lower sampling rate, say 10%
26+
private const int NumGroups = 2;
27+
private const string QueryTypeTag = "queryType";
28+
private const string QueryTypeUserInitiated = "userInitiated";
29+
private const string QueryTypeProgrammatic = "programmatic";
30+
31+
private readonly Dictionary<int, double> samplingRatios = new();
32+
private readonly List<Sampler> samplers = new();
33+
34+
public StratifiedSampler()
35+
{
36+
// Initialize sampling ratios for different groups
37+
this.samplingRatios[0] = 1.0;
38+
this.samplingRatios[1] = 0.2;
39+
40+
for (var i = 0; i < NumGroups; i++)
41+
{
42+
this.samplers.Add(new TraceIdRatioBasedSampler(this.samplingRatios[i]));
43+
}
44+
}
45+
46+
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
47+
{
48+
if (samplingParameters.Tags != null)
49+
{
50+
foreach (var tag in samplingParameters.Tags)
51+
{
52+
if (tag.Key.Equals(QueryTypeTag, StringComparison.OrdinalIgnoreCase))
53+
{
54+
var queryType = tag.Value as string;
55+
if (queryType == null)
56+
{
57+
continue;
58+
}
59+
60+
if (queryType.Equals(QueryTypeUserInitiated, StringComparison.OrdinalIgnoreCase))
61+
{
62+
Console.WriteLine($"StratifiedSampler handling userinitiated query");
63+
return this.samplers[0].ShouldSample(samplingParameters);
64+
}
65+
else if (queryType.Equals(QueryTypeProgrammatic, StringComparison.OrdinalIgnoreCase))
66+
{
67+
Console.WriteLine($"StratifiedSampler handling programmatic query");
68+
return this.samplers[1].ShouldSample(samplingParameters);
69+
}
70+
else
71+
{
72+
Console.WriteLine("Unexpected query type");
73+
}
74+
}
75+
}
76+
}
77+
78+
return new SamplingResult(SamplingDecision.Drop);
79+
}
80+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
11+
12+
</ItemGroup>
13+
14+
</Project>

0 commit comments

Comments
 (0)