All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.xds.orca.OrcaPerRequestUtil Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019 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.xds.orca;

import static com.google.common.base.Preconditions.checkNotNull;

import com.github.xds.data.orca.v3.OrcaLoadReport;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.ExperimentalApi;
import io.grpc.LoadBalancer;
import io.grpc.Metadata;
import io.grpc.internal.ForwardingClientStreamTracer;
import io.grpc.protobuf.ProtoUtils;
import io.grpc.services.InternalCallMetricRecorder;
import io.grpc.services.MetricReport;
import java.util.ArrayList;
import java.util.List;

/**
 * Utility class that provides method for {@link LoadBalancer} to install listeners to receive
 * per-request backend cost metrics in the format of Open Request Cost Aggregation (ORCA).
 */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9128")
public abstract class OrcaPerRequestUtil {
  private static final ClientStreamTracer NOOP_CLIENT_STREAM_TRACER = new ClientStreamTracer() {};
  private static final ClientStreamTracer.Factory NOOP_CLIENT_STREAM_TRACER_FACTORY =
      new ClientStreamTracer.Factory() {
        @Override
        public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
          return NOOP_CLIENT_STREAM_TRACER;
        }
      };
  private static final OrcaPerRequestUtil DEFAULT_INSTANCE =
      new OrcaPerRequestUtil() {
        @Override
        public ClientStreamTracer.Factory newOrcaClientStreamTracerFactory(
            OrcaPerRequestReportListener listener) {
          return newOrcaClientStreamTracerFactory(NOOP_CLIENT_STREAM_TRACER_FACTORY, listener);
        }

        @Override
        public ClientStreamTracer.Factory newOrcaClientStreamTracerFactory(
            ClientStreamTracer.Factory delegate, OrcaPerRequestReportListener listener) {
          return new OrcaReportingTracerFactory(delegate, listener);
        }
      };

  /**
   * Gets an {@code OrcaPerRequestUtil} instance that provides actual implementation of
   * {@link #newOrcaClientStreamTracerFactory}.
   */
  public static OrcaPerRequestUtil getInstance() {
    return DEFAULT_INSTANCE;
  }

  /**
   * Creates a new {@link io.grpc.ClientStreamTracer.Factory} with provided {@link
   * OrcaPerRequestReportListener} installed to receive callback when a per-request ORCA report is
   * received.
   *
   * 

Example usages for leaf level policy (e.g., WRR policy) * *

   *   {@code
   *   class WrrPicker extends SubchannelPicker {
   *
   *     public PickResult pickSubchannel(PickSubchannelArgs args) {
   *       Subchannel subchannel = ...  // WRR picking logic
   *       return PickResult.withSubchannel(
   *           subchannel,
   *           OrcaPerRequestReportUtil.getInstance().newOrcaClientStreamTracerFactory(listener));
   *     }
   *   }
   *   }
   * 
* * @param listener contains the callback to be invoked when a per-request ORCA report is received. */ public abstract ClientStreamTracer.Factory newOrcaClientStreamTracerFactory( OrcaPerRequestReportListener listener); /** * Creates a new {@link io.grpc.ClientStreamTracer.Factory} with provided {@link * OrcaPerRequestReportListener} installed to receive callback when a per-request ORCA report is * received. * *

Example usages: * *

    *
  • Delegating policy (e.g., xDS) *
       *       {@code
       *       class XdsPicker extends SubchannelPicker {
       *
       *         public PickResult pickSubchannel(PickSubchannelArgs args) {
       *           SubchannelPicker perLocalityPicker = ...  // locality picking logic
       *           Result result = perLocalityPicker.pickSubchannel(args);
       *           return PickResult.withSubchannel(
       *               result.getSubchannel(),
       *               OrcaPerRequestReportUtil.getInstance().newOrcaClientTracerFactory(
       *                   result.getStreamTracerFactory(), listener));
       *
       *         }
       *       }
       *       }
       *     
    *
  • *
  • Delegating policy with additional tracing logic *
       *       {@code
       *       class WrappingPicker extends SubchannelPicker {
       *
       *         public PickResult pickSubchannel(PickSubchannelArgs args) {
       *           Result result = delegate.pickSubchannel(args);
       *           return PickResult.withSubchannel(
       *               result.getSubchannel(),
       *               new ClientStreamTracer.Factory() {
       *                 public ClientStreamTracer newClientStreamTracer(
       *                     StreamInfo info, Metadata metadata) {
       *                   ClientStreamTracer.Factory orcaTracerFactory =
       *                       OrcaPerRequestReportUtil.getInstance().newOrcaClientStreamTracerFactory(
       *                           result.getStreamTracerFactory(), listener);
       *
       *                   // Wrap the tracer from the delegate factory if you need to trace the
       *                   // stream for your own.
       *                   final ClientStreamTracer orcaTracer =
       *                       orcaTracerFactory.newClientStreamTracer(info, metadata);
       *
       *                   return ForwardingClientStreamTracer() {
       *                     protected ClientStreamTracer delegate() {
       *                       return orcaTracer;
       *                     }
       *
       *                     public void inboundMessage(int seqNo) {
       *                       // Handle this event.
       *                       ...
       *                     }
       *                   };
       *                 }
       *               });
       *         }
       *       }
       *       }
       *     
    *
  • *
* * @param delegate the delegate factory to produce other client stream tracing. * @param listener contains the callback to be invoked when a per-request ORCA report is received. */ public abstract ClientStreamTracer.Factory newOrcaClientStreamTracerFactory( ClientStreamTracer.Factory delegate, OrcaPerRequestReportListener listener); /** * The listener interface for receiving per-request ORCA reports from backends. The class that is * interested in processing backend cost metrics implements this interface, and the object created * with that class is registered with a component, using methods in {@link OrcaPerRequestUtil}. * When an ORCA report is received, that object's {@code onLoadReport} method is invoked. */ public interface OrcaPerRequestReportListener { /** * Invoked when a per-request ORCA report is received. * *

Note this callback will be invoked from the network thread as the RPC finishes, * implementations should not block. * * @param report load report in the format of grpc {@link MetricReport}. */ void onLoadReport(MetricReport report); } /** * An {@link OrcaReportingTracerFactory} wraps a delegated {@link ClientStreamTracer.Factory} with * additional functionality to produce {@link ClientStreamTracer} instances that extract * per-request ORCA reports and push to registered listeners for calls they trace. */ @VisibleForTesting static final class OrcaReportingTracerFactory extends ClientStreamTracer.Factory { @VisibleForTesting static final Metadata.Key ORCA_ENDPOINT_LOAD_METRICS_KEY = Metadata.Key.of( "endpoint-load-metrics-bin", ProtoUtils.metadataMarshaller(OrcaLoadReport.getDefaultInstance())); private static final CallOptions.Key ORCA_REPORT_BROKER_KEY = CallOptions.Key.create("internal-orca-report-broker"); private final ClientStreamTracer.Factory delegate; private final OrcaPerRequestReportListener listener; OrcaReportingTracerFactory( ClientStreamTracer.Factory delegate, OrcaPerRequestReportListener listener) { this.delegate = checkNotNull(delegate, "delegate"); this.listener = checkNotNull(listener, "listener"); } @Override public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) { OrcaReportBroker broker = info.getCallOptions().getOption(ORCA_REPORT_BROKER_KEY); boolean augmented = false; if (broker == null) { broker = new OrcaReportBroker(); info = info.toBuilder() .setCallOptions(info.getCallOptions().withOption(ORCA_REPORT_BROKER_KEY, broker)) .build(); augmented = true; } broker.addListener(listener); ClientStreamTracer tracer = delegate.newClientStreamTracer(info, headers); if (augmented) { final ClientStreamTracer currTracer = tracer; final OrcaReportBroker currBroker = broker; // The actual tracer that performs ORCA report deserialization. tracer = new ForwardingClientStreamTracer() { @Override protected ClientStreamTracer delegate() { return currTracer; } @Override public void inboundTrailers(Metadata trailers) { OrcaLoadReport report = trailers.get(ORCA_ENDPOINT_LOAD_METRICS_KEY); if (report != null) { currBroker.onReport(report); } delegate().inboundTrailers(trailers); } }; } return tracer; } } static MetricReport fromOrcaLoadReport(OrcaLoadReport loadReport) { return InternalCallMetricRecorder.createMetricReport(loadReport.getCpuUtilization(), loadReport.getApplicationUtilization(), loadReport.getMemUtilization(), loadReport.getRpsFractional(), loadReport.getEps(), loadReport.getRequestCostMap(), loadReport.getUtilizationMap(), loadReport.getNamedMetricsMap()); } /** * A container class to hold registered {@link OrcaPerRequestReportListener}s and invoke all of * them when an {@link OrcaLoadReport} is received. */ private static final class OrcaReportBroker { private final List listeners = new ArrayList<>(); void addListener(OrcaPerRequestReportListener listener) { listeners.add(listener); } void onReport(OrcaLoadReport report) { MetricReport metricReport = fromOrcaLoadReport(report); for (OrcaPerRequestReportListener listener : listeners) { listener.onLoadReport(metricReport); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy