• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #18665

pending completion
#18665

push

github-actions

ejona86
services, xds, orca: use application_utilization and fallback to cpu_utilization if unset in WRR (#10256)

Implements updates to [A51][] and [A58][].

Imported cncf/xds using import.sh script.

A51: https://github.com/grpc/proposal/pull/374
A58: https://github.com/grpc/proposal/pull/373

30952 of 35074 relevant lines covered (88.25%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.72
/../services/src/main/java/io/grpc/services/CallMetricRecorder.java
1
/*
2
 * Copyright 2019 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
import com.google.common.annotations.VisibleForTesting;
20
import com.google.errorprone.annotations.InlineMe;
21
import io.grpc.Context;
22
import io.grpc.ExperimentalApi;
23
import java.util.Collections;
24
import java.util.Map;
25
import java.util.concurrent.ConcurrentHashMap;
26
import java.util.concurrent.atomic.AtomicReference;
27
import javax.annotation.concurrent.ThreadSafe;
28

29
/**
30
 * Utility to record call metrics for load-balancing. One instance per call.
31
 */
32
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6012")
33
@ThreadSafe
34
public final class CallMetricRecorder {
1✔
35
  private static final CallMetricRecorder NOOP = new CallMetricRecorder().disable();
1✔
36

37
  static final Context.Key<CallMetricRecorder> CONTEXT_KEY =
1✔
38
      Context.key("io.grpc.services.CallMetricRecorder");
1✔
39

40
  private final AtomicReference<ConcurrentHashMap<String, Double>> utilizationMetrics =
1✔
41
      new AtomicReference<>();
42
  private final AtomicReference<ConcurrentHashMap<String, Double>> requestCostMetrics =
1✔
43
      new AtomicReference<>();
44
  private double cpuUtilizationMetric = 0;
1✔
45
  private double applicationUtilizationMetric = 0;
1✔
46
  private double memoryUtilizationMetric = 0;
1✔
47
  private double qps = 0;
1✔
48
  private double eps = 0;
1✔
49
  private volatile boolean disabled;
50

51
  /**
52
   * Returns the call metric recorder attached to the current {@link Context}.  If there is none,
53
   * returns a no-op recorder.
54
   *
55
   * <p><strong>IMPORTANT:</strong>It returns the recorder specifically for the current RPC call.
56
   * <b>DO NOT</b> save the returned object or share it between different RPC calls.
57
   *
58
   * <p><strong>IMPORTANT:</strong>It must be called under the {@link Context} under which the RPC
59
   * handler was called.  If it is called from a different thread, the Context must be propagated to
60
   * the same thread, e.g., with {@link Context#wrap(Runnable)}.
61
   *
62
   * @since 1.23.0
63
   */
64
  public static CallMetricRecorder getCurrent() {
65
    CallMetricRecorder recorder = CONTEXT_KEY.get();
1✔
66
    return recorder != null ? recorder : NOOP;
1✔
67
  }
68

69
  /**
70
   * Records a call metric measurement for utilization in the range [0, 1]. Values outside the valid
71
   * range are ignored. If RPC has already finished, this method is no-op.
72
   *
73
   * <p>A latter record will overwrite its former name-sakes.
74
   *
75
   * @return this recorder object
76
   * @since 1.23.0
77
   */
78
  public CallMetricRecorder recordUtilizationMetric(String name, double value) {
79
    if (disabled || !MetricRecorderHelper.isUtilizationValid(value)) {
1✔
80
      return this;
1✔
81
    }
82
    if (utilizationMetrics.get() == null) {
1✔
83
      // The chance of race of creation of the map should be very small, so it should be fine
84
      // to create these maps that might be discarded.
85
      utilizationMetrics.compareAndSet(null, new ConcurrentHashMap<String, Double>());
1✔
86
    }
87
    utilizationMetrics.get().put(name, value);
1✔
88
    return this;
1✔
89
  }
90

91
  /**
92
   * Records a call metric measurement for request cost.
93
   * If RPC has already finished, this method is no-op.
94
   *
95
   * <p>A latter record will overwrite its former name-sakes.
96
   *
97
   * @return this recorder object
98
   * @since 1.47.0
99
   * @deprecated use {@link #recordRequestCostMetric} instead.
100
   *     This method will be removed in the future.
101
   */
102
  @Deprecated
103
  @InlineMe(replacement = "this.recordRequestCostMetric(name, value)")
104
  public CallMetricRecorder recordCallMetric(String name, double value) {
105
    return recordRequestCostMetric(name, value);
×
106
  }
107

108
  /**
109
   * Records a call metric measurement for request cost.
110
   * If RPC has already finished, this method is no-op.
111
   *
112
   * <p>A latter record will overwrite its former name-sakes.
113
   *
114
   * @return this recorder object
115
   * @since 1.48.1
116
   */
117
  public CallMetricRecorder recordRequestCostMetric(String name, double value) {
118
    if (disabled) {
1✔
119
      return this;
×
120
    }
121
    if (requestCostMetrics.get() == null) {
1✔
122
      // The chance of race of creation of the map should be very small, so it should be fine
123
      // to create these maps that might be discarded.
124
      requestCostMetrics.compareAndSet(null, new ConcurrentHashMap<String, Double>());
1✔
125
    }
126
    requestCostMetrics.get().put(name, value);
1✔
127
    return this;
1✔
128
  }
129

130
  /**
131
   * Records a call metric measurement for CPU utilization in the range [0, inf). Values outside the
132
   * valid range are ignored. If RPC has already finished, this method is no-op.
133
   *
134
   * <p>A latter record will overwrite its former name-sakes.
135
   *
136
   * @return this recorder object
137
   * @since 1.47.0
138
   */
139
  public CallMetricRecorder recordCpuUtilizationMetric(double value) {
140
    if (disabled || !MetricRecorderHelper.isCpuOrApplicationUtilizationValid(value)) {
1✔
141
      return this;
1✔
142
    }
143
    cpuUtilizationMetric = value;
1✔
144
    return this;
1✔
145
  }
146

147
  /**
148
   * Records a call metric measurement for application specific utilization in the range [0, inf).
149
   * Values outside the valid range are ignored. If RPC has already finished, this method is no-op.
150
   *
151
   * <p>A latter record will overwrite its former name-sakes.
152
   *
153
   * @return this recorder object
154
   */
155
  public CallMetricRecorder recordApplicationUtilizationMetric(double value) {
156
    if (disabled || !MetricRecorderHelper.isCpuOrApplicationUtilizationValid(value)) {
1✔
157
      return this;
1✔
158
    }
159
    applicationUtilizationMetric = value;
1✔
160
    return this;
1✔
161
  }
162

163
  /**
164
   * Records a call metric measurement for memory utilization in the range [0, 1]. Values outside
165
   * the valid range are ignored. If RPC has already finished, this method is no-op.
166
   *
167
   * <p>A latter record will overwrite its former name-sakes.
168
   *
169
   * @return this recorder object
170
   * @since 1.47.0
171
   */
172
  public CallMetricRecorder recordMemoryUtilizationMetric(double value) {
173
    if (disabled || !MetricRecorderHelper.isUtilizationValid(value)) {
1✔
174
      return this;
1✔
175
    }
176
    memoryUtilizationMetric = value;
1✔
177
    return this;
1✔
178
  }
179

180
  /**
181
   * Records a call metric measurement for queries per second (qps) in the range [0, inf). Values
182
   * outside the valid range are ignored. If RPC has already finished, this method is no-op.
183
   *
184
   * <p>A latter record will overwrite its former name-sakes.
185
   *
186
   * @return this recorder object
187
   * @since 1.54.0
188
   */
189
  public CallMetricRecorder recordQpsMetric(double value) {
190
    if (disabled || !MetricRecorderHelper.isRateValid(value)) {
1✔
191
      return this;
1✔
192
    }
193
    qps = value;
1✔
194
    return this;
1✔
195
  }
196

197
  /**
198
   * Records a call metric measurement for errors per second (eps) in the range [0, inf). Values
199
   * outside the valid range are ignored. If RPC has already finished, this method is no-op.
200
   *
201
   * <p>A latter record will overwrite its former name-sakes.
202
   *
203
   * @return this recorder object
204
   */
205
  public CallMetricRecorder recordEpsMetric(double value) {
206
    if (disabled || !MetricRecorderHelper.isRateValid(value)) {
1✔
207
      return this;
1✔
208
    }
209
    eps = value;
1✔
210
    return this;
1✔
211
  }
212

213
  /**
214
   * Returns all request cost metric values. No more metric values will be recorded after this
215
   * method is called. Calling this method multiple times returns the same collection of metric
216
   * values.
217
   *
218
   * @return a map containing all saved metric name-value pairs.
219
   */
220
  Map<String, Double> finalizeAndDump() {
221
    disabled = true;
1✔
222
    Map<String, Double> savedMetrics = requestCostMetrics.get();
1✔
223
    if (savedMetrics == null) {
1✔
224
      return Collections.emptyMap();
1✔
225
    }
226
    return Collections.unmodifiableMap(savedMetrics);
1✔
227
  }
228

229
  /**
230
   * Returns all save metric values. No more metric values will be recorded after this method is
231
   * called. Calling this method multiple times returns the same collection of metric values.
232
   *
233
   * @return a per-request ORCA reports containing all saved metrics.
234
   */
235
  MetricReport finalizeAndDump2() {
236
    Map<String, Double> savedRequestCostMetrics = finalizeAndDump();
1✔
237
    Map<String, Double> savedUtilizationMetrics = utilizationMetrics.get();
1✔
238
    if (savedUtilizationMetrics == null) {
1✔
239
      savedUtilizationMetrics = Collections.emptyMap();
1✔
240
    }
241
    return new MetricReport(cpuUtilizationMetric, applicationUtilizationMetric,
1✔
242
        memoryUtilizationMetric, qps, eps, Collections.unmodifiableMap(savedRequestCostMetrics),
1✔
243
        Collections.unmodifiableMap(savedUtilizationMetrics)
1✔
244
    );
245
  }
246

247
  @VisibleForTesting
248
  boolean isDisabled() {
249
    return disabled;
1✔
250
  }
251

252
  /**
253
   * Turn this recorder into a no-op one.
254
   */
255
  private CallMetricRecorder disable() {
256
    disabled = true;
1✔
257
    return this;
1✔
258
  }
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc