Skip to content

Commit

Permalink
[http] Add http.client.request.duration metric and .NET Framework sup…
Browse files Browse the repository at this point in the history
…port (#4870)

Co-authored-by: Johannes Tax <johannes@johannes.tax>
Co-authored-by: Piotr Kiełkowicz <pkiekowicz@splunk.com>
Co-authored-by: Mikel Blanchard <mblanchard@macrosssoftware.com>
Co-authored-by: Vishwesh Bankwar <vishweshbankwar@users.noreply.github.com>
  • Loading branch information
5 people committed Oct 3, 2023
1 parent 8ea6bb9 commit 4124d21
Show file tree
Hide file tree
Showing 13 changed files with 678 additions and 865 deletions.
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.");
private static readonly Histogram<double> HttpClientRequestDuration = Meter.CreateHistogram<double>("http.client.request.duration", "s", "Measures the duration of outbound HTTP requests.");

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

0 comments on commit 4124d21

Please sign in to comment.