Skip to content
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

Add optional inner samplers to make ParentBasedSampler more customizable #1727

Merged
merged 21 commits into from
Feb 27, 2021

Conversation

mbakalov
Copy link
Contributor

Fixes #1710.

Changes

According to the spec the ParentBasedSampler should support optional parameters to allow delegating sampling decision for cases when remote/local parent was/wasn't sampled.

Added a new constructor to ParentBasedSampler that takes 4 more Samplers as arguments.

The spec still doesn't say anything about linked spans, so I left that part in.

  • CHANGELOG.md updated for non-trivial changes
  • Changes in public API reviewed - there is a public API change for the new ctor.

@mbakalov mbakalov requested a review from a team January 27, 2021 02:24
@codecov
Copy link

codecov bot commented Jan 27, 2021

Codecov Report

Merging #1727 (c07cb11) into main (cb066cb) will increase coverage by 0.21%.
The diff coverage is 99.33%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1727      +/-   ##
==========================================
+ Coverage   83.77%   83.99%   +0.21%     
==========================================
  Files         187      187              
  Lines        5967     5984      +17     
==========================================
+ Hits         4999     5026      +27     
+ Misses        968      958      -10     
Impacted Files Coverage Δ
src/OpenTelemetry/Logs/OpenTelemetryLogger.cs 71.42% <ø> (ø)
src/OpenTelemetry/Trace/TracerProviderSdk.cs 95.16% <98.64%> (+4.38%) ⬆️
...metryProtocol/Implementation/ActivityExtensions.cs 86.89% <100.00%> (+0.05%) ⬆️
...ry.Instrumentation.AspNet/AspNetInstrumentation.cs 100.00% <100.00%> (ø)
...umentation.AspNet/Implementation/HttpInListener.cs 89.10% <100.00%> (ø)
...entation.AspNet/TracerProviderBuilderExtensions.cs 100.00% <100.00%> (ø)
...umentation.AspNetCore/AspNetCoreInstrumentation.cs 100.00% <100.00%> (ø)
...tation.AspNetCore/Implementation/HttpInListener.cs 86.56% <100.00%> (ø)
...tion.AspNetCore/TracerProviderBuilderExtensions.cs 100.00% <100.00%> (ø)
...ntation.GrpcNetClient/GrpcClientInstrumentation.cs 100.00% <100.00%> (+12.50%) ⬆️
... and 13 more

@cijothomas cijothomas changed the base branch from master to main January 27, 2021 22:09
@cijothomas
Copy link
Member

@mbakalov Given we are very close to 1.0, I'll hold off from this PR for 1.0, so that we can take more time and review.
The changes are additive only, so should not be an issue. Thanks for being patient!

@cijothomas
Copy link
Member

@mbakalov Given we are very close to 1.0, I'll hold off from this PR for 1.0, so that we can take more time and review.
The changes are additive only, so should not be an issue. Thanks for being patient!

Will come back to this this week.

@mbakalov mbakalov marked this pull request as draft February 12, 2021 01:45
@mbakalov
Copy link
Contributor Author

Changed the API a bit to make additional samplers optional parameters in the new constructor.

The compiler's overload resolution prefers the first constructor when no other parameters are provided, so the existing code will still compile and work as expected.

public ParentBasedSampler(Sampler rootSampler)
{
}

public ParentBasedSampler(
	Sampler rootSampler,
	Sampler remoteParentSampled = null,
	Sampler remoteParentNotSampled = null,
	Sampler localParentSampled = null,
	Sampler localParentNotSampled = null)
	: this(rootSampler)
{
}

We can do things like var pb = new ParentBasedSampler(new AlwaysOnSampler(), localParentNotSampled: new AlwaysOnSampler());, which I think is a nice API now.

I'd also done some benchmarking and surprisingly the version that always delegates to inner samplers didn't turn out to be slower at all! Looks like all the inner sampler calls are getting inlined anyway. I've no idea why the delegating version is even a tiny bit faster with a slightly smaller code size too - perhaps I am doing something wrong?

|                    Method |     Mean |    Error |   StdDev |   Median | Ratio | RatioSD | Code Size |
|-------------------------- |---------:|---------:|---------:|---------:|------:|--------:|----------:|
|     ShouldSample_RootOnly | 19.60 ns | 1.285 ns | 3.790 ns | 18.09 ns |  1.00 |    0.00 |     723 B |
| ShouldSample_NullChecking | 18.77 ns | 0.428 ns | 0.627 ns | 18.69 ns |  0.83 |    0.09 |     891 B |
|   ShouldSample_Delegating | 18.34 ns | 0.411 ns | 0.821 ns | 18.26 ns |  0.80 |    0.09 |     719 B |

