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

grpc / grpc-java / #19996

24 Sep 2025 12:08AM UTC coverage: 88.575% (+0.03%) from 88.543%
#19996

push

github

web-flow
Implement otel retry metrics (#12064)

implements [A96](https://github.com/grpc/proposal/pull/488/files)

34731 of 39211 relevant lines covered (88.57%)

0.89 hits per line

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

95.4
/../opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java
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.opentelemetry;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.internal.GrpcUtil.IMPLEMENTATION_VERSION;
21
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.HEDGE_BUCKETS;
22
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.LATENCY_BUCKETS;
23
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.RETRY_BUCKETS;
24
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.SIZE_BUCKETS;
25
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.TRANSPARENT_RETRY_BUCKETS;
26

27
import com.google.common.annotations.VisibleForTesting;
28
import com.google.common.base.Stopwatch;
29
import com.google.common.base.Supplier;
30
import com.google.common.collect.ImmutableList;
31
import com.google.common.collect.ImmutableMap;
32
import io.grpc.ExperimentalApi;
33
import io.grpc.InternalConfigurator;
34
import io.grpc.InternalConfiguratorRegistry;
35
import io.grpc.InternalManagedChannelBuilder;
36
import io.grpc.ManagedChannelBuilder;
37
import io.grpc.MetricSink;
38
import io.grpc.ServerBuilder;
39
import io.grpc.internal.GrpcUtil;
40
import io.grpc.opentelemetry.internal.OpenTelemetryConstants;
41
import io.opentelemetry.api.OpenTelemetry;
42
import io.opentelemetry.api.metrics.Meter;
43
import io.opentelemetry.api.metrics.MeterProvider;
44
import io.opentelemetry.api.trace.Tracer;
45
import java.util.ArrayList;
46
import java.util.Collection;
47
import java.util.Collections;
48
import java.util.HashMap;
49
import java.util.List;
50
import java.util.Map;
51

52
/**
53
 *  The entrypoint for OpenTelemetry metrics functionality in gRPC.
54
 *
55
 *  <p>GrpcOpenTelemetry uses {@link io.opentelemetry.api.OpenTelemetry} APIs for instrumentation.
56
 *  When no SDK is explicitly added no telemetry data will be collected. See
57
 *  {@code io.opentelemetry.sdk.OpenTelemetrySdk} for information on how to construct the SDK.
58
 *
59
 */
60
public final class GrpcOpenTelemetry {
61

62
  private static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
1✔
63
    @Override
64
    public Stopwatch get() {
65
      return Stopwatch.createUnstarted();
1✔
66
    }
67
  };
68

69
  @VisibleForTesting
70
  static boolean ENABLE_OTEL_TRACING =
1✔
71
      GrpcUtil.getFlag("GRPC_EXPERIMENTAL_ENABLE_OTEL_TRACING", false);
1✔
72

73
  private final OpenTelemetry openTelemetrySdk;
74
  private final MeterProvider meterProvider;
75
  private final Meter meter;
76
  private final Map<String, Boolean> enableMetrics;
77
  private final boolean disableDefault;
78
  private final OpenTelemetryMetricsResource resource;
79
  private final OpenTelemetryMetricsModule openTelemetryMetricsModule;
80
  private final OpenTelemetryTracingModule openTelemetryTracingModule;
81
  private final List<String> optionalLabels;
82
  private final MetricSink sink;
83

84
  public static Builder newBuilder() {
85
    return new Builder();
1✔
86
  }
87

88
  private GrpcOpenTelemetry(Builder builder) {
1✔
89
    this.openTelemetrySdk = checkNotNull(builder.openTelemetrySdk, "openTelemetrySdk");
1✔
90
    this.meterProvider = checkNotNull(openTelemetrySdk.getMeterProvider(), "meterProvider");
1✔
91
    this.meter = this.meterProvider
1✔
92
        .meterBuilder(OpenTelemetryConstants.INSTRUMENTATION_SCOPE)
1✔
93
        .setInstrumentationVersion(IMPLEMENTATION_VERSION)
1✔
94
        .build();
1✔
95
    this.enableMetrics = ImmutableMap.copyOf(builder.enableMetrics);
1✔
96
    this.disableDefault = builder.disableAll;
1✔
97
    this.resource = createMetricInstruments(meter, enableMetrics, disableDefault);
1✔
98
    this.optionalLabels = ImmutableList.copyOf(builder.optionalLabels);
1✔
99
    this.openTelemetryMetricsModule = new OpenTelemetryMetricsModule(
1✔
100
        STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins);
1✔
101
    this.openTelemetryTracingModule = new OpenTelemetryTracingModule(openTelemetrySdk);
1✔
102
    this.sink = new OpenTelemetryMetricSink(meter, enableMetrics, disableDefault, optionalLabels);
1✔
103
  }
1✔
104

105
  @VisibleForTesting
106
  OpenTelemetry getOpenTelemetryInstance() {
107
    return this.openTelemetrySdk;
1✔
108
  }
109

110
  @VisibleForTesting
111
  MeterProvider getMeterProvider() {
112
    return this.meterProvider;
1✔
113
  }
114

115
  @VisibleForTesting
116
  Meter getMeter() {
117
    return this.meter;
1✔
118
  }
119

120
  @VisibleForTesting
121
  OpenTelemetryMetricsResource getResource() {
122
    return this.resource;
×
123
  }
124

125
  @VisibleForTesting
126
  Map<String, Boolean> getEnableMetrics() {
127
    return this.enableMetrics;
1✔
128
  }
129

130
  @VisibleForTesting
131
  List<String> getOptionalLabels() {
132
    return optionalLabels;
1✔
133
  }
134

135
  MetricSink getSink() {
136
    return sink;
1✔
137
  }
138

139
  @VisibleForTesting
140
  Tracer getTracer() {
141
    return this.openTelemetryTracingModule.getTracer();
1✔
142
  }
143

144
  /**
145
   * Registers GrpcOpenTelemetry globally, applying its configuration to all subsequently created
146
   * gRPC channels and servers.
147
   */
148
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10591")
149
  public void registerGlobal() {
150
    InternalConfiguratorRegistry.setConfigurators(Collections.singletonList(
×
151
        new InternalConfigurator() {
×
152
          @Override
153
          public void configureChannelBuilder(ManagedChannelBuilder<?> channelBuilder) {
154
            GrpcOpenTelemetry.this.configureChannelBuilder(channelBuilder);
×
155
          }
×
156

157
          @Override
158
          public void configureServerBuilder(ServerBuilder<?> serverBuilder) {
159
            GrpcOpenTelemetry.this.configureServerBuilder(serverBuilder);
×
160
          }
×
161
        }));
162
  }
×
163

164
  /**
165
   * Configures the given {@link ManagedChannelBuilder} with OpenTelemetry metrics instrumentation.
166
   */
167
  public void configureChannelBuilder(ManagedChannelBuilder<?> builder) {
168
    InternalManagedChannelBuilder.addMetricSink(builder, sink);
1✔
169
    InternalManagedChannelBuilder.interceptWithTarget(
1✔
170
        builder, openTelemetryMetricsModule::getClientInterceptor);
171
    if (ENABLE_OTEL_TRACING) {
1✔
172
      builder.intercept(openTelemetryTracingModule.getClientInterceptor());
1✔
173
    }
174
  }
1✔
175

176
  /**
177
   * Configures the given {@link ServerBuilder} with OpenTelemetry metrics instrumentation.
178
   *
179
   * @param serverBuilder the server builder to configure
180
   */
181
  public void configureServerBuilder(ServerBuilder<?> serverBuilder) {
182
    serverBuilder.addStreamTracerFactory(openTelemetryMetricsModule.getServerTracerFactory());
1✔
183
    if (ENABLE_OTEL_TRACING) {
1✔
184
      serverBuilder.addStreamTracerFactory(
1✔
185
          openTelemetryTracingModule.getServerTracerFactory());
1✔
186
      serverBuilder.intercept(openTelemetryTracingModule.getServerSpanPropagationInterceptor());
1✔
187
    }
188
  }
1✔
189

190
  @VisibleForTesting
191
  static OpenTelemetryMetricsResource createMetricInstruments(Meter meter,
192
      Map<String, Boolean> enableMetrics, boolean disableDefault) {
193
    OpenTelemetryMetricsResource.Builder builder = OpenTelemetryMetricsResource.builder();
1✔
194

195
    if (isMetricEnabled("grpc.client.call.duration", enableMetrics, disableDefault)) {
1✔
196
      builder.clientCallDurationCounter(
1✔
197
          meter.histogramBuilder("grpc.client.call.duration")
1✔
198
              .setUnit("s")
1✔
199
              .setDescription(
1✔
200
                  "Time taken by gRPC to complete an RPC from application's perspective")
201
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
202
              .build());
1✔
203
    }
204

205
    if (isMetricEnabled("grpc.client.attempt.started", enableMetrics, disableDefault)) {
1✔
206
      builder.clientAttemptCountCounter(
1✔
207
          meter.counterBuilder("grpc.client.attempt.started")
1✔
208
              .setUnit("{attempt}")
1✔
209
              .setDescription("Number of client call attempts started")
1✔
210
              .build());
1✔
211
    }
212

213
    if (isMetricEnabled("grpc.client.attempt.duration", enableMetrics, disableDefault)) {
1✔
214
      builder.clientAttemptDurationCounter(
1✔
215
          meter.histogramBuilder(
1✔
216
                  "grpc.client.attempt.duration")
217
              .setUnit("s")
1✔
218
              .setDescription("Time taken to complete a client call attempt")
1✔
219
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
220
              .build());
1✔
221
    }
222

223
    if (isMetricEnabled("grpc.client.attempt.sent_total_compressed_message_size", enableMetrics,
1✔
224
        disableDefault)) {
225
      builder.clientTotalSentCompressedMessageSizeCounter(
1✔
226
          meter.histogramBuilder(
1✔
227
                  "grpc.client.attempt.sent_total_compressed_message_size")
228
              .setUnit("By")
1✔
229
              .setDescription("Compressed message bytes sent per client call attempt")
1✔
230
              .ofLongs()
1✔
231
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
232
              .build());
1✔
233
    }
234

235
    if (isMetricEnabled("grpc.client.attempt.rcvd_total_compressed_message_size", enableMetrics,
1✔
236
        disableDefault)) {
237
      builder.clientTotalReceivedCompressedMessageSizeCounter(
1✔
238
          meter.histogramBuilder(
1✔
239
                  "grpc.client.attempt.rcvd_total_compressed_message_size")
240
              .setUnit("By")
1✔
241
              .setDescription("Compressed message bytes received per call attempt")
1✔
242
              .ofLongs()
1✔
243
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
244
              .build());
1✔
245
    }
246

247
    if (isMetricEnabled("grpc.client.call.retries", enableMetrics, disableDefault)) {
1✔
248
      builder.clientCallRetriesCounter(
1✔
249
          meter.histogramBuilder(
1✔
250
                  "grpc.client.call.retries")
251
              .setUnit("{retry}")
1✔
252
              .setDescription("Number of retries during the client call. "
1✔
253
                  + "If there were no retries, 0 is not reported.")
254
              .ofLongs()
1✔
255
              .setExplicitBucketBoundariesAdvice(RETRY_BUCKETS)
1✔
256
              .build());
1✔
257
    }
258

259
    if (isMetricEnabled("grpc.client.call.transparent_retries", enableMetrics,
1✔
260
        disableDefault)) {
261
      builder.clientCallTransparentRetriesCounter(
1✔
262
          meter.histogramBuilder(
1✔
263
                  "grpc.client.call.transparent_retries")
264
              .setUnit("{transparent_retry}")
1✔
265
              .setDescription("Number of transparent retries during the client call. "
1✔
266
                  + "If there were no transparent retries, 0 is not reported.")
267
              .ofLongs()
1✔
268
              .setExplicitBucketBoundariesAdvice(TRANSPARENT_RETRY_BUCKETS)
1✔
269
              .build());
1✔
270
    }
271

272
    if (isMetricEnabled("grpc.client.call.hedges", enableMetrics, disableDefault)) {
1✔
273
      builder.clientCallHedgesCounter(
1✔
274
          meter.histogramBuilder(
1✔
275
                  "grpc.client.call.hedges")
276
              .setUnit("{hedge}")
1✔
277
              .setDescription("Number of hedges during the client call. "
1✔
278
                  + "If there were no hedges, 0 is not reported.")
279
              .ofLongs()
1✔
280
              .setExplicitBucketBoundariesAdvice(HEDGE_BUCKETS)
1✔
281
              .build());
1✔
282
    }
283

284
    if (isMetricEnabled("grpc.client.call.retry_delay", enableMetrics, disableDefault)) {
1✔
285
      builder.clientCallRetryDelayCounter(
1✔
286
          meter.histogramBuilder(
1✔
287
                  "grpc.client.call.retry_delay")
288
              .setUnit("s")
1✔
289
              .setDescription("Total time of delay while there is no active attempt during the "
1✔
290
                  + "client call")
291
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
292
              .build());
1✔
293
    }
294

295
    if (isMetricEnabled("grpc.server.call.started", enableMetrics, disableDefault)) {
1✔
296
      builder.serverCallCountCounter(
1✔
297
          meter.counterBuilder("grpc.server.call.started")
1✔
298
              .setUnit("{call}")
1✔
299
              .setDescription("Number of server calls started")
1✔
300
              .build());
1✔
301
    }
302

303
    if (isMetricEnabled("grpc.server.call.duration", enableMetrics, disableDefault)) {
1✔
304
      builder.serverCallDurationCounter(
1✔
305
          meter.histogramBuilder("grpc.server.call.duration")
1✔
306
              .setUnit("s")
1✔
307
              .setDescription(
1✔
308
                  "Time taken to complete a call from server transport's perspective")
309
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
310
              .build());
1✔
311
    }
312

313
    if (isMetricEnabled("grpc.server.call.sent_total_compressed_message_size",
1✔
314
        enableMetrics, disableDefault)) {
315
      builder.serverTotalSentCompressedMessageSizeCounter(
1✔
316
          meter.histogramBuilder(
1✔
317
                  "grpc.server.call.sent_total_compressed_message_size")
318
              .setUnit("By")
1✔
319
              .setDescription("Compressed message bytes sent per server call")
1✔
320
              .ofLongs()
1✔
321
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
322
              .build());
1✔
323
    }
324

325
    if (isMetricEnabled("grpc.server.call.rcvd_total_compressed_message_size",
1✔
326
        enableMetrics, disableDefault)) {
327
      builder.serverTotalReceivedCompressedMessageSizeCounter(
1✔
328
          meter.histogramBuilder(
1✔
329
                  "grpc.server.call.rcvd_total_compressed_message_size")
330
              .setUnit("By")
1✔
331
              .setDescription("Compressed message bytes received per server call")
1✔
332
              .ofLongs()
1✔
333
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
334
              .build());
1✔
335
    }
336

337
    return builder.build();
1✔
338
  }
339

340
  static boolean isMetricEnabled(String metricName, Map<String, Boolean> enableMetrics,
341
      boolean disableDefault) {
342
    Boolean explicitlyEnabled = enableMetrics.get(metricName);
1✔
343
    if (explicitlyEnabled != null) {
1✔
344
      return explicitlyEnabled;
1✔
345
    }
346
    return OpenTelemetryMetricsModule.DEFAULT_PER_CALL_METRICS_SET.contains(metricName)
1✔
347
        && !disableDefault;
348
  }
349

350

351
  /**
352
   * Builder for configuring {@link GrpcOpenTelemetry}.
353
   */
354
  public static class Builder {
355
    private OpenTelemetry openTelemetrySdk = OpenTelemetry.noop();
1✔
356
    private final List<OpenTelemetryPlugin> plugins = new ArrayList<>();
1✔
357
    private final Collection<String> optionalLabels = new ArrayList<>();
1✔
358
    private final Map<String, Boolean> enableMetrics = new HashMap<>();
1✔
359
    private boolean disableAll;
360

361
    private Builder() {}
1✔
362

363
    /**
364
     * Sets the {@link io.opentelemetry.api.OpenTelemetry} entrypoint to use. This can be used to
365
     * configure OpenTelemetry by returning the instance created by a
366
     * {@code io.opentelemetry.sdk.OpenTelemetrySdkBuilder}.
367
     */
368
    public Builder sdk(OpenTelemetry sdk) {
369
      this.openTelemetrySdk = sdk;
1✔
370
      return this;
1✔
371
    }
372

373
    Builder plugin(OpenTelemetryPlugin plugin) {
374
      plugins.add(checkNotNull(plugin, "plugin"));
1✔
375
      return this;
1✔
376
    }
377

378
    /**
379
     * Adds optionalLabelKey to all the metrics that can provide value for the
380
     * optionalLabelKey.
381
     */
382
    public Builder addOptionalLabel(String optionalLabelKey) {
383
      this.optionalLabels.add(optionalLabelKey);
1✔
384
      return this;
1✔
385
    }
386

387
    /**
388
     * Enables the specified metrics for collection and export. By default, only a subset of
389
     * metrics are enabled.
390
     */
391
    public Builder enableMetrics(Collection<String> enableMetrics) {
392
      for (String metric : enableMetrics) {
1✔
393
        this.enableMetrics.put(metric, true);
1✔
394
      }
1✔
395
      return this;
1✔
396
    }
397

398
    /**
399
     * Disables the specified metrics from being collected and exported.
400
     */
401
    public Builder disableMetrics(Collection<String> disableMetrics) {
402
      for (String metric : disableMetrics) {
1✔
403
        this.enableMetrics.put(metric, false);
1✔
404
      }
1✔
405
      return this;
1✔
406
    }
407

408
    /**
409
     * Disable all metrics. If set to true all metrics must be explicitly enabled.
410
     */
411
    public Builder disableAllMetrics() {
412
      this.enableMetrics.clear();
1✔
413
      this.disableAll = true;
1✔
414
      return this;
1✔
415
    }
416

417
    Builder enableTracing(boolean enable) {
418
      ENABLE_OTEL_TRACING = enable;
1✔
419
      return this;
1✔
420
    }
421

422
    /**
423
     * Returns a new {@link GrpcOpenTelemetry} built with the configuration of this {@link
424
     * Builder}.
425
     */
426
    public GrpcOpenTelemetry build() {
427
      return new GrpcOpenTelemetry(this);
1✔
428
    }
429
  }
430
}
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