CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-grpc--grpc-services

gRPC service utilities providing health checking, server reflection, channelz observability, and binary logging capabilities

Pending
Overview
Eval results
Files

metrics.mddocs/

Metrics and Load Reporting

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.

Capabilities

CallMetricRecorder

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
    }
}

MetricRecorder

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();
    }
}

MetricReport

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();
}

Integration Patterns

Combined Per-Call and Out-of-Band Metrics

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);
    }
}

Load Balancer Integration

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
            }
        });
    }
}

Metrics Collection and Analysis

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

docs

admin-services.md

binary-logging.md

channelz.md

health-checking.md

index.md

load-balancing.md

metrics.md

server-reflection.md

tile.json