Skip to content

Commit 755a50e

Browse files
add range validation & merge metrics from MetricRecorder and CallMetricRecorder in per-call reporting
1 parent 3d4d46d commit 755a50e

File tree

4 files changed

+135
-31
lines changed

4 files changed

+135
-31
lines changed

services/src/main/java/io/grpc/services/CallMetricRecorder.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6012")
3333
@ThreadSafe
3434
public final class CallMetricRecorder {
35+
3536
private static final CallMetricRecorder NOOP = new CallMetricRecorder().disable();
3637

3738
static final Context.Key<CallMetricRecorder> CONTEXT_KEY =
@@ -65,16 +66,16 @@ public static CallMetricRecorder getCurrent() {
6566
}
6667

6768
/**
68-
* Records a call metric measurement for utilization.
69-
* If RPC has already finished, this method is no-op.
69+
* Records a call metric measurement for utilization in the range [0, 1]. Values outside the valid
70+
* range are rejected. If RPC has already finished, this method is no-op.
7071
*
7172
* <p>A latter record will overwrite its former name-sakes.
7273
*
7374
* @return this recorder object
7475
* @since 1.23.0
7576
*/
7677
public CallMetricRecorder recordUtilizationMetric(String name, double value) {
77-
if (disabled) {
78+
if (disabled || !MetricRecorderHelper.isUtilizationValid(value)) {
7879
return this;
7980
}
8081
if (utilizationMetrics.get() == null) {
@@ -87,15 +88,15 @@ public CallMetricRecorder recordUtilizationMetric(String name, double value) {
8788
}
8889

8990
/**
90-
* Records a call metric measurement for request cost.
91-
* If RPC has already finished, this method is no-op.
91+
* Records a call metric measurement for request cost. If RPC has already finished, this method is
92+
* no-op.
9293
*
9394
* <p>A latter record will overwrite its former name-sakes.
9495
*
9596
* @return this recorder object
9697
* @since 1.47.0
97-
* @deprecated use {@link #recordRequestCostMetric} instead.
98-
* This method will be removed in the future.
98+
* @deprecated use {@link #recordRequestCostMetric} instead. This method will be removed in the
99+
* future.
99100
*/
100101
@Deprecated
101102
@InlineMe(replacement = "this.recordRequestCostMetric(name, value)")
@@ -104,8 +105,8 @@ public CallMetricRecorder recordCallMetric(String name, double value) {
104105
}
105106

106107
/**
107-
* Records a call metric measurement for request cost.
108-
* If RPC has already finished, this method is no-op.
108+
* Records a call metric measurement for request cost. If RPC has already finished, this method is
109+
* no-op.
109110
*
110111
* <p>A latter record will overwrite its former name-sakes.
111112
*
@@ -126,57 +127,56 @@ public CallMetricRecorder recordRequestCostMetric(String name, double value) {
126127
}
127128

128129
/**
129-
* Records a call metric measurement for CPU utilization.
130-
* If RPC has already finished, this method is no-op.
130+
* Records a call metric measurement for CPU utilization in the range [0, 1]. Values outside the
131+
* valid range are rejected. If RPC has already finished, this method is no-op.
131132
*
132133
* <p>A latter record will overwrite its former name-sakes.
133134
*
134135
* @return this recorder object
135136
* @since 1.47.0
136137
*/
137138
public CallMetricRecorder recordCpuUtilizationMetric(double value) {
138-
if (disabled) {
139+
if (disabled || !MetricRecorderHelper.isUtilizationValid(value)) {
139140
return this;
140141
}
141142
cpuUtilizationMetric = value;
142143
return this;
143144
}
144145

145146
/**
146-
* Records a call metric measurement for memory utilization.
147-
* If RPC has already finished, this method is no-op.
147+
* Records a call metric measurement for memory utilization in the range [0, 1]. Values outside
148+
* the valid range are rejected. If RPC has already finished, this method is no-op.
148149
*
149150
* <p>A latter record will overwrite its former name-sakes.
150151
*
151152
* @return this recorder object
152153
* @since 1.47.0
153154
*/
154155
public CallMetricRecorder recordMemoryUtilizationMetric(double value) {
155-
if (disabled) {
156+
if (disabled || !MetricRecorderHelper.isUtilizationValid(value)) {
156157
return this;
157158
}
158159
memoryUtilizationMetric = value;
159160
return this;
160161
}
161162

162163
/**
163-
* Records a call metric measurement for qps.
164-
* If RPC has already finished, this method is no-op.
164+
* Records a call metric measurement for qps in the range [0, inf). Values outside the valid range
165+
* are rejected. If RPC has already finished, this method is no-op.
165166
*
166167
* <p>A latter record will overwrite its former name-sakes.
167168
*
168169
* @return this recorder object
169170
* @since 1.54.0
170171
*/
171172
public CallMetricRecorder recordQpsMetric(double value) {
172-
if (disabled) {
173+
if (disabled || !MetricRecorderHelper.isQpsValid(value)) {
173174
return this;
174175
}
175176
qps = value;
176177
return this;
177178
}
178179

179-
180180
/**
181181
* Returns all request cost metric values. No more metric values will be recorded after this
182182
* method is called. Calling this method multiple times returns the same collection of metric

services/src/main/java/io/grpc/services/MetricRecorder.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
import java.util.concurrent.ConcurrentHashMap;
2323

2424
/**
25-
* Implements the service/APIs for Out-of-Band metrics reporting, only for utilization metrics.
26-
* A user should use the public set-APIs to update the server machine's utilization metrics data.
25+
* Implements the service/APIs for Out-of-Band metrics reporting, only for utilization metrics. A
26+
* user should use the public set-APIs to update the server machine's utilization metrics data.
2727
*/
2828
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9006")
2929
public final class MetricRecorder {
30+
3031
private volatile ConcurrentHashMap<String, Double> metricsData = new ConcurrentHashMap<>();
3132
private volatile double cpuUtilization;
3233
private volatile double memoryUtilization;
@@ -36,17 +37,22 @@ public static MetricRecorder newInstance() {
3637
return new MetricRecorder();
3738
}
3839

39-
private MetricRecorder() {}
40+
private MetricRecorder() {
41+
}
4042

4143
/**
42-
* Update the metrics value corresponding to the specified key.
44+
* Update the metrics value in the range [0, 1] corresponding to the specified key. Values outside
45+
* the valid range are rejected.
4346
*/
4447
public void putUtilizationMetric(String key, double value) {
48+
if (!MetricRecorderHelper.isUtilizationValid(value)) {
49+
return;
50+
}
4551
metricsData.put(key, value);
4652
}
4753

4854
/**
49-
* Replace the whole metrics data using the specified map.
55+
* Replace the whole metrics data using the specified map. No range validation.
5056
*/
5157
public void setAllUtilizationMetrics(Map<String, Double> metrics) {
5258
metricsData = new ConcurrentHashMap<>(metrics);
@@ -60,9 +66,13 @@ public void removeUtilizationMetric(String key) {
6066
}
6167

6268
/**
63-
* Update the CPU utilization metrics data.
69+
* Update the CPU utilization metrics data in the range [0, 1]. Values outside the valid range are
70+
* rejected.
6471
*/
6572
public void setCpuUtilizationMetric(double value) {
73+
if (!MetricRecorderHelper.isUtilizationValid(value)) {
74+
return;
75+
}
6676
cpuUtilization = value;
6777
}
6878

@@ -74,9 +84,13 @@ public void clearCpuUtilizationMetric() {
7484
}
7585

7686
/**
77-
* Update the memory utilization metrics data.
87+
* Update the memory utilization metrics data in the range [0, 1]. Values outside the valid range
88+
* are rejected.
7889
*/
7990
public void setMemoryUtilizationMetric(double value) {
91+
if (!MetricRecorderHelper.isUtilizationValid(value)) {
92+
return;
93+
}
8094
memoryUtilization = value;
8195
}
8296

@@ -88,9 +102,13 @@ public void clearMemoryUtilizationMetric() {
88102
}
89103

90104
/**
91-
* Update the QPS metrics data.
105+
* Update the QPS metrics data in the range [0, inf). Values outside the valid range are
106+
* rejected.
92107
*/
93108
public void setQps(double value) {
109+
if (!MetricRecorderHelper.isQpsValid(value)) {
110+
return;
111+
}
94112
qps = value;
95113
}
96114

@@ -102,7 +120,7 @@ public void clearQps() {
102120
}
103121

104122
MetricReport getMetricReport() {
105-
return new MetricReport(cpuUtilization, memoryUtilization, qps,
106-
Collections.emptyMap(), Collections.unmodifiableMap(metricsData));
123+
return new MetricReport(cpuUtilization, memoryUtilization, qps, Collections.emptyMap(),
124+
Collections.unmodifiableMap(metricsData));
107125
}
108126
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2023 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.services;
18+
19+
/**
20+
* Utility helper class to check whether values for {@link CallMetricRecorder} and
21+
* {@link MetricRecorder} are inside the valid range.
22+
*/
23+
final class MetricRecorderHelper {
24+
25+
/**
26+
* Return true if the utilization value is in the range [0, 1] and false otherwise.
27+
*/
28+
static boolean isUtilizationValid(double utilization) {
29+
return utilization >= 0.0 && utilization <= 1.0;
30+
}
31+
32+
/**
33+
* Return true if the qps value is in the range [0, inf) and false otherwise.
34+
*/
35+
static boolean isQpsValid(double qps) {
36+
return qps >= 0.0;
37+
}
38+
39+
// Prevent instantiation.
40+
private MetricRecorderHelper() {
41+
}
42+
}

xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import io.grpc.protobuf.ProtoUtils;
3232
import io.grpc.services.CallMetricRecorder;
3333
import io.grpc.services.InternalCallMetricRecorder;
34+
import io.grpc.services.InternalMetricRecorder;
35+
import io.grpc.services.MetricRecorder;
3436
import io.grpc.services.MetricReport;
3537

3638
/**
3739
* A {@link ServerInterceptor} that intercepts a {@link ServerCall} by running server-side RPC
3840
* handling under a {@link Context} that records custom per-request metrics provided by server
39-
* applications and sends to client side along with the response in the format of Open Request
40-
* Cost Aggregation (ORCA).
41+
* applications and sends to client side along with the response in the format of Open Request Cost
42+
* Aggregation (ORCA).
4143
*
4244
* @since 1.23.0
4345
*/
@@ -53,6 +55,8 @@ public final class OrcaMetricReportingServerInterceptor implements ServerInterce
5355
"endpoint-load-metrics-bin",
5456
ProtoUtils.metadataMarshaller(OrcaLoadReport.getDefaultInstance()));
5557

58+
private static MetricRecorder metricRecorder;
59+
5660
@VisibleForTesting
5761
OrcaMetricReportingServerInterceptor() {
5862
}
@@ -61,6 +65,10 @@ public static OrcaMetricReportingServerInterceptor getInstance() {
6165
return INSTANCE;
6266
}
6367

68+
public static void setMetricRecorder(MetricRecorder mr) {
69+
metricRecorder = mr;
70+
}
71+
6472
@Override
6573
public <ReqT, RespT> Listener<ReqT> interceptCall(
6674
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
@@ -77,6 +85,10 @@ public <ReqT, RespT> Listener<ReqT> interceptCall(
7785
public void close(Status status, Metadata trailers) {
7886
OrcaLoadReport report = fromInternalReport(
7987
InternalCallMetricRecorder.finalizeAndDump2(finalCallMetricRecorder));
88+
if (metricRecorder != null) {
89+
report = mergeMetrics(report,
90+
fromInternalReport(InternalMetricRecorder.getMetricReport(metricRecorder)));
91+
}
8092
if (!report.equals(OrcaLoadReport.getDefaultInstance())) {
8193
trailers.put(ORCA_ENDPOINT_LOAD_METRICS_KEY, report);
8294
}
@@ -94,8 +106,40 @@ private static OrcaLoadReport fromInternalReport(MetricReport internalReport) {
94106
return OrcaLoadReport.newBuilder()
95107
.setCpuUtilization(internalReport.getCpuUtilization())
96108
.setMemUtilization(internalReport.getMemoryUtilization())
109+
.setRpsFractional(internalReport.getQps())
97110
.putAllUtilization(internalReport.getUtilizationMetrics())
98111
.putAllRequestCost(internalReport.getRequestCostMetrics())
99112
.build();
100113
}
114+
115+
/**
116+
* Return a merged {@link OrcaLoadReport} where the metrics from {@link CallMetricRecorder} takes
117+
* a higher precedence compared to {@link MetricRecorder}.
118+
*/
119+
private static OrcaLoadReport mergeMetrics(OrcaLoadReport callMetricRecorderReport,
120+
OrcaLoadReport metricRecorderReport) {
121+
// Merge metrics from the MetricRecorder first since metrics from the CallMetricRecorder takes a
122+
// higher precedence.
123+
OrcaLoadReport.Builder builder = metricRecorderReport.toBuilder()
124+
.putAllUtilization(callMetricRecorderReport.getUtilizationMap())
125+
.putAllRequestCost(callMetricRecorderReport.getRequestCostMap());
126+
// Overwrite only if the values from CallMetricRecorder are set
127+
double cpu = callMetricRecorderReport.getCpuUtilization();
128+
if (isReportValueSet(cpu)) {
129+
builder.setCpuUtilization(cpu);
130+
}
131+
double mem = callMetricRecorderReport.getMemUtilization();
132+
if (isReportValueSet(mem)) {
133+
builder.setMemUtilization(mem);
134+
}
135+
double rps = callMetricRecorderReport.getRpsFractional();
136+
if (isReportValueSet(rps)) {
137+
builder.setRpsFractional(rps);
138+
}
139+
return builder.build();
140+
}
141+
142+
private static boolean isReportValueSet(double value) {
143+
return value != 0;
144+
}
101145
}

0 commit comments

Comments
 (0)