gRPC service utilities providing health checking, server reflection, channelz observability, and binary logging capabilities
—
ORCA metrics collection for load balancing and performance monitoring. Provides both per-call metrics recording and out-of-band metrics reporting for intelligent load balancing decisions.
Records metrics on a per-call basis for load balancing and performance analysis. One instance exists per gRPC call and is automatically attached to the call context.
/**
* Utility to record call metrics for load-balancing. One instance per call.
* Provides fluent API for recording various types of metrics.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6012")
@ThreadSafe
public final class CallMetricRecorder {
/**
* Returns the call metric recorder attached to the current Context
* @return CallMetricRecorder instance for the current call
*/
public static CallMetricRecorder getCurrent();
/**
* Records utilization metric in range [0, 1]
* @param name Metric name
* @param value Utilization value between 0.0 and 1.0
* @return this recorder object for chaining
*/
public CallMetricRecorder recordUtilizationMetric(String name, double value);
/**
* Records request cost metric (arbitrary units)
* @param name Metric name
* @param value Cost value (typically positive)
* @return this recorder object for chaining
*/
public CallMetricRecorder recordRequestCostMetric(String name, double value);
/**
* Records application-specific opaque custom metric
* @param name Metric name
* @param value Metric value
* @return this recorder object for chaining
*/
public CallMetricRecorder recordNamedMetric(String name, double value);
/**
* Records CPU utilization in range [0, inf)
* @param value CPU utilization value
* @return this recorder object for chaining
*/
public CallMetricRecorder recordCpuUtilizationMetric(double value);
/**
* Records application specific utilization in range [0, inf)
* @param value Application utilization value
* @return this recorder object for chaining
*/
public CallMetricRecorder recordApplicationUtilizationMetric(double value);
/**
* Records memory utilization in range [0, 1]
* @param value Memory utilization between 0.0 and 1.0
* @return this recorder object for chaining
*/
public CallMetricRecorder recordMemoryUtilizationMetric(double value);
/**
* Records queries per second in range [0, inf)
* @param value QPS value
* @return this recorder object for chaining
*/
public CallMetricRecorder recordQpsMetric(double value);
/**
* Records errors per second in range [0, inf)
* @param value EPS value
* @return this recorder object for chaining
*/
public CallMetricRecorder recordEpsMetric(double value);
/**
* Records request cost metric (deprecated, use recordRequestCostMetric)
* @param name Metric name
* @param value Cost value
* @return this recorder object for chaining
* @deprecated Use recordRequestCostMetric instead
*/
@Deprecated
public CallMetricRecorder recordCallMetric(String name, double value);
}Usage Examples:
import io.grpc.services.CallMetricRecorder;
import io.grpc.stub.StreamObserver;
public class MetricsAwareService extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(GetUserRequest request, StreamObserver<GetUserResponse> responseObserver) {
long startTime = System.nanoTime();
try {
// Business logic
UserResponse response = processGetUser(request);
// Record metrics for this call
CallMetricRecorder recorder = CallMetricRecorder.getCurrent();
long duration = System.nanoTime() - startTime;
double durationMs = duration / 1_000_000.0;
recorder
.recordRequestCostMetric("processing_time_ms", durationMs)
.recordCpuUtilizationMetric(getCurrentCpuUsage())
.recordMemoryUtilizationMetric(getCurrentMemoryUsage())
.recordUtilizationMetric("database_load", getDatabaseLoad());
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (Exception e) {
// Record error metrics
CallMetricRecorder.getCurrent()
.recordEpsMetric(1.0)
.recordNamedMetric("error_type", getErrorTypeCode(e));
responseObserver.onError(e);
}
}
private double getCurrentCpuUsage() {
// Implementation to get current CPU usage
return 0.75; // Example value
}
private double getCurrentMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
return (double) runtime.totalMemory() / runtime.maxMemory();
}
private double getDatabaseLoad() {
// Implementation to get database connection load
return 0.60; // Example value
}
}Out-of-band metrics reporting for server-wide utilization metrics that are reported independently of individual calls.
/**
* Implements Out-of-Band metrics reporting for utilization metrics.
* Reports server-wide metrics that apply across all calls.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9006")
public final class MetricRecorder {
/**
* Creates a new MetricRecorder instance
* @return MetricRecorder instance for out-of-band reporting
*/
public static MetricRecorder newInstance();
/**
* Updates metrics value in range [0, 1] for specified key
* @param key Metric name
* @param value Utilization value between 0.0 and 1.0
*/
public void putUtilizationMetric(String key, double value);
/**
* Replaces the whole metrics data using the specified map
* @param metrics Map of metric names to values
*/
public void setAllUtilizationMetrics(Map<String, Double> metrics);
/**
* Removes the metrics data entry for specified key
* @param key Metric name to remove
*/
public void removeUtilizationMetric(String key);
/**
* Updates CPU utilization in range [0, inf)
* @param value CPU utilization value
*/
public void setCpuUtilizationMetric(double value);
/** Clears CPU utilization metrics data */
public void clearCpuUtilizationMetric();
/**
* Updates application specific utilization in range [0, inf)
* @param value Application utilization value
*/
public void setApplicationUtilizationMetric(double value);
/** Clears application specific utilization metrics data */
public void clearApplicationUtilizationMetric();
/**
* Updates memory utilization in range [0, 1]
* @param value Memory utilization between 0.0 and 1.0
*/
public void setMemoryUtilizationMetric(double value);
/** Clears memory utilization metrics data */
public void clearMemoryUtilizationMetric();
/**
* Updates QPS metrics in range [0, inf)
* @param value Queries per second value
*/
public void setQpsMetric(double value);
/** Clears QPS metrics data */
public void clearQpsMetric();
/**
* Updates EPS metrics in range [0, inf)
* @param value Errors per second value
*/
public void setEpsMetric(double value);
/** Clears EPS metrics data */
public void clearEpsMetric();
}Usage Examples:
import io.grpc.services.MetricRecorder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ServerMetricsReporter {
private final MetricRecorder metricRecorder;
private final ScheduledExecutorService scheduler;
public ServerMetricsReporter() {
this.metricRecorder = MetricRecorder.newInstance();
this.scheduler = Executors.newScheduledThreadPool(1);
}
public void startReporting() {
// Report server metrics every 10 seconds
scheduler.scheduleAtFixedRate(this::reportMetrics, 0, 10, TimeUnit.SECONDS);
}
private void reportMetrics() {
// Collect current server metrics
double cpuUsage = SystemMetrics.getCpuUsage();
double memoryUsage = SystemMetrics.getMemoryUsage();
double diskUsage = SystemMetrics.getDiskUsage();
double networkLatency = SystemMetrics.getNetworkLatency();
// Report standard metrics
metricRecorder.setCpuUtilizationMetric(cpuUsage);
metricRecorder.setMemoryUtilizationMetric(memoryUsage);
// Report custom utilization metrics
metricRecorder.putUtilizationMetric("disk_usage", diskUsage);
metricRecorder.putUtilizationMetric("network_latency", networkLatency);
// Report performance metrics
double currentQps = PerformanceTracker.getCurrentQps();
double currentEps = PerformanceTracker.getCurrentEps();
metricRecorder.setQpsMetric(currentQps);
metricRecorder.setEpsMetric(currentEps);
System.out.println("Reported metrics: CPU=" + cpuUsage +
", Memory=" + memoryUsage +
", QPS=" + currentQps);
}
public void shutdown() {
scheduler.shutdown();
}
}Read-only object containing ORCA load report data for load balancing policies.
/**
* A gRPC object of orca load report for LB policies listening at per-rpc or oob orca load reports.
* Provides read-only access to collected metrics data.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9381")
public class MetricReport {
/** @return CPU utilization value */
public double getCpuUtilization();
/** @return Application utilization value */
public double getApplicationUtilization();
/** @return Memory utilization value */
public double getMemoryUtilization();
/** @return QPS (queries per second) value */
public double getQps();
/** @return EPS (errors per second) value */
public double getEps();
/** @return Map of request cost metrics */
public Map<String, Double> getRequestCostMetrics();
/** @return Map of utilization metrics */
public Map<String, Double> getUtilizationMetrics();
/** @return Map of named metrics */
public Map<String, Double> getNamedMetrics();
/** @return String representation of the metric report */
public String toString();
}public class ComprehensiveMetricsService extends OrderServiceGrpc.OrderServiceImplBase {
private final MetricRecorder serverMetrics;
public ComprehensiveMetricsService() {
this.serverMetrics = MetricRecorder.newInstance();
// Start background metrics reporting
startBackgroundMetricsReporting();
}
@Override
public void processOrder(OrderRequest request, StreamObserver<OrderResponse> responseObserver) {
long startTime = System.currentTimeMillis();
try {
// Process the order
OrderResponse response = handleOrder(request);
// Record per-call metrics
CallMetricRecorder callRecorder = CallMetricRecorder.getCurrent();
long processingTime = System.currentTimeMillis() - startTime;
callRecorder
.recordRequestCostMetric("order_processing_time", processingTime)
.recordUtilizationMetric("payment_gateway_load", getPaymentGatewayLoad())
.recordNamedMetric("order_value", request.getTotalAmount());
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (PaymentException e) {
CallMetricRecorder.getCurrent()
.recordEpsMetric(1.0)
.recordNamedMetric("payment_error", 1.0);
responseObserver.onError(e);
}
}
private void startBackgroundMetricsReporting() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
// Update server-wide metrics
serverMetrics.setCpuUtilizationMetric(getCurrentCpuUsage());
serverMetrics.setMemoryUtilizationMetric(getCurrentMemoryUsage());
serverMetrics.putUtilizationMetric("database_connections", getDatabaseConnectionUsage());
serverMetrics.putUtilizationMetric("cache_hit_rate", getCacheHitRate());
}, 0, 30, TimeUnit.SECONDS);
}
}public class LoadBalancerAwareClient {
private final ManagedChannel channel;
private final OrderServiceGrpc.OrderServiceStub stub;
public LoadBalancerAwareClient(String target) {
this.channel = ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy("weighted_round_robin") // Uses ORCA metrics
.usePlaintext()
.build();
this.stub = OrderServiceGrpc.newStub(channel);
}
public void processOrderWithMetrics(OrderRequest request) {
// The load balancer will automatically use ORCA metrics
// reported by CallMetricRecorder and MetricRecorder
// to make intelligent routing decisions
stub.processOrder(request, new StreamObserver<OrderResponse>() {
@Override
public void onNext(OrderResponse response) {
System.out.println("Order processed: " + response.getOrderId());
}
@Override
public void onError(Throwable t) {
System.err.println("Order processing failed: " + t.getMessage());
}
@Override
public void onCompleted() {
// Request completed
}
});
}
}public class MetricsCollector {
private final List<MetricReport> collectedReports = new ArrayList<>();
public void collectMetrics(MetricReport report) {
synchronized (collectedReports) {
collectedReports.add(report);
}
// Analyze metrics
analyzeReport(report);
}
private void analyzeReport(MetricReport report) {
System.out.println("Metrics Analysis:");
System.out.println("CPU Utilization: " + report.getCpuUtilization());
System.out.println("Memory Utilization: " + report.getMemoryUtilization());
System.out.println("QPS: " + report.getQps());
System.out.println("EPS: " + report.getEps());
// Analyze request cost metrics
Map<String, Double> costMetrics = report.getRequestCostMetrics();
costMetrics.forEach((name, value) ->
System.out.println("Cost Metric " + name + ": " + value)
);
// Analyze custom metrics
Map<String, Double> namedMetrics = report.getNamedMetrics();
namedMetrics.forEach((name, value) ->
System.out.println("Named Metric " + name + ": " + value)
);
// Alert on high utilization
if (report.getCpuUtilization() > 0.8) {
System.out.println("WARNING: High CPU utilization detected!");
}
if (report.getEps() > 10.0) {
System.out.println("WARNING: High error rate detected!");
}
}
public void printSummaryReport() {
synchronized (collectedReports) {
if (collectedReports.isEmpty()) {
System.out.println("No metrics collected yet");
return;
}
double avgCpu = collectedReports.stream()
.mapToDouble(MetricReport::getCpuUtilization)
.average()
.orElse(0.0);
double avgMemory = collectedReports.stream()
.mapToDouble(MetricReport::getMemoryUtilization)
.average()
.orElse(0.0);
System.out.println("Summary Report:");
System.out.println("Total Reports: " + collectedReports.size());
System.out.println("Average CPU: " + avgCpu);
System.out.println("Average Memory: " + avgMemory);
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-grpc--grpc-services