Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,9 @@ public Storage create(StorageOptions options) {
OpenTelemetry otel = options.getOpenTelemetry();
DefaultRetrier retrier =
new DefaultRetrier(
OtelStorageDecorator.retryContextDecorator(otel), grpcStorageOptions);
OtelStorageDecorator.retryContextDecorator(otel),
grpcStorageOptions,
grpcStorageOptions.getStorageClientMetrics());
if (ZeroCopyReadinessChecker.isReady()) {
LOGGER.config("zero-copy protobuf deserialization available, using it");
StorageStubSettings baseSettings =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.net.HostAndPort;
import io.grpc.ManagedChannelBuilder;
import io.grpc.opentelemetry.GrpcOpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
Expand Down Expand Up @@ -103,28 +104,12 @@ static ChannelConfigurator enableGrpcMetrics(
GCPResourceProvider resourceProvider = new GCPResourceProvider();
Attributes detectedAttributes = resourceProvider.getAttributes();

@Nullable String detectedProjectId =
detectedAttributes.get(AttributeKey.stringKey("cloud.account.id"));
if (projectId == null && detectedProjectId == null) {
log.warning(
"Unable to determine the Project ID in order to report metrics. No gRPC client metrics"
+ " will be reported.");
String projectIdToUse =
resolveProjectIdForMetrics(detectedAttributes, projectId, "gRPC client metrics");
if (projectIdToUse == null) {
return channelConfigurator != null ? channelConfigurator : ChannelConfigurator.identity();
}

String projectIdToUse = detectedProjectId == null ? projectId : detectedProjectId;
if (!projectIdToUse.equals(projectId)) {
log.warning(
"The Project ID configured for gRPC client metrics is "
+ projectIdToUse
+ ", but the Project ID of the storage client is "
+ projectId
+ ". Make sure that the service account in use has the required metric writing role "
+ "(roles/monitoring.metricWriter) in the project "
+ projectIdToUse
+ ", or metrics will not be written.");
}

String metricServiceEndpoint = getCloudMonitoringEndpoint(endpoint, universeDomain);
SdkMeterProvider provider =
createMeterProvider(
Expand Down Expand Up @@ -206,7 +191,143 @@ static String getCloudMonitoringEndpoint(String endpoint, String universeDomain)
metricServiceEndpoint = match;
}
}
return metricServiceEndpoint + ":" + endpoint.split(":")[1];
HostAndPort parsed = parseHostAndPort(endpoint);
int port = parsed != null ? parsed.getPortOrDefault(443) : 443;
return metricServiceEndpoint + ":" + port;
}

@Nullable
private static HostAndPort parseHostAndPort(@Nullable String host) {
if (host == null || host.trim().isEmpty()) {
return null;
}
String s = host.trim();
if (s.startsWith("google-c2p:///")) {
s = s.substring("google-c2p:///".length());
} else if (s.startsWith("https://")) {
s = s.substring("https://".length());
} else if (s.startsWith("http://")) {
s = s.substring("http://".length());
}
int slashIdx = s.indexOf('/');
if (slashIdx >= 0) {
s = s.substring(0, slashIdx);
}
if (s.isEmpty()) {
return null;
}
try {
return HostAndPort.fromString(s);
} catch (IllegalArgumentException e) {
return null;
}
}

static String formatStorageEndpoint(@Nullable String host) {
HostAndPort parsed = parseHostAndPort(host);
if (parsed == null) {
return "storage.googleapis.com:443";
}
return parsed.withDefaultPort(443).toString();
}

@Nullable
static String extractServerAddress(@Nullable String host) {
HostAndPort parsed = parseHostAndPort(host);
return parsed != null ? parsed.getHost() : null;
}

@Nullable
private static String resolveProjectIdForMetrics(
Attributes detectedAttributes,
@Nullable String configuredProjectId,
String metricsTypeLabel) {
@Nullable String detectedProjectId =
detectedAttributes.get(AttributeKey.stringKey("cloud.account.id"));
if (detectedProjectId != null && detectedProjectId.trim().isEmpty()) {
detectedProjectId = null;
}
@Nullable String cleanConfiguredProjectId =
(configuredProjectId != null && !configuredProjectId.trim().isEmpty())
? configuredProjectId.trim()
: null;
if (cleanConfiguredProjectId == null && detectedProjectId == null) {
log.warning(
"Unable to determine the Project ID in order to report metrics. No "
+ metricsTypeLabel
+ " will be reported.");
return null;
}
String projectIdToUse =
detectedProjectId != null ? detectedProjectId : cleanConfiguredProjectId;
if (cleanConfiguredProjectId != null && !projectIdToUse.equals(cleanConfiguredProjectId)) {
log.warning(
"The Project ID configured for "
+ metricsTypeLabel
+ " is "
+ projectIdToUse
+ ", but the Project ID of the storage client is "
+ cleanConfiguredProjectId
+ ". Make sure that the service account in use has the required metric writing"
+ " role (roles/monitoring.metricWriter) in the project "
+ projectIdToUse
+ ", or metrics will not be written.");
}
return projectIdToUse;
}

@Nullable
static SdkMeterProvider createDefaultClientMeterProvider(
@Nullable String host,
@Nullable String configuredProjectId,
@Nullable String universeDomain,
@Nullable Duration metricInterval,
String api) {
try {
GCPResourceProvider resourceProvider = new GCPResourceProvider();
Attributes detectedAttributes = resourceProvider.getAttributes();
return createDefaultClientMeterProvider(
host, configuredProjectId, universeDomain, metricInterval, api, detectedAttributes);
} catch (Exception e) {
log.warning("Failed to create default client MeterProvider: " + e.getMessage());
return null;
}
}

@VisibleForTesting
@Nullable
static SdkMeterProvider createDefaultClientMeterProvider(
@Nullable String host,
@Nullable String configuredProjectId,
@Nullable String universeDomain,
@Nullable Duration metricInterval,
String api,
Attributes detectedAttributes) {
try {
String projectIdToUse =
resolveProjectIdForMetrics(
detectedAttributes, configuredProjectId, "storage client metrics");
if (projectIdToUse == null) {
return null;
}
Attributes attributesToUse =
Attributes.builder()
.putAll(detectedAttributes)
.put(AttributeKey.stringKey("cloud.account.id"), projectIdToUse)
.build();
String formattedEndpoint = formatStorageEndpoint(host);
String metricServiceEndpoint = getCloudMonitoringEndpoint(formattedEndpoint, universeDomain);
return createClientMeterProvider(
metricServiceEndpoint,
projectIdToUse,
attributesToUse,
metricInterval,
/* shouldSuppressExceptions= */ true,
api);
} catch (Exception e) {
log.warning("Failed to create default client MeterProvider: " + e.getMessage());
return null;
}
}

static final ImmutableList<String> CLIENT_LATENCY_HISTOGRAMS =
Expand Down
Loading
Loading