diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQlMetricsInstrumentation.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQlMetricsInstrumentation.java index 9c6ae2f6..0ef7537a 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQlMetricsInstrumentation.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQlMetricsInstrumentation.java @@ -17,6 +17,7 @@ package org.springframework.graphql.boot.actuate.metrics; import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicLong; import graphql.ExecutionResult; import graphql.execution.instrumentation.InstrumentationContext; @@ -26,6 +27,7 @@ import graphql.execution.instrumentation.SimpleInstrumentationContext; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import graphql.schema.DataFetcher; +import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Timer; @@ -41,10 +43,16 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation { private final AutoTimer autoTimer; + private final DistributionSummary dataFetchingSummary; + GraphQlMetricsInstrumentation(MeterRegistry registry, GraphQlTagsProvider tagsProvider, AutoTimer autoTimer) { this.registry = registry; this.tagsProvider = tagsProvider; this.autoTimer = autoTimer; + this.dataFetchingSummary = DistributionSummary.builder("graphql.request.datafetch.count") + .baseUnit("calls") + .description("Count of DataFetcher calls per request.") + .register(this.registry); } @Override @@ -66,9 +74,10 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation { if (!result.getErrors().isEmpty()) { result.getErrors() .forEach((error) -> GraphQlMetricsInstrumentation.this.registry.counter("graphql.error", - GraphQlMetricsInstrumentation.this.tagsProvider.getErrorTags(parameters, error)) + GraphQlMetricsInstrumentation.this.tagsProvider.getErrorTags(parameters, error)) .increment()); } + GraphQlMetricsInstrumentation.this.dataFetchingSummary.record(state.getDataFetchingCount()); } }; } @@ -92,7 +101,6 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation { recordDataFetcherMetric(sample, dataFetcher, parameters, null); return value; } - } catch (Throwable throwable) { recordDataFetcherMetric(sample, dataFetcher, parameters, throwable); @@ -108,6 +116,8 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation { Timer.Builder timer = this.autoTimer.builder("graphql.datafetcher"); timer.tags(this.tagsProvider.getDataFetchingTags(dataFetcher, parameters, throwable)); sample.stop(timer.register(this.registry)); + RequestMetricsInstrumentationState state = parameters.getInstrumentationState(); + state.incrementDataFetchingCount(); } static class RequestMetricsInstrumentationState implements InstrumentationState { @@ -118,6 +128,8 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation { private Timer.Sample sample; + private AtomicLong dataFetchingCount = new AtomicLong(0L); + RequestMetricsInstrumentationState(AutoTimer autoTimer, MeterRegistry registry) { this.timer = autoTimer.builder("graphql.request"); this.registry = registry; @@ -136,6 +148,14 @@ class GraphQlMetricsInstrumentation extends SimpleInstrumentation { this.sample.stop(this.timer.register(this.registry)); } + void incrementDataFetchingCount() { + this.dataFetchingCount.incrementAndGet(); + } + + long getDataFetchingCount() { + return this.dataFetchingCount.get(); + } + } } diff --git a/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc b/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc index beb9e7d7..a8b2b6c8 100644 --- a/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc @@ -309,6 +309,20 @@ A `DataFetcher` metric timer is available at `/actuator/metrics/graphql.datafetc |"SUCCESS", "ERROR" |=== +[[boot-graphql-metrics-datafetcher-summary]] +=== `DataFetcher` Distribution Summary + +A https://micrometer.io/docs/concepts#_distribution_summaries[distribution summary] +that counts the number of non-trivial `DataFetcher` calls made per request. +This metric is useful for detecting "N+1" data fetching issues and consider batch loading; +it provides the `"TOTAL"` number of data fetcher calls made over the `"COUNT"` of recorded requests, +as well as the `"MAX"` calls made for a single request over the considered period. + +The distribution is available at `/actuator/metrics/graphql.request.datafetch.count`. + +More options are available for +{spring-boot-ref-docs}/application-properties.html#application-properties.actuator.management.metrics.distribution.maximum-expected-value[configuring distributions with application properties]. + [[boot-graphql-metrics-error-counter]] === Error Counter