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

grpc / grpc-java / #19255

29 May 2024 09:40PM UTC coverage: 88.298% (-0.1%) from 88.425%
#19255

push

github

ejona86
Create gcp-csm-observability

32038 of 36284 relevant lines covered (88.3%)

0.88 hits per line

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

86.29
/../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

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

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

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

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

72
  public static Builder newBuilder() {
73
    return new Builder();
1✔
74
  }
75

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

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

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

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

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

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

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

122
  MetricSink getSink() {
123
    return sink;
1✔
124
  }
125

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

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

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

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

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

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

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

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

195
    if (isMetricEnabled("grpc.client.attempt.sent_total_compressed_message_size", enableMetrics,
1✔
196
        disableDefault)) {
197
      builder.clientTotalSentCompressedMessageSizeCounter(
1✔
198
          meter.histogramBuilder(
1✔
199
                  "grpc.client.attempt.sent_total_compressed_message_size")
200
              .setUnit("By")
1✔
201
              .setDescription("Compressed message bytes sent per client call attempt")
1✔
202
              .ofLongs()
1✔
203
              .build());
1✔
204
    }
205

206
    if (isMetricEnabled("grpc.client.attempt.rcvd_total_compressed_message_size", enableMetrics,
1✔
207
        disableDefault)) {
208
      builder.clientTotalReceivedCompressedMessageSizeCounter(
1✔
209
          meter.histogramBuilder(
1✔
210
                  "grpc.client.attempt.rcvd_total_compressed_message_size")
211
              .setUnit("By")
1✔
212
              .setDescription("Compressed message bytes received per call attempt")
1✔
213
              .ofLongs()
1✔
214
              .build());
1✔
215
    }
216

217
    if (isMetricEnabled("grpc.server.call.started", enableMetrics, disableDefault)) {
1✔
218
      builder.serverCallCountCounter(
1✔
219
          meter.counterBuilder("grpc.server.call.started")
1✔
220
              .setUnit("{call}")
1✔
221
              .setDescription("Number of server calls started")
1✔
222
              .build());
1✔
223
    }
224

225
    if (isMetricEnabled("grpc.server.call.duration", enableMetrics, disableDefault)) {
1✔
226
      builder.serverCallDurationCounter(
1✔
227
          meter.histogramBuilder("grpc.server.call.duration")
1✔
228
              .setUnit("s")
1✔
229
              .setDescription(
1✔
230
                  "Time taken to complete a call from server transport's perspective")
231
              .build());
1✔
232
    }
233

234
    if (isMetricEnabled("grpc.server.call.sent_total_compressed_message_size", enableMetrics,
1✔
235
        disableDefault)) {
236
      builder.serverTotalSentCompressedMessageSizeCounter(
1✔
237
          meter.histogramBuilder(
1✔
238
                  "grpc.server.call.sent_total_compressed_message_size")
239
              .setUnit("By")
1✔
240
              .setDescription("Compressed message bytes sent per server call")
1✔
241
              .ofLongs()
1✔
242
              .build());
1✔
243
    }
244

245
    if (isMetricEnabled("grpc.server.call.rcvd_total_compressed_message_size", enableMetrics,
1✔
246
        disableDefault)) {
247
      builder.serverTotalReceivedCompressedMessageSizeCounter(
1✔
248
          meter.histogramBuilder(
1✔
249
                  "grpc.server.call.rcvd_total_compressed_message_size")
250
              .setUnit("By")
1✔
251
              .setDescription("Compressed message bytes received per server call")
1✔
252
              .ofLongs()
1✔
253
              .build());
1✔
254
    }
255

256
    return builder.build();
1✔
257
  }
258

259
  static boolean isMetricEnabled(String metricName, Map<String, Boolean> enableMetrics,
260
      boolean disableDefault) {
261
    Boolean explicitlyEnabled = enableMetrics.get(metricName);
1✔
262
    if (explicitlyEnabled != null) {
1✔
263
      return explicitlyEnabled;
×
264
    }
265
    return OpenTelemetryMetricsModule.DEFAULT_PER_CALL_METRICS_SET.contains(metricName)
1✔
266
        && !disableDefault;
267
  }
268

269

270
  /**
271
   * Builder for configuring {@link GrpcOpenTelemetry}.
272
   */
273
  public static class Builder {
274
    private OpenTelemetry openTelemetrySdk = OpenTelemetry.noop();
1✔
275
    private final List<OpenTelemetryPlugin> plugins = new ArrayList<>();
1✔
276
    private final Collection<String> optionalLabels = new ArrayList<>();
1✔
277
    private final Map<String, Boolean> enableMetrics = new HashMap<>();
1✔
278
    private boolean disableAll;
279

280
    private Builder() {}
1✔
281

282
    /**
283
     * Sets the {@link io.opentelemetry.api.OpenTelemetry} entrypoint to use. This can be used to
284
     * configure OpenTelemetry by returning the instance created by a
285
     * {@code io.opentelemetry.sdk.OpenTelemetrySdkBuilder}.
286
     */
287
    public Builder sdk(OpenTelemetry sdk) {
288
      this.openTelemetrySdk = sdk;
1✔
289
      return this;
1✔
290
    }
291

292
    Builder plugin(OpenTelemetryPlugin plugin) {
293
      plugins.add(checkNotNull(plugin, "plugin"));
×
294
      return this;
×
295
    }
296

297
    /**
298
     * Adds optionalLabelKey to all the metrics that can provide value for the
299
     * optionalLabelKey.
300
     */
301
    public Builder addOptionalLabel(String optionalLabelKey) {
302
      this.optionalLabels.add(optionalLabelKey);
1✔
303
      return this;
1✔
304
    }
305

306
    /**
307
     * Enables the specified metrics for collection and export. By default, only a subset of
308
     * metrics are enabled.
309
     */
310
    public Builder enableMetrics(Collection<String> enableMetrics) {
311
      for (String metric : enableMetrics) {
1✔
312
        this.enableMetrics.put(metric, true);
1✔
313
      }
1✔
314
      return this;
1✔
315
    }
316

317
    /**
318
     * Disables the specified metrics from being collected and exported.
319
     */
320
    public Builder disableMetrics(Collection<String> disableMetrics) {
321
      for (String metric : disableMetrics) {
1✔
322
        this.enableMetrics.put(metric, false);
1✔
323
      }
1✔
324
      return this;
1✔
325
    }
326

327
    /**
328
     * Disable all metrics. If set to true all metrics must be explicitly enabled.
329
     */
330
    public Builder disableAllMetrics() {
331
      this.enableMetrics.clear();
1✔
332
      this.disableAll = true;
1✔
333
      return this;
1✔
334
    }
335

336
    /**
337
     * Returns a new {@link GrpcOpenTelemetry} built with the configuration of this {@link
338
     * Builder}.
339
     */
340
    public GrpcOpenTelemetry build() {
341
      return new GrpcOpenTelemetry(this);
1✔
342
    }
343
  }
344
}
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

© 2026 Coveralls, Inc