diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java index e2e2e5c26..ea3f24c73 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java @@ -6,6 +6,7 @@ import org.eclipse.microprofile.config.ConfigProvider; +import io.smallrye.graphql.config.ConfigKey; import io.smallrye.graphql.spi.config.Config; import io.smallrye.graphql.spi.config.LogPayloadOption; diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/event/EventsService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/event/EventsService.java index 0e0144291..62b539126 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/event/EventsService.java +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/event/EventsService.java @@ -9,7 +9,7 @@ import graphql.GraphQL; import graphql.schema.GraphQLSchema; import io.smallrye.graphql.api.Context; -import io.smallrye.graphql.cdi.config.ConfigKey; +import io.smallrye.graphql.config.ConfigKey; import io.smallrye.graphql.execution.event.InvokeInfo; import io.smallrye.graphql.execution.event.Priorities; import io.smallrye.graphql.schema.model.Operation; diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java index 6f8055658..b1246e902 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java @@ -1,23 +1,19 @@ package io.smallrye.graphql.cdi.metrics; import java.time.Duration; -import java.util.Collections; -import java.util.IdentityHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import jakarta.enterprise.inject.spi.CDI; import jakarta.enterprise.util.AnnotationLiteral; -import org.eclipse.microprofile.metrics.Metadata; import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.Tag; import org.eclipse.microprofile.metrics.annotation.RegistryType; +import org.jboss.logging.Logger; import io.smallrye.graphql.api.Context; -import io.smallrye.graphql.cdi.config.ConfigKey; -import io.smallrye.graphql.schema.model.Operation; -import io.smallrye.graphql.spi.EventingService; +import io.smallrye.graphql.spi.MetricsService; /** * Listening for event and create metrics from it. Uses MP Metrics 3.x API. @@ -25,44 +21,17 @@ * @author Jan Martiska (jmartisk@redhat.com) * @author Phillip Kruger (phillip.kruger@redhat.com) */ -public class MPMetricsService implements EventingService { +public class MPMetricsService implements MetricsService { private MetricRegistry metricRegistry; - private final Map startTimes = Collections.synchronizedMap(new IdentityHashMap<>()); + private final Map metricsMemory = new ConcurrentHashMap<>(); private static final String METRIC_NAME = "mp_graphql"; - private final String DESCRIPTION = "Call statistics for the operation denoted by the 'name' tag"; + private Logger LOG = Logger.getLogger(MPMetricsService.class); - @Override - public Operation createOperation(Operation operation) { - final Tag[] tags = getTags(operation); - - Metadata metadata = Metadata.builder() - .withName(METRIC_NAME) - .withType(MetricType.SIMPLE_TIMER) - .withDescription(DESCRIPTION) - .build(); - getMetricRegistry().simpleTimer(metadata, tags); - return operation; - } - - @Override - public void beforeDataFetch(Context context) { - startTimes.put(context, System.nanoTime()); - } - - @Override - public void afterDataFetch(Context context) { - Long startTime = startTimes.remove(context); - if (startTime != null) { - long duration = System.nanoTime() - startTime; - getMetricRegistry().simpleTimer(METRIC_NAME, getTags(context)) - .update(Duration.ofNanos(duration)); - } - } - - @Override - public String getConfigKey() { - return ConfigKey.ENABLE_METRICS; + public MPMetricsService() { + // If MP Metrics are not available, this will throw an exception + // and make sure that this service doesn't get registered + getMetricRegistry(); } private MetricRegistry getMetricRegistry() { @@ -72,20 +41,30 @@ private MetricRegistry getMetricRegistry() { return metricRegistry; } - private Tag[] getTags(Context context) { + private Tag[] getTags(MetricMeasurement metricMeasurement) { return new Tag[] { - new Tag("name", context.getFieldName()), - new Tag("type", context.getOperationType()), - new Tag("source", String.valueOf(context.getSource() != null)) + new Tag("name", metricMeasurement.getName()), + new Tag("type", metricMeasurement.getOperationType()), + new Tag("source", String.valueOf(metricMeasurement.isSource())) }; } - private Tag[] getTags(Operation operation) { - return new Tag[] { - new Tag("name", operation.getName()), - new Tag("type", operation.getOperationType().toString()), - new Tag("source", String.valueOf(operation.isSourceField())) - }; + @Override + public void start(Long measurementId, Context context) { + metricsMemory.put(measurementId, new MetricMeasurement(context.getFieldName(), + context.hasSource(), + context.getOperationType(), + System.nanoTime())); + LOG.tracef("(" + measurementId + ") Started recording metrics for: %s", context.getFieldName()); + } + + @Override + public void end(Long measurementId) { + MetricMeasurement metricMeasurement = metricsMemory.remove(measurementId); + long duration = System.nanoTime() - metricMeasurement.getTimeStarted(); + getMetricRegistry().simpleTimer(METRIC_NAME, getTags(metricMeasurement)) + .update(Duration.ofNanos(duration)); + LOG.tracef("(" + measurementId + ") Finished recording metrics for: %s", metricMeasurement.getName()); } class VendorType extends AnnotationLiteral implements RegistryType { diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java new file mode 100644 index 000000000..b531e1d0b --- /dev/null +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java @@ -0,0 +1,31 @@ +package io.smallrye.graphql.cdi.metrics; + +public class MetricMeasurement { + private String name; + private boolean source; + private String operationType; + private long timeStarted; + + public MetricMeasurement(String name, boolean source, String operationType, long timeStarted) { + this.name = name; + this.source = source; + this.operationType = operationType; + this.timeStarted = timeStarted; + } + + public String getName() { + return name; + } + + public boolean isSource() { + return source; + } + + public String getOperationType() { + return operationType; + } + + public long getTimeStarted() { + return timeStarted; + } +} diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricsService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricsService.java deleted file mode 100644 index ec1e4b47d..000000000 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricsService.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.smallrye.graphql.cdi.metrics; - -import jakarta.annotation.Priority; - -import io.smallrye.graphql.api.Context; -import io.smallrye.graphql.cdi.config.ConfigKey; -import io.smallrye.graphql.execution.event.Priorities; -import io.smallrye.graphql.schema.model.Operation; -import io.smallrye.graphql.spi.EventingService; - -/** - * A wrapper that chooses an underlying service implementation for metrics - * and delegates to that implementation. The implementation is chosen based on - * detecting the availability of particular metrics APIs. - * Right now, it supports Micrometer and MP Metrics 3.x, while Micrometer - * is preferred. - */ -@Priority(Priorities.FIRST_IN_LAST_OUT + 100) -public class MetricsService implements EventingService { - - private final EventingService wrapped; - - public MetricsService() { - // Find out which metrics API is available - EventingService wrappedImpl; - try { - Class.forName("io.micrometer.core.instrument.MeterRegistry"); - wrappedImpl = new MicrometerMetricsService(); - } catch (ClassNotFoundException e) { - try { - Class.forName("org.eclipse.microprofile.metrics.MetricRegistry"); - wrappedImpl = new MPMetricsService(); - } catch (ClassNotFoundException classNotFoundException) { - wrappedImpl = null; - } - } - this.wrapped = wrappedImpl; - } - - @Override - public Operation createOperation(Operation operation) { - return wrapped.createOperation(operation); - } - - @Override - public void beforeDataFetch(Context context) { - wrapped.beforeDataFetch(context); - } - - @Override - public void afterDataFetch(Context context) { - wrapped.afterDataFetch(context); - } - - @Override - public String getConfigKey() { - return ConfigKey.ENABLE_METRICS; - } -} diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java index 1dde0b013..f9e8487c8 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java @@ -1,66 +1,52 @@ package io.smallrye.graphql.cdi.metrics; import java.time.Duration; -import java.util.Collections; -import java.util.IdentityHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jboss.logging.Logger; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.Timer; import io.smallrye.graphql.api.Context; -import io.smallrye.graphql.cdi.config.ConfigKey; -import io.smallrye.graphql.schema.model.Operation; -import io.smallrye.graphql.spi.EventingService; - -public class MicrometerMetricsService implements EventingService { +import io.smallrye.graphql.spi.MetricsService; +public class MicrometerMetricsService implements MetricsService { private final MeterRegistry meterRegistry = Metrics.globalRegistry; - private final Map startTimes = Collections.synchronizedMap(new IdentityHashMap<>()); + private final Map metricsMemory = new ConcurrentHashMap<>(); private static final String METRIC_NAME = "mp_graphql"; - private final String DESCRIPTION = "Call statistics for the operation denoted by the 'name' tag"; + private Logger LOG = Logger.getLogger(MicrometerMetricsService.class); - @Override - public Operation createOperation(Operation operation) { - final Tags tags = getTags(operation); - Timer.builder(METRIC_NAME) - .tags(tags) - .description(DESCRIPTION) - .register(meterRegistry); - return operation; + public MicrometerMetricsService() { + // If Micrometer is not available, this will throw an exception + // and make sure that this service doesn't get registered + meterRegistry.getMeters(); } - @Override - public void beforeDataFetch(Context context) { - startTimes.put(context, System.nanoTime()); + private Tags getTags(MetricMeasurement metricMeasurement) { + return Tags.of("name", metricMeasurement.getName()) + .and("type", metricMeasurement.getOperationType()) + .and("source", String.valueOf(metricMeasurement.isSource())); } @Override - public void afterDataFetch(Context context) { - Long startTime = startTimes.remove(context); - if (startTime != null) { - long duration = System.nanoTime() - startTime; - meterRegistry.timer(METRIC_NAME, getTags(context)) - .record(Duration.ofNanos(duration)); - } + public void start(Long measurementId, Context context) { + metricsMemory.put(measurementId, + new MetricMeasurement( + context.getFieldName(), + context.hasSource(), + context.getOperationType(), + System.nanoTime())); + LOG.tracef("(" + measurementId + ") Started recording metrics for: %s", context.getFieldName()); } @Override - public String getConfigKey() { - return ConfigKey.ENABLE_METRICS; + public void end(Long measurementId) { + MetricMeasurement metricMeasurement = metricsMemory.remove(measurementId); + long duration = System.nanoTime() - metricMeasurement.getTimeStarted(); + meterRegistry.timer(METRIC_NAME, getTags(metricMeasurement)) + .record(Duration.ofNanos(duration)); + LOG.tracef("(" + measurementId + ") Finished recording metrics for: %s", metricMeasurement.getName()); } - - private Tags getTags(Context context) { - return Tags.of("name", context.getFieldName()) - .and("type", context.getOperationType()) - .and("source", String.valueOf(context.getSource() != null)); - } - - private Tags getTags(Operation operation) { - return Tags.of("name", operation.getName()) - .and("type", operation.getOperationType().toString()) - .and("source", String.valueOf(operation.isSourceField())); - } - } diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/tracing/TracingService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/tracing/TracingService.java index b4ccdf13d..0f0713f16 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/tracing/TracingService.java +++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/tracing/TracingService.java @@ -14,7 +14,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import io.smallrye.graphql.api.Context; -import io.smallrye.graphql.cdi.config.ConfigKey; +import io.smallrye.graphql.config.ConfigKey; import io.smallrye.graphql.execution.event.Priorities; import io.smallrye.graphql.spi.EventingService; diff --git a/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.EventingService b/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.EventingService index 08ecb80f8..abec5ede0 100644 --- a/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.EventingService +++ b/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.EventingService @@ -1,3 +1,2 @@ io.smallrye.graphql.cdi.event.EventsService -io.smallrye.graphql.cdi.metrics.MetricsService io.smallrye.graphql.cdi.tracing.TracingService diff --git a/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.MetricsService b/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.MetricsService new file mode 100644 index 000000000..f9b1a0e1e --- /dev/null +++ b/server/implementation-cdi/src/main/resources/META-INF/services/io.smallrye.graphql.spi.MetricsService @@ -0,0 +1,2 @@ +io.smallrye.graphql.cdi.metrics.MicrometerMetricsService +io.smallrye.graphql.cdi.metrics.MPMetricsService \ No newline at end of file diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java b/server/implementation/src/main/java/io/smallrye/graphql/config/ConfigKey.java similarity index 98% rename from server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java rename to server/implementation/src/main/java/io/smallrye/graphql/config/ConfigKey.java index 49936a969..1002571bb 100644 --- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/config/ConfigKey.java @@ -1,4 +1,4 @@ -package io.smallrye.graphql.cdi.config; +package io.smallrye.graphql.config; /** * All the config options available diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java index ce0ec7099..0b134898d 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java @@ -35,6 +35,7 @@ import io.smallrye.graphql.execution.error.ExceptionHandler; import io.smallrye.graphql.execution.error.UnparseableDocumentException; import io.smallrye.graphql.execution.event.EventEmitter; +import io.smallrye.graphql.execution.metrics.MetricsEmitter; import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Schema; import io.smallrye.graphql.schema.model.Type; @@ -59,6 +60,7 @@ public class ExecutionService { private final Schema schema; private final EventEmitter eventEmitter = EventEmitter.getInstance(); + private final MetricsEmitter metricsEmitter = MetricsEmitter.getInstance(); private GraphQL graphQL; diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java index d75a0c518..49188a66a 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java @@ -25,6 +25,7 @@ */ public class SmallRyeContext implements Context { private final String createdBy; + private String fetchId; private JsonObject request; private String executionId; private Field field; diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractAsyncDataFetcher.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractAsyncDataFetcher.java index 1a8de7b08..e903e86bd 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractAsyncDataFetcher.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractAsyncDataFetcher.java @@ -59,6 +59,7 @@ protected O invokeAndTransform( te.appendDataFetcherResult(resultBuilder, dfe); } finally { eventEmitter.fireAfterDataFetch(context); + // metricsEmitter.end(measurementIds.remove()); } } diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractDataFetcher.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractDataFetcher.java index 59d67fa92..20200df2e 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractDataFetcher.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractDataFetcher.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; +import java.util.concurrent.LinkedBlockingQueue; import jakarta.validation.ConstraintViolationException; @@ -20,6 +21,7 @@ import io.smallrye.graphql.execution.datafetcher.helper.FieldHelper; import io.smallrye.graphql.execution.datafetcher.helper.OperationInvoker; import io.smallrye.graphql.execution.event.EventEmitter; +import io.smallrye.graphql.execution.metrics.MetricsEmitter; import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Type; import io.smallrye.graphql.transformation.AbstractDataFetcherException; @@ -41,7 +43,9 @@ public abstract class AbstractDataFetcher implements PlugableBatchableData protected ErrorResultHelper errorResultHelper = new ErrorResultHelper(); protected ArgumentHelper argumentHelper; protected EventEmitter eventEmitter = EventEmitter.getInstance(); + protected MetricsEmitter metricsEmitter = MetricsEmitter.getInstance(); protected BatchLoaderHelper batchLoaderHelper = new BatchLoaderHelper(); + protected LinkedBlockingQueue measurementIds = new LinkedBlockingQueue<>(); public AbstractDataFetcher(Operation operation, Type type) { this.operation = operation; @@ -59,10 +63,8 @@ public T get(final DataFetchingEnvironment dfe) throws Exception { final DataFetcherResult.Builder resultBuilder = DataFetcherResult.newResult() .localContext(dfe.getGraphQlContext()); - try { List transformedArguments = argumentHelper.getArguments(dfe); - return invokeAndTransform(smallRyeContext, dfe, resultBuilder, transformedArguments.toArray()); } catch (AbstractDataFetcherException abstractDataFetcherException) { //Arguments or result couldn't be transformed diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/BatchDataFetcher.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/BatchDataFetcher.java index 56fa519a6..f60a8a8e2 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/BatchDataFetcher.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/BatchDataFetcher.java @@ -13,6 +13,7 @@ import io.smallrye.graphql.execution.datafetcher.helper.ArgumentHelper; import io.smallrye.graphql.execution.datafetcher.helper.BatchLoaderHelper; import io.smallrye.graphql.execution.event.EventEmitter; +import io.smallrye.graphql.execution.metrics.MetricsEmitter; import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Type; import io.smallrye.mutiny.Uni; @@ -30,6 +31,7 @@ public class BatchDataFetcher implements DataFetcher { private final String batchLoaderName; private final BatchLoaderHelper batchLoaderHelper = new BatchLoaderHelper(); private final EventEmitter eventEmitter = EventEmitter.getInstance(); + private final MetricsEmitter metricsEmitter = MetricsEmitter.getInstance(); public BatchDataFetcher(Operation operation, Type type) { this.operation = operation; @@ -43,6 +45,8 @@ public T get(final DataFetchingEnvironment dfe) throws Exception { SmallRyeContext smallryeContext = SmallRyeContextManager.populateFromDataFetchingEnvironment(type, operation, dfe); eventEmitter.fireBeforeDataFetch(smallryeContext); + long measurementId = metricsEmitter.start(smallryeContext); + List transformedArguments = argumentHelper.getArguments(dfe, true); Object source = dfe.getSource(); @@ -52,7 +56,8 @@ public T get(final DataFetchingEnvironment dfe) throws Exception { batchContext.put(BatchLoaderHelper.ARGUMENTS, transformedArguments); batchContext.put(BatchLoaderHelper.DATA_FETCHING_ENVIRONMENT, dfe); - return (T) Uni.createFrom().completionStage(() -> dataLoader.load(source, batchContext)) + return (T) Uni.createFrom().completionStage(() -> dataLoader.load(source, batchContext)).onItemOrFailure() + .invoke(() -> metricsEmitter.end(measurementId)) .subscribe() .asCompletionStage(); diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java index 60585f1b3..31843d38f 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java @@ -70,7 +70,6 @@ private EventEmitter() { } enabledServices.sort(Comparator.comparing(this::getPriority)); this.enabledServices = enabledServices; - LOG.debugf("Enabled Eventingservices: %s", enabledServices); } private int getPriority(EventingService es) { diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java new file mode 100644 index 000000000..518b6caae --- /dev/null +++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java @@ -0,0 +1,54 @@ +package io.smallrye.graphql.execution.metrics; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; +import java.util.concurrent.ThreadLocalRandom; + +import org.jboss.logging.Logger; + +import io.smallrye.graphql.api.Context; +import io.smallrye.graphql.config.ConfigKey; +import io.smallrye.graphql.spi.MetricsService; +import io.smallrye.graphql.spi.config.Config; + +public class MetricsEmitter { + + private static final Logger LOG = Logger.getLogger(MetricsEmitter.class); + private final List enabledServices; + + public static MetricsEmitter getInstance() { + return new MetricsEmitter(); + } + + private MetricsEmitter() { + Config config = Config.get(); + boolean enabled = config.getConfigValue(ConfigKey.ENABLE_METRICS, boolean.class, false); + + // Find out which metrics API is available + ServiceLoader metricService = ServiceLoader.load(MetricsService.class); + Iterator it = metricService.iterator(); + List enabledServices = new ArrayList<>(); + + while (enabled && it.hasNext()) { + try { + enabledServices.add(it.next()); + } catch (Throwable t) { + // Ignore that service... + } + } + + this.enabledServices = enabledServices; + } + + public Long start(Context context) { + Long measurementId = ThreadLocalRandom.current().nextLong(); + enabledServices.forEach(metricsService -> metricsService.start(measurementId, context)); + return measurementId; + } + + public void end(Long measurementId) { + enabledServices.forEach(metricsService -> metricsService.end(measurementId)); + } +} diff --git a/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java b/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java new file mode 100644 index 000000000..1e1cb2393 --- /dev/null +++ b/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java @@ -0,0 +1,9 @@ +package io.smallrye.graphql.spi; + +import io.smallrye.graphql.api.Context; + +public interface MetricsService { + void start(Long measurementId, Context context); + + void end(Long measurementId); +} diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/DummyGraphQLApi.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/DummyGraphQLApi.java deleted file mode 100644 index 859b6caf8..000000000 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/DummyGraphQLApi.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.smallrye.graphql.tests.metrics; - -import jakarta.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.graphql.GraphQLApi; -import org.eclipse.microprofile.graphql.Mutation; -import org.eclipse.microprofile.graphql.Name; -import org.eclipse.microprofile.graphql.Query; -import org.eclipse.microprofile.graphql.Source; - -@GraphQLApi -@ApplicationScoped -public class DummyGraphQLApi { - - private Foo foo = new Foo(); - - @Query(value = "get") - public Foo helloQuery() { - return foo; - } - - @Mutation(value = "mutate") - public Foo mutation() { - foo.update(); - return foo; - } - - @Name("description") - public String source(@Source Foo foo) { - return "Awesome"; - } - -} diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/Foo.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/Foo.java deleted file mode 100644 index 127802d3a..000000000 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/Foo.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.smallrye.graphql.tests.metrics; - -import java.util.concurrent.atomic.AtomicInteger; - -public class Foo { - - private AtomicInteger version = new AtomicInteger(); - - public Integer getVersion() { - return version.get(); - } - - void update() { - version.incrementAndGet(); - } - -} diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MetricTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MetricTest.java deleted file mode 100644 index 82ee53334..000000000 --- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MetricTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.smallrye.graphql.tests.metrics; - -import java.net.URL; - -import jakarta.inject.Inject; - -import org.eclipse.microprofile.metrics.MetricID; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.SimpleTimer; -import org.eclipse.microprofile.metrics.Tag; -import org.eclipse.microprofile.metrics.annotation.RegistryType; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.container.test.api.RunAsClient; -import org.jboss.arquillian.junit.Arquillian; -import org.jboss.arquillian.junit.InSequence; -import org.jboss.arquillian.test.api.ArquillianResource; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.smallrye.graphql.tests.GraphQLAssured; - -@RunWith(Arquillian.class) -public class MetricTest { - - @Deployment - public static WebArchive deployment() { - return ShrinkWrap.create(WebArchive.class, "metrics-test.war") - .addAsResource(new StringAsset("smallrye.graphql.metrics.enabled=true"), - "META-INF/microprofile-config.properties") - .addClasses(DummyGraphQLApi.class, Foo.class); - } - - @Inject - @RegistryType(type = MetricRegistry.Type.VENDOR) - MetricRegistry metricRegistry; - - @ArquillianResource - URL testingURL; - - //@Test - //@InSequence(99) - public void verifyMetricsAreRegisteredEagerly() { - SimpleTimer metricForHelloQuery = metricRegistry.getSimpleTimers().get(new MetricID("mp_graphql", - new Tag("type", "QUERY"), new Tag("name", "get"), new Tag("source", "false"))); - Assert.assertNotNull("Metric should be registered eagerly (Query)", metricForHelloQuery); - - SimpleTimer metricForMutation = metricRegistry.getSimpleTimers().get(new MetricID("mp_graphql", - new Tag("type", "MUTATION"), new Tag("name", "mutate"), new Tag("source", "false"))); - Assert.assertNotNull("Metric should be registered eagerly (Mutation)", metricForMutation); - - SimpleTimer metricForSource = metricRegistry.getSimpleTimers().get(new MetricID("mp_graphql", - new Tag("type", "QUERY"), new Tag("name", "description"), new Tag("source", "true"))); - Assert.assertNotNull("Metric should be registered eagerly (Source)", metricForSource); - } - - @Test - @RunAsClient - @InSequence(100) - public void invokeApi() throws Exception { - GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL); - graphQLAssured.post("{get {version}}"); - graphQLAssured.post("{get {version}}"); - graphQLAssured.post("mutation {mutate{version}}"); - graphQLAssured.post("mutation {mutate{version}}"); - graphQLAssured.post("{get {description}}"); - } - - //@Test - //@InSequence(101) - public void verifyMetricsAreUpdated() { - SimpleTimer metricForGetQuery = metricRegistry.getSimpleTimers().get(new MetricID("mp_graphql", - new Tag("type", "QUERY"), new Tag("name", "get"), new Tag("source", "false"))); - Assert.assertEquals("The 'get' query was called three times, this should be reflected in metric value", - 3, metricForGetQuery.getCount()); - Assert.assertTrue("Total elapsed time for query should be greater than zero", - metricForGetQuery.getElapsedTime().toNanos() > 0); - - SimpleTimer metricForMutation = metricRegistry.getSimpleTimers().get(new MetricID("mp_graphql", - new Tag("type", "MUTATION"), new Tag("name", "mutate"), new Tag("source", "false"))); - Assert.assertEquals("The query was called twice, this should be reflected in metric value", - 2, metricForMutation.getCount()); - Assert.assertTrue("Total elapsed time for query should be greater than zero", - metricForMutation.getElapsedTime().toNanos() > 0); - - SimpleTimer metricForSource = metricRegistry.getSimpleTimers().get(new MetricID("mp_graphql", - new Tag("type", "QUERY"), new Tag("name", "description"), new Tag("source", "true"))); - Assert.assertEquals("The get{description} query was called once, this should be reflected in metric value", - 1, metricForSource.getCount()); - Assert.assertTrue("Total elapsed time for query should be greater than zero", - metricForSource.getElapsedTime().toNanos() > 0); - } - -}