Skip to content

Commit ef1350d

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: Add telemetry and metrics recording capabilities
The key changes include: 1. **New `Instrumentation.java` class:** This class provides a unified context manager for instrumenting agent invocations and tool executions. It uses OpenTelemetry to create trace spans, record exceptions, and manage the scope of telemetry contexts. It includes inner classes `AgentInvocation` and `ToolExecution`, both implementing `AutoCloseable` to automatically handle the lifecycle of spans and metric recording. 2. **New `Metrics.java` class:** This utility class is responsible for defining and recording various OpenTelemetry metrics (histograms) related to ADK components. These metrics cover: * Agent invocation duration, request size, response size, and workflow steps. * Tool execution duration, request size, and response size. The class uses `GlobalOpenTelemetry` to get a `Meter` and defines static histogram instances. It includes methods to record values for these metrics, often including attributes like agent name, tool name, and error type. 3. **New Unit Tests:** * `InstrumentationTest.java`: Contains unit tests for the `Instrumentation` class, verifying that spans are created correctly, contexts are managed, and metrics are recorded for both successful and error scenarios during agent invocations and tool executions. It uses `OpenTelemetryRule` for testing. * `MetricsTest.java`: Contains unit tests for the `Metrics` class, ensuring that the various static methods correctly record histogram data with the expected values and attributes. It also uses `OpenTelemetryRule`. PiperOrigin-RevId: 917904663
1 parent b4791ef commit ef1350d

4 files changed

