Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 39eb7f5

Browse files
chore: refactor metrics to be more structured and typesafe
Change-Id: I8573fdaf6d78398fdcad9a1c2cda16445f897276
1 parent d0d48c2 commit 39eb7f5

41 files changed

Lines changed: 2888 additions & 429 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

google-cloud-bigtable/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,26 @@
338338
<artifactId>junit</artifactId>
339339
<scope>test</scope>
340340
</dependency>
341+
<dependency>
342+
<groupId>org.junit.jupiter</groupId>
343+
<artifactId>junit-jupiter-api</artifactId>
344+
<scope>test</scope>
345+
</dependency>
346+
<dependency>
347+
<groupId>org.junit.jupiter</groupId>
348+
<artifactId>junit-jupiter</artifactId>
349+
<scope>test</scope>
350+
</dependency>
341351
<dependency>
342352
<groupId>org.mockito</groupId>
343353
<artifactId>mockito-core</artifactId>
344354
<scope>test</scope>
345355
</dependency>
356+
<dependency>
357+
<groupId>org.mockito</groupId>
358+
<artifactId>mockito-junit-jupiter</artifactId>
359+
<scope>test</scope>
360+
</dependency>
346361
<dependency>
347362
<groupId>com.google.guava</groupId>
348363
<artifactId>guava-testlib</artifactId>

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ public BigtableDataClient createDefault() {
109109
try {
110110
BigtableClientContext ctx =
111111
sharedClientContext.createChild(
112-
sharedClientContext.getInstanceName(), sharedClientContext.getAppProfileId());
112+
sharedClientContext.getClientInfo().getInstanceName(),
113+
sharedClientContext.getClientInfo().getAppProfileId());
113114

114115
return new BigtableDataClient(new EnhancedBigtableStub(perOpSettings, ctx));
115116
} catch (IOException e) {
@@ -130,7 +131,7 @@ public BigtableDataClient createDefault() {
130131
*/
131132
public BigtableDataClient createForAppProfile(@Nonnull String appProfileId) throws IOException {
132133
BigtableClientContext ctx =
133-
sharedClientContext.createChild(sharedClientContext.getInstanceName(), appProfileId);
134+
sharedClientContext.createChild(sharedClientContext.getClientInfo().getInstanceName(), appProfileId);
134135

135136
return new BigtableDataClient(new EnhancedBigtableStub(perOpSettings, ctx));
136137
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.bigtable.data.v2.internal.csm;
18+
19+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientPerConnectionErrorCount;
20+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.GrpcMetric;
21+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.MetricWrapper;
22+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.PacemakerDelay;
23+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableApplicationBlockingLatency;
24+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableAttemptLatency;
25+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableAttemptLatency2;
26+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableBatchWriteFlowControlFactor;
27+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableBatchWriteFlowControlTargetQps;
28+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableClientBlockingLatency;
29+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableConnectivityErrorCount;
30+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableFirstResponseLatency;
31+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableOperationLatency;
32+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableRemainingDeadline;
33+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableRetryCount;
34+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableServerLatency;
35+
import com.google.common.collect.ImmutableList;
36+
import io.opentelemetry.api.metrics.Meter;
37+
import io.opentelemetry.api.metrics.MeterProvider;
38+
import java.util.ArrayList;
39+
import java.util.HashMap;
40+
import java.util.List;
41+
import java.util.Map;
42+
43+
/**
44+
* Repository for all client metrics. This class has 2 audiences:
45+
*
46+
* <ul>
47+
* <li>VRpcTracer, which reference each metric directly
48+
* <li>Exporter, which will look up each metric by name and use the {@link MetricWrapper}
49+
* interface to augment the {@code MonitoredResource} and {@code Metric Labels}
50+
* </ul>
51+
*/
52+
public class MetricRegistry {
53+
static final String METER_NAME = "bigtable.googleapis.com/internal/client/";
54+
55+
final TableOperationLatency operationLatencyMetric;
56+
final TableAttemptLatency attemptLatencyMetric;
57+
final TableAttemptLatency2 attemptLatency2Metric;
58+
final TableRetryCount retryCountMetric;
59+
final TableFirstResponseLatency firstResponseLantencyMetric;
60+
final TableServerLatency serverLatencyMetric;
61+
final TableConnectivityErrorCount connectivityErrorCountMetric;
62+
final TableApplicationBlockingLatency applicationBlockingLatencyMetric;
63+
final TableClientBlockingLatency clientBlockingLatencyMetric;
64+
final ClientPerConnectionErrorCount perConnectionErrorCountMetric;
65+
final TableRemainingDeadline remainingDeadlineMetric;
66+
final TableBatchWriteFlowControlFactor batchWriteFlowControlFactorMetric;
67+
final TableBatchWriteFlowControlTargetQps batchWriteFlowControlTargetQpsMetric;
68+
69+
final PacemakerDelay pacemakerDelayMetric;
70+
71+
private final Map<String, MetricWrapper<?>> metrics = new HashMap<>();
72+
private final List<String> grpcMetricNames = new ArrayList<>();
73+
74+
public MetricRegistry() {
75+
operationLatencyMetric = register(new TableOperationLatency());
76+
attemptLatencyMetric = register(new TableAttemptLatency());
77+
attemptLatency2Metric = register(new TableAttemptLatency2());
78+
retryCountMetric = register(new TableRetryCount());
79+
firstResponseLantencyMetric = register(new TableFirstResponseLatency());
80+
serverLatencyMetric = register(new TableServerLatency());
81+
connectivityErrorCountMetric = register(new TableConnectivityErrorCount());
82+
applicationBlockingLatencyMetric = register(new TableApplicationBlockingLatency());
83+
clientBlockingLatencyMetric = register(new TableClientBlockingLatency());
84+
perConnectionErrorCountMetric = register(new ClientPerConnectionErrorCount());
85+
remainingDeadlineMetric = register(new TableRemainingDeadline());
86+
batchWriteFlowControlFactorMetric = register(new TableBatchWriteFlowControlFactor());
87+
batchWriteFlowControlTargetQpsMetric = register(new TableBatchWriteFlowControlTargetQps());
88+
89+
pacemakerDelayMetric = register(new PacemakerDelay());
90+
91+
registerGrpcMetric(
92+
"grpc.client.attempt.duration",
93+
ImmutableList.of("grpc.lb.locality", "grpc.method", "grpc.target", "grpc.status"));
94+
registerGrpcMetric(
95+
"grpc.lb.rls.default_target_picks",
96+
ImmutableList.of("grpc.lb.rls.data_plane_target", "grpc.lb.pick_result"));
97+
registerGrpcMetric(
98+
"grpc.lb.rls.target_picks",
99+
ImmutableList.of(
100+
"grpc.target",
101+
"grpc.lb.rls.server_target",
102+
"grpc.lb.rls.data_plane_target",
103+
"grpc.lb.pick_result"));
104+
registerGrpcMetric(
105+
"grpc.lb.rls.failed_picks", ImmutableList.of("grpc.target", "grpc.lb.rls.server_target"));
106+
// TODO: "grpc.xds_client.connected"
107+
registerGrpcMetric(
108+
"grpc.xds_client.server_failure", ImmutableList.of("grpc.target", "grpc.xds.server"));
109+
// TODO: "grpc.xds_client.resource_updates_valid",
110+
registerGrpcMetric(
111+
"grpc.xds_client.resource_updates_invalid",
112+
ImmutableList.of("grpc.target", "grpc.xds.server", "grpc.xds.resource_type"));
113+
// TODO: "grpc.xds_client.resources"
114+
115+
}
116+
117+
private void registerGrpcMetric(String name, List<String> labels) {
118+
grpcMetricNames.add(name);
119+
register(new GrpcMetric(name, labels));
120+
}
121+
122+
private <T extends MetricWrapper<?>> T register(T instrument) {
123+
metrics.put(instrument.getName(), instrument);
124+
return instrument;
125+
}
126+
127+
List<String> getGrpcMetricNames() {
128+
return ImmutableList.copyOf(grpcMetricNames);
129+
}
130+
131+
MetricWrapper<?> getMetric(String name) {
132+
return metrics.get(name);
133+
}
134+
135+
public RecorderRegistry newRecorderRegistry(MeterProvider meterProvider) {
136+
return new RecorderRegistry(meterProvider.get(METER_NAME));
137+
}
138+
139+
public class RecorderRegistry {
140+
public final TableOperationLatency.Recorder operationLatency;
141+
public final TableAttemptLatency.Recorder attemptLatency;
142+
public final TableAttemptLatency2.Recorder attemptLatency2;
143+
public final TableRetryCount.Recorder retryCount;
144+
public final TableFirstResponseLatency.Recorder firstResponseLantency;
145+
public final TableServerLatency.Recorder serverLatency;
146+
public final TableConnectivityErrorCount.Recorder connectivityErrorCount;
147+
public final TableApplicationBlockingLatency.Recorder applicationBlockingLatency;
148+
public final TableClientBlockingLatency.Recorder clientBlockingLatency;
149+
public final ClientPerConnectionErrorCount.Recorder perConnectionErrorCount;
150+
public final TableRemainingDeadline.Recorder remainingDeadline;
151+
public final TableBatchWriteFlowControlTargetQps.Recorder batchWriteFlowControlTargetQps;
152+
public final TableBatchWriteFlowControlFactor.Recorder batchWriteFlowControlFactor;
153+
154+
final PacemakerDelay.Recorder pacemakerDelay;
155+
156+
private RecorderRegistry(Meter meter) {
157+
operationLatency = operationLatencyMetric.newRecorder(meter);
158+
attemptLatency = attemptLatencyMetric.newRecorder(meter);
159+
attemptLatency2 = attemptLatency2Metric.newRecorder(meter);
160+
retryCount = retryCountMetric.newRecorder(meter);
161+
firstResponseLantency = firstResponseLantencyMetric.newRecorder(meter);
162+
serverLatency = serverLatencyMetric.newRecorder(meter);
163+
connectivityErrorCount = connectivityErrorCountMetric.newRecorder(meter);
164+
applicationBlockingLatency = applicationBlockingLatencyMetric.newRecorder(meter);
165+
clientBlockingLatency = clientBlockingLatencyMetric.newRecorder(meter);
166+
perConnectionErrorCount = perConnectionErrorCountMetric.newRecorder(meter);
167+
remainingDeadline = remainingDeadlineMetric.newRecorder(meter);
168+
batchWriteFlowControlTargetQps = batchWriteFlowControlTargetQpsMetric.newRecorder(meter);
169+
batchWriteFlowControlFactor = batchWriteFlowControlFactorMetric.newRecorder(meter);
170+
171+
pacemakerDelay = pacemakerDelayMetric.newRecorder(meter);
172+
}
173+
}
174+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.bigtable.data.v2.internal.csm;
17+
18+
import com.google.cloud.bigtable.data.v2.internal.csm.MetricRegistry.RecorderRegistry;
19+
import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
20+
import java.time.Duration;
21+
import java.time.Instant;
22+
23+
class Pacemaker implements Runnable {
24+
25+
static final Duration PACEMAKER_INTERVAL = Duration.ofMillis(100);
26+
27+
private final RecorderRegistry registry;
28+
private final ClientInfo clientInfo;
29+
private final String executorName;
30+
31+
private Instant prev;
32+
33+
Pacemaker(RecorderRegistry registry, ClientInfo clientInfo, String name) {
34+
this.prev = Instant.now();
35+
this.registry = registry;
36+
this.clientInfo = clientInfo;
37+
this.executorName = name;
38+
}
39+
40+
@Override
41+
public void run() {
42+
Instant current = Instant.now();
43+
Duration delta = Duration.between(prev, current).minus(PACEMAKER_INTERVAL);
44+
prev = current;
45+
registry.pacemakerDelay.record(
46+
clientInfo, executorName, delta.isNegative() ? Duration.ZERO : delta);
47+
}
48+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.bigtable.data.v2.internal.csm.attributes;
18+
19+
import com.google.auto.value.AutoValue;
20+
import com.google.bigtable.v2.FeatureFlags;
21+
import com.google.bigtable.v2.InstanceName;
22+
import com.google.cloud.bigtable.Version;
23+
import java.lang.management.ManagementFactory;
24+
import java.net.InetAddress;
25+
import java.net.UnknownHostException;
26+
import java.util.UUID;
27+
import java.util.concurrent.atomic.AtomicLong;
28+
import java.util.logging.Level;
29+
import java.util.logging.Logger;
30+
31+
/**
32+
* A value class to capture parameters that the client was instantiated with. These parameters will
33+
* be used by the Exporter to derive MonitoredResource for GrpcMetrics.
34+
*/
35+
@AutoValue
36+
public abstract class ClientInfo {
37+
private static final Logger logger = Logger.getLogger(ClientInfo.class.getName());
38+
39+
private static final AtomicLong uidSuffix = new AtomicLong(0);
40+
41+
/** The name and version of the client. */
42+
public abstract String getClientName();
43+
44+
/** A unique identifier to disambiguate TimeSeries from multiple processes on the same VM. */
45+
public abstract String getUid();
46+
47+
public abstract InstanceName getInstanceName();
48+
49+
public abstract String getAppProfileId();
50+
51+
public abstract Builder toBuilder();
52+
53+
public static Builder builder() {
54+
return new AutoValue_ClientInfo.Builder()
55+
.setClientName("java-bigtable/" + Version.VERSION)
56+
.setUid(computeUid() + "-" + uidSuffix.getAndIncrement());
57+
}
58+
59+
@AutoValue.Builder
60+
public abstract static class Builder {
61+
protected abstract Builder setClientName(String name);
62+
63+
protected abstract Builder setUid(String uid);
64+
65+
public abstract Builder setInstanceName(InstanceName name);
66+
67+
public abstract Builder setAppProfileId(String appProfileId);
68+
69+
public abstract ClientInfo build();
70+
}
71+
72+
private static String computeUid() {
73+
final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
74+
// If jvm doesn't have the expected format, fallback to the local hostname
75+
if (jvmName.indexOf('@') < 1) {
76+
String hostname = "localhost";
77+
try {
78+
hostname = InetAddress.getLocalHost().getHostName();
79+
} catch (UnknownHostException e) {
80+
logger.log(Level.INFO, "Unable to get the hostname.", e);
81+
}
82+
// Generate a random number and use the same format "random_number@hostname".
83+
return "java-" + UUID.randomUUID() + "@" + hostname;
84+
}
85+
return "java-" + UUID.randomUUID() + jvmName;
86+
}
87+
}

0 commit comments

Comments
 (0)