examples: add examples-orca (#9204)

This commit is contained in:
yifeizhuang 2022-05-26 10:23:43 -07:00 committed by GitHub
parent e2f7e676cf
commit 45dd17c799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 529 additions and 0 deletions

View File

@ -36,6 +36,7 @@ $ VERSION_FILES=(
examples/example-tls/build.gradle
examples/example-tls/pom.xml
examples/example-xds/build.gradle
examples/example-orca/build.gradle
)
```

View File

@ -0,0 +1,44 @@
gRPC ORCA Example
================
The ORCA example consists of a Hello World client and a Hello World server. Out-of-the-box the
client behaves the same the hello-world version and the server behaves similar to the
example-hostname. In addition, they have been integrated with backend metrics reporting features.
### Build the example
Build the ORCA hello-world example client & server. From the `grpc-java/examples/examples-orca`
directory:
```
$ ../gradlew installDist
```
This creates the scripts `build/install/example-orca/bin/custom-backend-metrics-client` and
`build/install/example-orca/bin/custom-backend-metrics-server`.
### Run the example
To use ORCA, you have to instrument both the client and the server.
At the client, in your own load balancer policy, you use gRPC APIs to install listeners to receive
per-query and out-of-band metric reports.
At the server, you add a server interceptor provided by gRPC in order to send per-query backend metrics.
And you register a bindable service, also provided by gRPC, in order to send out-of-band backend metrics.
Meanwhile, you update the metrics data from your own measurements.
That's it! In this example, we simply put all the necessary pieces together to demonstrate the
metrics reporting mechanism.
1. To start the ORCA enabled example server on its default port of 50051, run:
```
$ ./build/install/example-orca/bin/custom-backend-metrics-server
```
2. In a different terminal window, run the ORCA enabled example client:
```
$ ./build/install/example-orca/bin/custom-backend-metrics-client "orca tester" 1500
```
The first command line argument (`orca tester`) is the name you wish to include in
the greeting request to the server and the second argument
(`1500`) is the time period (in milliseconds) you want to run the client before it shut downed so that it will show
more periodic backend metrics reports. You are expected to see the metrics data printed out. Try it!

View File

@ -0,0 +1,62 @@
plugins {
id 'application' // Provide convenience executables for trying out the examples.
// ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions
id 'com.google.protobuf' version '0.8.17'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'java'
}
repositories {
maven { // The google mirror is less flaky than mavenCentral()
url "https://maven-central.storage-download.googleapis.com/maven2/" }
mavenCentral()
mavenLocal()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
def grpcVersion = '1.48.0-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.19.2'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
implementation "io.grpc:grpc-services:${grpcVersion}"
implementation "io.grpc:grpc-stub:${grpcVersion}"
implementation "io.grpc:grpc-xds:${grpcVersion}"
compileOnly "org.apache.tomcat:annotations-api:6.0.53"
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
}
generateProtoTasks {
all()*.plugins { grpc {} }
}
}
startScripts.enabled = false
task CustomBackendMetricsClient(type: CreateStartScripts) {
mainClass = 'io.grpc.examples.orca.CustomBackendMetricsClient'
applicationName = 'custom-backend-metrics-client'
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
classpath = startScripts.classpath
}
task CustomBackendMetricsServer(type: CreateStartScripts) {
mainClass = 'io.grpc.examples.orca.CustomBackendMetricsServer'
applicationName = 'custom-backend-metrics-server'
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
classpath = startScripts.classpath
}
applicationDistribution.into('bin') {
from(CustomBackendMetricsClient)
from(CustomBackendMetricsServer)
fileMode = 0755
}

View File

@ -0,0 +1 @@
rootProject.name = 'example-orca'

View File

@ -0,0 +1,106 @@
/*
* Copyright 2022 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.orca;
import static io.grpc.examples.orca.CustomBackendMetricsLoadBalancerProvider.EXAMPLE_LOAD_BALANCER;
import io.grpc.Channel;
import io.grpc.LoadBalancerRegistry;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple xDS client that requests a greeting from {@link CustomBackendMetricsServer}.
* The client channel is configured to use an example load balancer policy
* {@link CustomBackendMetricsLoadBalancerProvider} which integrates with ORCA metrics reporting.
*/
public class CustomBackendMetricsClient {
private static final Logger logger = Logger.getLogger(CustomBackendMetricsClient.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/** Construct client for accessing HelloWorld server using the existing channel. */
public CustomBackendMetricsClient(Channel channel) {
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
/** Say hello to server. */
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
/**
* Greet server. If provided, the first element of {@code args} is the name to use in the
* greeting. The second argument is the target server.
*/
public static void main(String[] args) throws Exception {
String user = "orca tester";
// The example defaults to the same behavior as the hello world example.
// To receive more periodic OOB metrics reports, use duration argument to a longer value.
String target = "localhost:50051";
long timeBeforeShutdown = 1500;
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name [duration [target]]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" duration The time period in milliseconds that the client application " +
"wait until shutdown. Defaults to " + timeBeforeShutdown);
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
user = args[0];
}
if (args.length > 1) {
timeBeforeShutdown = Long.parseLong(args[1]);
}
if (args.length > 2) {
target = args[2];
}
LoadBalancerRegistry.getDefaultRegistry().register(
new CustomBackendMetricsLoadBalancerProvider());
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy(EXAMPLE_LOAD_BALANCER)
.usePlaintext()
.build();
try {
CustomBackendMetricsClient client = new CustomBackendMetricsClient(channel);
client.greet(user);
Thread.sleep(timeBeforeShutdown);
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2022 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.orca;
import io.grpc.ConnectivityState;
import io.grpc.LoadBalancer;
import io.grpc.LoadBalancerProvider;
import io.grpc.LoadBalancerRegistry;
import io.grpc.util.ForwardingLoadBalancer;
import io.grpc.util.ForwardingLoadBalancerHelper;
import io.grpc.xds.orca.OrcaOobUtil;
import io.grpc.xds.orca.OrcaPerRequestUtil;
import io.grpc.xds.shaded.com.github.xds.data.orca.v3.OrcaLoadReport;
import java.util.concurrent.TimeUnit;
/**
* Implements a test LB policy that receives ORCA load reports.
* The load balancer mostly delegates to {@link io.grpc.internal.PickFirstLoadBalancerProvider},
* in addition, it installs {@link OrcaOobUtil.OrcaOobReportListener} and
* {@link OrcaPerRequestUtil.OrcaPerRequestReportListener} to be notified with backend metrics.
*/
final class CustomBackendMetricsLoadBalancerProvider extends LoadBalancerProvider {
static final String EXAMPLE_LOAD_BALANCER = "example_backend_metrics_load_balancer";
@Override
public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
return new CustomBackendMetricsLoadBalancer(helper);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public int getPriority() {
return 5;
}
@Override
public String getPolicyName() {
return EXAMPLE_LOAD_BALANCER;
}
private final class CustomBackendMetricsLoadBalancer extends ForwardingLoadBalancer {
private LoadBalancer delegate;
public CustomBackendMetricsLoadBalancer(LoadBalancer.Helper helper) {
this.delegate = LoadBalancerRegistry.getDefaultRegistry()
.getProvider("pick_first")
.newLoadBalancer(new CustomBackendMetricsLoadBalancerHelper(helper));
}
@Override
public LoadBalancer delegate() {
return delegate;
}
private final class CustomBackendMetricsLoadBalancerHelper
extends ForwardingLoadBalancerHelper {
private final LoadBalancer.Helper orcaHelper;
public CustomBackendMetricsLoadBalancerHelper(LoadBalancer.Helper helper) {
this.orcaHelper = OrcaOobUtil.newOrcaReportingHelper(helper);
}
@Override
public LoadBalancer.Subchannel createSubchannel(LoadBalancer.CreateSubchannelArgs args) {
LoadBalancer.Subchannel subchannel = super.createSubchannel(args);
// Installs ORCA OOB metrics reporting listener and configures to receive report every 1s.
// The interval can not be smaller than server minimum report interval configuration,
// otherwise it is treated as server minimum report interval.
OrcaOobUtil.setListener(subchannel, new OrcaOobUtil.OrcaOobReportListener() {
@Override
public void onLoadReport(OrcaLoadReport orcaLoadReport) {
System.out.println("Example load balancer received OOB metrics report:\n"
+ orcaLoadReport);
}
},
OrcaOobUtil.OrcaReportingConfig.newBuilder()
.setReportInterval(1, TimeUnit.SECONDS)
.build()
);
return subchannel;
}
@Override
public void updateBalancingState(ConnectivityState newState, LoadBalancer.SubchannelPicker newPicker) {
delegate().updateBalancingState(newState, new MayReportLoadPicker(newPicker));
}
@Override
public LoadBalancer.Helper delegate() {
return orcaHelper;
}
}
private final class MayReportLoadPicker extends LoadBalancer.SubchannelPicker {
private LoadBalancer.SubchannelPicker delegate;
public MayReportLoadPicker(LoadBalancer.SubchannelPicker delegate) {
this.delegate = delegate;
}
@Override
public LoadBalancer.PickResult pickSubchannel(LoadBalancer.PickSubchannelArgs args) {
LoadBalancer.PickResult result = delegate.pickSubchannel(args);
if (result.getSubchannel() == null) {
return result;
}
// Installs ORCA per-query metrics reporting listener.
return LoadBalancer.PickResult.withSubchannel(
result.getSubchannel(),
OrcaPerRequestUtil.getInstance().newOrcaClientStreamTracerFactory(
new OrcaPerRequestUtil.OrcaPerRequestReportListener() {
@Override
public void onLoadReport(OrcaLoadReport orcaLoadReport) {
System.out.println("Example load balancer received per-rpc metrics report:\n"
+ orcaLoadReport);
}
}));
}
}
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright 2022 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.orca;
import io.grpc.BindableService;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.services.CallMetricRecorder;
import io.grpc.services.MetricRecorder;
import io.grpc.stub.StreamObserver;
import io.grpc.xds.orca.OrcaMetricReportingServerInterceptor;
import io.grpc.xds.orca.OrcaServiceImpl;
import io.grpc.xds.shaded.com.github.xds.data.orca.v3.OrcaLoadReport;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* Server that manages startup/shutdown of a {@code Greeter} server.
*/
public class CustomBackendMetricsServer {
private static final Logger logger = Logger.getLogger(CustomBackendMetricsServer.class.getName());
private Server server;
private static Random random = new Random();
private MetricRecorder metricRecorder;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
metricRecorder = MetricRecorder.newInstance();
// Configure OOB metrics reporting minimum report interval to be 1s. This allows client
// configuration to be as short as 1s, suitable for test demonstration.
BindableService orcaOobService =
OrcaServiceImpl.createService(executor, metricRecorder, 1, TimeUnit.SECONDS);
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
// Enable OOB custom backend metrics reporting.
.addService(orcaOobService)
// Enable per-query custom backend metrics reporting.
.intercept(OrcaMetricReportingServerInterceptor.getInstance())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
CustomBackendMetricsServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}
});
}
private void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
CustomBackendMetricsServer server = new CustomBackendMetricsServer();
server.start();
server.blockUntilShutdown();
}
class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
OrcaLoadReport randomPerRpcMetrics = OrcaLoadReport.newBuilder()
.setCpuUtilization(random.nextDouble())
.setMemUtilization(random.nextDouble())
.putUtilization("util", random.nextDouble())
.putRequestCost("cost", random.nextDouble())
.build();
// Sets per-query backend metrics to a random test report.
CallMetricRecorder.getCurrent()
.recordMemoryUtilizationMetric(randomPerRpcMetrics.getMemUtilization())
.recordCallMetric("cost", randomPerRpcMetrics.getRequestCostOrDefault("cost", 0.0))
.recordUtilizationMetric("util", randomPerRpcMetrics.getUtilizationOrDefault("util", 0.0));
System.out.println("Hello World Server updates RPC metrics data:\n" + randomPerRpcMetrics);
OrcaLoadReport randomOobMetrics = OrcaLoadReport.newBuilder()
.setCpuUtilization(random.nextDouble())
.setMemUtilization(random.nextDouble())
.putUtilization("util", random.nextDouble())
.build();
// Sets OOB backend metrics to a random test report.
metricRecorder.setCpuUtilizationMetric(randomOobMetrics.getCpuUtilization());
metricRecorder.setMemoryUtilizationMetric(randomOobMetrics.getMemUtilization());
metricRecorder.setAllUtilizationMetrics(randomOobMetrics.getUtilizationMap());
System.out.println("Hello World Server updates OOB metrics data:\n" + randomOobMetrics);
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright 2022 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}