diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java index ac31f2cfab3e..b064495db68e 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java @@ -26,6 +26,7 @@ import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientSessionDuration; import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientSessionOpenLatency; import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientSessionUptime; +import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientUptime; import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientTransportLatency; import com.google.cloud.bigtable.data.v2.internal.csm.metrics.CustomAttemptLatency; import com.google.cloud.bigtable.data.v2.internal.csm.metrics.GrpcMetric; @@ -80,6 +81,7 @@ public class MetricRegistry { final ClientTransportLatency transportLatencyMetric; final ClientSessionUptime sessionUptimeMetric; + final ClientUptime clientUptimeMetric; final ClientSessionDuration sessionDurationMetric; final ClientSessionOpenLatency sessionOpenLatencyMetric; @@ -111,6 +113,7 @@ public MetricRegistry() { batchWriteFlowControlTargetQpsMetric = register(new ClientBatchWriteFlowControlTargetQps()); sessionUptimeMetric = register(new ClientSessionUptime()); + clientUptimeMetric = register(new ClientUptime()); sessionDurationMetric = register(new ClientSessionDuration()); sessionOpenLatencyMetric = register(new ClientSessionOpenLatency()); transportLatencyMetric = register(new ClientTransportLatency()); @@ -222,6 +225,7 @@ public class RecorderRegistry { public final ClientTransportLatency.Recorder transportLatency; public final ClientSessionUptime.Recorder sessionUptime; + public final ClientUptime.Recorder clientUptime; public final ClientSessionDuration.Recorder sessionDuration; public final ClientSessionOpenLatency.Recorder sessionOpenLatency; @@ -260,6 +264,7 @@ private RecorderRegistry(Meter meter, boolean disableInternalMetrics) { transportLatency = transportLatencyMetric.newRecorder(meter); sessionUptime = sessionUptimeMetric.newRecorder(meter); + clientUptime = clientUptimeMetric.newRecorder(meter); sessionDuration = sessionDurationMetric.newRecorder(meter); sessionOpenLatency = sessionOpenLatencyMetric.newRecorder(meter); diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java index 2eef8c9a3f3a..5d82af79bdb6 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java @@ -56,6 +56,7 @@ import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter; import com.google.cloud.opentelemetry.metric.MetricConfiguration; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; import com.google.common.base.Splitter; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; @@ -93,6 +94,7 @@ public class MetricsImpl implements Metrics, Closeable { .map(Boolean::parseBoolean) .orElse(false); + private final ClientInfo clientInfo; private final ApiTracerFactory userTracerFactory; private final @Nullable OpenTelemetrySdk internalOtel; private final @Nullable MetricRegistry.RecorderRegistry internalRecorder; @@ -113,6 +115,7 @@ public class MetricsImpl implements Metrics, Closeable { private final List sessionTracers = new ArrayList<>(); private final List> tasks = new ArrayList<>(); + private final Stopwatch clientUptimeStopwatch = Stopwatch.createUnstarted(); public MetricsImpl( MetricRegistry metricRegistry, @@ -123,6 +126,7 @@ public MetricsImpl( Tagger ocTagger, StatsRecorder ocRecorder, ScheduledExecutorService executor) { + this.clientInfo = clientInfo; this.userTracerFactory = Preconditions.checkNotNull(userTracerFactory); this.internalOtel = internalOtel; @@ -174,6 +178,10 @@ public void close() { for (ScheduledFuture task : tasks) { task.cancel(false); } + if (clientUptimeStopwatch.isRunning()) { + clientUptimeStopwatch.stop(); + recordAsyncClientMetrics(); + } if (internalOtel != null) { internalOtel.close(); } @@ -181,6 +189,7 @@ public void close() { @Override public void start() { + clientUptimeStopwatch.start(); if (channelPoolMetricsTracer != null) { tasks.add(channelPoolMetricsTracer.start(executor)); } @@ -189,7 +198,7 @@ public void start() { } if (internalOtel != null) { tasks.add( - executor.scheduleAtFixedRate(this::recordAsyncSessionMetrics, 1, 1, TimeUnit.MINUTES)); + executor.scheduleAtFixedRate(this::recordAsyncMetrics, 1, 1, TimeUnit.MINUTES)); } } @@ -250,6 +259,17 @@ private void recordAsyncSessionMetrics() { } } + private void recordAsyncMetrics() { + recordAsyncSessionMetrics(); + recordAsyncClientMetrics(); + } + + private void recordAsyncClientMetrics() { + if (internalRecorder != null) { + internalRecorder.clientUptime.record(clientInfo, clientUptimeStopwatch.elapsed()); + } + } + @Override public PoolFallbackListener getPoolFallbackListener() { return poolFallbackListener; diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientUptime.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientUptime.java new file mode 100644 index 000000000000..f79a9f8fc4a3 --- /dev/null +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientUptime.java @@ -0,0 +1,60 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigtable.data.v2.internal.csm.metrics; + +import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo; +import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.Units; +import com.google.cloud.bigtable.data.v2.internal.csm.schema.ClientSchema; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import java.time.Duration; + +public class ClientUptime extends MetricWrapper { + private static final String NAME = "bigtable.googleapis.com/internal/client/uptime"; + + public ClientUptime() { + super(ClientSchema.INSTANCE, NAME); + } + + public Recorder newRecorder(Meter meter) { + return new Recorder(meter); + } + + public class Recorder { + private final LongGauge instrument; + + private Recorder(Meter meter) { + instrument = + meter + .gaugeBuilder(NAME) + .setDescription("The uptime of the client") + .setUnit(Units.MILLISECOND) + .ofLongs() + .build(); + } + + public void record(ClientInfo clientInfo, Duration duration) { + Attributes attributes = + getSchema() + .createResourceAttrs(clientInfo) + .build(); + + instrument.set(duration.toMillis(), attributes); + } + } +} diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java index 2f157180faef..7f3fb69946bf 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java @@ -751,6 +751,29 @@ void testSessionUptime() { .build()); } + @Test + void testClientUptime() { + registry.clientUptime.record(clientInfo, Duration.ofMinutes(10)); + metricReader.forceFlush().join(1, TimeUnit.MINUTES); + + TimeSeries timeSeries = + metricService.getSingleTimeSeriesByName( + "bigtable.googleapis.com/internal/client/uptime"); + + assertThat(timeSeries.getResource()).isEqualTo(expectedClientMonitoredResource); + + assertThat(timeSeries.getMetric().getLabelsMap()).isEmpty(); + + assertThat(timeSeries.getPointsList()) + .comparingExpectedFieldsOnly() + .containsExactly( + Point.newBuilder() + .setValue( + TypedValue.newBuilder() + .setInt64Value(Duration.ofMinutes(10).toMillis())) + .build()); + } + @Test void testPacemaker() { registry.pacemakerDelay.record(clientInfo, "background", Duration.ofMillis(1));