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

HttpSemanticConventions - new metric in HttpClient Instrumentation #4891

Closed
wants to merge 13 commits into from
29 changes: 29 additions & 0 deletions src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@

## 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).
([#4891](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4891))
* 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 for both
[spans](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-spans.md)
and
[metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md).

## 1.5.1-beta.1

Released 2023-Jul-20
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,37 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation;

internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler
{
internal const string HttpClientDurationMetricName = "http.client.duration";
internal const string HttpClientRequestDurationMetricName = "http.client.request.duration";

internal const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop";

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

public HttpHandlerMetricsDiagnosticListener(string name, Meter meter, 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);

if (this.emitOldAttributes)
{
this.httpClientDuration = meter.CreateHistogram<double>(HttpClientDurationMetricName, "ms", "Measures the duration of outbound HTTP requests.");
}

if (this.emitNewAttributes)
{
this.httpClientRequestDuration = meter.CreateHistogram<double>(HttpClientRequestDurationMetricName, "s", "Measures the duration of outbound HTTP requests.");
}
}

public override void OnEventWritten(string name, object payload)
Expand Down Expand Up @@ -80,6 +93,11 @@ 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.
this.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
Expand All @@ -88,6 +106,7 @@ public override void OnEventWritten(string name, object payload)
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));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme));

if (!request.RequestUri.IsDefaultPort)
{
Expand All @@ -98,12 +117,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.
this.httpClientRequestDuration.Record(activity.Duration.TotalSeconds, tags);
}
}
}

Expand Down
32 changes: 25 additions & 7 deletions src/OpenTelemetry.Instrumentation.Http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,31 @@ to see how to enable this instrumentation in an ASP.NET application.

#### List of metrics produced

The instrumentation is implemented based on [metrics semantic
conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpclientduration).
Currently, the instrumentation supports the following metric.

| Name | Instrument Type | Unit | Description |
|-------|-----------------|------|-------------|
| `http.client.duration` | Histogram | `ms` | Measures the duration of outbound HTTP requests. |
A different metric is emitted depending on whether a user opts-in to the new
Http Semantic Conventions using `OTEL_SEMCONV_STABILITY_OPT_IN`.

* By default, the instrumentation emits the following metric.

| Name | Instrument Type | Unit | Description |
|-------|-----------------|------|-------------|
| `http.client.duration` | Histogram | `ms` | Measures the duration of outbound HTTP requests. |

* If user sets the environment variable to `http`, the instrumentation emits
the following metric.

| Name | Instrument Type | Unit | Description |
|-------|-----------------|------|-------------|
| `http.client.request.duration` | Histogram | `s` | Measures the duration of outbound HTTP requests. |

This metric is emitted in `seconds` as per the semantic convention. While
the convention [recommends using custom histogram buckets](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md)
, this feature is not yet available via .NET Metrics API.
A [workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
has been included in OTel SDK starting version `1.6.0` which applies
recommended buckets by default for `http.client.request.duration`.

* If user sets the environment variable to `http/dup`, the instrumentation
emits both `http.client.duration` and `http.client.request.duration`.

## Advanced configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@
using OpenTelemetry.Tests;
using OpenTelemetry.Trace;
using Xunit;
using Xunit.Abstractions;

namespace OpenTelemetry.Instrumentation.Http.Tests;

public partial class HttpClientTests : IDisposable
{
public readonly ITestOutputHelper TestOutputHelper;
private readonly IDisposable serverLifeTime;
private readonly string url;

public HttpClientTests()
public HttpClientTests(ITestOutputHelper testOutputHelper)
{
this.TestOutputHelper = testOutputHelper;
this.serverLifeTime = TestHttpServer.RunServer(
(ctx) =>
{
Expand Down Expand Up @@ -69,6 +72,12 @@ public HttpClientTests()
this.url = $"http://{host}:{port}/";
}

public static IEnumerable<object[]> TestData_Old => HttpTestData.ReadTestCases("http-out-test-cases_Old.json");

public static IEnumerable<object[]> TestData_New => HttpTestData.ReadTestCases("http-out-test-cases_New.json");

public static IEnumerable<object[]> TestData_Dupe => HttpTestData.ReadTestCases("http-out-test-cases_Dupe.json");

[Fact]
public void AddHttpClientInstrumentation_NamedOptions()
{
Expand Down
Loading
Loading