Skip to content

Commit

Permalink
add metrics rpc conventions implement (open-telemetry#4838)
Browse files Browse the repository at this point in the history
* add metrics rpc conventions

* handle format violations

* handle format violations

* resolve the comments suggestion

* update RpcClientMetrics format

* update RpcServerMetrics format

* resolve time precision and cardinality issue

* invoke buildServerFallbackView method

* add RpcServerMetricsTest and RpcClientMetricsTest

* server metrics attibutes remove net.perr* and add net.host

* Update instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcServerMetrics.java

* Update instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcClientMetrics.java

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
  • Loading branch information
2 people authored and RashmiRam committed May 23, 2022
1 parent 571aadc commit fa641df
Show file tree
Hide file tree
Showing 5 changed files with 605 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.rpc;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;

// this is temporary, see
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/3962#issuecomment-906606325
@SuppressWarnings("rawtypes")
final class MetricsView {

private static final Set<AttributeKey> alwaysInclude = buildAlwaysInclude();
private static final Set<AttributeKey> clientView = buildClientView();
private static final Set<AttributeKey> clientFallbackView = buildClientFallbackView();
private static final Set<AttributeKey> serverView = buildServerView();
private static final Set<AttributeKey> serverFallbackView = buildServerFallbackView();

private static Set<AttributeKey> buildAlwaysInclude() {
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes
Set<AttributeKey> view = new HashSet<>();
view.add(SemanticAttributes.RPC_SYSTEM);
view.add(SemanticAttributes.RPC_SERVICE);
view.add(SemanticAttributes.RPC_METHOD);
return view;
}

private static Set<AttributeKey> buildClientView() {
// the list of rpc client metrics attributes is from
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes
Set<AttributeKey> view = new HashSet<>(alwaysInclude);
view.add(SemanticAttributes.NET_PEER_NAME);
view.add(SemanticAttributes.NET_PEER_PORT);
view.add(SemanticAttributes.NET_TRANSPORT);
return view;
}

private static Set<AttributeKey> buildClientFallbackView() {
// the list of rpc client metrics attributes is from
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes
Set<AttributeKey> view = new HashSet<>(alwaysInclude);
view.add(SemanticAttributes.NET_PEER_IP);
view.add(SemanticAttributes.NET_PEER_PORT);
view.add(SemanticAttributes.NET_TRANSPORT);
return view;
}

private static Set<AttributeKey> buildServerView() {
// the list of rpc server metrics attributes is from
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes
Set<AttributeKey> view = new HashSet<>(alwaysInclude);
view.add(SemanticAttributes.NET_HOST_NAME);
view.add(SemanticAttributes.NET_TRANSPORT);
return view;
}

private static Set<AttributeKey> buildServerFallbackView() {
// the list of rpc server metrics attributes is from
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes
Set<AttributeKey> view = new HashSet<>(alwaysInclude);
view.add(SemanticAttributes.NET_HOST_IP);
view.add(SemanticAttributes.NET_TRANSPORT);
return view;
}

private static <T> boolean containsAttribute(
AttributeKey<T> key, Attributes startAttributes, Attributes endAttributes) {
return startAttributes.get(key) != null || endAttributes.get(key) != null;
}

static Attributes applyClientView(Attributes startAttributes, Attributes endAttributes) {
Set<AttributeKey> fullSet = clientView;
if (!containsAttribute(SemanticAttributes.NET_PEER_NAME, startAttributes, endAttributes)) {
fullSet = clientFallbackView;
}
return applyView(fullSet, startAttributes, endAttributes);
}

static Attributes applyServerView(Attributes startAttributes, Attributes endAttributes) {
Set<AttributeKey> fullSet = serverView;
if (!containsAttribute(SemanticAttributes.NET_HOST_NAME, startAttributes, endAttributes)) {
fullSet = serverFallbackView;
}
return applyView(fullSet, startAttributes, endAttributes);
}

static Attributes applyView(
Set<AttributeKey> view, Attributes startAttributes, Attributes endAttributes) {
AttributesBuilder filtered = Attributes.builder();
applyView(filtered, startAttributes, view);
applyView(filtered, endAttributes, view);
return filtered.build();
}

@SuppressWarnings("unchecked")
private static void applyView(
AttributesBuilder filtered, Attributes attributes, Set<AttributeKey> view) {
attributes.forEach(
(BiConsumer<AttributeKey, Object>)
(key, value) -> {
if (view.contains(key)) {
filtered.put(key, value);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.rpc;

import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyClientView;

import com.google.auto.value.AutoValue;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.annotations.UnstableApi;
import io.opentelemetry.instrumentation.api.instrumenter.RequestListener;
import io.opentelemetry.instrumentation.api.instrumenter.RequestMetrics;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* {@link RequestListener} which keeps track of <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#rpc-client">RPC
* client metrics</a>.
*
* <p>To use this class, you may need to add the {@code opentelemetry-api-metrics} artifact to your
* dependencies.
*/
@UnstableApi
public final class RpcClientMetrics implements RequestListener {

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);

private static final ContextKey<RpcClientMetrics.State> RPC_CLIENT_REQUEST_METRICS_STATE =
ContextKey.named("rpc-client-request-metrics-state");

private static final Logger logger = LoggerFactory.getLogger(RpcClientMetrics.class);

private final DoubleHistogram clientDurationHistogram;

private RpcClientMetrics(Meter meter) {
clientDurationHistogram =
meter
.histogramBuilder("rpc.client.duration")
.setDescription("The duration of an outbound RPC invocation")
.setUnit("milliseconds")
.build();
}

/**
* Returns a {@link RequestMetrics} which can be used to enable recording of {@link
* RpcClientMetrics} on an {@link
* io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}.
*/
@UnstableApi
public static RequestMetrics get() {
return RpcClientMetrics::new;
}

@Override
public Context start(Context context, Attributes startAttributes, long startNanos) {
return context.with(
RPC_CLIENT_REQUEST_METRICS_STATE,
new AutoValue_RpcClientMetrics_State(startAttributes, startNanos));
}

@Override
public void end(Context context, Attributes endAttributes, long endNanos) {
State state = context.get(RPC_CLIENT_REQUEST_METRICS_STATE);
if (state == null) {
logger.debug(
"No state present when ending context {}. Cannot record RPC request metrics.", context);
return;
}
clientDurationHistogram.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS,
applyClientView(state.startAttributes(), endAttributes),
context);
}

@AutoValue
abstract static class State {

abstract Attributes startAttributes();

abstract long startTimeNanos();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.rpc;

import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyServerView;

import com.google.auto.value.AutoValue;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.annotations.UnstableApi;
import io.opentelemetry.instrumentation.api.instrumenter.RequestListener;
import io.opentelemetry.instrumentation.api.instrumenter.RequestMetrics;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* {@link RequestListener} which keeps track of <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#rpc-server">RPC
* server metrics</a>.
*
* <p>To use this class, you may need to add the {@code opentelemetry-api-metrics} artifact to your
* dependencies.
*/
@UnstableApi
public final class RpcServerMetrics implements RequestListener {

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);

private static final ContextKey<RpcServerMetrics.State> RPC_SERVER_REQUEST_METRICS_STATE =
ContextKey.named("rpc-server-request-metrics-state");

private static final Logger logger = LoggerFactory.getLogger(RpcServerMetrics.class);

private final DoubleHistogram serverDurationHistogram;

private RpcServerMetrics(Meter meter) {
serverDurationHistogram =
meter
.histogramBuilder("rpc.server.duration")
.setDescription("The duration of an inbound RPC invocation")
.setUnit("milliseconds")
.build();
}

/**
* Returns a {@link RequestMetrics} which can be used to enable recording of {@link
* RpcServerMetrics} on an {@link
* io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}.
*/
@UnstableApi
public static RequestMetrics get() {
return RpcServerMetrics::new;
}

@Override
public Context start(Context context, Attributes startAttributes, long startNanos) {
return context.with(
RPC_SERVER_REQUEST_METRICS_STATE,
new AutoValue_RpcServerMetrics_State(startAttributes, startNanos));
}

@Override
public void end(Context context, Attributes endAttributes, long endNanos) {
State state = context.get(RPC_SERVER_REQUEST_METRICS_STATE);
if (state == null) {
logger.debug(
"No state present when ending context {}. Cannot record RPC request metrics.", context);
return;
}
serverDurationHistogram.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS,
applyServerView(state.startAttributes(), endAttributes),
context);
}

@AutoValue
abstract static class State {

abstract Attributes startAttributes();

abstract long startTimeNanos();
}
}
Loading

0 comments on commit fa641df

Please sign in to comment.