Skip to content

Commit 59e2da5

Browse files
author
Mateusz Rzeszutek
authored
Extract HTTP client experimental metrics to a separate class (open-telemetry#8769)
1 parent 62ca215 commit 59e2da5

File tree

44 files changed

+825
-267
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+825
-267
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.instrumenter.http;
7+
8+
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpRequestBodySize;
9+
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpResponseBodySize;
10+
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView;
11+
import static java.util.logging.Level.FINE;
12+
13+
import io.opentelemetry.api.common.Attributes;
14+
import io.opentelemetry.api.metrics.LongHistogram;
15+
import io.opentelemetry.api.metrics.Meter;
16+
import io.opentelemetry.context.Context;
17+
import io.opentelemetry.context.ContextKey;
18+
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
19+
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
20+
import java.util.logging.Logger;
21+
22+
/**
23+
* {@link OperationListener} which keeps track of <a
24+
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-client">non-stable
25+
* HTTP client metrics</a>: <a
26+
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpclientrequestsize">the
27+
* request size </a> and the <a
28+
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpclientresponsesize">
29+
* the response size</a>.
30+
*/
31+
public final class HttpClientExperimentalMetrics implements OperationListener {
32+
33+
private static final ContextKey<Attributes> HTTP_CLIENT_REQUEST_METRICS_START_ATTRIBUTES =
34+
ContextKey.named("http-client-experimental-metrics-start-attributes");
35+
36+
private static final Logger logger =
37+
Logger.getLogger(HttpClientExperimentalMetrics.class.getName());
38+
39+
/**
40+
* Returns a {@link OperationMetrics} which can be used to enable recording of {@link
41+
* HttpClientExperimentalMetrics} on an {@link
42+
* io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}.
43+
*/
44+
public static OperationMetrics get() {
45+
return HttpClientExperimentalMetrics::new;
46+
}
47+
48+
private final LongHistogram requestSize;
49+
private final LongHistogram responseSize;
50+
51+
private HttpClientExperimentalMetrics(Meter meter) {
52+
requestSize =
53+
meter
54+
.histogramBuilder("http.client.request.size")
55+
.setUnit("By")
56+
.setDescription("The size of HTTP request messages")
57+
.ofLongs()
58+
.build();
59+
responseSize =
60+
meter
61+
.histogramBuilder("http.client.response.size")
62+
.setUnit("By")
63+
.setDescription("The size of HTTP response messages")
64+
.ofLongs()
65+
.build();
66+
}
67+
68+
@Override
69+
public Context onStart(Context context, Attributes startAttributes, long startNanos) {
70+
return context.with(HTTP_CLIENT_REQUEST_METRICS_START_ATTRIBUTES, startAttributes);
71+
}
72+
73+
@Override
74+
public void onEnd(Context context, Attributes endAttributes, long endNanos) {
75+
Attributes startAttributes = context.get(HTTP_CLIENT_REQUEST_METRICS_START_ATTRIBUTES);
76+
if (startAttributes == null) {
77+
logger.log(
78+
FINE,
79+
"No state present when ending context {0}. Cannot record HTTP request metrics.",
80+
context);
81+
return;
82+
}
83+
84+
Attributes sizeAttributes = applyClientDurationAndSizeView(startAttributes, endAttributes);
85+
86+
Long requestBodySize = getHttpRequestBodySize(endAttributes, startAttributes);
87+
if (requestBodySize != null) {
88+
requestSize.record(requestBodySize, sizeAttributes, context);
89+
}
90+
91+
Long responseBodySize = getHttpResponseBodySize(endAttributes, startAttributes);
92+
if (responseBodySize != null) {
93+
responseSize.record(responseBodySize, sizeAttributes, context);
94+
}
95+
}
96+
}

instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
package io.opentelemetry.instrumentation.api.instrumenter.http;
77

8-
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpRequestBodySize;
9-
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpResponseBodySize;
108
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMetricsUtil.createDurationHistogram;
119
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMetricsUtil.nanosToUnit;
1210
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView;
@@ -15,7 +13,6 @@
1513
import com.google.auto.value.AutoValue;
1614
import io.opentelemetry.api.common.Attributes;
1715
import io.opentelemetry.api.metrics.DoubleHistogram;
18-
import io.opentelemetry.api.metrics.LongHistogram;
1916
import io.opentelemetry.api.metrics.Meter;
2017
import io.opentelemetry.context.Context;
2118
import io.opentelemetry.context.ContextKey;
@@ -31,7 +28,7 @@
3128
public final class HttpClientMetrics implements OperationListener {
3229

3330
private static final ContextKey<State> HTTP_CLIENT_REQUEST_METRICS_STATE =
34-
ContextKey.named("http-client-request-metrics-state");
31+
ContextKey.named("http-client-metrics-state");
3532

3633
private static final Logger logger = Logger.getLogger(HttpClientMetrics.class.getName());
3734

@@ -45,27 +42,11 @@ public static OperationMetrics get() {
4542
}
4643

4744
private final DoubleHistogram duration;
48-
private final LongHistogram requestSize;
49-
private final LongHistogram responseSize;
5045

5146
private HttpClientMetrics(Meter meter) {
5247
duration =
5348
createDurationHistogram(
5449
meter, "http.client.duration", "The duration of the outbound HTTP request");
55-
requestSize =
56-
meter
57-
.histogramBuilder("http.client.request.size")
58-
.setUnit("By")
59-
.setDescription("The size of HTTP request messages")
60-
.ofLongs()
61-
.build();
62-
responseSize =
63-
meter
64-
.histogramBuilder("http.client.response.size")
65-
.setUnit("By")
66-
.setDescription("The size of HTTP response messages")
67-
.ofLongs()
68-
.build();
6950
}
7051

7152
@Override
@@ -90,16 +71,6 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) {
9071
applyClientDurationAndSizeView(state.startAttributes(), endAttributes);
9172
duration.record(
9273
nanosToUnit(endNanos - state.startTimeNanos()), durationAndSizeAttributes, context);
93-
94-
Long requestBodySize = getHttpRequestBodySize(endAttributes, state.startAttributes());
95-
if (requestBodySize != null) {
96-
requestSize.record(requestBodySize, durationAndSizeAttributes, context);
97-
}
98-
99-
Long responseBodySize = getHttpResponseBodySize(endAttributes, state.startAttributes());
100-
if (responseBodySize != null) {
101-
responseSize.record(responseBodySize, durationAndSizeAttributes, context);
102-
}
10374
}
10475

10576
@AutoValue
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.instrumenter.http;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
10+
11+
import io.opentelemetry.api.common.Attributes;
12+
import io.opentelemetry.api.trace.Span;
13+
import io.opentelemetry.api.trace.SpanContext;
14+
import io.opentelemetry.api.trace.TraceFlags;
15+
import io.opentelemetry.api.trace.TraceState;
16+
import io.opentelemetry.context.Context;
17+
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
18+
import io.opentelemetry.instrumentation.api.instrumenter.net.internal.NetAttributes;
19+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
20+
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
21+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
22+
import java.util.concurrent.TimeUnit;
23+
import org.junit.jupiter.api.Test;
24+
25+
class HttpClientExperimentalMetricsTest {
26+
27+
@Test
28+
void collectsMetrics() {
29+
InMemoryMetricReader metricReader = InMemoryMetricReader.create();
30+
SdkMeterProvider meterProvider =
31+
SdkMeterProvider.builder().registerMetricReader(metricReader).build();
32+
33+
OperationListener listener =
34+
HttpClientExperimentalMetrics.get().create(meterProvider.get("test"));
35+
36+
Attributes requestAttributes =
37+
Attributes.builder()
38+
.put("http.method", "GET")
39+
.put("http.url", "https://localhost:1234/")
40+
.put("http.target", "/")
41+
.put("http.scheme", "https")
42+
.put("net.peer.name", "localhost")
43+
.put("net.peer.port", 1234)
44+
.put("http.request_content_length", 100)
45+
.build();
46+
47+
Attributes responseAttributes =
48+
Attributes.builder()
49+
.put("http.status_code", 200)
50+
.put("http.response_content_length", 200)
51+
.put(NetAttributes.NET_PROTOCOL_NAME, "http")
52+
.put(NetAttributes.NET_PROTOCOL_VERSION, "2.0")
53+
.put("net.sock.peer.addr", "1.2.3.4")
54+
.put("net.sock.peer.name", "somehost20")
55+
.put("net.sock.peer.port", 8080)
56+
.build();
57+
58+
Context parent =
59+
Context.root()
60+
.with(
61+
Span.wrap(
62+
SpanContext.create(
63+
"ff01020304050600ff0a0b0c0d0e0f00",
64+
"090a0b0c0d0e0f00",
65+
TraceFlags.getSampled(),
66+
TraceState.getDefault())));
67+
68+
Context context1 = listener.onStart(parent, requestAttributes, nanos(100));
69+
70+
assertThat(metricReader.collectAllMetrics()).isEmpty();
71+
72+
Context context2 = listener.onStart(Context.root(), requestAttributes, nanos(150));
73+
74+
assertThat(metricReader.collectAllMetrics()).isEmpty();
75+
76+
listener.onEnd(context1, responseAttributes, nanos(250));
77+
78+
assertThat(metricReader.collectAllMetrics())
79+
.satisfiesExactlyInAnyOrder(
80+
metric ->
81+
assertThat(metric)
82+
.hasName("http.client.request.size")
83+
.hasUnit("By")
84+
.hasHistogramSatisfying(
85+
histogram ->
86+
histogram.hasPointsSatisfying(
87+
point ->
88+
point
89+
.hasSum(100 /* bytes */)
90+
.hasAttributesSatisfying(
91+
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
92+
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200),
93+
equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"),
94+
equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"),
95+
equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
96+
equalTo(SemanticAttributes.NET_PEER_PORT, 1234),
97+
equalTo(
98+
SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4"))
99+
.hasExemplarsSatisfying(
100+
exemplar ->
101+
exemplar
102+
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
103+
.hasSpanId("090a0b0c0d0e0f00")))),
104+
metric ->
105+
assertThat(metric)
106+
.hasName("http.client.response.size")
107+
.hasUnit("By")
108+
.hasHistogramSatisfying(
109+
histogram ->
110+
histogram.hasPointsSatisfying(
111+
point ->
112+
point
113+
.hasSum(200 /* bytes */)
114+
.hasAttributesSatisfying(
115+
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
116+
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200),
117+
equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"),
118+
equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"),
119+
equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
120+
equalTo(SemanticAttributes.NET_PEER_PORT, 1234),
121+
equalTo(
122+
SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4"))
123+
.hasExemplarsSatisfying(
124+
exemplar ->
125+
exemplar
126+
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
127+
.hasSpanId("090a0b0c0d0e0f00")))));
128+
129+
listener.onEnd(context2, responseAttributes, nanos(300));
130+
131+
assertThat(metricReader.collectAllMetrics())
132+
.satisfiesExactlyInAnyOrder(
133+
metric ->
134+
assertThat(metric)
135+
.hasName("http.client.request.size")
136+
.hasHistogramSatisfying(
137+
histogram ->
138+
histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))),
139+
metric ->
140+
assertThat(metric)
141+
.hasName("http.client.response.size")
142+
.hasHistogramSatisfying(
143+
histogram ->
144+
histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */))));
145+
}
146+
147+
private static long nanos(int millis) {
148+
return TimeUnit.MILLISECONDS.toNanos(millis);
149+
}
150+
}

instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsTest.java

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -106,55 +106,7 @@ void collectsMetrics() {
106106
exemplar
107107
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
108108
.hasSpanId("090a0b0c0d0e0f00"))
109-
.hasBucketBoundaries(DEFAULT_BUCKETS))),
110-
metric ->
111-
assertThat(metric)
112-
.hasName("http.client.request.size")
113-
.hasUnit("By")
114-
.hasHistogramSatisfying(
115-
histogram ->
116-
histogram.hasPointsSatisfying(
117-
point ->
118-
point
119-
.hasSum(100 /* bytes */)
120-
.hasAttributesSatisfying(
121-
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
122-
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200),
123-
equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"),
124-
equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"),
125-
equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
126-
equalTo(SemanticAttributes.NET_PEER_PORT, 1234),
127-
equalTo(
128-
SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4"))
129-
.hasExemplarsSatisfying(
130-
exemplar ->
131-
exemplar
132-
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
133-
.hasSpanId("090a0b0c0d0e0f00")))),
134-
metric ->
135-
assertThat(metric)
136-
.hasName("http.client.response.size")
137-
.hasUnit("By")
138-
.hasHistogramSatisfying(
139-
histogram ->
140-
histogram.hasPointsSatisfying(
141-
point ->
142-
point
143-
.hasSum(200 /* bytes */)
144-
.hasAttributesSatisfying(
145-
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
146-
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200),
147-
equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"),
148-
equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"),
149-
equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
150-
equalTo(SemanticAttributes.NET_PEER_PORT, 1234),
151-
equalTo(
152-
SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4"))
153-
.hasExemplarsSatisfying(
154-
exemplar ->
155-
exemplar
156-
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
157-
.hasSpanId("090a0b0c0d0e0f00")))));
109+
.hasBucketBoundaries(DEFAULT_BUCKETS))));
158110

159111
listener.onEnd(context2, responseAttributes, nanos(300));
160112

@@ -165,19 +117,8 @@ void collectsMetrics() {
165117
.hasName("http.client.duration")
166118
.hasHistogramSatisfying(
167119
histogram ->
168-
histogram.hasPointsSatisfying(point -> point.hasSum(300 /* millis */))),
169-
metric ->
170-
assertThat(metric)
171-
.hasName("http.client.request.size")
172-
.hasHistogramSatisfying(
173-
histogram ->
174-
histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))),
175-
metric ->
176-
assertThat(metric)
177-
.hasName("http.client.response.size")
178-
.hasHistogramSatisfying(
179-
histogram ->
180-
histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */))));
120+
histogram.hasPointsSatisfying(
121+
point -> point.hasSum(300 /* millis */))));
181122
}
182123

183124
private static long nanos(int millis) {

0 commit comments

Comments
 (0)