Skip to content

Commit 9c4f9ee

Browse files
authored
feat: Add gRPC client attempt metrics (#1037)
* add client attempt and client call metrics
1 parent 78203b0 commit 9c4f9ee

File tree

8 files changed

+995
-81
lines changed

8 files changed

+995
-81
lines changed

grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInstruments.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616

1717
package net.devh.boot.grpc.client.metrics;
1818

19+
import java.time.Duration;
20+
1921
import io.micrometer.core.instrument.Counter;
22+
import io.micrometer.core.instrument.DistributionSummary;
2023
import io.micrometer.core.instrument.MeterRegistry;
24+
import io.micrometer.core.instrument.Timer;
25+
import io.micrometer.core.instrument.binder.BaseUnits;
2126

2227
/*
2328
* The instruments used to record metrics on client.
@@ -27,21 +32,70 @@ public final class MetricsClientInstruments {
2732
private MetricsClientInstruments() {}
2833

2934
/*
30-
* This is a client side metric defined in gRFC <a
31-
* href="https://github.com/grpc/proposal/blob/master/A66-otel-stats.md">A66</a>. Please note that this is the name
32-
* used for instrumentation and can be changed by exporters in an unpredictable manner depending on the destination.
35+
* Client side metrics defined in gRFC <a
36+
* href="https://github.com/grpc/proposal/blob/master/A66-otel-stats.md">A66</a>. Please note that these are the
37+
* names used for instrumentation and can be changed by exporters in an unpredictable manner depending on the
38+
* destination.
3339
*/
3440
private static final String CLIENT_ATTEMPT_STARTED = "grpc.client.attempt.started";
41+
private static final String CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE =
42+
"grpc.client.attempt.sent_total_compressed_message_size";
43+
private static final String CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE =
44+
"grpc.client.attempt.rcvd_total_compressed_message_size";
45+
private static final String CLIENT_ATTEMPT_DURATION =
46+
"grpc.client.attempt.duration";
47+
private static final String CLIENT_CALL_DURATION =
48+
"grpc.client.call.duration";
49+
private static final double[] DEFAULT_SIZE_BUCKETS =
50+
new double[] {1024d, 2048d, 4096d, 16384d, 65536d, 262144d, 1048576d,
51+
4194304d, 16777216d, 67108864d, 268435456d, 1073741824d, 4294967296d};
52+
private static final Duration[] DEFAULT_LATENCY_BUCKETS =
53+
new Duration[] {Duration.ofNanos(10000), Duration.ofNanos(50000), Duration.ofNanos(100000),
54+
Duration.ofNanos(300000), Duration.ofNanos(600000), Duration.ofNanos(800000),
55+
Duration.ofMillis(1), Duration.ofMillis(2), Duration.ofMillis(3), Duration.ofMillis(4),
56+
Duration.ofMillis(5), Duration.ofMillis(6), Duration.ofMillis(8), Duration.ofMillis(10),
57+
Duration.ofMillis(13), Duration.ofMillis(16), Duration.ofMillis(20), Duration.ofMillis(25),
58+
Duration.ofMillis(30), Duration.ofMillis(40), Duration.ofMillis(50), Duration.ofMillis(65),
59+
Duration.ofMillis(80), Duration.ofMillis(100), Duration.ofMillis(130), Duration.ofMillis(160),
60+
Duration.ofMillis(200), Duration.ofMillis(250), Duration.ofMillis(300), Duration.ofMillis(400),
61+
Duration.ofMillis(500), Duration.ofMillis(650), Duration.ofMillis(800),
62+
Duration.ofSeconds(1), Duration.ofSeconds(2), Duration.ofSeconds(5), Duration.ofSeconds(10),
63+
Duration.ofSeconds(20), Duration.ofSeconds(50), Duration.ofSeconds(100)};
3564

36-
static MetricsMeters newClientMetricsMeters(MeterRegistry registry) {
37-
MetricsMeters.Builder builder = MetricsMeters.newBuilder();
65+
static MetricsClientMeters newClientMetricsMeters(MeterRegistry registry) {
66+
MetricsClientMeters.Builder builder = MetricsClientMeters.newBuilder();
3867

3968
builder.setAttemptCounter(Counter.builder(CLIENT_ATTEMPT_STARTED)
4069
.description(
4170
"The total number of RPC attempts started from the client side, including "
4271
+ "those that have not completed.")
4372
.baseUnit("attempt")
4473
.withRegistry(registry));
74+
75+
builder.setSentMessageSizeDistribution(DistributionSummary.builder(
76+
CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
77+
.description("Compressed message bytes sent per client call attempt")
78+
.baseUnit(BaseUnits.BYTES)
79+
.serviceLevelObjectives(DEFAULT_SIZE_BUCKETS)
80+
.withRegistry(registry));
81+
82+
builder.setReceivedMessageSizeDistribution(DistributionSummary.builder(
83+
CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
84+
.description("Compressed message bytes received per call attempt")
85+
.baseUnit(BaseUnits.BYTES)
86+
.serviceLevelObjectives(DEFAULT_SIZE_BUCKETS)
87+
.withRegistry(registry));
88+
89+
builder.setClientAttemptDuration(Timer.builder(CLIENT_ATTEMPT_DURATION)
90+
.description("Time taken to complete a client call attempt")
91+
.serviceLevelObjectives(DEFAULT_LATENCY_BUCKETS)
92+
.withRegistry(registry));
93+
94+
builder.setClientCallDuration(Timer.builder(CLIENT_CALL_DURATION)
95+
.description("Time taken by gRPC to complete an RPC from application's perspective")
96+
.serviceLevelObjectives(DEFAULT_LATENCY_BUCKETS)
97+
.withRegistry(registry));
98+
4599
return builder.build();
46100
}
47101

grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptor.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package net.devh.boot.grpc.client.metrics;
1818

19+
import java.util.function.Supplier;
20+
21+
import com.google.common.base.Stopwatch;
22+
1923
import io.grpc.CallOptions;
2024
import io.grpc.Channel;
2125
import io.grpc.ClientCall;
@@ -34,16 +38,22 @@
3438
*/
3539
public class MetricsClientInterceptor implements ClientInterceptor {
3640

37-
private final MetricsMeters metricsMeters;
41+
private final MetricsClientMeters metricsClientMeters;
42+
private final Supplier<Stopwatch> stopwatchSupplier;
3843

3944
/**
4045
* Creates a new gRPC client interceptor that collects metrics into the given
4146
* {@link io.micrometer.core.instrument.MeterRegistry}.
4247
*
4348
* @param registry The MeterRegistry to use.
4449
*/
45-
public MetricsClientInterceptor(MeterRegistry registry) {
46-
this.metricsMeters = MetricsClientInstruments.newClientMetricsMeters(registry);
50+
public MetricsClientInterceptor(MeterRegistry registry, Supplier<Stopwatch> stopwatchSupplier) {
51+
this(MetricsClientInstruments.newClientMetricsMeters(registry), stopwatchSupplier);
52+
}
53+
54+
public MetricsClientInterceptor(MetricsClientMeters meters, Supplier<Stopwatch> stopwatchSupplier) {
55+
this.metricsClientMeters = meters;
56+
this.stopwatchSupplier = stopwatchSupplier;
4757
}
4858

4959
@Override
@@ -55,20 +65,22 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
5565
* same call. Each call needs a dedicated factory as they share the same method descriptor.
5666
*/
5767
final MetricsClientStreamTracers.CallAttemptsTracerFactory tracerFactory =
58-
new MetricsClientStreamTracers.CallAttemptsTracerFactory(method.getFullMethodName(),
59-
metricsMeters);
68+
new MetricsClientStreamTracers.CallAttemptsTracerFactory(
69+
new MetricsClientStreamTracers(stopwatchSupplier),
70+
method.getFullMethodName(),
71+
metricsClientMeters);
6072

6173
ClientCall<ReqT, RespT> call =
6274
next.newCall(method, callOptions.withStreamTracerFactory(tracerFactory));
6375

64-
// TODO(dnvindhya): Collect the actual response/error in the SimpleForwardingClientCall
6576
return new SimpleForwardingClientCall<ReqT, RespT>(call) {
6677
@Override
6778
public void start(Listener<RespT> responseListener, Metadata headers) {
6879
delegate().start(
6980
new SimpleForwardingClientCallListener<RespT>(responseListener) {
7081
@Override
7182
public void onClose(Status status, Metadata trailers) {
83+
tracerFactory.callEnded(status);
7284
super.onClose(status, trailers);
7385
}
7486
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.client.metrics;
18+
19+
import io.micrometer.core.instrument.Counter;
20+
import io.micrometer.core.instrument.DistributionSummary;
21+
import io.micrometer.core.instrument.Meter.MeterProvider;
22+
import io.micrometer.core.instrument.Timer;
23+
24+
/*
25+
* Collection of client metrics meters.
26+
*/
27+
public class MetricsClientMeters {
28+
29+
private MeterProvider<Counter> attemptCounter;
30+
private MeterProvider<DistributionSummary> sentMessageSizeDistribution;
31+
private MeterProvider<DistributionSummary> receivedMessageSizeDistribution;
32+
private MeterProvider<Timer> clientAttemptDuration;
33+
private MeterProvider<Timer> clientCallDuration;
34+
35+
private MetricsClientMeters(Builder builder) {
36+
this.attemptCounter = builder.attemptCounter;
37+
this.sentMessageSizeDistribution = builder.sentMessageSizeDistribution;
38+
this.receivedMessageSizeDistribution = builder.receivedMessageSizeDistribution;
39+
this.clientAttemptDuration = builder.clientAttemptDuration;
40+
this.clientCallDuration = builder.clientCallDuration;
41+
}
42+
43+
public MeterProvider<Counter> getAttemptCounter() {
44+
return this.attemptCounter;
45+
}
46+
47+
public MeterProvider<DistributionSummary> getSentMessageSizeDistribution() {
48+
return this.sentMessageSizeDistribution;
49+
}
50+
51+
public MeterProvider<DistributionSummary> getReceivedMessageSizeDistribution() {
52+
return this.receivedMessageSizeDistribution;
53+
}
54+
55+
public MeterProvider<Timer> getClientAttemptDuration() {
56+
return this.clientAttemptDuration;
57+
}
58+
59+
public MeterProvider<Timer> getClientCallDuration() {
60+
return this.clientCallDuration;
61+
}
62+
63+
public static Builder newBuilder() {
64+
return new Builder();
65+
}
66+
67+
static class Builder {
68+
69+
private MeterProvider<Counter> attemptCounter;
70+
private MeterProvider<DistributionSummary> sentMessageSizeDistribution;
71+
private MeterProvider<DistributionSummary> receivedMessageSizeDistribution;
72+
private MeterProvider<Timer> clientAttemptDuration;
73+
private MeterProvider<Timer> clientCallDuration;
74+
75+
private Builder() {}
76+
77+
public Builder setAttemptCounter(MeterProvider<Counter> counter) {
78+
this.attemptCounter = counter;
79+
return this;
80+
}
81+
82+
public Builder setSentMessageSizeDistribution(MeterProvider<DistributionSummary> distribution) {
83+
this.sentMessageSizeDistribution = distribution;
84+
return this;
85+
}
86+
87+
public Builder setReceivedMessageSizeDistribution(MeterProvider<DistributionSummary> distribution) {
88+
this.receivedMessageSizeDistribution = distribution;
89+
return this;
90+
}
91+
92+
public Builder setClientAttemptDuration(MeterProvider<Timer> timer) {
93+
this.clientAttemptDuration = timer;
94+
return this;
95+
}
96+
97+
public Builder setClientCallDuration(MeterProvider<Timer> timer) {
98+
this.clientCallDuration = timer;
99+
return this;
100+
}
101+
102+
public MetricsClientMeters build() {
103+
return new MetricsClientMeters(this);
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)