Skip to content

Commit

Permalink
Example to show how to achieve more consistent sampling across linked…
Browse files Browse the repository at this point in the history
… traces (#4346)
  • Loading branch information
kalyanaj authored Apr 21, 2023
1 parent 678e9c4 commit 58d9d69
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 0 deletions.
7 changes: 7 additions & 0 deletions OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tail-based-sampling-example
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stratified-sampling-example", "docs\trace\stratified-sampling-example\stratified-sampling-example.csproj", "{9C99621C-343E-479C-A943-332DB6129B71}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "links-sampler", "docs\trace\links-based-sampler\links-sampler.csproj", "{62AF4BD3-DCAE-4D44-AA5B-991C1071166B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Api.Tests", "test\OpenTelemetry.Api.Tests\OpenTelemetry.Api.Tests.csproj", "{FD8433F4-EDCF-475C-9B4A-625D3DE11671}"
EndProject
Global
Expand Down Expand Up @@ -543,6 +545,10 @@ Global
{9C99621C-343E-479C-A943-332DB6129B71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C99621C-343E-479C-A943-332DB6129B71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C99621C-343E-479C-A943-332DB6129B71}.Release|Any CPU.Build.0 = Release|Any CPU
{62AF4BD3-DCAE-4D44-AA5B-991C1071166B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62AF4BD3-DCAE-4D44-AA5B-991C1071166B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62AF4BD3-DCAE-4D44-AA5B-991C1071166B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62AF4BD3-DCAE-4D44-AA5B-991C1071166B}.Release|Any CPU.Build.0 = Release|Any CPU
{FD8433F4-EDCF-475C-9B4A-625D3DE11671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD8433F4-EDCF-475C-9B4A-625D3DE11671}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD8433F4-EDCF-475C-9B4A-625D3DE11671}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -589,6 +595,7 @@ Global
{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
{800DB925-6014-4136-AC01-3356CF7CADD3} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
{9C99621C-343E-479C-A943-332DB6129B71} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
{62AF4BD3-DCAE-4D44-AA5B-991C1071166B} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
Expand Down
53 changes: 53 additions & 0 deletions docs/trace/links-based-sampler/LinksAndParentBasedSampler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// <copyright file="LinksAndParentBasedSampler.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using OpenTelemetry.Trace;

namespace LinksAndParentBasedSamplerExample;

/// <summary>
/// An example of a composite sampler that has:
/// 1. A parent based sampler.
/// 2. A links based sampler.
/// The composite sampler first delegates to the parent based sampler and then to the
/// links based sampler. If either of these samplers decide to sample,
/// this composite sampler decides to sample.
/// </summary>
internal class LinksAndParentBasedSampler : Sampler
{
private readonly ParentBasedSampler parentBasedSampler;
private readonly LinksBasedSampler linksBasedSampler;

public LinksAndParentBasedSampler(ParentBasedSampler parentBasedSampler)
{
this.parentBasedSampler = parentBasedSampler ?? throw new ArgumentNullException(nameof(parentBasedSampler));
this.linksBasedSampler = new LinksBasedSampler();
}

public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
var samplingResult = this.parentBasedSampler.ShouldSample(samplingParameters);
if (samplingResult.Decision != SamplingDecision.Drop)
{
Console.WriteLine($"{samplingParameters.TraceId}: ParentBasedSampler decision: RecordAndSample");
return samplingResult;
}

Console.WriteLine($"{samplingParameters.TraceId}: ParentBasedSampler decision: Drop");

return this.linksBasedSampler.ShouldSample(samplingParameters);
}
}
49 changes: 49 additions & 0 deletions docs/trace/links-based-sampler/LinksBasedSampler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// <copyright file="LinksBasedSampler.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics;
using OpenTelemetry.Trace;

namespace LinksAndParentBasedSamplerExample;

/// <summary>
/// A non-probabilistic sampler that samples an activity if ANY of the linked activities
/// is sampled.
/// </summary>
internal class LinksBasedSampler : Sampler
{
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
if (samplingParameters.Links != null)
{
foreach (var activityLink in samplingParameters.Links)
{
if ((activityLink.Context.TraceFlags &
ActivityTraceFlags.Recorded) != 0)
{
// If any linked activity is sampled, we will include this activity as well.
Console.WriteLine($"{samplingParameters.TraceId}: At least one linked activity (TraceID: {activityLink.Context.TraceId}, SpanID: {activityLink.Context.SpanId}) is sampled. Hence, LinksBasedSampler decision is RecordAndSample");
return new SamplingResult(SamplingDecision.RecordAndSample);
}
}
}

// There are either no linked activities or none of them are sampled.
// Hence, we will drop this activity.
Console.WriteLine($"{samplingParameters.TraceId}: No linked span is sampled. Hence, LinksBasedSampler decision is Drop.");
return new SamplingResult(SamplingDecision.Drop);
}
}
68 changes: 68 additions & 0 deletions docs/trace/links-based-sampler/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// <copyright file="Program.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;

namespace LinksAndParentBasedSamplerExample;

internal class Program
{
private static readonly ActivitySource MyActivitySource = new("LinksAndParentBasedSampler.Example");

public static void Main(string[] args)
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(new LinksAndParentBasedSampler(new ParentBasedSampler(new TraceIdRatioBasedSampler(0.2))))
.AddSource("LinksAndParentBasedSampler.Example")
.AddConsoleExporter()
.Build();

for (var i = 0; i < 10; i++)
{
var links = GetActivityLinks(i);

// Create a new activity that links to the activities in the list of activity links.
using (var activity = MyActivitySource.StartActivity(ActivityKind.Internal, parentContext: default, tags: default, links: links))
{
activity?.SetTag("foo", "bar");
}

Console.WriteLine();
}
}

/// <summary>
/// Generates a list of activity links. A linked activity is sampled with a probability of 0.1.
/// </summary>
/// <returns>A list of links.</returns>
private static IEnumerable<ActivityLink> GetActivityLinks(int seed)
{
var random = new Random(seed);
var linkedActivitiesList = new List<ActivityLink>();

for (var i = 0; i < 5; i++)
{
int randomValue = random.Next(10);
var traceFlags = (randomValue == 0) ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None;
var context = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), traceFlags);
linkedActivitiesList.Add(new ActivityLink(context));
}

return linkedActivitiesList;
}
}
197 changes: 197 additions & 0 deletions docs/trace/links-based-sampler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Links Based Sampling: An Example

Certain scenarios such as a producer consumer scenario can be modelled using
"span links" to express causality between activities. An activity (span) in a trace
can link to any number of activities in other traces. When using a Parent Based
sampler, the sampling decision is made at the level of a single trace. This implies
that the sampling decision across such linked traces is taken independently without
any consideration to the links. This can result in incomplete information to reason
about a system. Ideally, it would be desirable to sample all linked traces together.

As one possible way to address this, this example shows how we can increase the
likelihood of having complete traces across linked traces.

## How does this sampling example work?

We use a composite sampler that makes use of two samplers:

1. A parent based sampler.
2. A links based sampler.

This composite sampler first delegates to the parent based sampler. If the
parent based sampler decides to sample, then the composite sampler decides
to sample. However, if the parent based sampler decides to drop, the composite
sampler delegates to the links based sampler. The links based sampler decides
to sample if the activity has any linked activities and if at least ONE of those
linked activities is sampled.

The links based sampler is not a probabilistic sampler. It is a biased sampler
that decides to sample an activity if any of the linked contexts are sampled.

## When should you consider such an option? What are the tradeoffs?

This may be a good option to consider if you want to get more complete traces
across linked traces. However, there are a few tradeoffs to consider:

- **Not guaranteed to give consistent sampling in all situations**: This
approach doesn't guarantee that you will get complete traces across linked
traces in all situations.

Let's look at a couple of cases using the same producer-consumer example
scenario. Let's say we have a producer activity (say with ID S1 in Trace T1) that
produces a message and a consumer activity (say with ID S2 in Trace T2) that
consumes the message.

