impl(o11y): introduce http.response.body_size#4157
impl(o11y): introduce http.response.body_size#4157diegomarquezp wants to merge 27 commits intomainfrom
http.response.body_size#4157Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly improves observability by adding a new Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces observability for HTTP response body size by adding a new attribute http.response.body.size. The changes span across the gax-httpjson and gax modules, introducing new APIs in ApiTracer and implementing the size recording in HttpJsonClientCallImpl. New tests are added to verify the functionality for both unary and server-streaming calls.
My review has identified a few areas for improvement:
- A potential logic error in
HttpJsonClientCallImplandOpencensusTracerregarding the incorrect invocation ofresponseReceived()for unary calls, leading to erroneous tracing data. - A minor documentation issue in the
ApiTracerinterface. - An incomplete implementation of the new
setAttributemethod inOpenTelemetryTraceManager, which could lead to non-conformance with OpenTelemetry Semantic Conventions for data types.
Details and suggestions are provided in the specific comments.
I am having trouble creating individual review comments. Click here to see my feedback.
gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCallImpl.java (442-445)
Calling responseReceived() within responseReceived(long responseSize) in OpencensusTracer is problematic. The responseReceived() method is designed to record a message event for streaming RPCs. However, responseReceived(long) is also invoked for unary calls to record the response size. This implementation will incorrectly add a streaming message event to spans for unary calls.
This is also inconsistent with the SpanTracer (OpenTelemetry) implementation, which only sets the size attribute.
To fix this, responseReceived() should not be called in OpencensusTracer.responseReceived(long). Instead, HttpJsonClientCallImpl should be modified to explicitly call tracer.responseReceived() for server-streaming calls, in addition to tracer.responseReceived(responseSize).
ApiTracer tracer = callOptions.getTracer();
if (tracer != null) {
long responseSize = responseBodySizeEnd - responseBodySizeStart;
tracer.responseReceived(responseSize);
if (methodDescriptor.getType() == MethodType.SERVER_STREAMING) {
tracer.responseReceived();
}
}gax-java/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java (411-416)
Calling responseReceived() within responseReceived(long responseSize) is problematic. The responseReceived() method is designed to record a message event for streaming RPCs. However, responseReceived(long) is also invoked for unary calls to record the response size. This implementation will incorrectly add a streaming message event to spans for unary calls.
This is also inconsistent with the SpanTracer (OpenTelemetry) implementation, which only sets the size attribute.
To fix this, responseReceived() should not be called here. Instead, HttpJsonClientCallImpl should be modified to explicitly call tracer.responseReceived() for server-streaming calls, in addition to tracer.responseReceived(responseSize).
public void responseReceived(long responseSize) {
span.putAttribute(
ObservabilityAttributes.HTTP_RESPONSE_BODY_SIZE,
AttributeValue.longAttributeValue(responseSize));
}gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java (182-184)
The Javadoc for responseReceived(long) states it's for a "streaming response". This is a bit misleading as the method is also used for unary calls to record the response body size. To avoid confusion, could you please make the documentation more generic?
/** Adds an annotation for the size of a received response body in bytes. */
default void responseReceived(long responseSize) {}
;
gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java (80-90)
The new setAttribute method only handles Long, Integer, and String explicitly, falling back to value.toString() for all other types. This could lead to incorrect attribute types for other common types like Double or Boolean, which would be converted to strings instead of being stored as their native types. To ensure conformance with OpenTelemetry Semantic Conventions for data types, please consider handling more primitive wrapper types directly.
public void setAttribute(String key, Object value) {
if (value instanceof Long) {
span.setAttribute(key, (Long) value);
} else if (value instanceof Integer) {
span.setAttribute(key, ((Integer) value).longValue());
} else if (value instanceof Double) {
span.setAttribute(key, (Double) value);
} else if (value instanceof Boolean) {
span.setAttribute(key, (Boolean) value);
} else if (value instanceof String) {
span.setAttribute(key, (String) value);
} else {
span.setAttribute(key, value.toString());
}
}References
- When defining OpenTelemetry attributes, conform to the Semantic Conventions (SemConv) for data types.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a new observability attribute http.response.body_size to track the size of HTTP response bodies. The implementation correctly uses a CountingInputStream to measure the bytes read from the response stream for both unary and server-streaming calls. The changes span across the tracing interfaces and implementations (OpenCensus, OpenTelemetry) to support this new attribute. New unit and integration tests are added to verify the functionality. The changes are well-implemented and improve the observability of the HTTP client. The suggestion to enhance the setAttribute method for better OpenTelemetry type conformity is noted.
gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java
Outdated
Show resolved
Hide resolved
|
/gcbrun |
| } | ||
|
|
||
| if (responseCountingStream == null) { | ||
| responseCountingStream = new CountingInputStream(runnableResult.getResponseContent()); |
There was a problem hiding this comment.
This does not seem to be an intuitive approach, can we rely on the ContentLength header instead? See discussion.
| ; | ||
|
|
||
| /** Adds an annotation that a streaming response has been received with size. */ | ||
| default void recordResponseSize(long responseSize) {} |
There was a problem hiding this comment.
If we go with the header approach, I think we can make this new method more generic and pass all headers to the tracer. Maybe something like "headersReceived".
…in tracing refactor)
|
|
| try { | ||
| String contentLengthStr = | ||
| value instanceof java.util.List | ||
| ? ((java.util.List<?>) value).get(0).toString() |
There was a problem hiding this comment.
Is this an edge case? I'm not sure we want to consider this edge case even if it could happen. I think it's OK to parseLong directly and consider other scenarios invalid.
There was a problem hiding this comment.
Actually I meant that we don't have to check if it is a list, but it's still better to do a try catch.
gax-java/gax/src/main/java/com/google/api/gax/tracing/BaseApiTracer.java
Outdated
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
Outdated
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java
Outdated
Show resolved
Hide resolved
…nd simplify parseContentLength
| * @return the content length in bytes, or -1 if the header is missing or malformed. | ||
| */ | ||
| private long extractContentLength(java.util.Map<String, Object> headers) { | ||
| if (headers == null) { |
There was a problem hiding this comment.
I think the logic in this method and the next method can be simplified to
if (headers == null || headers.get(CONTENT_LENGTH_KEY) == null) {
return -1;
}
return Long.parseLong(String.valueOf(headers.get(CONTENT_LENGTH_KEY)));
Add try catch as I mentioned in another comment though.


Introduces the response size attribute.
Note that the sibling attribute
http.request.body_sizeis not a straightforward implementation since the HTTP libraries don't offer an easy way to access the compressed body size.