Add http.client|server.request|response.size metrics (#6376)

This commit is contained in:
jack-berg 2022-07-27 11:28:11 -05:00 committed by GitHub
parent 6a24080fe9
commit c518bf8d7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 21 deletions

View File

@ -5,19 +5,23 @@
package io.opentelemetry.instrumentation.api.instrumenter.http;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView;
import static java.util.logging.Level.FINE;
import com.google.auto.value.AutoValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.LongHistogram;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* {@link OperationListener} which keeps track of <a
@ -43,6 +47,8 @@ public final class HttpClientMetrics implements OperationListener {
}
private final DoubleHistogram duration;
private final LongHistogram requestSize;
private final LongHistogram responseSize;
private HttpClientMetrics(Meter meter) {
duration =
@ -51,6 +57,20 @@ public final class HttpClientMetrics implements OperationListener {
.setUnit("ms")
.setDescription("The duration of the outbound HTTP request")
.build();
requestSize =
meter
.histogramBuilder("http.client.request.size")
.setUnit("By")
.setDescription("The size of HTTP request messages")
.ofLongs()
.build();
responseSize =
meter
.histogramBuilder("http.client.response.size")
.setUnit("By")
.setDescription("The size of HTTP response messages")
.ofLongs()
.build();
}
@Override
@ -70,10 +90,35 @@ public final class HttpClientMetrics implements OperationListener {
context);
return;
}
Attributes durationAndSizeAttributes =
applyClientDurationAndSizeView(state.startAttributes(), endAttributes);
duration.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS,
applyClientDurationView(state.startAttributes(), endAttributes),
context);
(endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAndSizeAttributes, context);
Long requestLength =
getAttribute(
SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, endAttributes, state.startAttributes());
if (requestLength != null) {
requestSize.record(requestLength, durationAndSizeAttributes);
}
Long responseLength =
getAttribute(
SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
endAttributes,
state.startAttributes());
if (responseLength != null) {
responseSize.record(responseLength, durationAndSizeAttributes);
}
}
@Nullable
private static <T> T getAttribute(AttributeKey<T> key, Attributes... attributesList) {
for (Attributes attributes : attributesList) {
T value = attributes.get(key);
if (value != null) {
return value;
}
}
return null;
}
@AutoValue

View File

@ -6,20 +6,24 @@
package io.opentelemetry.instrumentation.api.instrumenter.http;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyActiveRequestsView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyServerDurationView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyServerDurationAndSizeView;
import static java.util.logging.Level.FINE;
import com.google.auto.value.AutoValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.LongHistogram;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* {@link OperationListener} which keeps track of <a
@ -46,6 +50,8 @@ public final class HttpServerMetrics implements OperationListener {
private final LongUpDownCounter activeRequests;
private final DoubleHistogram duration;
private final LongHistogram requestSize;
private final LongHistogram responseSize;
private HttpServerMetrics(Meter meter) {
activeRequests =
@ -61,6 +67,20 @@ public final class HttpServerMetrics implements OperationListener {
.setUnit("ms")
.setDescription("The duration of the inbound HTTP request")
.build();
requestSize =
meter
.histogramBuilder("http.server.request.size")
.setUnit("By")
.setDescription("The size of HTTP request messages")
.ofLongs()
.build();
responseSize =
meter
.histogramBuilder("http.server.response.size")
.setUnit("By")
.setDescription("The size of HTTP response messages")
.ofLongs()
.build();
}
@Override
@ -83,10 +103,35 @@ public final class HttpServerMetrics implements OperationListener {
return;
}
activeRequests.add(-1, applyActiveRequestsView(state.startAttributes()), context);
Attributes durationAndSizeAttributes =
applyServerDurationAndSizeView(state.startAttributes(), endAttributes);
duration.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS,
applyServerDurationView(state.startAttributes(), endAttributes),
context);
(endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAndSizeAttributes, context);
Long requestLength =
getAttribute(
SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, endAttributes, state.startAttributes());
if (requestLength != null) {
requestSize.record(requestLength, durationAndSizeAttributes);
}
Long responseLength =
getAttribute(
SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
endAttributes,
state.startAttributes());
if (responseLength != null) {
responseSize.record(responseLength, durationAndSizeAttributes);
}
}
@Nullable
private static <T> T getAttribute(AttributeKey<T> key, Attributes... attributesList) {
for (Attributes attributes : attributesList) {
T value = attributes.get(key);
if (value != null) {
return value;
}
}
return null;
}
@AutoValue

View File

