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

SuppressInstrumentation from ActivityListener and DiagnosticSourceListener #1079

Merged
5 changes: 5 additions & 0 deletions src/OpenTelemetry/Instrumentation/DiagnosticSourceListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public void OnError(Exception error)

public void OnNext(KeyValuePair<string, object> value)
{
if (Sdk.SuppressInstrumentation)
Copy link
Member

Choose a reason for hiding this comment

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

Probably a good optimization to have this, but I don't think it's strictly required. For "legacy" Activity flows we pass them through an ActivitySource for sampling. So the logic below should also catch these?

Copy link
Member Author

Choose a reason for hiding this comment

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

@CodeBlanch Hmm 🤔, maybe I need you to point me to some code... because without this check here, this test I wrote would fail:

https://github.com/open-telemetry/opentelemetry-dotnet/pull/1079/files#diff-a266e12581b9c9ae7bd148bbf91dabb8R37-R59

So, it would seem that for legacy activities, we'll need a check somewhere in addition to the check I have in the ActivityListener for the new activities.

Copy link
Member

Choose a reason for hiding this comment

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

You're right, my bad. That was a test and you passed, great job!

It looks like ActivitySourceAdapter has its own sampler, so we'll need to do similar in here:

private void RunGetRequestedData(Activity activity)
{
ActivityContext parentContext;
if (string.IsNullOrEmpty(activity.ParentId))
{
parentContext = default;
}
else if (activity.Parent != null)
{
parentContext = activity.Parent.Context;
}
else
{
parentContext = new ActivityContext(
activity.TraceId,
activity.ParentSpanId,
activity.ActivityTraceFlags,
activity.TraceStateString,
isRemote: true);
}
var samplingParameters = new SamplingParameters(
parentContext,
activity.TraceId,
activity.DisplayName,
activity.Kind,
activity.TagObjects,
activity.Links);
var samplingResult = this.sampler.ShouldSample(samplingParameters);
switch (samplingResult.Decision)
{
case SamplingDecision.NotRecord:
activity.IsAllDataRequested = false;
break;
case SamplingDecision.Record:
activity.IsAllDataRequested = true;
break;
case SamplingDecision.RecordAndSampled:
activity.IsAllDataRequested = true;
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
break;
}
}
}
}

Or maybe we can refactor so more is shared?

Copy link
Member Author

@alanwest alanwest Aug 20, 2020

Choose a reason for hiding this comment

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

That was a test and you passed, great job!

Ha! Phew!

It looks like ActivitySourceAdapter has its own sampler, so we'll need to do similar in here

I don't think so because this is actually downstream of the check I have in place.

So, DiagnosticSourceListener.OnNext --- invokes ---> ListenerHandler.On[Start|End|Exception|Custom]

It is our ListenerHandlers that hold an instance of the ActivitySourceAdapter which is then the thing that simulates Start/Stop/etc and deals with sampling like the standard ActivitySource/ActivityListener.

Copy link
Member

Choose a reason for hiding this comment

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

Are you saying it wouldn't work, or just that this spot is sooner? If the later, I suppose that is compelling enough a reason to keep it here and I'm good with it 👍

Copy link
Member Author

@alanwest alanwest Aug 20, 2020

Choose a reason for hiding this comment

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

It would work for preventing this.activityProcessor.OnStart(activity);, but the reason I put the check where I did in DiagnosticSourceListener.OnNext is that the check there prevents all instrumentation callbacks defined on the ListenerHandler from being invoked. For example, if some library had:

myDiagnosticListener.Write("MyCustomEvent", payload);

If we had a ListenerHander wired up to this library then this Write would result in invoking the OnCustom callback. I figured it would be good to suppress all aspects of the library's instrumentation.

Though I guess in most (if not all) of these callbacks we check activity.IsAllDataRequested, so they'd result in noops anyways...

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense good call!

{
return;
}

if (!this.handler.SupportsNullActivity && Activity.Current == null)
{
InstrumentationEventSource.Log.NullActivity(value.Key);
Expand Down
9 changes: 6 additions & 3 deletions src/OpenTelemetry/Trace/TracerProviderSdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,21 @@ internal TracerProviderSdk(

if (sampler is AlwaysOnSampler)
{
listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) => ActivityDataRequest.AllDataAndRecorded;
listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ActivityDataRequest.AllDataAndRecorded : ActivityDataRequest.None;
}
else if (sampler is AlwaysOffSampler)
{
/*TODO: Change options.Parent.SpanId to options.Parent.TraceId
once AutoGenerateRootContextTraceId is removed.*/
listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) => PropagateOrIgnoreData(options.Parent.SpanId);
listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? PropagateOrIgnoreData(options.Parent.SpanId) : ActivityDataRequest.None;
}
else
{
// This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext.
listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) => ComputeActivityDataRequest(options, sampler);
listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ComputeActivityDataRequest(options, sampler) : ActivityDataRequest.None;
}

if (sources.Any())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// <copyright file="DiagnosticSourceListenerTest.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 Xunit;

namespace OpenTelemetry.Instrumentation.Tests
{
public class DiagnosticSourceListenerTest
{
private const string TestSourceName = "TestSourceName";
private DiagnosticSource diagnosticSource;
private TestListenerHandler testListenerHandler;
private DiagnosticSourceSubscriber testDiagnosticSourceSubscriber;

public DiagnosticSourceListenerTest()
{
this.diagnosticSource = new DiagnosticListener(TestSourceName);
this.testListenerHandler = new TestListenerHandler(TestSourceName);
this.testDiagnosticSourceSubscriber = new DiagnosticSourceSubscriber(this.testListenerHandler, null);
this.testDiagnosticSourceSubscriber.Subscribe();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ListenerHandlerIsNotInvokedWhenSuppressInstrumentationTrue(bool suppressInstrumentation)
{
using var scope = SuppressInstrumentationScope.Begin(suppressInstrumentation);

var activity = new Activity("Main");
this.diagnosticSource.StartActivity(activity, null);
this.diagnosticSource.StopActivity(activity, null);

if (suppressInstrumentation)
{
Assert.Equal(0, this.testListenerHandler.OnStartInvokedCount);
Assert.Equal(0, this.testListenerHandler.OnStopInvokedCount);
}
else
{
Assert.Equal(1, this.testListenerHandler.OnStartInvokedCount);
Assert.Equal(1, this.testListenerHandler.OnStopInvokedCount);
}
}
}
}
53 changes: 53 additions & 0 deletions test/OpenTelemetry.Tests/Instrumentation/TestListenerHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// <copyright file="TestListenerHandler.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;

namespace OpenTelemetry.Instrumentation.Tests
{
public class TestListenerHandler : ListenerHandler
{
public int OnStartInvokedCount = 0;
public int OnStopInvokedCount = 0;
public int OnExceptionInvokedCount = 0;
public int OnCustomInvokedCount = 0;

public TestListenerHandler(string sourceName)
: base(sourceName)
{
}

public override void OnStartActivity(Activity activity, object payload)
{
this.OnStartInvokedCount++;
}

public override void OnStopActivity(Activity activity, object payload)
{
this.OnStopInvokedCount++;
}

public override void OnException(Activity activity, object payload)
{
this.OnExceptionInvokedCount++;
}

public override void OnCustom(string name, Activity activity, object payload)
{
this.OnCustomInvokedCount++;
}
}
}
18 changes: 18 additions & 0 deletions test/OpenTelemetry.Tests/Trace/TracerProvideSdkTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,24 @@ public void TracerSdkSetsActivityDataRequestBasedOnSamplingDecision()
}
}

[Fact]
public void TracerSdkSetsActivityDataRequestToNoneWhenSuppressInstrumentationIsTrue()
{
using var scope = SuppressInstrumentationScope.Begin();

var testSampler = new TestSampler();
using var activitySource = new ActivitySource(ActivitySourceName);
using var sdk = Sdk.CreateTracerProviderBuilder()
.AddSource(ActivitySourceName)
.SetSampler(testSampler)
.Build();

using (var activity = activitySource.StartActivity("root"))
{
Assert.Null(activity);
}
}

[Fact]
public void ProcessorDoesNotReceiveNotRecordDecisionSpan()
{
Expand Down