Skip to content

Commit 45dd17c

Browse files
authored
examples: add examples-orca (grpc#9204)
1 parent e2f7e67 commit 45dd17c

File tree

8 files changed

+529
-0
lines changed

8 files changed

+529
-0
lines changed

RELEASING.md

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ $ VERSION_FILES=(
3636
examples/example-tls/build.gradle
3737
examples/example-tls/pom.xml
3838
examples/example-xds/build.gradle
39+
examples/example-orca/build.gradle
3940
)
4041
```
4142

examples/example-orca/README.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
gRPC ORCA Example
2+
================
3+
4+
The ORCA example consists of a Hello World client and a Hello World server. Out-of-the-box the
5+
client behaves the same the hello-world version and the server behaves similar to the
6+
example-hostname. In addition, they have been integrated with backend metrics reporting features.
7+
8+
### Build the example
9+
10+
Build the ORCA hello-world example client & server. From the `grpc-java/examples/examples-orca`
11+
directory:
12+
```
13+
$ ../gradlew installDist
14+
```
15+
16+
This creates the scripts `build/install/example-orca/bin/custom-backend-metrics-client` and
17+
`build/install/example-orca/bin/custom-backend-metrics-server`.
18+
19+
### Run the example
20+
21+
To use ORCA, you have to instrument both the client and the server.
22+
At the client, in your own load balancer policy, you use gRPC APIs to install listeners to receive
23+
per-query and out-of-band metric reports.
24+
At the server, you add a server interceptor provided by gRPC in order to send per-query backend metrics.
25+
And you register a bindable service, also provided by gRPC, in order to send out-of-band backend metrics.
26+
Meanwhile, you update the metrics data from your own measurements.
27+
28+
That's it! In this example, we simply put all the necessary pieces together to demonstrate the
29+
metrics reporting mechanism.
30+
31+
1. To start the ORCA enabled example server on its default port of 50051, run:
32+
```
33+
$ ./build/install/example-orca/bin/custom-backend-metrics-server
34+
```
35+
36+
2. In a different terminal window, run the ORCA enabled example client:
37+
```
38+
$ ./build/install/example-orca/bin/custom-backend-metrics-client "orca tester" 1500
39+
```
40+
The first command line argument (`orca tester`) is the name you wish to include in
41+
the greeting request to the server and the second argument
42+
(`1500`) is the time period (in milliseconds) you want to run the client before it shut downed so that it will show
43+
more periodic backend metrics reports. You are expected to see the metrics data printed out. Try it!
44+

examples/example-orca/build.gradle

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
plugins {
2+
id 'application' // Provide convenience executables for trying out the examples.
3+
// ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions
4+
id 'com.google.protobuf' version '0.8.17'
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+
mavenCentral()
14+
mavenLocal()
15+
}
16+
17+
sourceCompatibility = 1.8
18+
targetCompatibility = 1.8
19+
20+
def grpcVersion = '1.48.0-SNAPSHOT' // CURRENT_GRPC_VERSION
21+
def protocVersion = '3.19.2'
22+
23+
dependencies {
24+
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
25+
implementation "io.grpc:grpc-services:${grpcVersion}"
26+
implementation "io.grpc:grpc-stub:${grpcVersion}"
27+
implementation "io.grpc:grpc-xds:${grpcVersion}"
28+
compileOnly "org.apache.tomcat:annotations-api:6.0.53"
29+
30+
}
31+
32+
protobuf {
33+
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
34+
plugins {
35+
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
36+
}
37+
generateProtoTasks {
38+
all()*.plugins { grpc {} }
39+
}
40+
}
41+
42+
startScripts.enabled = false
43+
44+
task CustomBackendMetricsClient(type: CreateStartScripts) {
45+
mainClass = 'io.grpc.examples.orca.CustomBackendMetricsClient'
46+
applicationName = 'custom-backend-metrics-client'
47+
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
48+
classpath = startScripts.classpath
49+
}
50+
51+
task CustomBackendMetricsServer(type: CreateStartScripts) {
52+
mainClass = 'io.grpc.examples.orca.CustomBackendMetricsServer'
53+
applicationName = 'custom-backend-metrics-server'
54+
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
55+
classpath = startScripts.classpath
56+
}
57+
58+
applicationDistribution.into('bin') {
59+
from(CustomBackendMetricsClient)
60+
from(CustomBackendMetricsServer)
61+
fileMode = 0755
62+
}

examples/example-orca/settings.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'example-orca'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2022 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.orca;
18+
19+
import static io.grpc.examples.orca.CustomBackendMetricsLoadBalancerProvider.EXAMPLE_LOAD_BALANCER;
20+
21+
import io.grpc.Channel;
22+
import io.grpc.LoadBalancerRegistry;
23+
import io.grpc.ManagedChannel;
24+
import io.grpc.ManagedChannelBuilder;
25+
import io.grpc.StatusRuntimeException;
26+
import io.grpc.examples.helloworld.GreeterGrpc;
27+
import io.grpc.examples.helloworld.HelloReply;
28+
import io.grpc.examples.helloworld.HelloRequest;
29+
import java.util.concurrent.TimeUnit;
30+
import java.util.logging.Level;
31+
import java.util.logging.Logger;
32+
33+
/**
34+
* A simple xDS client that requests a greeting from {@link CustomBackendMetricsServer}.
35+
* The client channel is configured to use an example load balancer policy
36+
* {@link CustomBackendMetricsLoadBalancerProvider} which integrates with ORCA metrics reporting.
37+
*/
38+
public class CustomBackendMetricsClient {
39+
private static final Logger logger = Logger.getLogger(CustomBackendMetricsClient.class.getName());
40+
41+
private final GreeterGrpc.GreeterBlockingStub blockingStub;
42+
43+
/** Construct client for accessing HelloWorld server using the existing channel. */
44+
public CustomBackendMetricsClient(Channel channel) {
45+
blockingStub = GreeterGrpc.newBlockingStub(channel);
46+
}
47+
48+
/** Say hello to server. */
49+
public void greet(String name) {
50+
logger.info("Will try to greet " + name + " ...");
51+
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
52+
HelloReply response;
53+
try {
54+
response = blockingStub.sayHello(request);
55+
} catch (StatusRuntimeException e) {
56+
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
57+
return;
58+
}
59+
logger.info("Greeting: " + response.getMessage());
60+
}
61+
62+
/**
63+
* Greet server. If provided, the first element of {@code args} is the name to use in the
64+
* greeting. The second argument is the target server.
65+
*/
66+
public static void main(String[] args) throws Exception {
67+
String user = "orca tester";
68+
// The example defaults to the same behavior as the hello world example.
69+
// To receive more periodic OOB metrics reports, use duration argument to a longer value.
70+
String target = "localhost:50051";
71+
long timeBeforeShutdown = 1500;
72+
if (args.length > 0) {
73+
if ("--help".equals(args[0])) {
74+
System.err.println("Usage: [name [duration [target]]]");
75+
System.err.println("");
76+
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
77+
System.err.println(" duration The time period in milliseconds that the client application " +
78+
"wait until shutdown. Defaults to " + timeBeforeShutdown);
79+
System.err.println(" target The server to connect to. Defaults to " + target);
80+
System.exit(1);
81+
}
82+
user = args[0];
83+
}
84+
if (args.length > 1) {
85+
timeBeforeShutdown = Long.parseLong(args[1]);
86+
}
87+
88+
if (args.length > 2) {
89+
target = args[2];
90+
}
91+
92+
LoadBalancerRegistry.getDefaultRegistry().register(
93+
new CustomBackendMetricsLoadBalancerProvider());
94+
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
95+
.defaultLoadBalancingPolicy(EXAMPLE_LOAD_BALANCER)
96+
.usePlaintext()
97+
.build();
98+
try {
99+
CustomBackendMetricsClient client = new CustomBackendMetricsClient(channel);
100+
client.greet(user);
101+
Thread.sleep(timeBeforeShutdown);
102+
} finally {
103+
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
104+
}
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2022 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.orca;
18+
19+
import io.grpc.ConnectivityState;
20+
import io.grpc.LoadBalancer;
21+
import io.grpc.LoadBalancerProvider;
22+
import io.grpc.LoadBalancerRegistry;
23+
import io.grpc.util.ForwardingLoadBalancer;
24+
import io.grpc.util.ForwardingLoadBalancerHelper;
25+
import io.grpc.xds.orca.OrcaOobUtil;
26+
import io.grpc.xds.orca.OrcaPerRequestUtil;
27+
import io.grpc.xds.shaded.com.github.xds.data.orca.v3.OrcaLoadReport;
28+
import java.util.concurrent.TimeUnit;
29+
30+
/**
31+
* Implements a test LB policy that receives ORCA load reports.
32+
* The load balancer mostly delegates to {@link io.grpc.internal.PickFirstLoadBalancerProvider},
33+
* in addition, it installs {@link OrcaOobUtil.OrcaOobReportListener} and
34+
* {@link OrcaPerRequestUtil.OrcaPerRequestReportListener} to be notified with backend metrics.
35+
*/
36+
final class CustomBackendMetricsLoadBalancerProvider extends LoadBalancerProvider {
37+
38+
static final String EXAMPLE_LOAD_BALANCER = "example_backend_metrics_load_balancer";
39+
40+
@Override
41+
public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
42+
return new CustomBackendMetricsLoadBalancer(helper);
43+
}
44+
45+
@Override
46+
public boolean isAvailable() {
47+
return true;
48+
}
49+
50+
@Override
51+
public int getPriority() {
52+
return 5;
53+
}
54+
55+
@Override
56+
public String getPolicyName() {
57+
return EXAMPLE_LOAD_BALANCER;
58+
}
59+
60+
private final class CustomBackendMetricsLoadBalancer extends ForwardingLoadBalancer {
61+
private LoadBalancer delegate;
62+
63+
public CustomBackendMetricsLoadBalancer(LoadBalancer.Helper helper) {
64+
this.delegate = LoadBalancerRegistry.getDefaultRegistry()
65+
.getProvider("pick_first")
66+
.newLoadBalancer(new CustomBackendMetricsLoadBalancerHelper(helper));
67+
}
68+
69+
@Override
70+
public LoadBalancer delegate() {
71+
return delegate;
72+
}
73+
74+
private final class CustomBackendMetricsLoadBalancerHelper
75+
extends ForwardingLoadBalancerHelper {
76+
private final LoadBalancer.Helper orcaHelper;
77+
78+
public CustomBackendMetricsLoadBalancerHelper(LoadBalancer.Helper helper) {
79+
this.orcaHelper = OrcaOobUtil.newOrcaReportingHelper(helper);
80+
}
81+
82+
@Override
83+
public LoadBalancer.Subchannel createSubchannel(LoadBalancer.CreateSubchannelArgs args) {
84+
LoadBalancer.Subchannel subchannel = super.createSubchannel(args);
85+
// Installs ORCA OOB metrics reporting listener and configures to receive report every 1s.
86+
// The interval can not be smaller than server minimum report interval configuration,
87+
// otherwise it is treated as server minimum report interval.
88+
OrcaOobUtil.setListener(subchannel, new OrcaOobUtil.OrcaOobReportListener() {
89+
@Override
90+
public void onLoadReport(OrcaLoadReport orcaLoadReport) {
91+
System.out.println("Example load balancer received OOB metrics report:\n"
92+
+ orcaLoadReport);
93+
}
94+
},
95+
OrcaOobUtil.OrcaReportingConfig.newBuilder()
96+
.setReportInterval(1, TimeUnit.SECONDS)
97+
.build()
98+
);
99+
return subchannel;
100+
}
101+
102+
@Override
103+
public void updateBalancingState(ConnectivityState newState, LoadBalancer.SubchannelPicker newPicker) {
104+
delegate().updateBalancingState(newState, new MayReportLoadPicker(newPicker));
105+
}
106+
107+
@Override
108+
public LoadBalancer.Helper delegate() {
109+
return orcaHelper;
110+
}
111+
}
112+
113+
private final class MayReportLoadPicker extends LoadBalancer.SubchannelPicker {
114+
private LoadBalancer.SubchannelPicker delegate;
115+
116+
public MayReportLoadPicker(LoadBalancer.SubchannelPicker delegate) {
117+
this.delegate = delegate;
118+
}
119+
120+
@Override
121+
public LoadBalancer.PickResult pickSubchannel(LoadBalancer.PickSubchannelArgs args) {
122+
LoadBalancer.PickResult result = delegate.pickSubchannel(args);
123+
if (result.getSubchannel() == null) {
124+
return result;
125+
}
126+
// Installs ORCA per-query metrics reporting listener.
127+
return LoadBalancer.PickResult.withSubchannel(
128+
result.getSubchannel(),
129+
OrcaPerRequestUtil.getInstance().newOrcaClientStreamTracerFactory(
130+
new OrcaPerRequestUtil.OrcaPerRequestReportListener() {
131+
@Override
132+
public void onLoadReport(OrcaLoadReport orcaLoadReport) {
133+
System.out.println("Example load balancer received per-rpc metrics report:\n"
134+
+ orcaLoadReport);
135+
}
136+
}));
137+
}
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)