Finally, I've kept the current behavior of "always sample if a linked activity was sampled". To retain current semantics, it comes before the "parent not sampled" delegation, which is probably unexpected for the user. Should we provide a Sampler parenkLinkSampled parameter as well perhaps?

Switching this back to "ready for review" for more feedback 😄

@mbakalov mbakalov marked this pull request as ready for review February 16, 2021 12:20
@mbakalov
Copy link
Contributor Author

Added and cleaned up the benchmarking code I used to here:

[Benchmark(Baseline = true)]
public void ShouldSample_Original()
{
	this.sampler.ShouldSample_Original(this.remoteParentSampledParams);

	// this.sampler.ShouldSample_Original(this.localParentSampledParams);
	// this.sampler.ShouldSample_Original(this.remoteParentNotSampledParams);
	// this.sampler.ShouldSample_Original(this.localParentNotSampledParams);
}

[Benchmark]
public void ShouldSample_Delegating()
{
	this.sampler.ShouldSample(this.remoteParentSampledParams);

	// this.delegatingSampler.ShouldSample_Delegating(this.localParentSampledParams);
	// this.delegatingSampler.ShouldSample_Delegating(this.remoteParentNotSampledParams);
	// this.delegatingSampler.ShouldSample_Delegating(this.localParentNotSampledParams);
}
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1379 (1909/November2018Update/19H2)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.103
  [Host]     : .NET Core 3.1.12 (CoreCLR 4.700.21.6504, CoreFX 4.700.21.6905), X64 RyuJIT
  DefaultJob : .NET Core 3.1.12 (CoreCLR 4.700.21.6504, CoreFX 4.700.21.6905), X64 RyuJIT
|                  Method |     Mean |    Error |   StdDev |   Median | Ratio | RatioSD |
|------------------------ |---------:|---------:|---------:|---------:|------:|--------:|
|   ShouldSample_Original | 15.11 ns | 0.387 ns | 1.032 ns | 14.77 ns |  1.00 |    0.00 |
| ShouldSample_Delegating | 13.84 ns | 0.284 ns | 0.526 ns | 13.67 ns |  0.91 |    0.06 |

The ShouldSample_Delegating is the current suggested implementation that always delegates, while the ShouldSample_Original was the code we have in 1.0.1 currently, also repeated below. No idea why the delegating one is slightly faster!

public SamplingResult ShouldSample_Original(in SamplingParameters samplingParameters)
{
	var parentContext = samplingParameters.ParentContext;
	if (parentContext.TraceId == default)
	{
		// If no parent, use the rootSampler to determine sampling.
		return this.rootSampler.ShouldSample(samplingParameters);
	}

	// If the parent is sampled keep the sampling decision.
	if ((parentContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
	{
		return new SamplingResult(SamplingDecision.RecordAndSample);
	}

	if (samplingParameters.Links != null)
	{
		// If any linked context is sampled keep the sampling decision.
		// TODO: This is not mentioned in the spec.
		// Follow up with spec to see if context from Links
		// must be used in ParentBasedSampler.
		foreach (var parentLink in samplingParameters.Links)
		{
			if ((parentLink.Context.TraceFlags & ActivityTraceFlags.Recorded) != 0)
			{
				return new SamplingResult(SamplingDecision.RecordAndSample);
			}
		}
	}

	// If parent was not sampled, do not sample.
	return new SamplingResult(SamplingDecision.Drop);
}

@mbakalov
Copy link
Contributor Author

@cijothomas - just noticed a comment about links. I can remove as part of this PR. Will make an update later today!

@cijothomas
Copy link
Member

ve as part of this PR. Will

Could you do a standalone PR for that alone to make it easy for tracking purposes :)

Copy link
Member

@cijothomas cijothomas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a line to Changelog.md as well

@cijothomas
Copy link
Member

Leaving unmerged for 1 more day to give other reviewers a chance to raise concerns.

@cijothomas cijothomas merged commit 381908c into open-telemetry:main Feb 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow more customizable ParentBasedSampler
4 participants