Skip to content

Commit 552c4f7

Browse files
committed
Add distribution summary metric for datafetcher calls
This commit adds a new `"graphql.request.datafetch.count"` distribution summary metric for counting the number of data fetching calls per request. Closes gh-146
1 parent 74ba906 commit 552c4f7

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQlMetricsInstrumentation.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.graphql.boot.actuate.metrics;
1818

1919
import java.util.concurrent.CompletionStage;
20+
import java.util.concurrent.atomic.AtomicLong;
2021

2122
import graphql.ExecutionResult;
2223
import graphql.execution.instrumentation.InstrumentationContext;
@@ -26,6 +27,7 @@
2627
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
2728
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
2829
import graphql.schema.DataFetcher;
30+
import io.micrometer.core.instrument.DistributionSummary;
2931
import io.micrometer.core.instrument.MeterRegistry;
3032
import io.micrometer.core.instrument.Tag;
3133
import io.micrometer.core.instrument.Timer;
@@ -41,10 +43,16 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation {
4143

4244
private final AutoTimer autoTimer;
4345

46+
private final DistributionSummary dataFetchingSummary;
47+
4448
GraphQlMetricsInstrumentation(MeterRegistry registry, GraphQlTagsProvider tagsProvider, AutoTimer autoTimer) {
4549
this.registry = registry;
4650
this.tagsProvider = tagsProvider;
4751
this.autoTimer = autoTimer;
52+
this.dataFetchingSummary = DistributionSummary.builder("graphql.request.datafetch.count")
53+
.baseUnit("calls")
54+
.description("Count of DataFetcher calls per request.")
55+
.register(this.registry);
4856
}
4957

5058
@Override
@@ -66,9 +74,10 @@ public void onCompleted(ExecutionResult result, Throwable exc) {
6674
if (!result.getErrors().isEmpty()) {
6775
result.getErrors()
6876
.forEach((error) -> GraphQlMetricsInstrumentation.this.registry.counter("graphql.error",
69-
GraphQlMetricsInstrumentation.this.tagsProvider.getErrorTags(parameters, error))
77+
GraphQlMetricsInstrumentation.this.tagsProvider.getErrorTags(parameters, error))
7078
.increment());
7179
}
80+
GraphQlMetricsInstrumentation.this.dataFetchingSummary.record(state.getDataFetchingCount());
7281
}
7382
};
7483
}
@@ -92,7 +101,6 @@ public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
92101
recordDataFetcherMetric(sample, dataFetcher, parameters, null);
93102
return value;
94103
}
95-
96104
}
97105
catch (Throwable throwable) {
98106
recordDataFetcherMetric(sample, dataFetcher, parameters, throwable);
@@ -108,6 +116,8 @@ private void recordDataFetcherMetric(Timer.Sample sample, DataFetcher<?> dataFet
108116
Timer.Builder timer = this.autoTimer.builder("graphql.datafetcher");
109117
timer.tags(this.tagsProvider.getDataFetchingTags(dataFetcher, parameters, throwable));
110118
sample.stop(timer.register(this.registry));
119+
RequestMetricsInstrumentationState state = parameters.getInstrumentationState();
120+
state.incrementDataFetchingCount();
111121
}
112122

113123
static class RequestMetricsInstrumentationState implements InstrumentationState {
@@ -118,6 +128,8 @@ static class RequestMetricsInstrumentationState implements InstrumentationState
118128

119129
private Timer.Sample sample;
120130

131+
private AtomicLong dataFetchingCount = new AtomicLong(0L);
132+
121133
RequestMetricsInstrumentationState(AutoTimer autoTimer, MeterRegistry registry) {
122134
this.timer = autoTimer.builder("graphql.request");
123135
this.registry = registry;
@@ -136,6 +148,14 @@ void stopTimer() {
136148
this.sample.stop(this.timer.register(this.registry));
137149
}
138150

151+
void incrementDataFetchingCount() {
152+
this.dataFetchingCount.incrementAndGet();
153+
}
154+
155+
long getDataFetchingCount() {
156+
return this.dataFetchingCount.get();
157+
}
158+
139159
}
140160

141161
}

spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,20 @@ A `DataFetcher` metric timer is available at `/actuator/metrics/graphql.datafetc
309309
|"SUCCESS", "ERROR"
310310
|===
311311

312+
[[boot-graphql-metrics-datafetcher-summary]]
313+
=== `DataFetcher` Distribution Summary
314+
315+
A https://micrometer.io/docs/concepts#_distribution_summaries[distribution summary]
316+
that counts the number of non-trivial `DataFetcher` calls made per request.
317+
This metric is useful for detecting "N+1" data fetching issues and consider batch loading;
318+
it provides the `"TOTAL"` number of data fetcher calls made over the `"COUNT"` of recorded requests,
319+
as well as the `"MAX"` calls made for a single request over the considered period.
320+
321+
The distribution is available at `/actuator/metrics/graphql.request.datafetch.count`.
322+
323+
More options are available for
324+
{spring-boot-ref-docs}/application-properties.html#application-properties.actuator.management.metrics.distribution.maximum-expected-value[configuring distributions with application properties].
325+
312326

313327
[[boot-graphql-metrics-error-counter]]
314328
=== Error Counter

0 commit comments

Comments
 (0)