-
Notifications
You must be signed in to change notification settings - Fork 933
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
Add gen_ai metrics to AWS Bedrock instrumentation #13408
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.13.1.jar | ||
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.13.2.jar | ||
No changes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
Comparing source compatibility of opentelemetry-instrumentation-api-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-api-2.13.1.jar | ||
Comparing source compatibility of opentelemetry-instrumentation-api-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-api-2.13.2.jar | ||
No changes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.api.incubator.semconv.genai; | ||
|
||
import static io.opentelemetry.api.common.AttributeKey.stringKey; | ||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_USAGE_INPUT_TOKENS; | ||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_USAGE_OUTPUT_TOKENS; | ||
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.common.AttributesBuilder; | ||
import io.opentelemetry.api.metrics.DoubleHistogram; | ||
import io.opentelemetry.api.metrics.DoubleHistogramBuilder; | ||
import io.opentelemetry.api.metrics.LongHistogram; | ||
import io.opentelemetry.api.metrics.LongHistogramBuilder; | ||
import io.opentelemetry.api.metrics.Meter; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.ContextKey; | ||
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; | ||
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; | ||
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; | ||
import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* {@link OperationListener} which keeps track of <a | ||
* href="https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#generative-ai-client-metrics">Generative | ||
* AI Client Metrics</a>. | ||
*/ | ||
public final class GenAiClientMetrics implements OperationListener { | ||
|
||
private static final double NANOS_PER_S = TimeUnit.SECONDS.toNanos(1); | ||
|
||
private static final ContextKey<State> GEN_AI_CLIENT_METRICS_STATE = | ||
ContextKey.named("gen-ai-client-metrics-state"); | ||
|
||
private static final Logger logger = Logger.getLogger(DbClientMetrics.class.getName()); | ||
|
||
static final AttributeKey<String> GEN_AI_TOKEN_TYPE = stringKey("gen_ai.token.type"); | ||
|
||
/** | ||
* Returns an {@link OperationMetrics} instance which can be used to enable recording of {@link | ||
* GenAiClientMetrics}. | ||
* | ||
* @see | ||
* io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder#addOperationMetrics(OperationMetrics) | ||
*/ | ||
public static OperationMetrics get() { | ||
return OperationMetricsUtil.create("gen_ai client", GenAiClientMetrics::new); | ||
} | ||
|
||
private final LongHistogram tokenUsage; | ||
private final DoubleHistogram operationDuration; | ||
|
||
private GenAiClientMetrics(Meter meter) { | ||
LongHistogramBuilder tokenUsageBuilder = meter | ||
.histogramBuilder("gen_ai.client.token.usage") | ||
.ofLongs() | ||
.setUnit("{token}") | ||
.setDescription("Measures number of input and output tokens used") | ||
.setExplicitBucketBoundariesAdvice(GenAiMetricsAdvice.CLIENT_TOKEN_USAGE_BUCKETS); | ||
GenAiMetricsAdvice.applyClientTokenUsageAdvice(tokenUsageBuilder); | ||
this.tokenUsage = tokenUsageBuilder.build(); | ||
DoubleHistogramBuilder operationDurationBuilder = | ||
meter | ||
.histogramBuilder("gen_ai.client.operation.duration") | ||
.setUnit("s") | ||
.setDescription("GenAI operation duration") | ||
.setExplicitBucketBoundariesAdvice(GenAiMetricsAdvice.CLIENT_OPERATION_DURATION_BUCKETS); | ||
GenAiMetricsAdvice.applyClientOperationDurationAdvice(operationDurationBuilder); | ||
this.operationDuration = operationDurationBuilder.build(); | ||
} | ||
|
||
@Override | ||
public Context onStart(Context context, Attributes startAttributes, long startNanos) { | ||
return context.with( | ||
GEN_AI_CLIENT_METRICS_STATE, | ||
new AutoValue_GenAiClientMetrics_State(startAttributes, startNanos)); | ||
} | ||
|
||
@Override | ||
public void onEnd(Context context, Attributes endAttributes, long endNanos) { | ||
State state = context.get(GEN_AI_CLIENT_METRICS_STATE); | ||
if (state == null) { | ||
logger.log( | ||
FINE, | ||
"No state present when ending context {0}. Cannot record database operation metrics.", | ||
context); | ||
return; | ||
} | ||
|
||
AttributesBuilder attributesBuilder = state.startAttributes().toBuilder().putAll(endAttributes); | ||
|
||
operationDuration.record( | ||
(endNanos - state.startTimeNanos()) / NANOS_PER_S, attributesBuilder.build(), context); | ||
|
||
Long inputTokens = endAttributes.get(GEN_AI_USAGE_INPUT_TOKENS); | ||
if (inputTokens != null) { | ||
tokenUsage.record( | ||
inputTokens, attributesBuilder.put(GEN_AI_TOKEN_TYPE, "input").build(), context); | ||
} | ||
Long outputTokens = endAttributes.get(GEN_AI_USAGE_OUTPUT_TOKENS); | ||
if (outputTokens != null) { | ||
tokenUsage.record( | ||
outputTokens, attributesBuilder.put(GEN_AI_TOKEN_TYPE, "output").build(), 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,72 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.api.incubator.semconv.genai; | ||
|
||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_OPERATION_NAME; | ||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_REQUEST_MODEL; | ||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_RESPONSE_MODEL; | ||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_SYSTEM; | ||
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics.GEN_AI_TOKEN_TYPE; | ||
import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; | ||
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; | ||
import static java.util.Arrays.asList; | ||
import static java.util.Collections.unmodifiableList; | ||
|
||
import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder; | ||
import io.opentelemetry.api.incubator.metrics.ExtendedLongHistogramBuilder; | ||
import io.opentelemetry.api.metrics.DoubleHistogramBuilder; | ||
import io.opentelemetry.api.metrics.LongHistogramBuilder; | ||
import io.opentelemetry.semconv.ErrorAttributes; | ||
import java.util.List; | ||
|
||
final class GenAiMetricsAdvice { | ||
|
||
static final List<Double> CLIENT_OPERATION_DURATION_BUCKETS = | ||
unmodifiableList( | ||
asList( | ||
0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24, 20.48, 40.96, | ||
81.92)); | ||
|
||
static final List<Long> CLIENT_TOKEN_USAGE_BUCKETS = | ||
unmodifiableList( | ||
asList( | ||
1L, 4L, 16L, 64L, 256L, 1024L, 4096L, 16384L, 65536L, 262144L, 1048576L, 4194304L, | ||
16777216L, 67108864L)); | ||
|
||
static void applyClientTokenUsageAdvice(LongHistogramBuilder builder) { | ||
if (!(builder instanceof ExtendedLongHistogramBuilder)) { | ||
return; | ||
} | ||
((ExtendedLongHistogramBuilder) builder) | ||
.setAttributesAdvice( | ||
asList( | ||
GEN_AI_OPERATION_NAME, | ||
GEN_AI_SYSTEM, | ||
GEN_AI_TOKEN_TYPE, | ||
GEN_AI_REQUEST_MODEL, | ||
SERVER_PORT, | ||
GEN_AI_RESPONSE_MODEL, | ||
SERVER_ADDRESS)); | ||
} | ||
|
||
static void applyClientOperationDurationAdvice(DoubleHistogramBuilder builder) { | ||
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Followed the pattern of the other metrics classes but this post-dates me. IIUC, api-incubator has to be on the classpath to prevent cardinality explosion - is that OK? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it - I guess it means it would only not work for an alternative SDK which probably we don't expect in practice. Should we log an error before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
makes sense 👍 I think I'm going to need to figure out a way bribe two other languages to implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized perhaps the others don't do it for the same thing I ran into, where we wouldn't want to log if it's no-op, but there doesn't seem to be a great way to check for that on a metric builder. Let me leave it as-is following others for now and think some more on it. |
||
return; | ||
} | ||
((ExtendedDoubleHistogramBuilder) builder) | ||
.setAttributesAdvice( | ||
asList( | ||
GEN_AI_OPERATION_NAME, | ||
GEN_AI_SYSTEM, | ||
ErrorAttributes.ERROR_TYPE, | ||
GEN_AI_REQUEST_MODEL, | ||
SERVER_PORT, | ||
GEN_AI_RESPONSE_MODEL, | ||
SERVER_ADDRESS)); | ||
} | ||
|
||
private GenAiMetricsAdvice() {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace
database operation
with whatever is appropriateThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, thanks fixed