Skip to content

Commit 9ec2f11

Browse files
Verify (#9732)
* feat: add automatic resource mapping for metrics * feat: add a verification subcommand
1 parent 10e9a3c commit 9ec2f11

File tree

5 files changed

+301
-29
lines changed

5 files changed

+301
-29
lines changed

bigtable/bigtable-proxy/pom.xml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@
2121
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2222

2323
<!-- dep versions -->
24+
<!-- Overall -->
2425
<libraries-bom.version>26.50.0</libraries-bom.version>
25-
26+
<!-- OpenTelemetry -->
2627
<otel.version>1.44.1</otel.version>
28+
<otel-contrib.version>1.41.0-alpha</otel-contrib.version>
29+
<shared-resourcemapping.version>0.33.0</shared-resourcemapping.version>
2730
<exporter-metrics.version>0.33.0</exporter-metrics.version>
31+
<!-- Utility -->
2832
<slf4j.version>2.0.16</slf4j.version>
2933
<logback.version>1.5.12</logback.version>
3034
<auto-value.version>1.11.0</auto-value.version>
3135
<picocli.version>4.7.6</picocli.version>
36+
<!-- Test -->
3237
<junit.version>4.13.2</junit.version>
3338
<truth.version>1.4.4</truth.version>
3439
</properties>
@@ -125,6 +130,20 @@
125130
<artifactId>exporter-metrics</artifactId>
126131
<version>${exporter-metrics.version}</version>
127132
</dependency>
133+
<dependency>
134+
<groupId>io.opentelemetry.contrib</groupId>
135+
<artifactId>opentelemetry-gcp-resources</artifactId>
136+
<version>${otel-contrib.version}</version>
137+
</dependency>
138+
<dependency>
139+
<groupId>io.opentelemetry</groupId>
140+
<artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
141+
</dependency>
142+
<dependency>
143+
<groupId>com.google.cloud.opentelemetry</groupId>
144+
<artifactId>shared-resourcemapping</artifactId>
145+
<version>${shared-resourcemapping.version}</version>
146+
</dependency>
128147

129148
<!-- Logging -->
130149
<dependency>

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/Main.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.bigtable.examples.proxy;
1818

1919
import com.google.cloud.bigtable.examples.proxy.commands.Serve;
20+
import com.google.cloud.bigtable.examples.proxy.commands.Verify;
2021
import org.slf4j.Logger;
2122
import org.slf4j.LoggerFactory;
2223
import org.slf4j.bridge.SLF4JBridgeHandler;
@@ -27,10 +28,10 @@
2728
* Main entry point for proxy commands under {@link
2829
* com.google.cloud.bigtable.examples.proxy.commands}.
2930
*/
30-
@Command(subcommands = {Serve.class}, name = "bigtable-proxy")
31+
@Command(
32+
subcommands = {Serve.class, Verify.class},
33+
name = "bigtable-proxy")
3134
public final class Main {
32-
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
33-
3435
public static void main(String[] args) {
3536
SLF4JBridgeHandler.install();
3637
new CommandLine(new Main()).execute(args);
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Copyright 2024 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+
* http://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.examples.proxy.commands;
18+
19+
import com.google.auth.Credentials;
20+
import com.google.auth.oauth2.GoogleCredentials;
21+
import com.google.bigtable.v2.BigtableGrpc;
22+
import com.google.bigtable.v2.BigtableGrpc.BigtableBlockingStub;
23+
import com.google.bigtable.v2.CheckAndMutateRowRequest;
24+
import com.google.bigtable.v2.CheckAndMutateRowResponse;
25+
import com.google.bigtable.v2.Mutation;
26+
import com.google.bigtable.v2.Mutation.DeleteFromRow;
27+
import com.google.bigtable.v2.ReadRowsRequest;
28+
import com.google.bigtable.v2.ReadRowsResponse;
29+
import com.google.bigtable.v2.RowFilter;
30+
import com.google.bigtable.v2.RowFilter.Chain;
31+
import com.google.bigtable.v2.RowSet;
32+
import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter;
33+
import com.google.cloud.opentelemetry.metric.MetricConfiguration;
34+
import com.google.cloud.opentelemetry.resource.GcpResource;
35+
import com.google.cloud.opentelemetry.resource.ResourceTranslator;
36+
import com.google.common.collect.ImmutableList;
37+
import com.google.common.net.PercentEscaper;
38+
import com.google.protobuf.ByteString;
39+
import io.grpc.CallCredentials;
40+
import io.grpc.CallOptions;
41+
import io.grpc.Channel;
42+
import io.grpc.ClientCall;
43+
import io.grpc.ClientInterceptor;
44+
import io.grpc.Deadline;
45+
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
46+
import io.grpc.ManagedChannel;
47+
import io.grpc.ManagedChannelBuilder;
48+
import io.grpc.Metadata;
49+
import io.grpc.Metadata.Key;
50+
import io.grpc.MethodDescriptor;
51+
import io.grpc.StatusRuntimeException;
52+
import io.grpc.auth.MoreCallCredentials;
53+
import io.opentelemetry.api.common.Attributes;
54+
import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider;
55+
import io.opentelemetry.sdk.common.CompletableResultCode;
56+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
57+
import io.opentelemetry.sdk.metrics.data.MetricData;
58+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
59+
import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData;
60+
import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData;
61+
import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData;
62+
import io.opentelemetry.sdk.resources.Resource;
63+
import java.io.IOException;
64+
import java.time.Instant;
65+
import java.time.temporal.ChronoUnit;
66+
import java.util.Iterator;
67+
import java.util.concurrent.Callable;
68+
import java.util.concurrent.TimeUnit;
69+
import picocli.CommandLine.Command;
70+
import picocli.CommandLine.Help.Visibility;
71+
import picocli.CommandLine.Option;
72+
73+
@Command(name = "verify", description = "Verify environment is properly set up")
74+
public class Verify implements Callable<Void> {
75+
@Option(
76+
names = "--bigtable-project-id",
77+
required = true,
78+
description = "Project that contains a Bigtable instance to use for connectivity test")
79+
String bigtableProjectId;
80+
81+
@Option(
82+
names = "--bigtable-instance-id",
83+
required = true,
84+
description = "Bigtable instance to use for connectivity test")
85+
String bigtableInstanceId;
86+
87+
@Option(
88+
names = "--bigtable-table-id",
89+
required = true,
90+
description = "Bigtable table to use for connectivity test")
91+
String bigtableTableId;
92+
93+
@Option(
94+
names = "--metrics-project-id",
95+
required = true,
96+
description = "The project id where metrics should be exported")
97+
String metricsProjectId = null;
98+
99+
@Option(
100+
names = "--bigtable-data-endpoint",
101+
converter = Endpoint.ArgConverter.class,
102+
showDefaultValue = Visibility.ALWAYS)
103+
Endpoint dataEndpoint = Endpoint.create("bigtable.googleapis.com", 443);
104+
105+
106+
Credentials credentials = null;
107+
108+
@Override
109+
public Void call() throws Exception {
110+
if (credentials == null) {
111+
credentials = GoogleCredentials.getApplicationDefault();
112+
}
113+
checkBigtable(
114+
MoreCallCredentials.from(credentials),
115+
String.format(
116+
"projects/%s/instances/%s/tables/%s",
117+
bigtableProjectId, bigtableInstanceId, bigtableTableId));
118+
119+
checkMetrics(credentials);
120+
return null;
121+
}
122+
123+
private void checkBigtable(CallCredentials callCredentials, String tableName) {
124+
ManagedChannel channel =
125+
ManagedChannelBuilder.forAddress(dataEndpoint.getName(), dataEndpoint.getPort()).build();
126+
127+
try {
128+
Metadata md = new Metadata();
129+
PercentEscaper escaper = new PercentEscaper("", true);
130+
md.put(
131+
Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER),
132+
String.format("table_name=%s&app_profile_id=%s", escaper.escape(tableName), ""));
133+
134+
BigtableBlockingStub stub =
135+
BigtableGrpc.newBlockingStub(channel)
136+
.withCallCredentials(callCredentials)
137+
.withInterceptors(new MetadataInterceptor(md));
138+
139+
ReadRowsRequest readRequest =
140+
ReadRowsRequest.newBuilder()
141+
.setTableName(
142+
String.format(
143+
"projects/%s/instances/%s/tables/%s",
144+
bigtableProjectId, bigtableTableId, bigtableTableId))
145+
.setRowsLimit(1)
146+
.setRows(
147+
RowSet.newBuilder().addRowKeys(ByteString.copyFromUtf8("some-nonexistent-row")))
148+
.setFilter(
149+
RowFilter.newBuilder()
150+
.setChain(
151+
Chain.newBuilder()
152+
.addFilters(RowFilter.newBuilder().setCellsPerRowLimitFilter(1))
153+
.addFilters(
154+
RowFilter.newBuilder().setStripValueTransformer(true).build())))
155+
.build();
156+
157+
Iterator<ReadRowsResponse> readIt =
158+
stub.withDeadline(Deadline.after(1, TimeUnit.SECONDS)).readRows(readRequest);
159+
160+
try {
161+
while (readIt.hasNext()) {
162+
readIt.next();
163+
}
164+
System.out.println("Bigtable Read: OK");
165+
} catch (StatusRuntimeException e) {
166+
System.out.println("Bigtable Read: Failed - " + e.getStatus());
167+
return;
168+
}
169+
170+
CheckAndMutateRowRequest rwReq =
171+
CheckAndMutateRowRequest.newBuilder()
172+
.setTableName(tableName)
173+
.setRowKey(ByteString.copyFromUtf8("some-non-existent-row"))
174+
.setPredicateFilter(RowFilter.newBuilder().setBlockAllFilter(true))
175+
.addTrueMutations(
176+
Mutation.newBuilder().setDeleteFromRow(DeleteFromRow.getDefaultInstance()))
177+
.build();
178+
179+
try {
180+
CheckAndMutateRowResponse ignored = stub.checkAndMutateRow(rwReq);
181+
System.out.println("Bigtable Read/Write: OK");
182+
} catch (StatusRuntimeException e) {
183+
System.out.println("Bigtable Read/Write: Failed - " + e.getStatus());
184+
return;
185+
}
186+
} finally {
187+
channel.shutdown();
188+
}
189+
}
190+
191+
void checkMetrics(Credentials creds) throws IOException {
192+
Instant now = Instant.now().truncatedTo(ChronoUnit.MINUTES);
193+
Instant end = Instant.now().truncatedTo(ChronoUnit.MINUTES);
194+
195+
GCPResourceProvider resourceProvider = new GCPResourceProvider();
196+
Resource resource = Resource.create(resourceProvider.getAttributes());
197+
GcpResource gcpResource = ResourceTranslator.mapResource(resource);
198+
199+
MetricExporter exporter =
200+
GoogleCloudMetricExporter.createWithConfiguration(
201+
MetricConfiguration.builder()
202+
.setCredentials(creds)
203+
.setProjectId(metricsProjectId)
204+
.setInstrumentationLibraryLabelsEnabled(false)
205+
.build());
206+
207+
ImmutableList<MetricData> metricData =
208+
ImmutableList.of(
209+
ImmutableMetricData.createLongGauge(
210+
resource,
211+
InstrumentationScopeInfo.create("bigtable-proxy"),
212+
"bigtableproxy.presence",
213+
"Number of proxy processes",
214+
"{process}",
215+
ImmutableGaugeData.create(
216+
ImmutableList.of(
217+
ImmutableLongPointData.create(
218+
TimeUnit.MILLISECONDS.toNanos(now.toEpochMilli()),
219+
TimeUnit.MILLISECONDS.toNanos(end.toEpochMilli()),
220+
Attributes.empty(),
221+
1L)))));
222+
CompletableResultCode result = exporter.export(metricData);
223+
result.join(1, TimeUnit.MINUTES);
224+
225+
if (result.isSuccess()) {
226+
System.out.println("Metrics write: OK");
227+
} else {
228+
System.out.println("Metrics write: FAILED: " + result.getFailureThrowable().getMessage());
229+
}
230+
}
231+
232+
private static class MetadataInterceptor implements ClientInterceptor {
233+
private final Metadata metadata;
234+
235+
private MetadataInterceptor(Metadata metadata) {
236+
this.metadata = metadata;
237+
}
238+
239+
@Override
240+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
241+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
242+
return new SimpleForwardingClientCall<>(next.newCall(method, callOptions)) {
243+
@Override
244+
public void start(Listener<RespT> responseListener, Metadata headers) {
245+
headers.merge(metadata);
246+
super.start(responseListener, headers);
247+
}
248+
};
249+
}
250+
}
251+
}

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/metrics/MetricsImpl.java

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,9 @@
1616

1717
package com.google.cloud.bigtable.examples.proxy.metrics;
1818

19-
import com.google.api.gax.core.FixedCredentialsProvider;
20-
import com.google.api.gax.grpc.GrpcTransportChannel;
21-
import com.google.api.gax.rpc.FixedTransportChannelProvider;
2219
import com.google.auth.Credentials;
23-
import com.google.cloud.monitoring.v3.MetricServiceSettings;
2420
import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter;
2521
import com.google.cloud.opentelemetry.metric.MetricConfiguration;
26-
import io.grpc.ManagedChannelBuilder;
2722
import io.grpc.Status;
2823
import io.opentelemetry.api.common.AttributeKey;
2924
import io.opentelemetry.api.common.Attributes;
@@ -32,16 +27,19 @@
3227
import io.opentelemetry.api.metrics.LongHistogram;
3328
import io.opentelemetry.api.metrics.LongUpDownCounter;
3429
import io.opentelemetry.api.metrics.Meter;
30+
import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider;
3531
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
3632
import io.opentelemetry.sdk.metrics.export.MetricExporter;
3733
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
34+
import io.opentelemetry.sdk.resources.Resource;
3835
import java.io.Closeable;
3936
import java.io.IOException;
4037
import java.time.Duration;
4138
import java.util.concurrent.atomic.AtomicInteger;
39+
import java.util.function.Supplier;
4240

4341
public class MetricsImpl implements Closeable, Metrics {
44-
private static final String METRIC_PREFIX = "bigtableproxy.";
42+
public static final String METRIC_PREFIX = "bigtableproxy.";
4543

4644
static final AttributeKey<String> API_CLIENT_KEY = AttributeKey.stringKey("apiclient");
4745
static final AttributeKey<String> RESOURCE_KEY = AttributeKey.stringKey("resource");
@@ -64,6 +62,9 @@ public class MetricsImpl implements Closeable, Metrics {
6462
private final AtomicInteger numOutstandingRpcs = new AtomicInteger();
6563
private final AtomicInteger maxSeen = new AtomicInteger();
6664

65+
static Supplier<Resource> gcpResourceSupplier =
66+
() -> Resource.create(new GCPResourceProvider().getAttributes());
67+
6768
public MetricsImpl(Credentials credentials, String projectId) throws IOException {
6869
meterProvider = createMeterProvider(credentials, projectId);
6970
Meter meter = meterProvider.meterBuilder("bigtableproxy").build();
@@ -162,34 +163,18 @@ public void close() {
162163
meterProvider.close();
163164
}
164165

165-
private static SdkMeterProvider createMeterProvider(Credentials credentials, String projectId)
166-
throws IOException {
167-
MetricServiceSettings.Builder metricServiceSettingsBuilder = MetricServiceSettings.newBuilder();
168-
metricServiceSettingsBuilder
169-
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
170-
.setTransportChannelProvider(
171-
FixedTransportChannelProvider.create(
172-
GrpcTransportChannel.create(
173-
ManagedChannelBuilder.forTarget(
174-
MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT)
175-
// default 8 KiB
176-
.maxInboundMetadataSize(16 * 1000)
177-
.build())))
178-
.createMetricDescriptorSettings()
179-
.setSimpleTimeoutNoRetriesDuration(
180-
Duration.ofMillis(MetricConfiguration.DEFAULT_DEADLINE.toMillis()))
181-
.build();
182-
166+
private static SdkMeterProvider createMeterProvider(Credentials credentials, String projectId) {
183167
MetricConfiguration config =
184168
MetricConfiguration.builder()
185169
.setProjectId(projectId)
186-
.setMetricServiceSettings(metricServiceSettingsBuilder.build())
170+
.setCredentials(credentials)
187171
.setInstrumentationLibraryLabelsEnabled(false)
188172
.build();
189173

190174
MetricExporter exporter = GoogleCloudMetricExporter.createWithConfiguration(config);
191175

192176
return SdkMeterProvider.builder()
177+
.setResource(gcpResourceSupplier.get())
193178
.registerMetricReader(
194179
PeriodicMetricReader.builder(exporter).setInterval(Duration.ofMinutes(1)).build())
195180
.build();

0 commit comments

Comments
 (0)