Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,18 @@ 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) {
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());
Expand All @@ -68,20 +75,46 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(

StorageMetricsContext metricsCtx = StorageMetricsContext.from(callOptions);
Context otelContext = metricsCtx != null ? metricsCtx.getOtelContext() : Context.current();
String operationName =
(metricsCtx != null && metricsCtx.getOperationName() != null)
? metricsCtx.getOperationName()
: "ReadObject";

return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
@Override
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) {
// The first response message from the server (including metadata-only
// responses for 0-byte objects) marks Time To First Byte (TTFB).
if (recordTtfb && 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_GCP_STORAGE_OPERATION, operationName)
.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 {
if (callRecorded.compareAndSet(false, true)
&& metrics.getRpcClientCallDuration() != null) {
if (recordDuration && callRecorded.compareAndSet(false, true)) {
double durationSeconds =
Math.max(0.0, (clock.nanoTime() - startTimeNanos) / 1_000_000_000.0);
String networkPath = detectNetworkPath(getAttributes(), defaultEndpoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ public void initialize(HttpRequest request) throws java.io.IOException {
if (delegate != null) {
delegate.initialize(request);
}
if (metrics == null || metrics.getHttpClientRequestDuration() == null) {
if (metrics == null) {
return;
}
boolean recordDuration = metrics.getHttpClientRequestDuration() != null;
boolean recordTtfb = metrics.getTtfb() != null;
if (!recordDuration && !recordTtfb) {
return;
}

Expand All @@ -80,73 +85,97 @@ 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);

metrics
.getHttpClientRequestDuration()
.record(durationSeconds, attrBuilder.build(), Context.current());
}
});
if (recordDuration) {
String method =
request.getRequestMethod() != null ? request.getRequestMethod() : "UNKNOWN";
String urlTemplate = extractUrlTemplate(request.getUrl());

request.setIOExceptionHandler(
(req, supportsRetry) -> {
if (recorded.compareAndSet(false, true)
&& metrics.getHttpClientRequestDuration() != null) {
double durationSeconds =
Math.max(0.0, (clock.nanoTime() - startTimeNanos.get()) / 1_000_000_000.0);
String method = req.getRequestMethod() != null ? req.getRequestMethod() : "UNKNOWN";
String urlTemplate = extractUrlTemplate(req.getUrl());
String serverAddress = resolveServerAddress(req.getUrl(), defaultServerAddress);

AttributesBuilder attrBuilder =
metrics
.clientAttributesBuilder()
.put(StorageClientMetrics.KEY_HTTP_REQUEST_METHOD, method)
.put(StorageClientMetrics.KEY_HTTP_RESPONSE_STATUS_CODE, 0L)
.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,
StorageErrorTypeMapper.STATUS_CONNECTIVITY);

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());
}

// Evaluate isMediaDownloadRequest(request) at response time rather than in
// initialize(HttpRequest), because callers such as MediaHttpDownloader may append
// query parameters (e.g. alt=media) to request.getUrl() after buildGetRequest().
if (recordTtfb
&& statusCode >= 200
&& statusCode < 300
&& isMediaDownloadRequest(request)) {
AttributesBuilder ttfbAttrs =
metrics
.clientAttributesBuilder()
.put(StorageClientMetrics.KEY_RPC_SYSTEM_NAME, "http")
.put(StorageClientMetrics.KEY_GCP_STORAGE_OPERATION, "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());
}
}
return existingIoHandler != null
&& existingIoHandler.handleIOException(req, supportsRetry);
});

if (recordDuration) {
request.setIOExceptionHandler(
(req, supportsRetry) -> {
if (recorded.compareAndSet(false, true)) {
double durationSeconds =
Math.max(0.0, (clock.nanoTime() - startTimeNanos.get()) / 1_000_000_000.0);
String method = req.getRequestMethod() != null ? req.getRequestMethod() : "UNKNOWN";
String urlTemplate = extractUrlTemplate(req.getUrl());
String serverAddress = resolveServerAddress(req.getUrl(), defaultServerAddress);

AttributesBuilder attrBuilder =
metrics
.clientAttributesBuilder()
.put(StorageClientMetrics.KEY_HTTP_REQUEST_METHOD, method)
.put(StorageClientMetrics.KEY_HTTP_RESPONSE_STATUS_CODE, 0L)
.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,
StorageErrorTypeMapper.STATUS_CONNECTIVITY);

metrics
.getHttpClientRequestDuration()
.record(durationSeconds, attrBuilder.build(), Context.current());
}
return existingIoHandler != null
&& existingIoHandler.handleIOException(req, supportsRetry);
});
}
}

static String resolveServerAddress(
Expand Down Expand Up @@ -223,4 +252,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"));
}
}
Loading