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

[http] Add http.client.request.duration metric and .NET Framework support #4870

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
17d67e0
snapshot
matt-hensley Sep 15, 2023
800a71d
null actvitiy is tracing is disabled
matt-hensley Sep 15, 2023
4a9239e
setup metics without tracing
matt-hensley Sep 15, 2023
d7b2472
only emit when there is a listener
matt-hensley Sep 15, 2023
46de1a9
modify tests for netfx
matt-hensley Sep 15, 2023
4e445b5
always capture response status code
matt-hensley Sep 18, 2023
f0ca432
Merge branch 'main' into net462httpclientmetricsv3
matt-hensley Sep 18, 2023
bed7b2b
undo conditional extraction
matt-hensley Sep 18, 2023
ba04ac0
activity duration or stopwatch
matt-hensley Sep 18, 2023
48c354a
activity duration or stopwatch
matt-hensley Sep 18, 2023
872ce63
Merge branch 'net462httpclientmetricsv3' of https://github.com/matt-h…
matt-hensley Sep 18, 2023
6999167
activity may be null here
matt-hensley Sep 18, 2023
42e9022
duration -> durationMs for now
matt-hensley Sep 18, 2023
527106f
StartTimestamp handling
matt-hensley Sep 18, 2023
e31f687
adopt taglist
matt-hensley Sep 18, 2023
01840c0
using directive is unnecessary
matt-hensley Sep 18, 2023
9ef2915
using directive is unnecessary
matt-hensley Sep 18, 2023
47fc955
Merge branch 'net462httpclientmetricsv3' of https://github.com/matt-h…
matt-hensley Sep 18, 2023
d9ca497
remove URL attribute
matt-hensley Sep 18, 2023
308ca8f
status enum to int
matt-hensley Sep 18, 2023
480940e
remove ternary
matt-hensley Sep 19, 2023
27ad5ab
early return if tracing and metrics are not enabled
matt-hensley Sep 19, 2023
025c02d
set activity
matt-hensley Sep 19, 2023
dfaa8f7
Update src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebR…
matt-hensley Sep 19, 2023
ad10683
Merge branch 'net462httpclientmetricsv3' of https://github.com/matt-h…
matt-hensley Sep 19, 2023
8bd64ef
Update src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebR…
matt-hensley Sep 19, 2023
6ca5c9d
Update src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebR…
matt-hensley Sep 19, 2023
61a01f7
Merge branch 'net462httpclientmetricsv3' of https://github.com/matt-h…
matt-hensley Sep 19, 2023
b4dd250
flag name change
matt-hensley Sep 19, 2023
bca1361
remove Moq from test
matt-hensley Sep 19, 2023
5200afb
need to pick context off current activity for propagator to work
matt-hensley Sep 19, 2023
4d15ca6
extra line
matt-hensley Sep 19, 2023
dd07ca0
modify tests to handle metrics and/or tracing on/off
matt-hensley Sep 19, 2023
fc47558
null handling
matt-hensley Sep 19, 2023
9ae0193
null check for safety, tests don't blow up here for some reason?
matt-hensley Sep 20, 2023
9c4c3b5
Merge branch 'main' into net462httpclientmetricsv3
matt-hensley Sep 20, 2023
dcdde2c
remove note that netfx does not have metrics
matt-hensley Sep 20, 2023
a063be1
changelog entry
matt-hensley Sep 20, 2023
7ce3c51
correct whitespace
matt-hensley Sep 22, 2023
bef3862
Merge branch 'main' into net462httpclientmetricsv3
matt-hensley Sep 22, 2023
812ad91
reorder methods per linter
matt-hensley Sep 22, 2023
2cfc3e2
putting the combinations in one test is causing intermittent failures
matt-hensley Sep 22, 2023
051cb42
Merge branch 'main' into net462httpclientmetricsv3
matt-hensley Sep 23, 2023
0f37992
lift null checks
matt-hensley Sep 23, 2023
86c8ab0
make this isn't hit when tracing is disabled
matt-hensley Sep 23, 2023
7704a82
Update src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
matt-hensley Sep 25, 2023
7e03ca7
Merge branch 'main' into net462httpclientmetricsv3
matt-hensley Sep 25, 2023
0ce3ced
modify test to ensure duration was written
matt-hensley Sep 26, 2023
5218b65
Fixes, tweaks, and cleanup.
CodeBlanch Sep 27, 2023
e70629b
Code review.
CodeBlanch Sep 29, 2023
616f299
Add support for http.client.request.duration.
CodeBlanch Oct 2, 2023
1502d64
Update src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHand…
CodeBlanch Oct 2, 2023
34e39f4
MD lint.
CodeBlanch Oct 2, 2023
9f1f887
Merge branch 'net462httpclientmetricsv3' of https://github.com/matt-h…
CodeBlanch Oct 2, 2023
f2e864b
Merge branch 'main' into net462httpclientmetricsv3
CodeBlanch Oct 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

## Unreleased

* Introduced a new metric, `http.client.request.duration` measured in seconds.
The OTel SDK
[applies custom histogram buckets](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
for this metric to comply with the
[Semantic Convention for Http Metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md).
This new metric is only available for users who opt-in to the new
semantic convention by configuring the `OTEL_SEMCONV_STABILITY_OPT_IN`
environment variable to either `http` (to emit only the new metric) or
`http/dup` (to emit both the new and old metrics).
([#4870](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4870))

* New metric: `http.client.request.duration`
* Unit: `s` (seconds)
* Histogram Buckets: `0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5,
0.75, 1, 2.5, 5, 7.5, 10`
* Old metric: `http.client.duration`
* Unit: `ms` (milliseconds)
* Histogram Buckets: `0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500,
5000, 7500, 10000`

Note: The older `http.client.duration` metric and
`OTEL_SEMCONV_STABILITY_OPT_IN` environment variable will eventually be
removed after the HTTP semantic conventions are marked stable. At which time
this instrumentation can publish a stable release. Refer to the specification
for more information regarding the new HTTP semantic conventions:
* [http-spans](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-spans.md)
* [http-metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md)

* Added support for publishing `http.client.duration` &
`http.client.request.duration` metrics on .NET Framework
([#4870](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4870))

## 1.5.1-beta.1

Released 2023-Jul-20
Expand Down
14 changes: 4 additions & 10 deletions src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
// limitations under the License.
// </copyright>

using System.Diagnostics.Metrics;
using System.Reflection;
using OpenTelemetry.Instrumentation.Http.Implementation;

namespace OpenTelemetry.Instrumentation.Http;
Expand All @@ -25,18 +23,13 @@ namespace OpenTelemetry.Instrumentation.Http;
/// </summary>
internal sealed class HttpClientMetrics : IDisposable
{
internal static readonly AssemblyName AssemblyName = typeof(HttpClientMetrics).Assembly.GetName();
internal static readonly string InstrumentationName = AssemblyName.Name;
internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString();

private static readonly HashSet<string> ExcludedDiagnosticSourceEvents = new()
{
"System.Net.Http.Request",
"System.Net.Http.Response",
};

private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
private readonly Meter meter;

private readonly Func<string, object, object, bool> isEnabled = (activityName, obj1, obj2)
=> !ExcludedDiagnosticSourceEvents.Contains(activityName);
Expand All @@ -47,15 +40,16 @@ internal sealed class HttpClientMetrics : IDisposable
/// <param name="options">HttpClient metric instrumentation options.</param>
public HttpClientMetrics(HttpClientMetricInstrumentationOptions options)
{
this.meter = new Meter(InstrumentationName, InstrumentationVersion);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener", this.meter, options), this.isEnabled, HttpInstrumentationEventSource.Log.UnknownErrorProcessingEvent);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(
new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener", options),
this.isEnabled,
HttpInstrumentationEventSource.Log.UnknownErrorProcessingEvent);
this.diagnosticSourceSubscriber.Subscribe();
}

/// <inheritdoc/>
public void Dispose()
{
this.diagnosticSourceSubscriber?.Dispose();
this.meter?.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#if NETFRAMEWORK
using System.Net.Http;
#endif
using System.Reflection;
using OpenTelemetry.Trace;
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;

Expand All @@ -31,21 +32,25 @@ internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler
{
internal const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop";

internal static readonly AssemblyName AssemblyName = typeof(HttpClientMetrics).Assembly.GetName();
internal static readonly string MeterName = AssemblyName.Name;
internal static readonly string MeterVersion = AssemblyName.Version.ToString();
internal static readonly Meter Meter = new(MeterName, MeterVersion);
private static readonly Histogram<double> HttpClientDuration = Meter.CreateHistogram<double>("http.client.duration", "ms", "Measures the duration of outbound HTTP requests.");
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
private static readonly Histogram<double> HttpClientRequestDuration = Meter.CreateHistogram<double>("http.client.request.duration", "s", "Measures the duration of outbound HTTP requests.");
Copy link
Member

Choose a reason for hiding this comment

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

nit: we are creating an instrument here even in when it will not be used (based on the option selected by user).

Copy link
Member

Choose a reason for hiding this comment

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

Agree, but I wasn't worried about it. For example if user is using only tracing they'll have a Meter + Histogram today that is just sitting there of no use to them. So what's one more thingy? 🤣 Keeping them static readonlys is nice for hot path perf when they are used though. Plus I figured when @TimothyMothra cleans this up we'll go back to just a single histogram in play. Decided to just keep it dumb/simple, basically.


private static readonly PropertyFetcher<HttpRequestMessage> StopRequestFetcher = new("Request");
private static readonly PropertyFetcher<HttpResponseMessage> StopResponseFetcher = new("Response");
private readonly Histogram<double> httpClientDuration;
private readonly HttpClientMetricInstrumentationOptions options;
private readonly bool emitOldAttributes;
private readonly bool emitNewAttributes;

public HttpHandlerMetricsDiagnosticListener(string name, Meter meter, HttpClientMetricInstrumentationOptions options)
public HttpHandlerMetricsDiagnosticListener(string name, HttpClientMetricInstrumentationOptions options)
: base(name)
{
this.httpClientDuration = meter.CreateHistogram<double>("http.client.duration", "ms", "Measures the duration of outbound HTTP requests.");
this.options = options;

this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);

this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
}

Expand All @@ -61,11 +66,11 @@ public override void OnEventWritten(string name, object payload)
var activity = Activity.Current;
if (TryFetchRequest(payload, out HttpRequestMessage request))
{
TagList tags = default;

// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
if (this.emitOldAttributes)
{
TagList tags = default;

tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)));
Expand All @@ -80,11 +85,18 @@ public override void OnEventWritten(string name, object payload)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)));
}

// We are relying here on HttpClient library to set duration before writing the stop event.
// https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
HttpClientDuration.Record(activity.Duration.TotalMilliseconds, tags);
}

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
if (this.emitNewAttributes)
{
TagList tags = default;

tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRequestMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeServerAddress, request.RequestUri.Host));
Expand All @@ -98,12 +110,12 @@ public override void OnEventWritten(string name, object payload)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)));
}
}

// We are relying here on HttpClient library to set duration before writing the stop event.
// https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
this.httpClientDuration.Record(activity.Duration.TotalMilliseconds, tags);
// We are relying here on HttpClient library to set duration before writing the stop event.
// https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
HttpClientRequestDuration.Record(activity.Duration.TotalSeconds, tags);
}
}
}

Expand Down
Loading