Repository navigation
feat(storage): add gcp.storage.client.ttfb metric for read operations #14745
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
base: feat/storage-http-request-duration
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -53,11 +53,13 @@ final class StorageGrpcMetricsInterceptor implements ClientInterceptor { | |
| @Override | ||
| public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( | ||
| MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { | ||
| if (metrics == null || metrics.getRpcClientCallDuration() == null) { | ||
| if (metrics == null | ||
| || (metrics.getRpcClientCallDuration() == null && metrics.getTtfb() == null)) { | ||
| return next.newCall(method, callOptions); | ||
| } | ||
|
|
||
| String shortMethod = extractShortMethodName(method.getFullMethodName()); | ||
| boolean isReadMethod = "ReadObject".equals(shortMethod) || "BidiReadObject".equals(shortMethod); | ||
|
Comment on lines
+56
to
+62
Contributor
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. If Additionally, if the method is not a read method (e.g., We can solve both issues by extracting the method check earlier and defining if (metrics == null) {
return next.newCall(method, callOptions);
}
String shortMethod = extractShortMethodName(method.getFullMethodName());
boolean isReadMethod = "ReadObject".equals(shortMethod) || "BidiReadObject".equals(shortMethod);
boolean recordDuration = metrics.getRpcClientCallDuration() != null;
boolean recordTtfb = isReadMethod && metrics.getTtfb() != null;
if (!recordDuration && !recordTtfb) {
return next.newCall(method, callOptions);
} |
||
| String initialServerAddress = | ||
| StorageOptions.extractServerAddress( | ||
| defaultEndpoint != null ? defaultEndpoint : next.authority()); | ||
|
|
@@ -74,9 +76,32 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( | |
| public void start(Listener<RespT> responseListener, Metadata headers) { | ||
| long startTimeNanos = clock.nanoTime(); | ||
| AtomicBoolean callRecorded = new AtomicBoolean(false); | ||
| AtomicBoolean ttfbRecorded = new AtomicBoolean(false); | ||
|
|
||
| super.start( | ||
| new SimpleForwardingClientCallListener<RespT>(responseListener) { | ||
| @Override | ||
| public void onMessage(RespT message) { | ||
| if (isReadMethod | ||
| && metrics.getTtfb() != null | ||
| && ttfbRecorded.compareAndSet(false, true)) { | ||
| double ttfbSeconds = | ||
| Math.max(0.0, (clock.nanoTime() - startTimeNanos) / 1_000_000_000.0); | ||
| String networkPath = detectNetworkPath(getAttributes(), defaultEndpoint); | ||
| AttributesBuilder ttfbAttrs = | ||
| metrics | ||
| .clientAttributesBuilder() | ||
| .put(StorageClientMetrics.KEY_RPC_SYSTEM_NAME, "grpc") | ||
| .put(StorageClientMetrics.KEY_RPC_METHOD, shortMethod) | ||
| .put(StorageClientMetrics.KEY_NETWORK_PATH, networkPath) | ||
| .put( | ||
| StorageClientMetrics.KEY_SERVER_ADDRESS, | ||
| serverAddress != null ? serverAddress : ""); | ||
| metrics.getTtfb().record(ttfbSeconds, ttfbAttrs.build(), otelContext); | ||
| } | ||
| super.onMessage(message); | ||
| } | ||
|
|
||
| @Override | ||
| public void onClose(Status status, Metadata trailers) { | ||
| try { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,7 +55,8 @@ public void initialize(HttpRequest request) throws java.io.IOException { | |||||||||||||||||||||||||||||
| if (delegate != null) { | ||||||||||||||||||||||||||||||
| delegate.initialize(request); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if (metrics == null || metrics.getHttpClientRequestDuration() == null) { | ||||||||||||||||||||||||||||||
| if (metrics == null | ||||||||||||||||||||||||||||||
| || (metrics.getHttpClientRequestDuration() == null && metrics.getTtfb() == null)) { | ||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
61
Contributor
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. We can optimize this guard by checking if we actually need to record any metrics for this specific request before registering the response interceptor. If Using effectively final
Suggested change
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -80,37 +81,54 @@ public void initialize(HttpRequest request) throws java.io.IOException { | |||||||||||||||||||||||||||||
| if (existingResp != null) { | ||||||||||||||||||||||||||||||
| existingResp.interceptResponse(response); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if (recorded.compareAndSet(false, true) | ||||||||||||||||||||||||||||||
| && metrics.getHttpClientRequestDuration() != null) { | ||||||||||||||||||||||||||||||
| if (recorded.compareAndSet(false, true)) { | ||||||||||||||||||||||||||||||
| double durationSeconds = | ||||||||||||||||||||||||||||||
| Math.max(0.0, (clock.nanoTime() - startTimeNanos.get()) / 1_000_000_000.0); | ||||||||||||||||||||||||||||||
| int statusCode = response.getStatusCode(); | ||||||||||||||||||||||||||||||
| String method = | ||||||||||||||||||||||||||||||
| request.getRequestMethod() != null ? request.getRequestMethod() : "UNKNOWN"; | ||||||||||||||||||||||||||||||
| String urlTemplate = extractUrlTemplate(request.getUrl()); | ||||||||||||||||||||||||||||||
| String serverAddress = resolveServerAddress(request.getUrl(), defaultServerAddress); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| AttributesBuilder attrBuilder = | ||||||||||||||||||||||||||||||
| metrics | ||||||||||||||||||||||||||||||
| .clientAttributesBuilder() | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_HTTP_REQUEST_METHOD, method) | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_HTTP_RESPONSE_STATUS_CODE, (long) statusCode) | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_NETWORK_PATH, | ||||||||||||||||||||||||||||||
| StorageClientMetrics.NETWORK_PATH_CLOUDPATH) | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_URL_TEMPLATE, urlTemplate) | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_SERVER_ADDRESS, | ||||||||||||||||||||||||||||||
| serverAddress != null ? serverAddress : "") | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_ERROR_TYPE, | ||||||||||||||||||||||||||||||
| statusCode >= 400 | ||||||||||||||||||||||||||||||
| ? StorageErrorTypeMapper.mapHttpResponseCode(statusCode) | ||||||||||||||||||||||||||||||
| : StorageErrorTypeMapper.STATUS_OK); | ||||||||||||||||||||||||||||||
| if (metrics.getHttpClientRequestDuration() != null) { | ||||||||||||||||||||||||||||||
| String method = | ||||||||||||||||||||||||||||||
| request.getRequestMethod() != null ? request.getRequestMethod() : "UNKNOWN"; | ||||||||||||||||||||||||||||||
| String urlTemplate = extractUrlTemplate(request.getUrl()); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| metrics | ||||||||||||||||||||||||||||||
| .getHttpClientRequestDuration() | ||||||||||||||||||||||||||||||
| .record(durationSeconds, attrBuilder.build(), Context.current()); | ||||||||||||||||||||||||||||||
| AttributesBuilder attrBuilder = | ||||||||||||||||||||||||||||||
| metrics | ||||||||||||||||||||||||||||||
| .clientAttributesBuilder() | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_HTTP_REQUEST_METHOD, method) | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_HTTP_RESPONSE_STATUS_CODE, (long) statusCode) | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_NETWORK_PATH, | ||||||||||||||||||||||||||||||
| StorageClientMetrics.NETWORK_PATH_CLOUDPATH) | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_URL_TEMPLATE, urlTemplate) | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_SERVER_ADDRESS, | ||||||||||||||||||||||||||||||
| serverAddress != null ? serverAddress : "") | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_ERROR_TYPE, | ||||||||||||||||||||||||||||||
| statusCode >= 400 | ||||||||||||||||||||||||||||||
| ? StorageErrorTypeMapper.mapHttpResponseCode(statusCode) | ||||||||||||||||||||||||||||||
| : StorageErrorTypeMapper.STATUS_OK); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| metrics | ||||||||||||||||||||||||||||||
| .getHttpClientRequestDuration() | ||||||||||||||||||||||||||||||
| .record(durationSeconds, attrBuilder.build(), Context.current()); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (metrics.getTtfb() != null && statusCode < 400 && isMediaDownloadRequest(request)) { | ||||||||||||||||||||||||||||||
|
Contributor
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. The
Suggested change
|
||||||||||||||||||||||||||||||
| AttributesBuilder ttfbAttrs = | ||||||||||||||||||||||||||||||
| metrics | ||||||||||||||||||||||||||||||
| .clientAttributesBuilder() | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_RPC_SYSTEM_NAME, "http") | ||||||||||||||||||||||||||||||
| .put(StorageClientMetrics.KEY_RPC_METHOD, "ReadObject") | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_NETWORK_PATH, | ||||||||||||||||||||||||||||||
| StorageClientMetrics.NETWORK_PATH_CLOUDPATH) | ||||||||||||||||||||||||||||||
| .put( | ||||||||||||||||||||||||||||||
| StorageClientMetrics.KEY_SERVER_ADDRESS, | ||||||||||||||||||||||||||||||
| serverAddress != null ? serverAddress : ""); | ||||||||||||||||||||||||||||||
| metrics.getTtfb().record(durationSeconds, ttfbAttrs.build(), Context.current()); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -223,4 +241,17 @@ static String extractUrlTemplate(@Nullable GenericUrl url) { | |||||||||||||||||||||||||||||
| String[] parts = trimmed.split("/", 2); | ||||||||||||||||||||||||||||||
| return parts.length == 1 ? "/{bucket}" : "/{bucket}/{object}"; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| static boolean isMediaDownloadRequest(@Nullable HttpRequest request) { | ||||||||||||||||||||||||||||||
| if (request == null) { | ||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if (!"GET".equalsIgnoreCase(request.getRequestMethod()) || request.getUrl() == null) { | ||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| GenericUrl url = request.getUrl(); | ||||||||||||||||||||||||||||||
| String path = url.getRawPath(); | ||||||||||||||||||||||||||||||
| return (path != null && path.startsWith("/download/storage/v1/")) | ||||||||||||||||||||||||||||||
| || "media".equals(url.getFirst("alt")); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
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.
Relaxing this guard to allow the interceptor to run when
metrics.getRpcClientCallDuration()is null (butmetrics.getTtfb()is non-null) will cause aNullPointerExceptioninonClosebecauseonCloselacks a null check forrpcClientCallDuration.In
StorageHttpMetricsInterceptor.java, a null check was correctly added around the recording ofhttpClientRequestDuration(line 90). A similar null check must be added inonClosein this file.Please update
onCloseto check for null before recording: