![JAR search and dependency download from the Maven repository](/logo.png)
io.grpc.xds.orca.OrcaPerRequestUtil Maven / Gradle / Ivy
/*
* 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);
}
}
}
}