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

HttpWebRequest ActivitySource #694

Merged

Conversation

CodeBlanch
Copy link
Member

@CodeBlanch CodeBlanch commented May 22, 2020

Resolves #680.
Resolves #639.

Changes

Updated HttpWebRequestDiagnosticSource to use the new .NET 5 ActivitySource API.

Work remaining:

  • Update unit tests.
  • Update sample.

/cc @cijothomas @tarekgh

Checklist

  • I ran Unit Tests locally.

@CodeBlanch
Copy link
Member Author

@tarekgh @cijothomas Request & response conversion was pretty straight-forward. Previously we had an option for setting HTTP flavor or not, wasn't sure how to bring that forward. The exception case got messy real fast. Before we had a mapping of the error to OT Status, I couldn't bring that forward. I know we're working on that. But there are also cases where we don't get a status code, just an exception. I was not sure what to do in that case. I feel like there should be a way to mark the Activity as faulted and set the exception? I'll drop a couple of comments on the code areas to highlight the before and after.

PS: I have it calling SetCustomProperty to put the raw objects on the Activity at the moment. Not sure if we want that? OT isn't using it, that would be for downstream users?

}

private static void ProcessException(TelemetrySpan span, Exception exception)
{
Copy link
Member Author

Choose a reason for hiding this comment

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

Previous exception handling is here. Three cases: 1) We have a response code (brought forward), 2) We have a WebExceptionStatus (gap), and 3) Unknown exception (kind of brought forward, I set the exception.Message as a tag).

}

if (exception is WebException wexc && wexc.Response is HttpWebResponse response)
Copy link
Member Author

Choose a reason for hiding this comment

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

Here is the new exception handling.

catch (Exception ex)
{
// If anything went wrong, just no-op. Write an event so at least we can find out.
var activity = ActivitySource.StartActivity(InitializationFailedActivityName, ActivityKind.Server);
Copy link
Member

Choose a reason for hiding this comment

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

This could be an abuse of activity - activity should represent an operation.. something with a begin and end notion.
Could we just log to EventSource at Error level instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated to use InstrumentationEventSource. Please review and LMK if that works!

var activity = ActivitySource.StartActivity(
ActivityName,
ActivityKind.Client,
Activity.Current?.Context ?? default,
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't need to pass Activity.Current.context here.. It should be implicit.
We should pass context, only if we extracted context from remote.

I believe you are passing it because the StartActivity method expects context as well, when tags are to be passed at creation time?

Copy link
Member

Choose a reason for hiding this comment

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

I could be wrong here, so asking @tarekgh for expert opinion :)

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 ran into a bit of a "chicken-and-egg" situation here. I wanted tags to be present when Activity is started so anyone listening would have some data to make more intelligent sampling decisions. But ActivitySource API only gives me .StartActivity operations. No way to create Activity, build it up, and then Start it. So I tried to pass in everything I could on the StartActivity call which required then the context.

@tarekgh Any guidance to offer? What should I pass for context here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe you can pass just default here. That will cause the Activity created and the parent context will automatically picked from Activity.Current.


In reply to: 429364884 [](ancestors = 429364884)

this.Write(activity.OperationName + ".Start", new { Request = request });
}
}
activity.SetCustomProperty("request", request);
Copy link
Member

Choose a reason for hiding this comment

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

Did our reflection hack DiagnosticSource inject w3ctraceparent headers? Or was it done at Listener?
In the new model, listener won't be able to do it, as "request" is part of customProperty, and not available at StartActivity callback.

Copy link
Member Author

Choose a reason for hiding this comment

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

Header injection is done right before the SetCustomProperty call as part of the InstrumentRequest call. Line 481 on the diff. No change in that regard.

activity.AddTag(SpanAttributeConstants.HttpStatusCodeKey, ((int)response.StatusCode).ToString());
activity.AddTag(SpanAttributeConstants.HttpStatusTextKey, response.StatusDescription);

activity.SetCustomProperty("response", response);
Copy link
Member

Choose a reason for hiding this comment

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

I am looking for where activity.Stop is called. Can you point me to where we stop activity?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's called on line 205 in ProcessResult right after RaiseResponseEvent or RaiseExceptionEvent is fired.

.AddInstrumentation((t) => new SqlClientInstrumentation(t, sqlOptions));
}

/// <summary>
/// Enables the outgoing requests automatic data collection.
Copy link
Member

Choose a reason for hiding this comment

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

lets make the comment more explicitly state that this enables outgoing calls made by HttpWebRequest, and is applicable in .NET Framework only.. something of that tone.
Maybe rename the method itself to be more specific about httpwebrequest.

We can later add a common AddAllDependencyInstrumentation helper covering all dependencies - thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

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

My thinking here was this one call would spin up all our dependency instrumentation, similar to what public static TracerBuilder AddDependencyInstrumentation(this TracerBuilder builder) does today. Right now it just has the one .NET Framework thing because we haven't converted the others, but that will be changing right?

It should look something like this at the end of the day:

        public static OpenTelemetryBuilder AddDependencyInstrumentation(
            this OpenTelemetryBuilder builder,
            Action<HttpClientInstrumentationOptions> configureHttpInstrumentationOptions = null,
            Action<SqlClientInstrumentationOptions> configureSqlInstrumentationOptions = null)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            builder.AddActivitySource(AzureClientsActivitySource.ActivitySourceName);
            builder.AddActivitySource(AzurePipelineActivitySource.ActivitySourceName);
            builder.AddActivitySource<HttpClientInstrumentationOptions>(HttpClientActivitySource.ActivitySourceName, configureHttpInstrumentationOptions);
            builder.AddActivitySource<SqlClientInstrumentationOptions>(SqlClientActivitySource.ActivitySourceName, configureSqlInstrumentationOptions);
#if NET461
            GC.KeepAlive(HttpWebRequestActivitySource.Instance);

            builder.AddActivitySource(HttpWebRequestActivitySource.ActivitySourceName);
#endif
            return builder;
        }

So it's not specific to .NET Framework. I also invented a way to pass options, looks like we have a gap there.

If we had this, then we would remove the existing TraceBuilder extensions?

Copy link
Member

Choose a reason for hiding this comment

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

I actually to say we need to have separate methods for each dependency - like AddSqlDependencyInstrumentation, AddHttpClientInstrumentation etc. So users can pick individual ones if they want.

We'll also have a method like AddDependencyInstrumentation() which most users would use. (exactly the one you shared above).

(having separate method allows us to turn on/off individual collections. very helpful when investigating some leak or issues, and want to isolate to a particular instrumentation)

Copy link
Member

Choose a reason for hiding this comment

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

And you are right about removing TracerBuilder extension method- Once we finish span->activity work, there would be no TracerBuilder!

Copy link
Member Author

Choose a reason for hiding this comment

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

I split the new stuff out into OpenTelemetryBuilderExtensions.cs and added a method for registering all sources (which will grow) or the HttpWebRequest source only. LMK.

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.

Excellent start @CodeBlanch . I have added comments. I am not an expert in the httwebrequest reflection hack, so some comments may not make sense! let me know.


InstrumentRequest(request, activity);

activity.SetCustomProperty("request", request);
Copy link
Contributor

@tarekgh tarekgh May 22, 2020

Choose a reason for hiding this comment

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

request [](start = 40, length = 7)

I would recommend to name the property name in more specific way to avoid collision for anyone else using the Activity with other scenarios.

maybe you can name something like HttpWebRequest, or HttpWebRequest (I am using underscores here but you can use any other symbol),. just to be unlikely wanyone else will use that name.

This should apply to the response and exceptions too.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated so the custom properties are named "HttpWebRequest.Request", "HttpWebRequest.Response", and "HttpWebRequest.Exception." The idea being they now have a kind of namespace so they don't collide with what the user might want to do.

@tarekgh
Copy link
Contributor

tarekgh commented May 22, 2020

The result of the latest discussions regarding OT Status can be seen here open-telemetry/opentelemetry-specification#306 (comment)

For your case, I would recommend setting some tag indicating if there is error or not (with setting proper error code if needed). My understanding http headers also have field to set the error code if needed. For exception, either you want o include the whole exception object using SetCustomProperty or you may call exception.ToString() and then stick the result as a tag instead. I would recommend using tags if you don't really need the exception object.


In reply to: 632503031 [](ancestors = 632503031)

@CodeBlanch CodeBlanch marked this pull request as ready for review May 23, 2020 19:03

this.openTelemetry = OpenTelemetrySdk.EnableOpenTelemetry(
(builder) => builder.AddDependencyInstrumentation()
.UseJaegerActivityExporter(c =>
Copy link
Member Author

Choose a reason for hiding this comment

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

@pjanotti FYI I updated to use JaegerActivityExporter since it was merged while I was working on this.

{
activity.DisplayName = BuildDisplayName(request);
Copy link
Member Author

Choose a reason for hiding this comment

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

@cijothomas @tarekgh We want to set Activity.DisplayName to what was previously Span.Name right?

Copy link
Contributor

Choose a reason for hiding this comment

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

that is right.

/// <remarks>
/// Basic implementation only. Most logic from TracerBuilder will be ported here.
/// </remarks>
public static void EnableOpenTelemetry(Action<OpenTelemetryBuilder> configureOpenTelemetryBuilder)
public static IDisposable EnableOpenTelemetry(Action<OpenTelemetryBuilder> configureOpenTelemetryBuilder)
Copy link
Member Author

Choose a reason for hiding this comment

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

@cijothomas I couldn't make the unit tests work without a way to destroy the ActivityListener we create internally so I return it as an IDisposable here. But we'll also need to also stop any ActivityProcessors/ActivityExporters on application shutdown right?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we should gracefully call processors & exporters to ensure they flush active spans before the application closes. I don't see a problem with making this IDisposable but we'll need to ensure docs and examples are updated to include this as a best practice too.


/* TBD: Span Status is not currently support on Activity.
Copy link
Member Author

Choose a reason for hiding this comment

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

Known gaps with Span.Status & Activity/Source.

}
else
{
activity.AddTag("error", exception.Message);
Copy link
Member

Choose a reason for hiding this comment

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

this is not a wellknown tagname right? Were we doing this previously? Most exporters export selected, well-known tags, so this may not be used

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 due to the Status gap with Span. Previous logic is here:

private static void ProcessException(TelemetrySpan span, Exception exception)
{
if (exception is WebException wexc)
{
if (wexc.Response is HttpWebResponse response)
{
span.PutHttpStatusCode((int)response.StatusCode, response.StatusDescription);
return;
}
switch (wexc.Status)
{
case WebExceptionStatus.Timeout:
span.Status = Status.DeadlineExceeded;
return;
case WebExceptionStatus.NameResolutionFailure:
span.Status = Status.InvalidArgument.WithDescription(exception.Message);
return;
case WebExceptionStatus.SendFailure:
case WebExceptionStatus.ConnectFailure:
case WebExceptionStatus.SecureChannelFailure:
case WebExceptionStatus.TrustFailure:
span.Status = Status.FailedPrecondition.WithDescription(exception.Message);
return;
case WebExceptionStatus.ServerProtocolViolation:
span.Status = Status.Unimplemented.WithDescription(exception.Message);
return;
case WebExceptionStatus.RequestCanceled:
span.Status = Status.Cancelled;
return;
case WebExceptionStatus.MessageLengthLimitExceeded:
span.Status = Status.ResourceExhausted.WithDescription(exception.Message);
return;
}
}
span.Status = Status.Unknown.WithDescription(exception.Message);
}
}

Today we get exception.Message as Status.Description ("ot.status_description") in cases where the exception wasn't tied to an Http status code. Agree "error" feels weird but not sure what to do. I could switch to "ot.status_description" but that also feels weird without "ot.status_code" also being sent? This was more or less a temporary thing while we wait to find out what we'll be doing about the Status gap.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed: this is temporary while OTel defines status and error reporting. That said error is a semantic tag in OpenTracing - https://docs.google.com/spreadsheets/d/1c8QDEBrN6ihVlTiz7Ak_CE-o2flR7XgdT8yFQeolPRc/edit#gid=2124992250 so it seems better to avoid it.

For the time being, setting both ot.status_description and ot.status_code seem reasonable to me.

Copy link
Member

Choose a reason for hiding this comment

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

Agree about setting ot.status_description and ot.status_code

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated

activity.AddTag("error", exception.Message);
}

activity.SetCustomProperty("HttpWebRequest.Exception", exception);
Copy link
Member

Choose a reason for hiding this comment

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

if some processor/exporter wants to add exception.Message, then can do so by reading this exception. That means we can avoid adding the "error" tag.

@CodeBlanch
Copy link
Member Author

@cijothomas Updated to check Activity.IsAllDataRequested based on our discussion.

Copy link
Member

@MikeGoldsmith MikeGoldsmith left a comment

Choose a reason for hiding this comment

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

I think this is a good start and accept then iterate as needed.

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.

I agree w/ @MikeGoldsmith: LGTM, I have a couple of Qs/suggestions but it is better to address them separately.

/// </summary>
public static class SpanHelper
{
private static readonly ConcurrentDictionary<StatusCanonicalCode, string> CanonicalCodeToStringCache = new ConcurrentDictionary<StatusCanonicalCode, string>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion for a separate PR depending if Status doesn't change much: since there are only 17 canonical codes an array should suffice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Only 17 he says :) I updated the PR since I was in there trying to work on some of your other feedback.

@@ -73,24 +73,24 @@ public override void OnStartActivity(Activity activity, object payload)
return;
}

this.Tracer.StartActiveSpanFromActivity(Constants.HttpSpanPrefix + request.Method, activity, SpanKind.Client, out var span);
this.Tracer.StartActiveSpanFromActivity(HttpTagHelper.GetOperationNameForHttpMethod(request.Method), activity, SpanKind.Client, out var span);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: there are reasons to not include the path by default in the name but it is very useful on the root span, it will be good to have an option to add it to the span name.

return hostTagValue;
}

private static string ConvertMethodToOperationName(string method) => $"HTTP {method}";
Copy link
Contributor

Choose a reason for hiding this comment

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

I interpret the addition of "HTTP " as a suggestion of the spec, in this case, it seems reasonable to directly use the Method field and avoid all caching and related. Or at least have an option for it. We can have a better conversation about it in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

That was a more recent change. Check out: #633. I agree having options to control it would be great. It was even talked about on that PR. I just went in and tried to make that work, but there are a few ways to do it we should probably discuss first. All of this logic is static, so I would need static options. Not really a big deal, but that would only work for this particular ActivitySource which we happen to control fully. For other sources, living in external code, we should have an options solution for those too. I see two places. In our ActivityListener, or in our ActivityProcessor. I think from an API perspective, something like this would be great:

builder.AddActivitySource(HttpWebRequestActivitySource.ActivitySourceName, (options) => 
{
   options.ActivityStarted => (Activity httpActivity) => ...code to modify name...
});

We would probably need to change to multiple listeners (instead of one) to make that work cleanly though?


InstrumentRequest(request, activity);

activity.SetCustomProperty("HttpWebRequest.Request", request);
Copy link
Contributor

Choose a reason for hiding this comment

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

Q.: why the property is set even if not recording? Similar for other methods.

Copy link
Member Author

Choose a reason for hiding this comment

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

Short answer: No reason, just my gut feel for how it should be.

Long answer: The only reason to add these custom properties at all is just in case someone downstream needs to add or change the data based on the real objects. If I don't add them in all cases (like it is currently doing), they won't be able to change anything when recording in the limited mode. I felt like we needed to add them always (if we're going to add them at all) so if downstream consumers wanted to have limited/full logic, they could. At the cost of an allocation to store the objects:

https://github.com/dotnet/runtime/blob/bd4697644e7024d1e5be94b674f73066bee112a4/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs#L816

Copy link
Contributor

Choose a reason for hiding this comment

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

That is a good point. Perhaps we want to have some convention for the keys so child spans don't depend on a specific implementation handling the parent. I am not sure if this is necessary but we may want to provide some helper to "flatten" the search for the property: the child needs to navigate parents to search for this property.

throw new ArgumentNullException(nameof(builder));
}

GC.KeepAlive(HttpWebRequestActivitySource.Instance);
Copy link
Contributor

Choose a reason for hiding this comment

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

Q.: Why is this 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.

To be honest, not totally sure. That came from the original version in dotnet/runtime:

https://github.com/dotnet/runtime/blob/bd4697644e7024d1e5be94b674f73066bee112a4/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticListener.cs#L40

I think it is basically a clever way of telling the runtime to initialize the static stuff in the HttpWebRequestActivitySource class. We need to touch some code for the runtime to do that, but we don't actually want to call anything. Doesn't really have anything to do with garbage collection AFAIK.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. It feels confusing because it references GC, perhaps it should be a method instead: Init() or Initialize() - anyway I think it is fine to address it later.

Copy link
Member

Choose a reason for hiding this comment

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

The Gc.Keepalive was part of the existing code, not introduced in this PR. Adding this note here and proceeding to merge this PR.


// The following parameter is not used now.
GetRequestedDataUsingParentId = (ref ActivityCreationOptions<string> options) => ActivityDataRequest.AllData,
GetRequestedDataUsingParentId = (ref ActivityCreationOptions<string> options) => ActivityDataRequest.None, // Defer to parent whether or not we should record.
Copy link
Member Author

Choose a reason for hiding this comment

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

@cijothomas I think we had a bug here. Before we returned ActivityDataRequest.AllData which would turn on sampling for a child when its parent was not being sampled. The way I'm reading the source (https://github.com/dotnet/runtime/blob/bd4697644e7024d1e5be94b674f73066bee112a4/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs#L123-L126) we need to return less than the parent in order to preserve its sampling decision?

@tarekgh API is a bit confusing here. Maybe return should be null when you want to defer to the parent's current ActivityDataRequest mode? Also, only the Id is available from the parent, would we ever need the full parent Activity here?

/cc @MikeGoldsmith @pjanotti

Copy link
Member Author

Choose a reason for hiding this comment

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

Nevermind! Reverted the change. I realized writing a unit test for this that Activity w/ parent still flows through GetRequestedDataUsingContext. Once dotnet/runtime#37185 is available I'll add that test. Can't do it right now unless this code is changed to pass Activity.Current?.Context ?? default as parentContext when starting its Activity, which is what the PR is addressing. All that being said, we'll have Parent.ActivityTraceFlags at time of sampling (gets us Recorded), but don't we also need Parent.IsAllDataRequested? That's not on ActivityContext.

My comment that the API is a bit confusing still stands :)

Copy link
Contributor

@tarekgh tarekgh May 31, 2020

Choose a reason for hiding this comment

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

@CodeBlanch

API is a bit confusing here. Maybe return should be null when you want to defer to the parent's current ActivityDataRequest mode? Also, only the Id is available from the parent, would we ever need the full parent Activity here?

The GetRequestedDataUsingParentId callback is called only when anyone calls Activity.StartActivity and passing the parent Id. Originally this callback requested by @noahfalk and will be used in the scenario in which the parent Id is enough to make the decision. I don't think the parent Activity object will be available in such scenarios because if it does, then GetRequestedDataUsingContext would be called instead. That is why the parent Activity is not used in such callbacks and we just use the Parent Id. The question now is, why you think the API is confusing? and what exactly the suggestion here? the listener has full control here to decide if interested in sampling such Activity with that parent Id or just ignore it and later decide about the child activities. why this is not enough?

Copy link
Member Author

Choose a reason for hiding this comment

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

@tarekgh Sorry this got long. I tried to make it "not a rambling mess" but the topic is big.

why you think the API is confusing?

Hard to say exactly! Sorry I know that isn't helpful :) We have the concept of "sampling" which I think people are naturally familiar with (somewhat?) but nothing in ActivitySource is really named along the lines of that. We have GetRequestedDataUsingParentId & GetRequestedDataUsingContext. Mental leap there to relate "GetRequestedData" to sampling and what we're trying to accomplish. I think just renaming these GetSamplingModeUsing... (GetSamplingLevel?) would help a lot. Maybe rename ActivityDataRequest -> ActivitySamplingMode. Just my $.02!

Here's an example of how hard it is to use this correctly:

Imagine a request comes into an application and an Activity is started. Sampler logic is run. It decides to sample at ActivityDataRequest.PropagationData level, for whatever reason. Now we go to call some dependency. A new Activity is created. Sampler looks at this new one. All it gets from the Parent (Activity.Current.Context) is ActivityTraceFlags, which will be ActivityTraceFlags.None in the case of ActivityDataRequest.PropagationData when Parent was sampled. What is a sampler to do here? It might think... the parent isn't recorded, nor should I be. Or it might think, I can decide to sample if I want to. But really it needs to know if ActivityContext != default than the parent was ActivityDataRequest.PropagationData and it should probably do the same for this child?

To fix that up I think we need to keep ActivityDataRequest on the Activity (or ActivityContext) and pass that in to GetRequestedDataUsing... for children. That will be maximum clarity.

Check out our current implementation:

GetRequestedDataUsingContext = (ref ActivityCreationOptions<ActivityContext> options) =>
{
var shouldSample = sampler.ShouldSample(
options.Parent,
options.Parent.TraceId,
default(ActivitySpanId), // Passing default SpanId here. The actual SpanId is not known before actual Activity creation
options.Name,
options.Kind,
options.Tags,
options.Links);
if (shouldSample.IsSampled)
{
return ActivityDataRequest.AllDataAndRecorded;
}
else
{
return ActivityDataRequest.None;
}
// TODO: Improve this to properly use ActivityDataRequest.AllData, PropagationData as well.
},

It doesn't have the rule for PropagationData so for my exercise above, we won't propagate the child. Bug?

It should be:

                    if (shouldSample.IsSampled)
                    {
                        return ActivityDataRequest.AllDataAndRecorded;
                    }
                    else if (options.Parent != default)
                    {
                        return ActivityDataRequest.PropagationData;
                    }
                    else
                    {
                        return ActivityDataRequest.None;
                    }

No?

Copy link
Member

Choose a reason for hiding this comment

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

There seem to be two separate suggestions here:

  1. Better name for RequestedData - like SamplingMode
  2. Make the ActivityDataRequest of parent available to GetRequestedDataUsingContext callback, so child can know the exact parent decision - instead of just knowing ActivityContext

Both are relevant, but not tied to this PR alone - lets discuss in separate issue, and proceed with merging this PR.

(I can create issues or @CodeBlanch can - let me know)

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.

Thanks!

@cijothomas cijothomas merged commit 96e1eb9 into open-telemetry:master Jun 2, 2020
@CodeBlanch CodeBlanch deleted the httpwebrequest-activitysource branch June 3, 2020 00:45
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.

Update HttpWebRequestDiagnosticSource to use .NET 5 ActivitySource Perf: Cache HTTP operation name
5 participants