diff --git a/repositories.bzl b/repositories.bzl index 884f9041cfc..eb393c95a97 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -86,10 +86,10 @@ def grpc_java_repositories(): if not native.existing_rule("com_github_cncf_xds"): http_archive( name = "com_github_cncf_xds", - strip_prefix = "xds-32f1caf87195bf3390061c29f18987e51ca56a88", - sha256 = "fcd0b50c013452fda9c5e28c131c287b655ebb361271a76ad3bffc08b3ecd82e", + strip_prefix = "xds-e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7", + sha256 = "0d33b83f8c6368954e72e7785539f0d272a8aba2f6e2e336ed15fd1514bc9899", urls = [ - "https://github.com/cncf/xds/archive/32f1caf87195bf3390061c29f18987e51ca56a88.tar.gz", + "https://github.com/cncf/xds/archive/e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7.tar.gz", ], ) if not native.existing_rule("com_github_grpc_grpc"): diff --git a/services/src/main/java/io/grpc/services/CallMetricRecorder.java b/services/src/main/java/io/grpc/services/CallMetricRecorder.java index de8aa48d300..f491fb7542e 100644 --- a/services/src/main/java/io/grpc/services/CallMetricRecorder.java +++ b/services/src/main/java/io/grpc/services/CallMetricRecorder.java @@ -42,6 +42,7 @@ public final class CallMetricRecorder { private final AtomicReference> requestCostMetrics = new AtomicReference<>(); private double cpuUtilizationMetric = 0; + private double applicationUtilizationMetric = 0; private double memoryUtilizationMetric = 0; private double qps = 0; private double eps = 0; @@ -127,7 +128,7 @@ public CallMetricRecorder recordRequestCostMetric(String name, double value) { } /** - * Records a call metric measurement for CPU utilization in the range [0, 1]. Values outside the + * Records a call metric measurement for CPU utilization in the range [0, inf). Values outside the * valid range are ignored. If RPC has already finished, this method is no-op. * *

A latter record will overwrite its former name-sakes. @@ -136,13 +137,29 @@ public CallMetricRecorder recordRequestCostMetric(String name, double value) { * @since 1.47.0 */ public CallMetricRecorder recordCpuUtilizationMetric(double value) { - if (disabled || !MetricRecorderHelper.isCpuUtilizationValid(value)) { + if (disabled || !MetricRecorderHelper.isCpuOrApplicationUtilizationValid(value)) { return this; } cpuUtilizationMetric = value; return this; } + /** + * Records a call metric measurement for application specific utilization in the range [0, inf). + * Values outside the valid range are ignored. If RPC has already finished, this method is no-op. + * + *

A latter record will overwrite its former name-sakes. + * + * @return this recorder object + */ + public CallMetricRecorder recordApplicationUtilizationMetric(double value) { + if (disabled || !MetricRecorderHelper.isCpuOrApplicationUtilizationValid(value)) { + return this; + } + applicationUtilizationMetric = value; + return this; + } + /** * Records a call metric measurement for memory utilization in the range [0, 1]. Values outside * the valid range are ignored. If RPC has already finished, this method is no-op. @@ -221,8 +238,8 @@ MetricReport finalizeAndDump2() { if (savedUtilizationMetrics == null) { savedUtilizationMetrics = Collections.emptyMap(); } - return new MetricReport(cpuUtilizationMetric, memoryUtilizationMetric, qps, eps, - Collections.unmodifiableMap(savedRequestCostMetrics), + return new MetricReport(cpuUtilizationMetric, applicationUtilizationMetric, + memoryUtilizationMetric, qps, eps, Collections.unmodifiableMap(savedRequestCostMetrics), Collections.unmodifiableMap(savedUtilizationMetrics) ); } diff --git a/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java b/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java index 5865357aabf..3b0cbbbda35 100644 --- a/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java +++ b/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java @@ -45,10 +45,10 @@ public static MetricReport finalizeAndDump2(CallMetricRecorder recorder) { return recorder.finalizeAndDump2(); } - public static MetricReport createMetricReport(double cpuUtilization, double memoryUtilization, - double qps, double eps, Map requestCostMetrics, - Map utilizationMetrics) { - return new MetricReport(cpuUtilization, memoryUtilization, qps, eps, requestCostMetrics, - utilizationMetrics); + public static MetricReport createMetricReport(double cpuUtilization, + double applicationUtilization, double memoryUtilization, double qps, double eps, + Map requestCostMetrics, Map utilizationMetrics) { + return new MetricReport(cpuUtilization, applicationUtilization, memoryUtilization, qps, eps, + requestCostMetrics, utilizationMetrics); } } diff --git a/services/src/main/java/io/grpc/services/MetricRecorder.java b/services/src/main/java/io/grpc/services/MetricRecorder.java index 248cbb13e56..c585e7b5407 100644 --- a/services/src/main/java/io/grpc/services/MetricRecorder.java +++ b/services/src/main/java/io/grpc/services/MetricRecorder.java @@ -29,6 +29,7 @@ public final class MetricRecorder { private volatile ConcurrentHashMap metricsData = new ConcurrentHashMap<>(); private volatile double cpuUtilization; + private volatile double applicationUtilization; private volatile double memoryUtilization; private volatile double qps; private volatile double eps; @@ -69,7 +70,7 @@ public void removeUtilizationMetric(String key) { * are ignored. */ public void setCpuUtilizationMetric(double value) { - if (!MetricRecorderHelper.isCpuUtilizationValid(value)) { + if (!MetricRecorderHelper.isCpuOrApplicationUtilizationValid(value)) { return; } cpuUtilization = value; @@ -82,6 +83,24 @@ public void clearCpuUtilizationMetric() { cpuUtilization = 0; } + /** + * Update the application specific utilization metrics data in the range [0, inf). Values outside + * the valid range are ignored. + */ + public void setApplicationUtilizationMetric(double value) { + if (!MetricRecorderHelper.isCpuOrApplicationUtilizationValid(value)) { + return; + } + applicationUtilization = value; + } + + /** + * Clear the application specific utilization metrics data. + */ + public void clearApplicationUtilizationMetric() { + applicationUtilization = 0; + } + /** * Update the memory utilization metrics data in the range [0, 1]. Values outside the valid range * are ignored. @@ -135,7 +154,7 @@ public void clearEpsMetric() { } MetricReport getMetricReport() { - return new MetricReport(cpuUtilization, memoryUtilization, qps, eps, + return new MetricReport(cpuUtilization, applicationUtilization, memoryUtilization, qps, eps, Collections.emptyMap(), Collections.unmodifiableMap(metricsData)); } } diff --git a/services/src/main/java/io/grpc/services/MetricRecorderHelper.java b/services/src/main/java/io/grpc/services/MetricRecorderHelper.java index 66439ac6044..6d042fb0f19 100644 --- a/services/src/main/java/io/grpc/services/MetricRecorderHelper.java +++ b/services/src/main/java/io/grpc/services/MetricRecorderHelper.java @@ -30,11 +30,12 @@ static boolean isUtilizationValid(double utilization) { } /** - * Return true if the cpu utilization value is in the range [0, inf) and false otherwise. - * Occasionally users have over 100% cpu utilization and get a runaway effect where the backend - * with highest qps gets more and more qps sent to it. So we allow cpu utilization > 1.0. + * Return true if the cpu utilization or application specific utilization value is in the range + * [0, inf) and false otherwise. Occasionally users have over 100% cpu utilization and get a + * runaway effect where the backend with highest qps gets more and more qps sent to it. So we + * allow cpu utilization > 1.0, similarly for application specific utilization. */ - static boolean isCpuUtilizationValid(double utilization) { + static boolean isCpuOrApplicationUtilizationValid(double utilization) { return utilization >= 0.0; } diff --git a/services/src/main/java/io/grpc/services/MetricReport.java b/services/src/main/java/io/grpc/services/MetricReport.java index 0ab8a386c10..35cbfc056bf 100644 --- a/services/src/main/java/io/grpc/services/MetricReport.java +++ b/services/src/main/java/io/grpc/services/MetricReport.java @@ -29,16 +29,18 @@ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9381") public final class MetricReport { private double cpuUtilization; + private double applicationUtilization; private double memoryUtilization; private double qps; private double eps; private Map requestCostMetrics; private Map utilizationMetrics; - MetricReport(double cpuUtilization, double memoryUtilization, double qps, double eps, - Map requestCostMetrics, - Map utilizationMetrics) { + MetricReport(double cpuUtilization, double applicationUtilization, double memoryUtilization, + double qps, double eps, Map requestCostMetrics, + Map utilizationMetrics) { this.cpuUtilization = cpuUtilization; + this.applicationUtilization = applicationUtilization; this.memoryUtilization = memoryUtilization; this.qps = qps; this.eps = eps; @@ -50,6 +52,10 @@ public double getCpuUtilization() { return cpuUtilization; } + public double getApplicationUtilization() { + return applicationUtilization; + } + public double getMemoryUtilization() { return memoryUtilization; } @@ -74,6 +80,7 @@ public double getEps() { public String toString() { return MoreObjects.toStringHelper(this) .add("cpuUtilization", cpuUtilization) + .add("applicationUtilization", applicationUtilization) .add("memoryUtilization", memoryUtilization) .add("requestCost", requestCostMetrics) .add("utilization", utilizationMetrics) diff --git a/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java b/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java index 439cd7628a3..cb0bfc5d83f 100644 --- a/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java +++ b/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java @@ -45,6 +45,7 @@ public void dumpDumpsAllSavedMetricValues() { recorder.recordRequestCostMetric("cost2", 10293.0); recorder.recordRequestCostMetric("cost3", 1.0); recorder.recordCpuUtilizationMetric(0.1928); + recorder.recordApplicationUtilizationMetric(0.9987); recorder.recordMemoryUtilizationMetric(0.474); recorder.recordQpsMetric(2522.54); recorder.recordEpsMetric(1.618); @@ -55,15 +56,18 @@ public void dumpDumpsAllSavedMetricValues() { Truth.assertThat(dump.getRequestCostMetrics()) .containsExactly("cost1", 37465.12, "cost2", 10293.0, "cost3", 1.0); Truth.assertThat(dump.getCpuUtilization()).isEqualTo(0.1928); + Truth.assertThat(dump.getApplicationUtilization()).isEqualTo(0.9987); Truth.assertThat(dump.getMemoryUtilization()).isEqualTo(0.474); Truth.assertThat(dump.getQps()).isEqualTo(2522.54); Truth.assertThat(dump.getEps()).isEqualTo(1.618); Truth.assertThat(dump.toString()).contains("eps=1.618"); + Truth.assertThat(dump.toString()).contains("applicationUtilization=0.9987"); } @Test public void noMetricsRecordedAfterSnapshot() { Map initDump = recorder.finalizeAndDump(); + recorder.recordApplicationUtilizationMetric(0.01); recorder.recordUtilizationMetric("cost", 0.154353423); recorder.recordQpsMetric(3.14159); recorder.recordEpsMetric(1.618); @@ -87,6 +91,7 @@ public void noMetricsRecordedIfUtilizationIsGreaterThanUpperBound() { @Test public void noMetricsRecordedIfUtilizationAndQpsAreLessThanLowerBound() { recorder.recordCpuUtilizationMetric(-0.001); + recorder.recordApplicationUtilizationMetric(-0.001); recorder.recordMemoryUtilizationMetric(-0.001); recorder.recordQpsMetric(-0.001); recorder.recordEpsMetric(-0.001); @@ -94,6 +99,7 @@ public void noMetricsRecordedIfUtilizationAndQpsAreLessThanLowerBound() { MetricReport dump = recorder.finalizeAndDump2(); Truth.assertThat(dump.getCpuUtilization()).isEqualTo(0); + Truth.assertThat(dump.getApplicationUtilization()).isEqualTo(0); Truth.assertThat(dump.getMemoryUtilization()).isEqualTo(0); Truth.assertThat(dump.getQps()).isEqualTo(0); Truth.assertThat(dump.getEps()).isEqualTo(0); @@ -108,6 +114,8 @@ public void lastValueWinForMetricsWithSameName() { recorder.recordRequestCostMetric("cost1", 6441.341); recorder.recordRequestCostMetric("cost1", 4654.67); recorder.recordRequestCostMetric("cost2", 75.83); + recorder.recordApplicationUtilizationMetric(0.92); + recorder.recordApplicationUtilizationMetric(1.78); recorder.recordMemoryUtilizationMetric(0.13); recorder.recordMemoryUtilizationMetric(0.31); recorder.recordUtilizationMetric("util1", 0.2837421); @@ -121,6 +129,7 @@ public void lastValueWinForMetricsWithSameName() { MetricReport dump = recorder.finalizeAndDump2(); Truth.assertThat(dump.getRequestCostMetrics()) .containsExactly("cost1", 4654.67, "cost2", 75.83); + Truth.assertThat(dump.getApplicationUtilization()).isEqualTo(1.78); Truth.assertThat(dump.getMemoryUtilization()).isEqualTo(0.93840); Truth.assertThat(dump.getUtilizationMetrics()) .containsExactly("util1", 0.843233); diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index cf54eb4bb17..48442a84b22 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -228,12 +228,16 @@ final class OrcaReportListener implements OrcaPerRequestReportListener, OrcaOobR @Override public void onLoadReport(MetricReport report) { double newWeight = 0; - if (report.getCpuUtilization() > 0 && report.getQps() > 0) { + // Prefer application utilization and fallback to CPU utilization if unset. + double utilization = + report.getApplicationUtilization() > 0 ? report.getApplicationUtilization() + : report.getCpuUtilization(); + if (utilization > 0 && report.getQps() > 0) { double penalty = 0; if (report.getEps() > 0 && errorUtilizationPenalty > 0) { penalty = report.getEps() / report.getQps() * errorUtilizationPenalty; } - newWeight = report.getQps() / (report.getCpuUtilization() + penalty); + newWeight = report.getQps() / (utilization + penalty); } if (newWeight == 0) { return; diff --git a/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java b/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java index 2470202b2af..34922dfc887 100644 --- a/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java +++ b/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java @@ -115,6 +115,7 @@ public void close(Status status, Metadata trailers) { private static OrcaLoadReport.Builder fromInternalReport(MetricReport internalReport) { return OrcaLoadReport.newBuilder() .setCpuUtilization(internalReport.getCpuUtilization()) + .setApplicationUtilization(internalReport.getApplicationUtilization()) .setMemUtilization(internalReport.getMemoryUtilization()) .setRpsFractional(internalReport.getQps()) .setEps(internalReport.getEps()) @@ -138,6 +139,10 @@ private static void mergeMetrics( if (isReportValueSet(cpu)) { metricRecorderReportBuilder.setCpuUtilization(cpu); } + double applicationUtilization = callMetricRecorderReport.getApplicationUtilization(); + if (isReportValueSet(applicationUtilization)) { + metricRecorderReportBuilder.setApplicationUtilization(applicationUtilization); + } double mem = callMetricRecorderReport.getMemoryUtilization(); if (isReportValueSet(mem)) { metricRecorderReportBuilder.setMemUtilization(mem); diff --git a/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java b/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java index 2778cfdf9ea..97b98cd4a28 100644 --- a/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java +++ b/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java @@ -254,8 +254,9 @@ public void inboundTrailers(Metadata trailers) { static MetricReport fromOrcaLoadReport(OrcaLoadReport loadReport) { return InternalCallMetricRecorder.createMetricReport(loadReport.getCpuUtilization(), - loadReport.getMemUtilization(), loadReport.getRpsFractional(), loadReport.getEps(), - loadReport.getRequestCostMap(), loadReport.getUtilizationMap()); + loadReport.getApplicationUtilization(), loadReport.getMemUtilization(), + loadReport.getRpsFractional(), loadReport.getEps(), loadReport.getRequestCostMap(), + loadReport.getUtilizationMap()); } /** diff --git a/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java b/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java index ce92225cb1e..60cb3e1089f 100644 --- a/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java +++ b/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java @@ -149,6 +149,7 @@ private OrcaLoadReport generateMetricsReport() { MetricReport internalReport = InternalMetricRecorder.getMetricReport(metricRecorder); return OrcaLoadReport.newBuilder().setCpuUtilization(internalReport.getCpuUtilization()) + .setApplicationUtilization(internalReport.getApplicationUtilization()) .setMemUtilization(internalReport.getMemoryUtilization()) .setRpsFractional(internalReport.getQps()) .setEps(internalReport.getEps()) diff --git a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java index 011bdbb3427..daf58a174d9 100644 --- a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java @@ -214,10 +214,10 @@ public void wrrLifeCycle() { WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo(1); assertThat(weightedPicker.pickSubchannel(mockArgs) .getSubchannel()).isEqualTo(weightedSubchannel1); @@ -260,10 +260,10 @@ public void enableOobLoadReportConfig() { WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.9, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.9, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo(1); PickResult pickResult = weightedPicker.pickSubchannel(mockArgs); assertThat(pickResult.getSubchannel()).isEqualTo(weightedSubchannel1); @@ -340,25 +340,39 @@ weightedSubchannel3.new OrcaReportListener(weightedConfig.errorUtilizationPenalt @Test public void pickByWeight_LargeWeight() { MetricReport report1 = InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 999, 0, new HashMap<>(), new HashMap<>()); + 0.1, 0, 0.1, 999, 0, new HashMap<>(), new HashMap<>()); MetricReport report2 = InternalCallMetricRecorder.createMetricReport( - 0.9, 0.1, 2, 0, new HashMap<>(), new HashMap<>()); + 0.9, 0, 0.1, 2, 0, new HashMap<>(), new HashMap<>()); MetricReport report3 = InternalCallMetricRecorder.createMetricReport( - 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>()); + 0.86, 0, 0.1, 100, 0, new HashMap<>(), new HashMap<>()); double totalWeight = 999 / 0.1 + 2 / 0.9 + 100 / 0.86; pickByWeight(report1, report2, report3, 999 / 0.1 / totalWeight, 2 / 0.9 / totalWeight, 100 / 0.86 / totalWeight); } + @Test + public void pickByWeight_largeWeight_useApplicationUtilization() { + MetricReport report1 = InternalCallMetricRecorder.createMetricReport( + 0.44, 0.1, 0.1, 999, 0, new HashMap<>(), new HashMap<>()); + MetricReport report2 = InternalCallMetricRecorder.createMetricReport( + 0.12, 0.9, 0.1, 2, 0, new HashMap<>(), new HashMap<>()); + MetricReport report3 = InternalCallMetricRecorder.createMetricReport( + 0.33, 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>()); + double totalWeight = 999 / 0.1 + 2 / 0.9 + 100 / 0.86; + + pickByWeight(report1, report2, report3, 999 / 0.1 / totalWeight, 2 / 0.9 / totalWeight, + 100 / 0.86 / totalWeight); + } + @Test public void pickByWeight_largeWeight_withEps_defaultErrorUtilizationPenalty() { MetricReport report1 = InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 999, 13, new HashMap<>(), new HashMap<>()); + 0.1, 0, 0.1, 999, 13, new HashMap<>(), new HashMap<>()); MetricReport report2 = InternalCallMetricRecorder.createMetricReport( - 0.9, 0.1, 2, 1.8, new HashMap<>(), new HashMap<>()); + 0.9, 0, 0.1, 2, 1.8, new HashMap<>(), new HashMap<>()); MetricReport report3 = InternalCallMetricRecorder.createMetricReport( - 0.86, 0.1, 100, 3, new HashMap<>(), new HashMap<>()); + 0.86, 0, 0.1, 100, 3, new HashMap<>(), new HashMap<>()); double weight1 = 999 / (0.1 + 13 / 999F * weightedConfig.errorUtilizationPenalty); double weight2 = 2 / (0.9 + 1.8 / 2F * weightedConfig.errorUtilizationPenalty); double weight3 = 100 / (0.86 + 3 / 100F * weightedConfig.errorUtilizationPenalty); @@ -371,25 +385,39 @@ public void pickByWeight_largeWeight_withEps_defaultErrorUtilizationPenalty() { @Test public void pickByWeight_normalWeight() { MetricReport report1 = InternalCallMetricRecorder.createMetricReport( - 0.12, 0.1, 22, 0, new HashMap<>(), new HashMap<>()); + 0.12, 0, 0.1, 22, 0, new HashMap<>(), new HashMap<>()); MetricReport report2 = InternalCallMetricRecorder.createMetricReport( - 0.28, 0.1, 40, 0, new HashMap<>(), new HashMap<>()); + 0.28, 0, 0.1, 40, 0, new HashMap<>(), new HashMap<>()); MetricReport report3 = InternalCallMetricRecorder.createMetricReport( - 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>()); + 0.86, 0, 0.1, 100, 0, new HashMap<>(), new HashMap<>()); double totalWeight = 22 / 0.12 + 40 / 0.28 + 100 / 0.86; pickByWeight(report1, report2, report3, 22 / 0.12 / totalWeight, 40 / 0.28 / totalWeight, 100 / 0.86 / totalWeight ); } + @Test + public void pickByWeight_normalWeight_useApplicationUtilization() { + MetricReport report1 = InternalCallMetricRecorder.createMetricReport( + 0.72, 0.12, 0.1, 22, 0, new HashMap<>(), new HashMap<>()); + MetricReport report2 = InternalCallMetricRecorder.createMetricReport( + 0.98, 0.28, 0.1, 40, 0, new HashMap<>(), new HashMap<>()); + MetricReport report3 = InternalCallMetricRecorder.createMetricReport( + 0.99, 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>()); + double totalWeight = 22 / 0.12 + 40 / 0.28 + 100 / 0.86; + pickByWeight(report1, report2, report3, 22 / 0.12 / totalWeight, + 40 / 0.28 / totalWeight, 100 / 0.86 / totalWeight + ); + } + @Test public void pickByWeight_normalWeight_withEps_defaultErrorUtilizationPenalty() { MetricReport report1 = InternalCallMetricRecorder.createMetricReport( - 0.12, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>()); + 0.12, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>()); MetricReport report2 = InternalCallMetricRecorder.createMetricReport( - 0.28, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>()); + 0.28, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>()); MetricReport report3 = InternalCallMetricRecorder.createMetricReport( - 0.86, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>()); + 0.86, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>()); double weight1 = 22 / (0.12 + 19.7 / 22F * weightedConfig.errorUtilizationPenalty); double weight2 = 40 / (0.28 + 0.998 / 40F * weightedConfig.errorUtilizationPenalty); double weight3 = 100 / (0.86 + 3.14159 / 100F * weightedConfig.errorUtilizationPenalty); @@ -405,11 +433,11 @@ public void pickByWeight_normalWeight_withEps_customErrorUtilizationPenalty() { .setErrorUtilizationPenalty(1.75F).build(); MetricReport report1 = InternalCallMetricRecorder.createMetricReport( - 0.12, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>()); + 0.12, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>()); MetricReport report2 = InternalCallMetricRecorder.createMetricReport( - 0.28, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>()); + 0.28, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>()); MetricReport report3 = InternalCallMetricRecorder.createMetricReport( - 0.86, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>()); + 0.86, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>()); double weight1 = 22 / (0.12 + 19.7 / 22F * weightedConfig.errorUtilizationPenalty); double weight2 = 40 / (0.28 + 0.998 / 40F * weightedConfig.errorUtilizationPenalty); double weight3 = 100 / (0.86 + 3.14159 / 100F * weightedConfig.errorUtilizationPenalty); @@ -425,11 +453,11 @@ public void pickByWeight_avgWeight_zeroCpuUtilization_withEps_customErrorUtiliza .setErrorUtilizationPenalty(1.75F).build(); MetricReport report1 = InternalCallMetricRecorder.createMetricReport( - 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>()); + 0, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>()); MetricReport report2 = InternalCallMetricRecorder.createMetricReport( - 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>()); + 0, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>()); MetricReport report3 = InternalCallMetricRecorder.createMetricReport( - 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>()); + 0, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>()); double avgSubchannelPickRatio = 1.0 / 3; pickByWeight(report1, report2, report3, avgSubchannelPickRatio, avgSubchannelPickRatio, @@ -480,10 +508,10 @@ public void blackoutPeriod() { WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); assertThat(fakeClock.forwardTime(5, TimeUnit.SECONDS)).isEqualTo(1); Map pickCount = new HashMap<>(); for (int i = 0; i < 1000; i++) { @@ -540,10 +568,10 @@ public void updateWeightTimer() { WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo(1); assertThat(weightedPicker.pickSubchannel(mockArgs) .getSubchannel()).isEqualTo(weightedSubchannel1); @@ -557,10 +585,10 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt assertThat(fakeClock.getPendingTasks().size()).isEqualTo(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); //timer fires, new weight updated assertThat(fakeClock.forwardTime(500, TimeUnit.MILLISECONDS)).isEqualTo(1); assertThat(weightedPicker.pickSubchannel(mockArgs) @@ -591,10 +619,10 @@ public void weightExpired() { WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1); Map pickCount = new HashMap<>(); for (int i = 0; i < 1000; i++) { @@ -655,7 +683,7 @@ public void rrFallback() { WrrSubchannel subchannel = (WrrSubchannel)pickResult.getSubchannel(); subchannel.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, qpsByChannel.get(subchannel), 0, + 0.1, 0, 0.1, qpsByChannel.get(subchannel), 0, new HashMap<>(), new HashMap<>())); } assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 1.0 / 2)) @@ -671,7 +699,7 @@ subchannel.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoad WrrSubchannel subchannel = (WrrSubchannel) pickResult.getSubchannel(); subchannel.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, qpsByChannel.get(subchannel), 0, + 0.1, 0, 0.1, qpsByChannel.get(subchannel), 0, new HashMap<>(), new HashMap<>())); fakeClock.forwardTime(50, TimeUnit.MILLISECONDS); } @@ -710,10 +738,10 @@ public void unknownWeightIsAvgWeight() { WrrSubchannel weightedSubchannel3 = (WrrSubchannel) weightedPicker.getList().get(2); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1); Map pickCount = new HashMap<>(); for (int i = 0; i < 1000; i++) { @@ -754,10 +782,10 @@ public void pickFromOtherThread() throws Exception { WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1); weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.1, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport( InternalCallMetricRecorder.createMetricReport( - 0.2, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); + 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>())); CyclicBarrier barrier = new CyclicBarrier(2); Map pickCount = new ConcurrentHashMap<>(); pickCount.put(weightedSubchannel1, new AtomicInteger(0)); diff --git a/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java b/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java index d8a1492f7cf..00562be3dc5 100644 --- a/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java +++ b/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java @@ -70,9 +70,10 @@ public class OrcaMetricReportingServerInterceptorTest { private static final SimpleRequest REQUEST = SimpleRequest.newBuilder().setRequestMessage("Simple request").build(); - private final Map applicationUtilizationMetrics = new HashMap<>(); + private final Map applicationUtilizationMetricsMap = new HashMap<>(); private final Map applicationCostMetrics = new HashMap<>(); private double cpuUtilizationMetrics = 0; + private double applicationUtilizationMetrics = 0; private double memoryUtilizationMetrics = 0; private double qpsMetrics = 0; private double epsMetrics = 0; @@ -89,7 +90,7 @@ public void setUp() throws Exception { @Override public void unaryRpc( SimpleRequest request, StreamObserver responseObserver) { - for (Map.Entry entry : applicationUtilizationMetrics.entrySet()) { + for (Map.Entry entry : applicationUtilizationMetricsMap.entrySet()) { CallMetricRecorder.getCurrent().recordUtilizationMetric(entry.getKey(), entry.getValue()); } @@ -98,6 +99,8 @@ public void unaryRpc( entry.getValue()); } CallMetricRecorder.getCurrent().recordCpuUtilizationMetric(cpuUtilizationMetrics); + CallMetricRecorder.getCurrent() + .recordApplicationUtilizationMetric(applicationUtilizationMetrics); CallMetricRecorder.getCurrent().recordMemoryUtilizationMetric(memoryUtilizationMetrics); CallMetricRecorder.getCurrent().recordQpsMetric(qpsMetrics); CallMetricRecorder.getCurrent().recordEpsMetric(epsMetrics); @@ -190,10 +193,11 @@ public void responseTrailersContainAllReportedMetricsFromCallMetricRecorder() { applicationCostMetrics.put("cost1", 1231.4543); applicationCostMetrics.put("cost2", 0.1367); applicationCostMetrics.put("cost3", 7614.145); - applicationUtilizationMetrics.put("util1", 0.1082); - applicationUtilizationMetrics.put("util2", 0.4936); - applicationUtilizationMetrics.put("util3", 0.5342); + applicationUtilizationMetricsMap.put("util1", 0.1082); + applicationUtilizationMetricsMap.put("util2", 0.4936); + applicationUtilizationMetricsMap.put("util3", 0.5342); cpuUtilizationMetrics = 0.3465; + applicationUtilizationMetrics = 0.99887; memoryUtilizationMetrics = 0.764; qpsMetrics = 3.1415926535; epsMetrics = 1.618; @@ -206,6 +210,7 @@ public void responseTrailersContainAllReportedMetricsFromCallMetricRecorder() { assertThat(report.getRequestCostMap()) .containsExactly("cost1", 1231.4543, "cost2", 0.1367, "cost3", 7614.145); assertThat(report.getCpuUtilization()).isEqualTo(0.3465); + assertThat(report.getApplicationUtilization()).isEqualTo(0.99887); assertThat(report.getMemUtilization()).isEqualTo(0.764); assertThat(report.getRpsFractional()).isEqualTo(3.1415926535); assertThat(report.getEps()).isEqualTo(1.618); @@ -213,11 +218,12 @@ public void responseTrailersContainAllReportedMetricsFromCallMetricRecorder() { @Test public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricRecorder() { - applicationUtilizationMetrics.put("util1", 0.1482); - applicationUtilizationMetrics.put("util2", 0.4036); - applicationUtilizationMetrics.put("util3", 0.5742); + applicationUtilizationMetricsMap.put("util1", 0.1482); + applicationUtilizationMetricsMap.put("util2", 0.4036); + applicationUtilizationMetricsMap.put("util3", 0.5742); cpuUtilizationMetrics = 0.3465; memoryUtilizationMetrics = 0.967; + metricRecorder.setApplicationUtilizationMetric(2.718); metricRecorder.setMemoryUtilizationMetric(0.764); metricRecorder.setQpsMetric(1.618); metricRecorder.setEpsMetric(3.14159); @@ -236,6 +242,7 @@ public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricR "serverUtil2", 0.2233); assertThat(report.getRequestCostMap()).isEmpty(); assertThat(report.getCpuUtilization()).isEqualTo(0.3465); + assertThat(report.getApplicationUtilization()).isEqualTo(2.718); assertThat(report.getMemUtilization()).isEqualTo(0.967); assertThat(report.getRpsFractional()).isEqualTo(1.618); assertThat(report.getEps()).isEqualTo(3.14159); @@ -243,9 +250,11 @@ public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricR @Test public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricRecorderNoMap() { + applicationUtilizationMetrics = 1.414; qpsMetrics = 5142.77; epsMetrics = 2233.88; metricRecorder.setCpuUtilizationMetric(0.314159); + metricRecorder.setApplicationUtilizationMetric(2.718); metricRecorder.setMemoryUtilizationMetric(0.764); metricRecorder.setQpsMetric(1.618); metricRecorder.setEpsMetric(3.14159); @@ -258,6 +267,7 @@ public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricR assertThat(report.getUtilizationMap()).isEmpty(); assertThat(report.getRequestCostMap()).isEmpty(); assertThat(report.getCpuUtilization()).isEqualTo(0.314159); + assertThat(report.getApplicationUtilization()).isEqualTo(1.414); assertThat(report.getMemUtilization()).isEqualTo(0.764); assertThat(report.getRpsFractional()).isEqualTo(5142.77); assertThat(report.getEps()).isEqualTo(2233.88); diff --git a/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java b/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java index 91f0e1f493a..ef06e6fccfe 100644 --- a/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java +++ b/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java @@ -119,6 +119,7 @@ public boolean matches(MetricReport argument) { static boolean reportEqual(MetricReport a, MetricReport b) { return a.getCpuUtilization() == b.getCpuUtilization() + && a.getApplicationUtilization() == b.getApplicationUtilization() && a.getMemoryUtilization() == b.getMemoryUtilization() && a.getQps() == b.getQps() && a.getEps() == b.getEps() diff --git a/xds/src/test/java/io/grpc/xds/orca/OrcaServiceImplTest.java b/xds/src/test/java/io/grpc/xds/orca/OrcaServiceImplTest.java index 72c518eb5b2..be3cec548a7 100644 --- a/xds/src/test/java/io/grpc/xds/orca/OrcaServiceImplTest.java +++ b/xds/src/test/java/io/grpc/xds/orca/OrcaServiceImplTest.java @@ -143,6 +143,7 @@ public void testRequestIntervalLess() { ClientCall call = channel.newCall( OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT); defaultTestService.putUtilizationMetric("buffer", 0.2); + defaultTestService.setApplicationUtilizationMetric(0.314159); defaultTestService.setQpsMetric(1.9); defaultTestService.setEpsMetric(0.2233); call.start(listener, new Metadata()); @@ -151,10 +152,11 @@ public void testRequestIntervalLess() { call.halfClose(); call.request(1); OrcaLoadReport expect = OrcaLoadReport.newBuilder().putUtilization("buffer", 0.2) - .setRpsFractional(1.9).setEps(0.2233).build(); + .setApplicationUtilization(0.314159).setRpsFractional(1.9).setEps(0.2233).build(); verify(listener).onMessage(eq(expect)); reset(listener); defaultTestService.removeUtilizationMetric("buffer0"); + defaultTestService.clearApplicationUtilizationMetric(); defaultTestService.clearQpsMetric(); defaultTestService.clearEpsMetric(); assertThat(fakeClock.forwardTime(500, TimeUnit.NANOSECONDS)).isEqualTo(0); @@ -248,6 +250,7 @@ public void testApis() throws Exception { ImmutableMap firstUtilization = ImmutableMap.of("util", 0.1); OrcaLoadReport goldenReport = OrcaLoadReport.newBuilder() .setCpuUtilization(random.nextDouble() * 10) + .setApplicationUtilization(random.nextDouble() * 10) .setMemUtilization(random.nextDouble()) .putAllUtilization(firstUtilization) .putUtilization("queue", 1.0) @@ -255,6 +258,7 @@ public void testApis() throws Exception { .setEps(1.618) .build(); defaultTestService.setCpuUtilizationMetric(goldenReport.getCpuUtilization()); + defaultTestService.setApplicationUtilizationMetric(goldenReport.getApplicationUtilization()); defaultTestService.setMemoryUtilizationMetric(goldenReport.getMemUtilization()); defaultTestService.setAllUtilizationMetrics(firstUtilization); defaultTestService.putUtilizationMetric("queue", 1.0); @@ -265,6 +269,7 @@ public void testApis() throws Exception { assertThat(reports.next()).isEqualTo(goldenReport); defaultTestService.clearCpuUtilizationMetric(); + defaultTestService.clearApplicationUtilizationMetric(); defaultTestService.clearMemoryUtilizationMetric(); defaultTestService.clearQpsMetric(); defaultTestService.clearEpsMetric(); @@ -281,6 +286,7 @@ public void testApis() throws Exception { assertThat(reports.next()).isEqualTo(goldenReport); defaultTestService.setCpuUtilizationMetric(-0.001); + defaultTestService.setApplicationUtilizationMetric(-0.001); defaultTestService.setMemoryUtilizationMetric(-0.001); defaultTestService.setMemoryUtilizationMetric(1.001); defaultTestService.setQpsMetric(-0.001); diff --git a/xds/third_party/xds/import.sh b/xds/third_party/xds/import.sh index d11e44161c2..f759cb0d35f 100755 --- a/xds/third_party/xds/import.sh +++ b/xds/third_party/xds/import.sh @@ -18,7 +18,7 @@ set -e BRANCH=main # import VERSION from one of the google internal CLs -VERSION=32f1caf87195bf3390061c29f18987e51ca56a88 +VERSION=e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7 GIT_REPO="https://github.com/cncf/xds.git" GIT_BASE_DIR=xds SOURCE_PROTO_BASE_DIR=xds diff --git a/xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto b/xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto index 4996164a325..53da75f78ac 100644 --- a/xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto +++ b/xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto @@ -14,8 +14,10 @@ import "validate/validate.proto"; message OrcaLoadReport { // CPU utilization expressed as a fraction of available CPU resources. This - // should be derived from the latest sample or measurement. - double cpu_utilization = 1 [(validate.rules).double.gte = 0, (validate.rules).double.lte = 1]; + // should be derived from the latest sample or measurement. The value may be + // larger than 1.0 when the usage exceeds the reporter dependent notion of + // soft limits. + double cpu_utilization = 1 [(validate.rules).double.gte = 0]; // Memory utilization expressed as a fraction of available memory // resources. This should be derived from the latest sample or measurement. @@ -45,4 +47,12 @@ message OrcaLoadReport { // Application specific opaque metrics. map named_metrics = 8; + + // Application specific utilization expressed as a fraction of available + // resources. For example, an application may report the max of CPU and memory + // utilization for better load balancing if it is both CPU and memory bound. + // This should be derived from the latest sample or measurement. + // The value may be larger than 1.0 when the usage exceeds the reporter + // dependent notion of soft limits. + double application_utilization = 9 [(validate.rules).double.gte = 0]; }