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

Instrumentation Adapters to support Activity API #701

Merged
merged 33 commits into from Jun 11, 2020
Merged

Instrumentation Adapters to support Activity API #701

merged 33 commits into from Jun 11, 2020

Conversation

cijothomas
Copy link
Member

@cijothomas cijothomas commented May 26, 2020

Goal: Fixes 3-a,c,d,e,f from #684

An alternate proposal for #692
There are quite a number of TODOs part of this PR. I want to address them outside of this PR, as this PR already got too much with all the instrumentationadapters!. Want to fix all instrumentionadapters so as to find any issues with Activity API and give .NET Team feedback on the same.

This approach does the following:
Adds builder.AddActivitySource(string.Empty) to get callbacks for Activities created by old instrumentations. (they do new Activity(), instead of using ActivitySource). The ActivitySource will be empty for the activities created using old approach, and hence string.Empty is subscribed to get them.

Uses the same activity created by existing instrumentation, and adds tags to it by reading from payload.
The instrumentation now makes calls to Sampler, and set IsAllDataRequested and TraceFlags on Activity appropriately.

Compared to #692 , this avoids creating a new duplicate Activity. But this now forces Instrumentation to have dependency on the SDK, to do sampling.

Major TODOs I'll address following this PR:

  1. Kind will be settable once .NET ships new build. This PR currently uses reflection.

  2. Avoid instrumentation being dependent on Sampling - its currently hardcoded. We are exploring if .NET can expose a method to run GetRequestedDataUsingContext pipeline (this is where we attach sampling logic) for activities created outside ActivitySource. If this happens, we'll simply switch to it. If not, we'll expose similar in OP.API, so instrumentation simply calls this method which internally can determine sampling. (Exact details to be designed as followup)

  3. Subscribing to ActivitySource String.Empty is probably too noisy. Need to design an alternative. Its possible to NOT subscribe to old Activity using ActivitySource, and instead make Instrumentation call the Start/Stop (or equivalent). (Exact details to be designed as followup)

  4. Support ITextFormat for ActivityContext. This PR simply ignores this code path now.

  5. Find a way to do filters. The current approach of httpclient/aspnet/core Filter mechanism is broken with this PR.

  6. AzureClient instrumentation - kept it currently, but this will be moved outside of this repo.

  7. Move the diagnosticsource listener code from SDK to API, so Instrumentation can depend on it by just referring to the OT API. (This issue is already existing, but listing here as well)

  8. Cleanup ListenerHandle - there is code copying activity tags into span etc - they need to be removed once Activity replaces Span.

(tests doing some of the above are disabled until actual code fix is done)

I will open issues to track these separately and submit follow ups.

var request = context.Request;

// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
if (activity != null)
Copy link
Contributor

Choose a reason for hiding this comment

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

activity [](start = 16, length = 8)

is it possible the Activity can be null?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. I didnt need the activity null check.

@@ -35,6 +36,7 @@ internal class HttpInListener : ListenerHandler
private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template");
private readonly bool hostingSupportsW3C = false;
private readonly AspNetCoreInstrumentationOptions options;
private readonly ActivitySampler sampler = new AlwaysOnActivitySampler();
Copy link
Member Author

Choose a reason for hiding this comment

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

hardcoded sampler for just prototyping.

activity.DisplayName = path;
}

var samplingDecision = this.sampler.ShouldSample(activity.Context, activity.TraceId, default, path, activity.Kind, activity.Tags, activity.Links);
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the part which makes me not 100% convinved this approach is right. All instrumentationadatpers now need to know about Samplers (which is SDK concept).

Instead of simply checking if activity.IsAllDataRequested, its now first setting IsAllDataRequested by running sampling algorithm

