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

Commit d201b00

Browse files
chore: define strongly typed metric wrappers (#2801)
* chore: define strongly typed metric wrappers Change-Id: Ia788364f22d718850f2909826e4a356442c9a009 * chore: generate libraries at Tue Feb 24 18:57:54 UTC 2026 * add a couple of missing metrics Change-Id: Iaa1b72947298c9ec1cdbf78c930787fce603d03a * remove jetstream leak Change-Id: I46cf492d6285260f9e40fb2128f9cd5ad1624ca5 * chore: generate libraries at Tue Feb 24 19:56:14 UTC 2026 * copyrights Change-Id: I7da59e27f80ca676cafe34a7deed3c8602763f8c * move uid generation to EnvInfo so that its deferred until export time instead of client initialization Change-Id: I164e224da667085efee83178d5d9c32b17c8c36b * clean up buckets Change-Id: Ifdc19c86e0a98ccd085597104554ec80a0beafcb * add missing debug tags Change-Id: I402a0c3b9de4927a34acf4d09f98d62dd055e962 * typo in desc Change-Id: Ibfccad890697f208d0fc63525deb9987018545bc * fix copy/paste error Change-Id: I22338065e271a17bd8f84fdd185e95e4a0d09ed9 * format Change-Id: Ibcf9583c49018e7a638fa59f2987c38e594a7a8f --------- Co-authored-by: cloud-java-bot <cloud-java-bot@google.com>
1 parent 6b2f585 commit d201b00

35 files changed

Lines changed: 2866 additions & 0 deletions

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>
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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.ClientBatchWriteFlowControlFactor;
20+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientBatchWriteFlowControlTargetQps;
21+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientChannelPoolOutstandingRpcs;
22+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientDpCompatGuage;
23+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientPerConnectionErrorCount;
24+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.GrpcMetric;
25+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.MetricWrapper;
26+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.PacemakerDelay;
27+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableApplicationBlockingLatency;
28+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableAttemptLatency;
29+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableAttemptLatency2;
30+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableClientBlockingLatency;
31+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableConnectivityErrorCount;
32+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableDebugTagCount;
33+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableFirstResponseLatency;
34+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableOperationLatency;
35+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableRemainingDeadline;
36+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableRetryCount;
37+
import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableServerLatency;
38+
import com.google.common.collect.ImmutableList;
39+
import io.opentelemetry.api.metrics.Meter;
40+
import io.opentelemetry.api.metrics.MeterProvider;
41+
import java.util.ArrayList;
42+
import java.util.HashMap;
43+
import java.util.List;
44+
import java.util.Map;
45+
46+
/**
47+
* Repository for all client metrics. This class has 2 audiences:
48+
*
49+
* <ul>
50+
* <li>VRpcTracer, which reference each metric directly
51+
* <li>Exporter, which will look up each metric by name and use the {@link MetricWrapper}
52+
* interface to augment the {@code MonitoredResource} and {@code Metric Labels}
53+
* </ul>
54+
*/
55+
public class MetricRegistry {
56+
static final String METER_NAME = "bigtable.googleapis.com/internal/client/";
57+
58+
final TableOperationLatency operationLatencyMetric;
59+
final TableAttemptLatency attemptLatencyMetric;
60+
final TableAttemptLatency2 attemptLatency2Metric;
61+
final TableRetryCount retryCountMetric;
62+
final TableFirstResponseLatency firstResponseLantencyMetric;
63+
final TableServerLatency serverLatencyMetric;
64+
final ClientChannelPoolOutstandingRpcs channelPoolOutstandingRpcsMetric;
65+
final TableConnectivityErrorCount connectivityErrorCountMetric;
66+
final ClientDpCompatGuage dpCompatGuageMetric;
67+
final TableApplicationBlockingLatency applicationBlockingLatencyMetric;
68+
final TableClientBlockingLatency clientBlockingLatencyMetric;
69+
final ClientPerConnectionErrorCount perConnectionErrorCountMetric;
70+
final TableRemainingDeadline remainingDeadlineMetric;
71+
final ClientBatchWriteFlowControlFactor batchWriteFlowControlFactorMetric;
72+
final ClientBatchWriteFlowControlTargetQps batchWriteFlowControlTargetQpsMetric;
73+
74+
final TableDebugTagCount debugTagCountMetric;
75+
final PacemakerDelay pacemakerDelayMetric;
76+
77+
private final Map<String, MetricWrapper<?>> metrics = new HashMap<>();
78+
private final List<String> grpcMetricNames = new ArrayList<>();
79+
80+
public MetricRegistry() {
81+
operationLatencyMetric = register(new TableOperationLatency());
82+
attemptLatencyMetric = register(new TableAttemptLatency());
83+
attemptLatency2Metric = register(new TableAttemptLatency2());
84+
retryCountMetric = register(new TableRetryCount());
85+
firstResponseLantencyMetric = register(new TableFirstResponseLatency());
86+
serverLatencyMetric = register(new TableServerLatency());
87+
channelPoolOutstandingRpcsMetric = register(new ClientChannelPoolOutstandingRpcs());
88+
connectivityErrorCountMetric = register(new TableConnectivityErrorCount());
89+
applicationBlockingLatencyMetric = register(new TableApplicationBlockingLatency());
90+
clientBlockingLatencyMetric = register(new TableClientBlockingLatency());
91+
perConnectionErrorCountMetric = register(new ClientPerConnectionErrorCount());
92+
dpCompatGuageMetric = register(new ClientDpCompatGuage());
93+
remainingDeadlineMetric = register(new TableRemainingDeadline());
94+
batchWriteFlowControlFactorMetric = register(new ClientBatchWriteFlowControlFactor());
95+
batchWriteFlowControlTargetQpsMetric = register(new ClientBatchWriteFlowControlTargetQps());
96+
97+
debugTagCountMetric = register(new TableDebugTagCount());
98+
pacemakerDelayMetric = register(new PacemakerDelay());
99+
100+
// From
101+
// https://github.com/grpc/grpc-java/blob/31fdb6c2268b4b1c8ba6c995ee46c58e84a831aa/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java#L138-L165
102+
registerGrpcMetric(
103+
"grpc.client.attempt.duration",
104+
ImmutableList.of("grpc.lb.locality", "grpc.status", "grpc.method", "grpc.target"));
105+
registerGrpcMetric(
106+
"grpc.lb.rls.default_target_picks",
107+
ImmutableList.of(
108+
"grpc.target",
109+
"grpc.lb.rls.server_target",
110+
"grpc.lb.rls.data_plane_target",
111+
"grpc.lb.pick_result"));
112+
registerGrpcMetric(
113+
"grpc.lb.rls.target_picks",
114+
ImmutableList.of(
115+
"grpc.target",
116+
"grpc.lb.rls.server_target",
117+
"grpc.lb.rls.data_plane_target",
118+
"grpc.lb.pick_result"));
119+
registerGrpcMetric(
120+
"grpc.lb.rls.failed_picks", ImmutableList.of("grpc.target", "grpc.lb.rls.server_target"));
121+
122+
// From
123+
// https://github.com/grpc/grpc-java/blob/31fdb6c2268b4b1c8ba6c995ee46c58e84a831aa/xds/src/main/java/io/grpc/xds/XdsClientMetricReporterImpl.java#L67-L94
124+
// TODO: "grpc.xds_client.connected"
125+
registerGrpcMetric(
126+
"grpc.xds_client.server_failure", ImmutableList.of("grpc.target", "grpc.xds.server"));
127+
// TODO: "grpc.xds_client.resource_updates_valid",
128+
registerGrpcMetric(
129+
"grpc.xds_client.resource_updates_invalid",
130+
ImmutableList.of("grpc.target", "grpc.xds.server", "grpc.xds.resource_type"));
131+
// TODO: "grpc.xds_client.resources"
132+
133+
// From
134+
// https://github.com/grpc/proposal/blob/86990145a7cef9e5473a132709b2556fec00c4c6/A94-subchannel-otel-metrics.md
135+
registerGrpcMetric(
136+
"grpc.subchannel.disconnections",
137+
ImmutableList.of(
138+
"grpc.target", "grpc.lb.backend_service", "grpc.lb.locality", "grpc.disconnect_error"));
139+
140+
registerGrpcMetric(
141+
"grpc.subchannel.connection_attempts_succeeded",
142+
ImmutableList.of("grpc.target", "grpc.lb.backend_service", "grpc.lb.locality"));
143+
144+
registerGrpcMetric(
145+
"grpc.subchannel.connection_attempts_failed",
146+
ImmutableList.of("grpc.target", "grpc.lb.backend_service", "grpc.lb.locality"));
147+
148+
registerGrpcMetric(
149+
"grpc.subchannel.open_connections",
150+
ImmutableList.of(
151+
"grpc.target", "grpc.security_level", "grpc.lb.backend_service", "grpc.lb.locality"));
152+
}
153+
154+
private void registerGrpcMetric(String name, List<String> labels) {
155+
grpcMetricNames.add(name);
156+
register(new GrpcMetric(name, labels));
157+
}
158+
159+
private <T extends MetricWrapper<?>> T register(T instrument) {
160+
metrics.put(instrument.getName(), instrument);
161+
return instrument;
162+
}
163+
164+
List<String> getGrpcMetricNames() {
165+
return ImmutableList.copyOf(grpcMetricNames);
166+
}
167+
168+
MetricWrapper<?> getMetric(String name) {
169+
return metrics.get(name);
170+
}
171+
172+
public RecorderRegistry newRecorderRegistry(MeterProvider meterProvider) {
173+
return new RecorderRegistry(meterProvider.get(METER_NAME));
174+
}
175+
176+
public class RecorderRegistry {
177+
public final TableOperationLatency.Recorder operationLatency;
178+
public final TableAttemptLatency.Recorder attemptLatency;
179+
public final TableAttemptLatency2.Recorder attemptLatency2;
180+
public final TableRetryCount.Recorder retryCount;
181+
public final TableFirstResponseLatency.Recorder firstResponseLantency;
182+
public final TableServerLatency.Recorder serverLatency;
183+
public final ClientChannelPoolOutstandingRpcs.Recorder channelPoolOutstandingRpcs;
184+
public final TableConnectivityErrorCount.Recorder connectivityErrorCount;
185+
public final ClientDpCompatGuage.Recorder dpCompatGuage;
186+
public final TableApplicationBlockingLatency.Recorder applicationBlockingLatency;
187+
public final TableClientBlockingLatency.Recorder clientBlockingLatency;
188+
public final ClientPerConnectionErrorCount.Recorder perConnectionErrorCount;
189+
public final TableRemainingDeadline.Recorder remainingDeadline;
190+
public final ClientBatchWriteFlowControlTargetQps.Recorder batchWriteFlowControlTargetQps;
191+
public final ClientBatchWriteFlowControlFactor.Recorder batchWriteFlowControlFactor;
192+
193+
public final TableDebugTagCount.Recorder debugTagCount;
194+
195+
final PacemakerDelay.Recorder pacemakerDelay;
196+
197+
private RecorderRegistry(Meter meter) {
198+
operationLatency = operationLatencyMetric.newRecorder(meter);
199+
attemptLatency = attemptLatencyMetric.newRecorder(meter);
200+
attemptLatency2 = attemptLatency2Metric.newRecorder(meter);
201+
retryCount = retryCountMetric.newRecorder(meter);
202+
firstResponseLantency = firstResponseLantencyMetric.newRecorder(meter);
203+
serverLatency = serverLatencyMetric.newRecorder(meter);
204+
channelPoolOutstandingRpcs = channelPoolOutstandingRpcsMetric.newRecorder(meter);
205+
connectivityErrorCount = connectivityErrorCountMetric.newRecorder(meter);
206+
dpCompatGuage = dpCompatGuageMetric.newRecorder(meter);
207+
applicationBlockingLatency = applicationBlockingLatencyMetric.newRecorder(meter);
208+
clientBlockingLatency = clientBlockingLatencyMetric.newRecorder(meter);
209+
perConnectionErrorCount = perConnectionErrorCountMetric.newRecorder(meter);
210+
remainingDeadline = remainingDeadlineMetric.newRecorder(meter);
211+
batchWriteFlowControlTargetQps = batchWriteFlowControlTargetQpsMetric.newRecorder(meter);
212+
batchWriteFlowControlFactor = batchWriteFlowControlFactorMetric.newRecorder(meter);
213+
214+
debugTagCount = debugTagCountMetric.newRecorder(meter);
215+
pacemakerDelay = pacemakerDelayMetric.newRecorder(meter);
216+
}
217+
}
218+
}
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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.InstanceName;
21+
import com.google.cloud.bigtable.Version;
22+
23+
/**
24+
* A value class to capture parameters that the client was instantiated with. These parameters will
25+
* be used by the Exporter to derive MonitoredResource for GrpcMetrics.
26+
*/
27+
@AutoValue
28+
public abstract class ClientInfo {
29+
/** The name and version of the client. */
30+
public abstract String getClientName();
31+
32+
/** A unique identifier to disambiguate TimeSeries from multiple processes on the same VM. */
33+
public abstract InstanceName getInstanceName();
34+
35+
public abstract String getAppProfileId();
36+
37+
public abstract Builder toBuilder();
38+
39+
public static Builder builder() {
40+
return new AutoValue_ClientInfo.Builder().setClientName("java-bigtable/" + Version.VERSION);
41+
}
42+
43+
@AutoValue.Builder
44+
public abstract static class Builder {
45+
protected abstract Builder setClientName(String name);
46+
47+
public abstract Builder setInstanceName(InstanceName name);
48+
49+
public abstract Builder setAppProfileId(String appProfileId);
50+
51+
public abstract ClientInfo build();
52+
}
53+
}

0 commit comments

Comments
 (0)