Skip to content

Commit 889893d

Browse files
authored
examples: Add GCP CSM Observability example (#11267)
Add GCP CSM observability example client and example server.
1 parent 85ed053 commit 889893d

File tree

8 files changed

+539
-0
lines changed

8 files changed

+539
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
gRPC GCP CSM Observability Example
2+
================
3+
4+
The GCP CSM Observability example consists of a Hello World client and a Hello World server and shows how to configure CSM Observability
5+
for gRPC client and gRPC server.
6+
7+
## Configuration
8+
9+
`CsmObservabilityClient` takes the following command-line arguments -
10+
* user - Name to be greeted.
11+
* target - Server address. Default value is `xds:///helloworld:50051`.
12+
* When client tries to connect to target, gRPC would use xDS to resolve this target and connect to the server backend.
13+
* prometheusPort - Port used for exposing prometheus metrics. Default value is `9464`.
14+
15+
16+
`CsmObservabilityServer` takes the following command-line arguments -
17+
* port - Port used for running Hello World server. Default value is `50051`.
18+
* prometheusPort - Port used for exposing prometheus metrics. Default value is `9464`.
19+
20+
## Build the example
21+
22+
From the `grpc-java/examples/`directory i.e,
23+
```
24+
cd grpc-java/examples
25+
```
26+
Run the following to generate client and server images respectively.
27+
28+
Client:
29+
```
30+
docker build -f example-gcp-csm-observability/csm-client.Dockerfile .
31+
```
32+
Server:
33+
```
34+
docker build -f example-gcp-csm-observability/csm-server.Dockerfile .
35+
```
36+
37+
To push to a registry, add a tag to the image either by adding a `-t` flag to `docker build` command above or run:
38+
39+
```
40+
docker image tag ${sha from build command above} ${tag}
41+
```
42+
43+
And then push the tagged image using `docker push`.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
plugins {
2+
// Provide convenience executables for trying out the examples.
3+
id 'application'
4+
id 'com.google.protobuf' version '0.9.4'
5+
// Generate IntelliJ IDEA's .idea & .iml project files
6+
id 'idea'
7+
id 'java'
8+
}
9+
10+
repositories {
11+
maven { // The google mirror is less flaky than mavenCentral()
12+
url "https://maven-central.storage-download.googleapis.com/maven2/"
13+
}
14+
mavenCentral()
15+
mavenLocal()
16+
}
17+
18+
java {
19+
sourceCompatibility = JavaVersion.VERSION_1_8
20+
targetCompatibility = JavaVersion.VERSION_1_8
21+
}
22+
23+
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
24+
// are looking at a tagged version of the example and not "master"!
25+
26+
// Feel free to delete the comment at the next line. It is just for safely
27+
// updating the version in our release process.
28+
def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION
29+
def protocVersion = '3.25.1'
30+
def openTelemetryVersion = '1.38.0'
31+
def openTelemetryPrometheusVersion = '1.38.0-alpha'
32+
33+
dependencies {
34+
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
35+
implementation "io.grpc:grpc-stub:${grpcVersion}"
36+
implementation "io.grpc:grpc-gcp-csm-observability:${grpcVersion}"
37+
implementation "io.opentelemetry:opentelemetry-sdk:${openTelemetryVersion}"
38+
implementation "io.opentelemetry:opentelemetry-sdk-metrics:${openTelemetryVersion}"
39+
implementation "io.opentelemetry:opentelemetry-exporter-logging:${openTelemetryVersion}"
40+
implementation "io.opentelemetry:opentelemetry-exporter-prometheus:${openTelemetryPrometheusVersion}"
41+
compileOnly "org.apache.tomcat:annotations-api:6.0.53"
42+
runtimeOnly "io.grpc:grpc-xds:${grpcVersion}"
43+
runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}"
44+
}
45+
46+
protobuf {
47+
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
48+
plugins {
49+
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
50+
}
51+
generateProtoTasks {
52+
all()*.plugins { grpc {} }
53+
}
54+
}
55+
56+
startScripts.enabled = false
57+
58+
task CsmObservabilityHelloWorldServer(type: CreateStartScripts) {
59+
mainClass = 'io.grpc.examples.csmobservability.CsmObservabilityServer'
60+
applicationName = 'csm-observability-server'
61+
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
62+
classpath = startScripts.classpath
63+
}
64+
65+
task CsmObservabilityHelloWorldClient(type: CreateStartScripts) {
66+
mainClass = 'io.grpc.examples.csmobservability.CsmObservabilityClient'
67+
applicationName = 'csm-observability-client'
68+
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
69+
classpath = startScripts.classpath
70+
}
71+
72+
application {
73+
applicationDistribution.into('bin') {
74+
from(CsmObservabilityHelloWorldServer)
75+
from(CsmObservabilityHelloWorldClient)
76+
fileMode = 0755
77+
}
78+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2024 gRPC authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
#
16+
# Stage 1: Build CSM client
17+
#
18+
19+
FROM eclipse-temurin:11-jdk AS build
20+
21+
WORKDIR /grpc-java/examples
22+
COPY . .
23+
24+
RUN cd example-gcp-csm-observability && ../gradlew installDist -PskipCodegen=true -PskipAndroid=true
25+
26+
#
27+
# Stage 2:
28+
#
29+
# - Copy only the necessary files to reduce Docker image size.
30+
# - Have an ENTRYPOINT script which will launch the CSM client
31+
# with the given parameters.
32+
#
33+
34+
FROM eclipse-temurin:11-jre
35+
36+
WORKDIR /grpc-java/
37+
COPY --from=build /grpc-java/examples/example-gcp-csm-observability/build/install/example-gcp-csm-observability/. .
38+
39+
# Intentionally after the COPY to force the update on each build.
40+
# Update Ubuntu system packages:
41+
RUN apt-get update \
42+
&& apt-get -y upgrade \
43+
&& apt-get -y autoremove \
44+
&& rm -rf /var/lib/apt/lists/*
45+
46+
# Client
47+
ENTRYPOINT ["bin/csm-observability-client"]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2024 gRPC authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
#
16+
# Stage 1: Build CSM server
17+
#
18+
19+
FROM eclipse-temurin:11-jdk AS build
20+
21+
WORKDIR /grpc-java/examples
22+
COPY . .
23+
24+
RUN cd example-gcp-csm-observability && ../gradlew installDist -PskipCodegen=true -PskipAndroid=true
25+
26+
#
27+
# Stage 2:
28+
#
29+
# - Copy only the necessary files to reduce Docker image size.
30+
# - Have an ENTRYPOINT script which will launch the CSM server
31+
# with the given parameters.
32+
#
33+
34+
FROM eclipse-temurin:11-jre
35+
36+
WORKDIR /grpc-java/
37+
COPY --from=build /grpc-java/examples/example-gcp-csm-observability/build/install/example-gcp-csm-observability/. .
38+
39+
# Intentionally after the COPY to force the update on each build.
40+
# Update Ubuntu system packages:
41+
RUN apt-get update \
42+
&& apt-get -y upgrade \
43+
&& apt-get -y autoremove \
44+
&& rm -rf /var/lib/apt/lists/*
45+
46+
# Server
47+
ENTRYPOINT ["bin/csm-observability-server"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'example-gcp-csm-observability'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
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 io.grpc.examples.csmobservability;
18+
19+
import io.grpc.Channel;
20+
import io.grpc.Grpc;
21+
import io.grpc.InsecureChannelCredentials;
22+
import io.grpc.ManagedChannel;
23+
import io.grpc.ManagedChannelBuilder;
24+
import io.grpc.StatusRuntimeException;
25+
import io.grpc.examples.helloworld.GreeterGrpc;
26+
import io.grpc.examples.helloworld.HelloReply;
27+
import io.grpc.examples.helloworld.HelloRequest;
28+
import io.grpc.gcp.csm.observability.CsmObservability;
29+
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
30+
import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
31+
import io.opentelemetry.sdk.OpenTelemetrySdk;
32+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
33+
import java.util.concurrent.TimeUnit;
34+
import java.util.concurrent.atomic.AtomicBoolean;
35+
import java.util.logging.Level;
36+
import java.util.logging.Logger;
37+
38+
/**
39+
* A simple CSM observability client that requests a greeting from the {@link HelloWorldServer} and
40+
* generates CSM telemetry data based on the configuration.
41+
*/
42+
public class CsmObservabilityClient {
43+
private static final Logger logger = Logger.getLogger(CsmObservabilityClient.class.getName());
44+
45+
private final GreeterGrpc.GreeterBlockingStub blockingStub;
46+
47+
/** Construct client for accessing HelloWorld server using the existing channel. */
48+
public CsmObservabilityClient(Channel channel) {
49+
blockingStub = GreeterGrpc.newBlockingStub(channel);
50+
}
51+
52+
/** Say hello to server. */
53+
public void greet(String name) {
54+
logger.info("Will try to greet " + name + " ...");
55+
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
56+
HelloReply response;
57+
try {
58+
response = blockingStub.sayHello(request);
59+
} catch (StatusRuntimeException e) {
60+
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
61+
return;
62+
}
63+
logger.info("Greeting: " + response.getMessage());
64+
}
65+
66+
/**
67+
* Greet server. If provided, the first element of {@code args} is the name to use in the
68+
* greeting. The second argument is the target server.
69+
*/
70+
public static void main(String[] args) throws Exception {
71+
String user = "world";
72+
String target = "xds:///helloworld:50051";
73+
int prometheusPort = 9464;
74+
AtomicBoolean sendRpcs = new AtomicBoolean(true);
75+
if (args.length > 0) {
76+
if ("--help".equals(args[0])) {
77+
System.err.println("Usage: [name [target [prometheusPort]]]");
78+
System.err.println("");
79+
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
80+
System.err.println(" target The server to connect to. Defaults to " + target);
81+
System.err.println(" prometheusPort The port to expose prometheus metrics. Defaults to " + prometheusPort);
82+
System.exit(1);
83+
}
84+
user = args[0];
85+
}
86+
if (args.length > 1) {
87+
target = args[1];
88+
}
89+
if (args.length > 2) {
90+
prometheusPort = Integer.parseInt(args[2]);
91+
}
92+
93+
Thread mainThread = Thread.currentThread();
94+
95+
Runtime.getRuntime().addShutdownHook(new Thread() {
96+
@Override
97+
public void run() {
98+
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
99+
System.err.println("*** shutting down gRPC client since JVM is shutting down");
100+
101+
sendRpcs.set(false);
102+
try {
103+
mainThread.join();
104+
} catch (InterruptedException e) {
105+
e.printStackTrace(System.err);
106+
}
107+
System.err.println("*** client shut down");
108+
}
109+
});
110+
111+
// Adds a PrometheusHttpServer to convert OpenTelemetry metrics to Prometheus format and
112+
// expose these via a HttpServer exporter to the SdkMeterProvider.
113+
SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
114+
.registerMetricReader(
115+
PrometheusHttpServer.builder().setPort(prometheusPort).build())
116+
.build();
117+
118+
// Initialize OpenTelemetry SDK with MeterProvider configured with Prometeheus.
119+
OpenTelemetrySdk openTelemetrySdk =
120+
OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
121+
122+
// Initialize CSM Observability.
123+
CsmObservability observability = CsmObservability.newBuilder()
124+
.sdk(openTelemetrySdk)
125+
.build();
126+
// Registers CSM observabiity globally.
127+
observability.registerGlobal();
128+
129+
// Create a communication channel to the server, known as a Channel.
130+
ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
131+
.build();
132+
CsmObservabilityClient client = new CsmObservabilityClient(channel);
133+
134+
try {
135+
// Run RPCs every second.
136+
while (sendRpcs.get()) {
137+
client.greet(user);
138+
// Sleep for a bit before sending the next RPC.
139+
Thread.sleep(1000);
140+
}
141+
} finally {
142+
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
143+
// Shut down CSM Observability.
144+
observability.close();
145+
// Shut down OpenTelemetry SDK.
146+
openTelemetrySdk.close();
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)