Lines changed: 866 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.telemetry;
18+
19+
import com.google.adk.agents.BaseAgent;
20+
import com.google.adk.agents.InvocationContext;
21+
import com.google.adk.events.Event;
22+
import com.google.adk.tools.BaseTool;
23+
import io.opentelemetry.api.trace.Span;
24+
import io.opentelemetry.context.Context;
25+
import io.opentelemetry.context.Scope;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.concurrent.CopyOnWriteArrayList;
29+
import org.jspecify.annotations.Nullable;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
32+
33+
/** Unified context manager utility class for agent and tool execution telemetry in ADK. */
34+
public final class Instrumentation {
35+
36+
private static final Logger logger = LoggerFactory.getLogger(Instrumentation.class);
37+
38+
private Instrumentation() {}
39+
40+
/** Stores all telemetry related state. */
41+
public static final class TelemetryContext {
42+
private final Context otelContext;
43+
private @Nullable Event functionResponseEvent;
44+
45+
public TelemetryContext(Context otelContext) {
46+
this.otelContext = otelContext;
47+
}
48+
49+
public Context otelContext() {
50+
return otelContext;
51+
}
52+
53+
public @Nullable Event functionResponseEvent() {
54+
return functionResponseEvent;
55+
}
56+
57+
public void setFunctionResponseEvent(@Nullable Event functionResponseEvent) {
58+
this.functionResponseEvent = functionResponseEvent;
59+
}
60+
}
61+
62+
/** AutoCloseable telemetry tracking scope for agent invocations. */
63+
public static final class AgentInvocation implements AutoCloseable {
64+
private final long startTimeNanos;
65+
private final BaseAgent agent;
66+
private final InvocationContext ctx;
67+
private final Span span;
68+
private final Scope scope;
69+
private final TelemetryContext telemetryContext;
70+
private @Nullable Throwable caughtError;
71+
private final List<Event> events = new CopyOnWriteArrayList<>();
72+
73+
@SuppressWarnings("MustBeClosedChecker")
74+
public AgentInvocation(InvocationContext ctx, BaseAgent agent) {
75+
this.startTimeNanos = System.nanoTime();
76+
this.agent = agent;
77+
this.ctx = ctx;
78+
this.span = Tracing.getTracer().spanBuilder("invoke_agent " + agent.name()).startSpan();
79+
Tracing.traceAgentInvocation(span, agent.name(), agent.description(), ctx);
80+
this.scope = span.makeCurrent();
81+
this.telemetryContext = new TelemetryContext(Context.current());
82+
}
83+
84+
public InvocationContext getCtx() {
85+
return ctx;
86+
}
87+
88+
public void addEvent(Event event) {
89+
events.add(event);
90+
}
91+
92+
public TelemetryContext context() {
93+
return telemetryContext;
94+
}
95+
96+
public void setError(Throwable caughtError) {
97+
this.caughtError = caughtError;
98+
span.recordException(caughtError);
99+
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, caughtError.getMessage());
100+
}
101+
102+
@Override
103+
public void close() {
104+
scope.close();
105+
span.end();
106+
double elapsedMs = (System.nanoTime() - startTimeNanos) / 1e6;
107+
try {
108+
Metrics.recordAgentInvocationDuration(agent.name(), elapsedMs, caughtError);
109+
Metrics.recordAgentRequestSize(agent.name(), ctx.userContent().orElse(null));
110+
Metrics.recordAgentResponseSize(agent.name(), events);
111+
Metrics.recordAgentWorkflowSteps(agent.name(), events);
112+
} catch (RuntimeException e) {
113+
logger.error("Failed to record agent metrics for agent {}", agent.name(), e);
114+
}
115+
}
116+
}
117+
118+
/** AutoCloseable telemetry tracking scope for tool executions. */
119+
public static final class ToolExecution implements AutoCloseable {
120+
private final long startTimeNanos;
121+
private final BaseTool tool;
122+
private final BaseAgent agent;
123+
private final Map<String, Object> functionArgs;
124+
private final Span span;
125+
private final Scope scope;
126+
private final TelemetryContext telemetryContext;
127+
private @Nullable Throwable caughtError;
128+
129+
@SuppressWarnings("MustBeClosedChecker")
130+
public ToolExecution(BaseTool tool, BaseAgent agent, Map<String, Object> functionArgs) {
131+
this.startTimeNanos = System.nanoTime();
132+
this.tool = tool;
133+
this.agent = agent;
134+
this.functionArgs = functionArgs;
135+
this.span = Tracing.getTracer().spanBuilder("execute_tool " + tool.name()).startSpan();
136+
this.scope = span.makeCurrent();
137+
this.telemetryContext = new TelemetryContext(Context.current());
138+
}
139+
140+
public TelemetryContext context() {
141+
return telemetryContext;
142+
}
143+
144+
public void setError(Throwable caughtError) {
145+
this.caughtError = caughtError;
146+
span.recordException(caughtError);
147+
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, caughtError.getMessage());
148+
}
149+
150+
@Override
151+
public void close() {
152+
scope.close();
153+
Event responseEvent = caughtError == null ? telemetryContext.functionResponseEvent() : null;
154+
Tracing.traceToolExecution(
155+
span,
156+
tool.name(),
157+
tool.description(),
158+
tool.getClass().getSimpleName(),
159+
functionArgs,
160+
responseEvent,
161+
caughtError instanceof Exception ? (Exception) caughtError : null);
162+
span.end();
163+
164+
double elapsedMs = (System.nanoTime() - startTimeNanos) / 1e6;
165+
try {
166+
Metrics.recordToolExecutionDuration(tool.name(), agent.name(), elapsedMs, caughtError);
167+
Metrics.recordToolRequestSize(tool.name(), agent.name(), functionArgs);
168+
Metrics.recordToolResponseSize(tool.name(), agent.name(), responseEvent);
169+
} catch (RuntimeException e) {
170+
logger.error("Failed to record tool execution duration for tool {}", tool.name(), e);
171+
}
172+
}
173+
}
174+
175+
/** Creates an AgentInvocation context to record agent invocation telemetry. */
176+
public static AgentInvocation recordAgentInvocation(InvocationContext ctx, BaseAgent agent) {
177+
return new AgentInvocation(ctx, agent);
178+
}
179+
180+
/** Creates a ToolExecution context to record tool execution telemetry. */
181+
public static ToolExecution recordToolExecution(
182+
BaseTool tool, BaseAgent agent, Map<String, Object> functionArgs) {
183+
return new ToolExecution(tool, agent, functionArgs);
184+
}
185+
}

0 commit comments

Comments
 (0)