@ -68,14 +68,16 @@ final class TemporaryMetricsView {
return view;
}
static Attributes applyClientDurationView(Attributes startAttributes, Attributes endAttributes) {
static Attributes applyClientDurationAndSizeView(
Attributes startAttributes, Attributes endAttributes) {
AttributesBuilder filtered = Attributes.builder();
applyView(filtered, startAttributes, durationClientView);
applyView(filtered, endAttributes, durationClientView);
return filtered.build();
}
static Attributes applyServerDurationView(Attributes startAttributes, Attributes endAttributes) {
static Attributes applyServerDurationAndSizeView(
Attributes startAttributes, Attributes endAttributes) {
AttributesBuilder filtered = Attributes.builder();
applyView(filtered, startAttributes, durationServerView);
applyView(filtered, endAttributes, durationServerView);

View File

@ -41,6 +41,7 @@ class HttpClientMetricsTest {
.put("net.peer.name", "localhost")
.put("net.peer.ip", "0.0.0.0")
.put("net.peer.port", 1234)
.put("http.request_content_length", 100)
.build();
Attributes responseAttributes =
@ -48,6 +49,7 @@ class HttpClientMetricsTest {
.put("http.flavor", "2.0")
.put("http.server_name", "server")
.put("http.status_code", 200)
.put("http.response_content_length", 200)
.build();
Context parent =
@ -92,7 +94,39 @@ class HttpClientMetricsTest {
exemplar ->
exemplar
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
.hasSpanId("090a0b0c0d0e0f00")))));
.hasSpanId("090a0b0c0d0e0f00")))),
metric ->
assertThat(metric)
.hasName("http.client.request.size")
.hasUnit("By")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point ->
point
.hasSum(100 /* bytes */)
.hasAttributesSatisfying(
equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
equalTo(SemanticAttributes.NET_PEER_PORT, 1234),
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
equalTo(SemanticAttributes.HTTP_FLAVOR, "2.0"),
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200)))),
metric ->
assertThat(metric)
.hasName("http.client.response.size")
.hasUnit("By")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point ->
point
.hasSum(200 /* bytes */)
.hasAttributesSatisfying(
equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
equalTo(SemanticAttributes.NET_PEER_PORT, 1234),
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
equalTo(SemanticAttributes.HTTP_FLAVOR, "2.0"),
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200)))));
listener.onEnd(context2, responseAttributes, nanos(300));
@ -103,8 +137,19 @@ class HttpClientMetricsTest {
.hasName("http.client.duration")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point -> point.hasSum(300 /* millis */))));
histogram.hasPointsSatisfying(point -> point.hasSum(300 /* millis */))),
metric ->
assertThat(metric)
.hasName("http.client.request.size")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))),
metric ->
assertThat(metric)
.hasName("http.client.response.size")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */))));
}
private static long nanos(int millis) {

View File

@ -39,6 +39,7 @@ class HttpServerMetricsTest {
.put("http.scheme", "https")
.put("net.host.name", "localhost")
.put("net.host.port", 1234)
.put("http.request_content_length", 100)
.build();
Attributes responseAttributes =
@ -46,6 +47,7 @@ class HttpServerMetricsTest {
.put("http.flavor", "2.0")
.put("http.server_name", "server")
.put("http.status_code", 200)
.put("http.response_content_length", 200)
.build();
SpanContext spanContext1 =
@ -154,7 +156,39 @@ class HttpServerMetricsTest {
exemplar ->
exemplar
.hasTraceId(spanContext1.getTraceId())
.hasSpanId(spanContext1.getSpanId())))));
.hasSpanId(spanContext1.getSpanId())))),
metric ->
assertThat(metric)
.hasName("http.server.request.size")
.hasUnit("By")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point ->
point
.hasSum(100 /* bytes */)
.hasAttributesSatisfying(
equalTo(SemanticAttributes.HTTP_SCHEME, "https"),
equalTo(SemanticAttributes.HTTP_HOST, "host"),
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200),
equalTo(SemanticAttributes.HTTP_FLAVOR, "2.0")))),
metric ->
assertThat(metric)
.hasName("http.server.response.size")
.hasUnit("By")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point ->
point
.hasSum(200 /* bytes */)
.hasAttributesSatisfying(
equalTo(SemanticAttributes.HTTP_SCHEME, "https"),
equalTo(SemanticAttributes.HTTP_HOST, "host"),
equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200),
equalTo(SemanticAttributes.HTTP_FLAVOR, "2.0")))));
listener.onEnd(context2, responseAttributes, nanos(300));
@ -178,7 +212,19 @@ class HttpServerMetricsTest {
exemplar ->
exemplar
.hasTraceId(spanContext2.getTraceId())
.hasSpanId(spanContext2.getSpanId())))));
.hasSpanId(spanContext2.getSpanId())))),
metric ->
assertThat(metric)
.hasName("http.server.request.size")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))),
metric ->
assertThat(metric)
.hasName("http.server.response.size")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */))));
}
@Test

View File

@ -6,8 +6,8 @@
package io.opentelemetry.instrumentation.api.instrumenter.http;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyActiveRequestsView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyServerDurationView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyServerDurationAndSizeView;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
import io.opentelemetry.api.common.Attributes;
@ -18,7 +18,7 @@ import org.junit.jupiter.api.Test;
class TemporaryMetricsViewTest {
@Test
void shouldApplyClientDurationView() {
void shouldApplyClientDurationAndSizeView() {
Attributes startAttributes =
Attributes.builder()
.put(
@ -38,7 +38,8 @@ class TemporaryMetricsViewTest {
.put(SemanticAttributes.NET_PEER_PORT, 443)
.build();
OpenTelemetryAssertions.assertThat(applyClientDurationView(startAttributes, endAttributes))
OpenTelemetryAssertions.assertThat(
applyClientDurationAndSizeView(startAttributes, endAttributes))
.containsOnly(
attributeEntry(SemanticAttributes.NET_PEER_NAME.getKey(), "somehost2"),
attributeEntry(SemanticAttributes.NET_PEER_PORT.getKey(), 443),
@ -47,7 +48,7 @@ class TemporaryMetricsViewTest {
}
@Test
void shouldApplyServerDurationView() {
void shouldApplyServerDurationAndSizeView() {
Attributes startAttributes =
Attributes.builder()
.put(SemanticAttributes.HTTP_METHOD, "GET")
@ -73,7 +74,8 @@ class TemporaryMetricsViewTest {
.put(SemanticAttributes.NET_PEER_PORT, 443)
.build();
OpenTelemetryAssertions.assertThat(applyServerDurationView(startAttributes, endAttributes))
OpenTelemetryAssertions.assertThat(
applyServerDurationAndSizeView(startAttributes, endAttributes))
.containsOnly(
attributeEntry(SemanticAttributes.HTTP_SCHEME.getKey(), "https"),
attributeEntry(SemanticAttributes.HTTP_HOST.getKey(), "somehost"),