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

grpc / grpc-java / #19286

14 Jun 2024 06:10PM UTC coverage: 88.314%. Remained the same
#19286

push

github

web-flow
opentelemetry: Add explicit histogram buckets for per-call metrics (#11281) (#11287)

Add explicit histogram buckets for per-call metrics as specified in gRFC A66 https://github.com/grpc/proposal/blob/master/A66-otel-stats.md#units.

32066 of 36309 relevant lines covered (88.31%)

0.88 hits per line

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

87.02
/../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.LATENCY_BUCKETS;
22
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.SIZE_BUCKETS;
23

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

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

57
  private static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
1✔
58
    @Override
59
    public Stopwatch get() {
60
      return Stopwatch.createUnstarted();
×
61
    }
62
  };
63

64
  private final OpenTelemetry openTelemetrySdk;
65
  private final MeterProvider meterProvider;
66
  private final Meter meter;
67
  private final Map<String, Boolean> enableMetrics;
68
  private final boolean disableDefault;
69
  private final OpenTelemetryMetricsResource resource;
70
  private final OpenTelemetryMetricsModule openTelemetryMetricsModule;
71
  private final List<String> optionalLabels;
72
  private final MetricSink sink;
73

74
  public static Builder newBuilder() {
75
    return new Builder();
1✔
76
  }
77

78
  private GrpcOpenTelemetry(Builder builder) {
1✔
79
    this.openTelemetrySdk = checkNotNull(builder.openTelemetrySdk, "openTelemetrySdk");
1✔
80
    this.meterProvider = checkNotNull(openTelemetrySdk.getMeterProvider(), "meterProvider");
1✔
81
    this.meter = this.meterProvider
1✔
82
        .meterBuilder(OpenTelemetryConstants.INSTRUMENTATION_SCOPE)
1✔
83
        .setInstrumentationVersion(IMPLEMENTATION_VERSION)
1✔
84
        .build();
1✔
85
    this.enableMetrics = ImmutableMap.copyOf(builder.enableMetrics);
1✔
86
    this.disableDefault = builder.disableAll;
1✔
87
    this.resource = createMetricInstruments(meter, enableMetrics, disableDefault);
1✔
88
    this.optionalLabels = ImmutableList.copyOf(builder.optionalLabels);
1✔
89
    this.openTelemetryMetricsModule = new OpenTelemetryMetricsModule(
1✔
90
        STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins);
1✔
91
    this.sink = new OpenTelemetryMetricSink(meter, enableMetrics, disableDefault, optionalLabels);
1✔
92
  }
1✔
93

94
  @VisibleForTesting
95
  OpenTelemetry getOpenTelemetryInstance() {
96
    return this.openTelemetrySdk;
1✔
97
  }
98

99
  @VisibleForTesting
100
  MeterProvider getMeterProvider() {
101
    return this.meterProvider;
1✔
102
  }
103

104
  @VisibleForTesting
105
  Meter getMeter() {
106
    return this.meter;
1✔
107
  }
108

109
  @VisibleForTesting
110
  OpenTelemetryMetricsResource getResource() {
111
    return this.resource;
×
112
  }
113

114
  @VisibleForTesting
115
  Map<String, Boolean> getEnableMetrics() {
116
    return this.enableMetrics;
1✔
117
  }
118

119
  @VisibleForTesting
120
  List<String> getOptionalLabels() {
121
    return optionalLabels;
1✔
122
  }
123

124
  MetricSink getSink() {
125
    return sink;
1✔
126
  }
127

128
  /**
129
   * Registers GrpcOpenTelemetry globally, applying its configuration to all subsequently created
130
   * gRPC channels and servers.
131
   */
132
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10591")
133
  public void registerGlobal() {
134
    InternalConfiguratorRegistry.setConfigurators(Collections.singletonList(
×
135
        new InternalConfigurator() {
×
136
          @Override
137
          public void configureChannelBuilder(ManagedChannelBuilder<?> channelBuilder) {
138
            GrpcOpenTelemetry.this.configureChannelBuilder(channelBuilder);
×
139
          }
×
140

141
          @Override
142
          public void configureServerBuilder(ServerBuilder<?> serverBuilder) {
143
            GrpcOpenTelemetry.this.configureServerBuilder(serverBuilder);
×
144
          }
×
145
        }));
146
  }
×
147

148
  /**
149
   * Configures the given {@link ManagedChannelBuilder} with OpenTelemetry metrics instrumentation.
150
   */