Now, let's say that the producing activity S1 in trace T1 is sampled, say using the
decision of a parent based sampler. Now, let's say that the activity S2 in trace
T2 is not sampled based on the parent based sampler decision for T2. However,
since this activity S2 in T2 is linked to the producing activity (S1 in T1) that
is sampled, this mechanism ensures that the consuming activity (S2 in T2) will
also get sampled.

Alternatively, let's consider what happens if the producing activity S1 in
trace T1 is not sampled, say using the decision of a parent based sampler.
Now, let's say that the consuming activity S2 in trace T2 is sampled, based
on the decision of a parent based sampler. In this case, we can see that
activity S2 in trace T2 is sampled even though activity S1 in trace T1 is not
sampled. This is an example of a situation where this approach is not helpful.

Another example of a situation where you would get a partial trace is if the
consuming activity S2 in trace T2 is not the root activity in trace T2. In this
case, let's say there's a different activity S3 in trace T2 that is the root
activity. Let's say that the sampling decision for activity S3 was to drop it.
Now, since S2 in trace T2 links to S1 in trace T1, with this approach S2 will
be sampled (based on the linked context). Hence, the produced trace T2 will be
a partial trace as it will not include activity S3 but will include activity S2.

- **Can lead to higher volume of data**: Since this approach will sample in
activities even if one of the linked activities is sampled, it can lead to higher
volumes of data, as compared to regular head based sampling. This is because
we are making a non-probabilistic sampling decision here based on the sampling
decisions of linked activities. For example, if there are 20 linked activities and
even if only one of them is sampled, then the linking activity will be sampled.

## Sample Output

You should see output such as the below when you run this example.

```text
af448bc1cb3e5be4e4b56a8b6650785c: ParentBasedSampler decision: Drop
af448bc1cb3e5be4e4b56a8b6650785c: No linked span is sampled. Hence,
LinksBasedSampler decision is Drop.
1b08120fa35c3f4a37e0b6326dc7688c: ParentBasedSampler decision: Drop
1b08120fa35c3f4a37e0b6326dc7688c: No linked span is sampled. Hence,
LinksBasedSampler decision is Drop.
ff710bd70baf2e8e843e7b38d1fc4cc1: ParentBasedSampler decision: RecordAndSample
Activity.TraceId: ff710bd70baf2e8e843e7b38d1fc4cc1
Activity.SpanId: 620d9b218afbf926
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: LinksAndParentBasedSampler.Example
Activity.DisplayName: Main
Activity.Kind: Internal
Activity.StartTime: 2023-04-18T16:52:16.0373932Z
Activity.Duration: 00:00:00.0022481
Activity.Tags:
foo: bar
Activity.Links:
f7464f714b23713c9008f8fc884fc391 7d1c96a6f2c95556
6660db8951e10644f63cd385e7b9549e 526e615b7a70121a
4c94df8e520b32ff25fc44e0c8063c81 8080d0aaafa641af
70d8ba08181b5ec073ec8b5db778c41f 99ea6162257046ab
d96954e9e76835f442f62eece3066be4 ae9332547b80f50f
Resource associated with Activity:
service.name: unknown_service:links-sampler
68121534d69b2248c4816c0c5281f908: ParentBasedSampler decision: Drop
68121534d69b2248c4816c0c5281f908: No linked span is sampled. Hence,
LinksBasedSampler decision is Drop.
5042f2c52a08143f5f42be3818eb41fa: ParentBasedSampler decision: Drop
5042f2c52a08143f5f42be3818eb41fa: At least one linked activity
(TraceID: 5c1185c94f56ebe3c2ccb4b9880afb17, SpanID: 1f77abf0bded4ab9) is sampled.
Hence, LinksBasedSampler decision is RecordAndSample
Activity.TraceId: 5042f2c52a08143f5f42be3818eb41fa
Activity.SpanId: 0f8a9bfa9d7770e6
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: LinksAndParentBasedSampler.Example
Activity.DisplayName: Main
Activity.Kind: Internal
Activity.StartTime: 2023-04-18T16:52:16.0806081Z
Activity.Duration: 00:00:00.0018874
Activity.Tags:
foo: bar
Activity.Links:
ed77487f4a646399aea5effc818d8bfa fcdde951f29a13e0
f79860fdfb949f2c1f1698d1ed8036b9 e422cb771057bf7c
6326338d0c0cf3afe7c5946d648b94dc affc7a6c013ea273
c0750a9fa146062083b55227ac965ad4 b09d59ed3129779d
5c1185c94f56ebe3c2ccb4b9880afb17 1f77abf0bded4ab9
Resource associated with Activity:
service.name: unknown_service:links-sampler
568a2b9489c58e7a5a769d264a9ddf28: ParentBasedSampler decision: Drop
568a2b9489c58e7a5a769d264a9ddf28: No linked span is sampled. Hence,
LinksBasedSampler decision is Drop.
4f8d972b0d7727821ce4a307a7be8e8f: ParentBasedSampler decision: Drop
4f8d972b0d7727821ce4a307a7be8e8f: No linked span is sampled. Hence,
LinksBasedSampler decision is Drop.
ce940241ed33e1a030da3e9d201101d3: ParentBasedSampler decision: Drop
ce940241ed33e1a030da3e9d201101d3: At least one linked activity
(TraceID: ba0d91887309399029719e2a71a12f62, SpanID: 61aafe295913080f) is sampled.
Hence, LinksBasedSampler decision is RecordAndSample
Activity.TraceId: ce940241ed33e1a030da3e9d201101d3
Activity.SpanId: 5cf3d63926ce4fd5
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: LinksAndParentBasedSampler.Example
Activity.DisplayName: Main
Activity.Kind: Internal
Activity.StartTime: 2023-04-18T16:52:16.1127688Z
Activity.Duration: 00:00:00.0021072
Activity.Tags:
foo: bar
Activity.Links:
5223cff39311c741ef50aca58e4270c3 e401b6840acebf43
398b43fee8a75b068cdd11018ef528b0 24cfa4d5fb310b9d
34351a0f492d65ef92ca0db3238f5146 5c0a56a16291d765
ba0d91887309399029719e2a71a12f62 61aafe295913080f
de18a8af2d20972cd4f9439fcd51e909 4c40bc6037e58bf9
Resource associated with Activity:
service.name: unknown_service:links-sampler
ac46618da4495897bacd7d399e6fc6d8: ParentBasedSampler decision: Drop
ac46618da4495897bacd7d399e6fc6d8: No linked span is sampled. Hence,
LinksBasedSampler decision is Drop.
68a3a05e0348d2a2c1c3db34bc3fd2f5: ParentBasedSampler decision: Drop
68a3a05e0348d2a2c1c3db34bc3fd2f5: At least one linked activity
(TraceID: 87773d89fba942b0109d6ce0876bb67e, SpanID: 2aaac98d4e48c261) is sampled.
Hence, LinksBasedSampler decision is RecordAndSample
Activity.TraceId: 68a3a05e0348d2a2c1c3db34bc3fd2f5
Activity.SpanId: 3d0222f56b0e1e5d
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: LinksAndParentBasedSampler.Example
Activity.DisplayName: Main
Activity.Kind: Internal
Activity.StartTime: 2023-04-18T16:52:16.1553354Z
Activity.Duration: 00:00:00.0049821
Activity.Tags:
foo: bar
Activity.Links:
7175fbd18da2783dc594d1e8f3260c74 13019d9a06a5505b
59c9bdd52eb5cf23eae9001006743fcf 25573e0f1b290b8d
87773d89fba942b0109d6ce0876bb67e 2aaac98d4e48c261
0a1f65c47f556336b4028b515d363810 0816a2a2b7d4ea0b
7602375d3eae7e849a9dc27e858dc1c2 b918787b895b1374
Resource associated with Activity:
service.name: unknown_service:links-sampler
```
Loading

0 comments on commit 58d9d69

Please sign in to comment.