@@ -41,6 +43,16 @@ public SimpleActivityProcessor(ActivityExporter exporter)
/// <inheritdoc />
public override void OnStart(Activity activity)
{
// Perform sampling decision if source is empty.
Copy link
Member Author

Choose a reason for hiding this comment

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

this is not 100% correct, as the sampling decision is now made with the current ActivityKind (which would be Internal, the default value). Though none of the built-in Samplers makes use of ActivityKind, nothing prevents one from writing Sampler which takes Kind into consideration.

More better would be to "feed" this activity created outside of ActivitySource, into the ActivityListener's GetRequestedDataUsingContext pipeline. That can be done in the instrumentationadapter itself, after setting the Kind to correct one, (and possibly enrich activity with more tags/links/events etc. if needed)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @CodeBlanch for proposing the idea of "feeding" an external activity to the GetRequestedDataUsingContext pipeline.

Copy link
Member

Choose a reason for hiding this comment

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

Did you present the idea to .NET team? Any feedback?

Copy link
Contributor

Choose a reason for hiding this comment

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

@CodeBlanch we (the .NET team) will look at that to decide what is the best way can achieve this scenario. Thanks for your feedback, it is really helpful.

Copy link
Member Author

Choose a reason for hiding this comment

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

Given that making sampling decision in SDK piece is going to be incorrect (as we have incorrect Kind), I'll move the sampling logic into the InstrumentationAdapters. Of course this forces Instrumentation to have dependency on Sampling - but doing this to make progress and uncover any other potential issues.

If everything else is good, and the only remaining issue is Instrumentation being aware of Samplers - we'll try to get a new API in activity source to run the GetRequestedDataUsingContext.

Copy link
Contributor

Choose a reason for hiding this comment

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

Given that making sampling decision in SDK piece is going to be incorrect (as we have incorrect Kind)

Is this to set the Kind? reflection can work here till we expose the Kind setter.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can't set the Kind to the correct one in the OT SDK as SDK doesn't know what is the actual kind. InstrumentationAdapter knows it, and it sets it.

@@ -39,6 +39,8 @@ public static class OpenTelemetryBuilderExtensions
throw new ArgumentNullException(nameof(builder));
}

// Asp.Net Core is not instrumented with ActivitySource, hence
Copy link
Member

Choose a reason for hiding this comment

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

@cijothomas Question... if we listen for string.Empty in theory we'll get activities from a bunch of legacy DiagnosticSources flowing through our listener, do we need to filter out the stuff we don't care about somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right. This will be followed up.

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Jun 8, 2020

CLA Check
The committers are authorized under a signed CLA.

@cijothomas cijothomas closed this Jun 8, 2020
@cijothomas cijothomas reopened this Jun 8, 2020
@cijothomas cijothomas marked this pull request as ready for review June 9, 2020 20:25
@cijothomas cijothomas changed the title Asp.Net Core Requests Instrumentation - alternate approach reusing existing Activity Instrumentation Adapters to support Activity API Jun 9, 2020
});
OpenTelemetrySdk.Default.EnableOpenTelemetry(
(builder) => builder.AddRequestInstrumentation().AddDependencyInstrumentation()
.UseJaegerActivityExporter(o =>
Copy link
Member Author

Choose a reason for hiding this comment

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

moved to jaeger as zipkin exporter with activity is not yet ready

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var res = httpClient.GetStringAsync("http://google.com").Result;
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this is only for tests and not part of the final merge.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes I can remove that. The testhttpClient example was doing something similar.

Copy link
Member

Choose a reason for hiding this comment

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

To provide a little history, when I was building the .NET Framework instrumentation I added in those calls so I could do integration testing, basically. Make sure incoming & outgoing requests would show up in Jaeger/Zipkin correctly. In the end it made the samples more interesting so I left them in. 🤷‍♂️

Copy link
Contributor

@pjanotti pjanotti Jun 10, 2020

Choose a reason for hiding this comment

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

I see, what about adding a comment stating that it exists to also include outgoing on the sample?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes adding a comment.

<ProjectReference Include="..\..\..\src\OpenTelemetry.Instrumentation.AspNetCore\OpenTelemetry.Instrumentation.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\src\OpenTelemetry.Instrumentation.Dependencies\OpenTelemetry.Instrumentation.Dependencies.csproj" />
<ProjectReference Include="..\..\..\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
Copy link
Contributor

Choose a reason for hiding this comment

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

I will create the Activity exporter for Zipkin this week or the next.

"Endpoint": "http://localhost:9411/api/v2/spans"
"Jaeger": {
"ServiceName": "jaeger-test",
"Host": "localhost",
Copy link
Contributor

Choose a reason for hiding this comment

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

Side note: it feels strange that we need to set Jaeger Host and Port to what it should be default values. Anyway, to be addressed in a separate PR.

Copy link
Member

Choose a reason for hiding this comment

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

The defaults look like they are in there:

public string AgentHost { get; set; } = "localhost";
/// <summary>
/// Gets or sets the Jaeger agent "compact thrift protocol" port. Default value: 6831.
/// </summary>
public int AgentPort { get; set; } = 6831;

But IMO it's still worthwhile to show how you can change them via settings in the samples.

Copy link
Contributor

Choose a reason for hiding this comment

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

it's still worthwhile to show how you can change them via settings in the samples.

Makes sense, perhaps we should comment where appropriate so the code setting (again) the default values don't get copied and pasted everywhere.


TelemetrySpan span;
if (this.options.TextFormat is TraceContextFormat)
activity.DisplayName = path;
Copy link
Contributor

Choose a reason for hiding this comment

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

Due to the risk of high-cardinality on the path OTel spec suggests using the HTTP method instead. That said the path is much more useful in general and having an option to add it (more likely the absolute path) will be useful. There are other locations also using the path as DisplayName, but other correctly use the helper that gets the name per OTel spec. BTW, the link on the comment above is broken.

Copy link
Member

Choose a reason for hiding this comment

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

@pjanotti We updated the outgoing calls (#633) but it looks like we didn't do incoming.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes incoming requests were still using path as SpanName, hence I used it as activity.DisplayName.
This shouldn't block this PR, as we can (and should) address this separate from this PR.

{
public static OpenTelemetrySdk Default = new OpenTelemetrySdk();
Copy link
Member

Choose a reason for hiding this comment

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

My vote would be for:

public class OpenTelemetrySdk : IDisposable
{
   private OpenTelemetrySdk() {}
   public static OpenTelemetrySdk EnableOpenTelemetry(Action<OpenTelemetryBuilder> configureOpenTelemetryBuilder)
}

Usage: OpenTelemetrySdk.EnableOpenTelemetry(...)

Over:

public class OpenTelemetrySdk : IDisposable
{
   public static OpenTelemetrySdk Default = new OpenTelemetrySdk();
   private OpenTelemetrySdk() {}
   public OpenTelemetrySdk EnableOpenTelemetry(Action<OpenTelemetryBuilder> configureOpenTelemetryBuilder)
}

Usage: OpenTelemetrySdk.Default.EnableOpenTelemetry(...)

Reason: The Default doesn't seem to add anything and it's confusing because it is IDisposable. When I first saw it I thought I had to call OpenTelemetrySdk.Default.Dispose() on shutdown. But really you need to dispose your instance, eg:

private OpenTelemetrySdk _OpenTelemetrySdk;
public void OnAppStart() => _OpenTelemetrySdk = OpenTelemetrySdk.EnableOpenTelemetry(...);
public void OnAppStop() => _OpenTelemetrySdk?.Dispose();

If we want to keep the default, the pattern should be like this?

public void OnAppStart() => OpenTelemetrySdk.Default.EnableOpenTelemetry(...);
public void OnAppStop() => _OpenTelemetrySdk.Default.Dispose();

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not 100% sure but I tend to like your approach @CodeBlanch but I think we can address it in a separate PR/discussion.

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 shared another proposal which sounds very good! I will add it as a follow up PR.

Copy link
Contributor

@pjanotti pjanotti left a comment

Choose a reason for hiding this comment

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

@cijothomas we have some follow-ups but it is in a good point to merge.

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.

None yet

4 participants