151
  public void configureChannelBuilder(ManagedChannelBuilder<?> builder) {
152
    InternalManagedChannelBuilder.addMetricSink(builder, sink);
×
153
    InternalManagedChannelBuilder.interceptWithTarget(
×
154
        builder, openTelemetryMetricsModule::getClientInterceptor);
155
  }
×
156

157
  /**
158
   * Configures the given {@link ServerBuilder} with OpenTelemetry metrics instrumentation.
159
   *
160
   * @param serverBuilder the server builder to configure
161
   */
162
  public void configureServerBuilder(ServerBuilder<?> serverBuilder) {
163
    serverBuilder.addStreamTracerFactory(openTelemetryMetricsModule.getServerTracerFactory());
×
164
  }
×
165

166
  @VisibleForTesting
167
  static OpenTelemetryMetricsResource createMetricInstruments(Meter meter,
168
      Map<String, Boolean> enableMetrics, boolean disableDefault) {
169
    OpenTelemetryMetricsResource.Builder builder = OpenTelemetryMetricsResource.builder();
1✔
170

171
    if (isMetricEnabled("grpc.client.call.duration", enableMetrics, disableDefault)) {
1✔
172
      builder.clientCallDurationCounter(
1✔
173
          meter.histogramBuilder("grpc.client.call.duration")
1✔
174
              .setUnit("s")
1✔
175
              .setDescription(
1✔
176
                  "Time taken by gRPC to complete an RPC from application's perspective")
177
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
178
              .build());
1✔
179
    }
180

181
    if (isMetricEnabled("grpc.client.attempt.started", enableMetrics, disableDefault)) {
1✔
182
      builder.clientAttemptCountCounter(
1✔
183
          meter.counterBuilder("grpc.client.attempt.started")
1✔
184
              .setUnit("{attempt}")
1✔
185
              .setDescription("Number of client call attempts started")
1✔
186
              .build());
1✔
187
    }
188

189
    if (isMetricEnabled("grpc.client.attempt.duration", enableMetrics, disableDefault)) {
1✔
190
      builder.clientAttemptDurationCounter(
1✔
191
          meter.histogramBuilder(
1✔
192
                  "grpc.client.attempt.duration")
193
              .setUnit("s")
1✔
194
              .setDescription("Time taken to complete a client call attempt")
1✔
195
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
196
              .build());
1✔
197
    }
198

199
    if (isMetricEnabled("grpc.client.attempt.sent_total_compressed_message_size", enableMetrics,
1✔
200
        disableDefault)) {
201
      builder.clientTotalSentCompressedMessageSizeCounter(
1✔
202
          meter.histogramBuilder(
1✔
203
                  "grpc.client.attempt.sent_total_compressed_message_size")
204
              .setUnit("By")
1✔
205
              .setDescription("Compressed message bytes sent per client call attempt")
1✔
206
              .ofLongs()
1✔
207
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
208
              .build());
1✔
209
    }
210

211
    if (isMetricEnabled("grpc.client.attempt.rcvd_total_compressed_message_size", enableMetrics,
1✔
212
        disableDefault)) {
213
      builder.clientTotalReceivedCompressedMessageSizeCounter(
1✔
214
          meter.histogramBuilder(
1✔
215
                  "grpc.client.attempt.rcvd_total_compressed_message_size")
216
              .setUnit("By")
1✔
217
              .setDescription("Compressed message bytes received per call attempt")
1✔
218
              .ofLongs()
1✔
219
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
220
              .build());
1✔
221
    }
222

223
    if (isMetricEnabled("grpc.server.call.started", enableMetrics, disableDefault)) {
1✔
224
      builder.serverCallCountCounter(
1✔
225
          meter.counterBuilder("grpc.server.call.started")
1✔
226
              .setUnit("{call}")
1✔
227
              .setDescription("Number of server calls started")
1✔
228
              .build());
1✔
229
    }
230

231
    if (isMetricEnabled("grpc.server.call.duration", enableMetrics, disableDefault)) {
1✔
232
      builder.serverCallDurationCounter(
1✔
233
          meter.histogramBuilder("grpc.server.call.duration")
1✔
234
              .setUnit("s")
1✔
235
              .setDescription(
1✔
236
                  "Time taken to complete a call from server transport's perspective")
237
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
238
              .build());
1✔
239
    }
240

241
    if (isMetricEnabled("grpc.server.call.sent_total_compressed_message_size", enableMetrics,
1✔
242
        disableDefault)) {
243
      builder.serverTotalSentCompressedMessageSizeCounter(
1✔
244
          meter.histogramBuilder(
1✔
245
                  "grpc.server.call.sent_total_compressed_message_size")
246
              .setUnit("By")
1✔
247
              .setDescription("Compressed message bytes sent per server call")
1✔
248
              .ofLongs()
1✔
249
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
250
              .build());
1✔
251
    }
252

253
    if (isMetricEnabled("grpc.server.call.rcvd_total_compressed_message_size", enableMetrics,
1✔
254
        disableDefault)) {
255
      builder.serverTotalReceivedCompressedMessageSizeCounter(
1✔
256
          meter.histogramBuilder(
1✔
257
                  "grpc.server.call.rcvd_total_compressed_message_size")
258
              .setUnit("By")
1✔
259
              .setDescription("Compressed message bytes received per server call")
1✔
260
              .ofLongs()
1✔
261
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
262
              .build());
1✔
263
    }
264

265
    return builder.build();
1✔
266
  }
267

268
  static boolean isMetricEnabled(String metricName, Map<String, Boolean> enableMetrics,
269
      boolean disableDefault) {
270
    Boolean explicitlyEnabled = enableMetrics.get(metricName);
1✔
271
    if (explicitlyEnabled != null) {
1✔
272
      return explicitlyEnabled;
×
273
    }
274
    return OpenTelemetryMetricsModule.DEFAULT_PER_CALL_METRICS_SET.contains(metricName)
1✔
275
        && !disableDefault;
276
  }
277

278

279
  /**
280
   * Builder for configuring {@link GrpcOpenTelemetry}.
281
   */
282
  public static class Builder {
283
    private OpenTelemetry openTelemetrySdk = OpenTelemetry.noop();
1✔
284
    private final List<OpenTelemetryPlugin> plugins = new ArrayList<>();
1✔
285
    private final Collection<String> optionalLabels = new ArrayList<>();
1✔
286
    private final Map<String, Boolean> enableMetrics = new HashMap<>();
1✔
287
    private boolean disableAll;
288

289
    private Builder() {}
1✔
290

291
    /**
292
     * Sets the {@link io.opentelemetry.api.OpenTelemetry} entrypoint to use. This can be used to
293
     * configure OpenTelemetry by returning the instance created by a
294
     * {@code io.opentelemetry.sdk.OpenTelemetrySdkBuilder}.
295
     */
296
    public Builder sdk(OpenTelemetry sdk) {
297
      this.openTelemetrySdk = sdk;
1✔
298
      return this;
1✔
299
    }
300

301
    Builder plugin(OpenTelemetryPlugin plugin) {
302
      plugins.add(checkNotNull(plugin, "plugin"));
×
303
      return this;
×
304
    }
305

306
    /**
307
     * Adds optionalLabelKey to all the metrics that can provide value for the
308
     * optionalLabelKey.
309
     */
310
    public Builder addOptionalLabel(String optionalLabelKey) {
311
      this.optionalLabels.add(optionalLabelKey);
1✔
312
      return this;
1✔
313
    }
314

315
    /**
316
     * Enables the specified metrics for collection and export. By default, only a subset of
317
     * metrics are enabled.
318
     */
319
    public Builder enableMetrics(Collection<String> enableMetrics) {
320
      for (String metric : enableMetrics) {
1✔
321
        this.enableMetrics.put(metric, true);
1✔
322
      }
1✔
323
      return this;
1✔
324
    }
325

326
    /**
327
     * Disables the specified metrics from being collected and exported.
328
     */
329
    public Builder disableMetrics(Collection<String> disableMetrics) {
330
      for (String metric : disableMetrics) {
1✔
331
        this.enableMetrics.put(metric, false);
1✔
332
      }
1✔
333
      return this;
1✔
334
    }
335

336
    /**
337
     * Disable all metrics. If set to true all metrics must be explicitly enabled.
338
     */
339
    public Builder disableAllMetrics() {
340
      this.enableMetrics.clear();
1✔
341
      this.disableAll = true;
1✔
342
      return this;
1✔
343
    }
344

345
    /**
346
     * Returns a new {@link GrpcOpenTelemetry} built with the configuration of this {@link
347
     * Builder}.
348
     */
349
    public GrpcOpenTelemetry build() {
350
      return new GrpcOpenTelemetry(this);
1✔
351
    }
352
  }
353
}
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