From 64dcb2398e8bcb062a232d66eed4c21cd42598e4 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 20 Mar 2021 12:07:46 -0500 Subject: [PATCH 01/74] Combine the formerly-separate literal for the SyntheticSimplyTimed with the annotation instead --- .../metrics/LiteralSyntheticSimplyTimed.java | 36 ------------------- .../metrics/SyntheticSimplyTimed.java | 20 ++++++++++- 2 files changed, 19 insertions(+), 37 deletions(-) delete mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/LiteralSyntheticSimplyTimed.java diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/LiteralSyntheticSimplyTimed.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/LiteralSyntheticSimplyTimed.java deleted file mode 100644 index 81473b33338..00000000000 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/LiteralSyntheticSimplyTimed.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package io.helidon.microprofile.metrics; - -import javax.enterprise.util.AnnotationLiteral; - -/** - * Implementation of the synthetic {@code REST.request} {@code SimpleTimer} metric {@link SyntheticSimplyTimed}. - */ -class LiteralSyntheticSimplyTimed extends AnnotationLiteral implements SyntheticSimplyTimed { - - private static final long serialVersionUID = 1L; - - private static final LiteralSyntheticSimplyTimed INSTANCE = new LiteralSyntheticSimplyTimed(); - - static LiteralSyntheticSimplyTimed getInstance() { - return INSTANCE; - } - - private LiteralSyntheticSimplyTimed() { - } -} diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticSimplyTimed.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticSimplyTimed.java index d694116245c..87dfcf47e50 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticSimplyTimed.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticSimplyTimed.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.enterprise.util.AnnotationLiteral; import javax.interceptor.InterceptorBinding; @Inherited @@ -34,4 +35,21 @@ * and updated when the method is invoked. */ @interface SyntheticSimplyTimed { + + /** + * Implementation of the synthetic {@code REST.request} {@code SimpleTimer} metric {@link SyntheticSimplyTimed}. + */ + class Literal extends AnnotationLiteral implements SyntheticSimplyTimed { + + private static final long serialVersionUID = 1L; + + private static final Literal INSTANCE = new Literal(); + + static Literal getInstance() { + return INSTANCE; + } + + private Literal() { + } + } } From 17957a53ed4ca9191c1a5ffd59037b61282d95de Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 20 Mar 2021 12:10:00 -0500 Subject: [PATCH 02/74] Introduce interface and general-purpose impl for intercept runner abstraction --- .../microprofile/metrics/InterceptRunner.java | 106 +++++++++++ .../metrics/InterceptRunnerImpl.java | 171 ++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunnerImpl.java diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java new file mode 100644 index 00000000000..23ce17f9579 --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import javax.interceptor.InvocationContext; + +/** + * Abstraction of processing around an interception point, independent from the details of any + * particular interceptor or the specific type of work done (e.g., updating metrics) before the intercepted invocation runs and + * after it completes. + *

+ * To use {@code InterceptRunner}, clients: + *

    + *
  • Create an instance of a class which implements {@code InterceptorRunner}.
  • + *
  • From the interceptor's {@code @AroundConstruct} and {@code @AroundInvoke} methods, invoke one of the variants of + * the runner's {@link #run(InvocationContext, Supplier, BiConsumer) run} method. Which variant depends on whether the + * specific interceptor needs to operate on the work items + *
      + *
    • only before (e.g., to increment a counter metric), or
    • + *
    • both + * before and after (e.g., to update a metric that measures time spent in the intercepted method)
    • + *
    + * the intercepted + * method + * runs. + *

    + * The interceptor passes the {@code run} method: + *

      + *
    • a {@code Supplier>} of the work items,
    • + *
    • a pre-invoke {@code Consumer} of work item which performs an action on each work item before the + * intercepted invocation runs, and
    • + *
    • an after-completion {@code Consumer} of work item which performs an action on each work item after the + * intercepted invocation has finished, only for the "before-and-after" + * {@link #run(InvocationContext, Supplier, BiConsumer, BiConsumer) run} variant.
    • + *
    + *

    + *
  • + *
+ *

+ *

+ * The runner + *

    + *
  1. invokes the pre-invoke consumer for all work items,
  2. + *
  3. invokes the intercepted executable, then
  4. + *
  5. (if provided) invokes the after-completion consumer for all work + * items.
  6. + *
+ *

+ *

+ * The interface requires a {@code Supplier>} for work items because, in the before-and-after case, the runner + * might need to process the work items twice. In those cases, the {@code Supplier} might be accessed twice. + *

+ */ +interface InterceptRunner { + + /** + * Invokes the intercepted executable represented by the {@code InvocationContext}, performing the pre-invoke + * operation on each work item. + * + * @param context {@code InvocationContext} for the intercepted invocation + * @param workItems the work items the interceptor will operate on + * @param preInvokeHandler the pre-invoke operation to perform on each work item + * @param type of the work items + * @return the return value from the invoked executable + * @throws Exception for any error thrown by the {@code Iterable} of work items or the invoked executable itself + */ + Object run( + InvocationContext context, + Supplier> workItems, + BiConsumer preInvokeHandler) throws Exception; + + /** + * Invokes the intercepted executable represented by the {@code InvocationContext}, performing the pre-invoke + * and completion operation on each work item. + * + * @param context {@code InvocationContext} for the intercepted invocation + * @param workItems the work items the interceptor will operate on + * @param preInvokeHandler the pre-invoke operation to perform on each work item + * @param completeHandler the post-completion operation to perform on each work item + * @param type of the work items + * @return the return value from the invoked executable + * @throws Exception for any error thrown by the {@code Iterable} of work items or the invoked executable itself + */ + Object run( + InvocationContext context, + Supplier> workItems, + BiConsumer preInvokeHandler, + BiConsumer completeHandler) throws Exception; +} diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunnerImpl.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunnerImpl.java new file mode 100644 index 00000000000..bd0f3b3f2b1 --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunnerImpl.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.interceptor.InvocationContext; +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.CompletionCallback; +import javax.ws.rs.container.Suspended; + +/** + * A general-purpose implementation of {@link InterceptRunner}, supporting asynchronous JAX-RS endpoints as indicated by the + * presence of a {@code @Suspended AsyncResponse} parameter. + */ +class InterceptRunnerImpl implements InterceptRunner { + + /* + * In this impl, constructor runners and synchronous method runners are identical and have no saved context at all, so we + * can use the same instance for all except async method runners. + */ + private static final InterceptRunner INSTANCE = new InterceptRunnerImpl(); + + /** + * Returns the appropriate {@code InterceptRunner} for the executable. + * + * @param executable the {@code Constructor} or {@code Method} requiring interceptor support + * @return the {@code InterceptRunner} + */ + static InterceptRunner create(Executable executable) { + if (executable instanceof Constructor) { + return INSTANCE; + } + if (executable instanceof Method) { + final int asyncResponseSlot = InterceptRunnerImpl.asyncResponseSlot((Method) executable); + return asyncResponseSlot >= 0 + ? AsyncMethodRunnerImpl.create(asyncResponseSlot) + : INSTANCE; + } + throw new IllegalArgumentException("Executable " + executable.getName() + " is not a constructor or method"); + } + + @Override + public Object run( + InvocationContext context, + Supplier> workItems, + BiConsumer preInvokeHandler) throws Exception { + workItems.get() + .forEach(workItem -> preInvokeHandler.accept(context, workItem)); + return context.proceed(); + } + + @Override + public Object run( + InvocationContext context, + Supplier> workItems, + BiConsumer preInvokeHandler, + BiConsumer completeHandler) throws Exception { + workItems.get() + .forEach(workItem -> preInvokeHandler.accept(context, workItem)); + try { + return context.proceed(); + } finally { + workItems.get() + .forEach(workItem -> completeHandler.accept(context, workItem)); + } + } + + /** + * An {@code InterceptorRunner} which supports JAX-RS asynchronous methods. + */ + private static class AsyncMethodRunnerImpl extends InterceptRunnerImpl { + private final int asyncResponseSlot; + + static InterceptRunner create(int asyncResponseSlot) { + return new AsyncMethodRunnerImpl(asyncResponseSlot); + } + + private AsyncMethodRunnerImpl(int asyncResponseSlot) { + this.asyncResponseSlot = asyncResponseSlot; + } + + @Override + public Object run( + InvocationContext context, + Supplier> workItems, + BiConsumer preInvokeHandler, + BiConsumer completeHandler) throws Exception { + + // Check the post-invoke handler now because we don't want an NPE thrown from some other call stack when we try to use + // it in the completion callback. Any other null argument would trigger an NPE from the current call stack. + Objects.requireNonNull(completeHandler, "postInvokeHandler"); + + workItems.get() + .forEach(workItem -> preInvokeHandler.accept(context, workItem)); + AsyncResponse asyncResponse = AsyncResponse.class.cast(context.getParameters()[asyncResponseSlot]); + asyncResponse.register(FinishCallback.create(context, completeHandler, workItems)); + return context.proceed(); + } + + @Override + public String toString() { + return new StringJoiner(", ", AsyncMethodRunnerImpl.class.getSimpleName() + "[", "]") + .add("asyncResponseSlot=" + asyncResponseSlot) + .toString(); + } + } + + private static class FinishCallback implements CompletionCallback { + + private static final Logger LOGGER = Logger.getLogger(FinishCallback.class.getName()); + + private final InvocationContext context; + private final BiConsumer completeHandler; + private final Supplier> workItems; + + static FinishCallback create(InvocationContext context, BiConsumer completeHandler, + Supplier> workItems) { + return new FinishCallback<>(context, completeHandler, workItems); + } + private FinishCallback(InvocationContext context, BiConsumer completeHandler, + Supplier> workItems) { + this.context = context; + this.completeHandler = completeHandler; + this.workItems = workItems; + } + + @Override + public void onComplete(Throwable throwable) { + workItems.get().forEach(workItem -> completeHandler.accept(context, workItem)); + if (throwable != null) { + LOGGER.log(Level.FINE, "Throwable detected by interceptor async callback", throwable); + } + } + } + + private static int asyncResponseSlot(Method interceptedMethod) { + int result = 0; + + for (Parameter p : interceptedMethod.getParameters()) { + if (AsyncResponse.class.isAssignableFrom(p.getType()) && p.getAnnotation(Suspended.class) != null) { + return result; + } + result++; + } + return -1; + } +} From 277f318fb7471dc949e5a1562cb795037dc97971 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 20 Mar 2021 12:16:41 -0500 Subject: [PATCH 03/74] Introduce InterceptInfo which gathers info about each intercepted executable (method or constructor); use that from the CDI extension in finding and storing all interception points and (importantly) the metrics to be updated at each; revise some utility methods that retreive annotation info from executables to return more than one instance --- .../microprofile/metrics/InterceptInfo.java | 85 +++++++ .../microprofile/metrics/MetricUtil.java | 13 +- .../metrics/MetricsCdiExtension.java | 240 +++++++++++------- 3 files changed, 233 insertions(+), 105 deletions(-) create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java new file mode 100644 index 00000000000..a370e039129 --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Executable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Records information about an intercepted method. + *

+ * Specifically: + *

    + *
  • the work items to be updated when the corresponding method is intercepted, organized by the annotation class that + * gave rise to each work item; and
  • + *
  • the {@code InterceptRunner} to use in updating the work items and invoking the method or constructor.
  • + *
+ *

+ * + * @param base type of the work items handled by the interceptor represented by this instance + */ +class InterceptInfo { + + private final InterceptRunner runner; + + private final Map, Collection> workItemsByAnnotationType = new HashMap<>(); + + /** + * Creates a new instance based on the provided {@code Executable}. + * + * @param executable the constructor or method subject to interception + * @return the new instance + */ + static InterceptInfo create(Executable executable) { + return new InterceptInfo<>(InterceptRunnerImpl.create(executable)); + } + + private InterceptInfo(InterceptRunner runner) { + this.runner = runner; + } + + InterceptRunner runner() { + return runner; + } + + Supplier> workItems(Class annotationType) { + /* + * Build a supplier of the iterable, because before-and-after runners will need to process the work items twice so we need + * to give the runner a supplier. + */ + return () -> workItemsByAnnotationType.get(annotationType); + } + + /** + * Adds a work item to this info, identifying the interceptor type that will update this work item. + * @param annotationClass type of the interceptor + * @param workItem the newly-created workItem + */ + void addWorkItem(Class annotationClass, T workItem) { + + // Using a set for the actual collection subtly handles the case where a class-level and a method- or constructor-level + // annotation both indicate the same workItem. We do not want to update the same workItem twice in that case. + + Collection workItems = workItemsByAnnotationType.computeIfAbsent(annotationClass, c -> new HashSet<>()); + workItems.add(workItem); + } +} diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java index 5a9db28a709..cc35fb9f7ac 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; @@ -98,12 +99,10 @@ static LookupResult lookupAnnotation( static List> lookupAnnotations( AnnotatedType annotatedType, - AnnotatedMethod annotatedMethod, + AnnotatedMember annotatedMember, Class annotClass) { - List> result = lookupAnnotations(annotatedMethod, annotClass); - if (result.isEmpty()) { - result = lookupAnnotations(annotatedType, annotClass); - } + List> result = lookupAnnotations(annotatedMember, annotClass); + result.addAll(lookupAnnotations(annotatedType, annotClass)); return result; } @@ -303,7 +302,7 @@ public enum MatchingType { private static MatchingType matchingType(Annotated annotated) { return annotated instanceof AnnotatedMember - ? (((AnnotatedMember) annotated).getJavaMember() instanceof Method + ? (((AnnotatedMember) annotated).getJavaMember() instanceof Executable ? MatchingType.METHOD : MatchingType.CLASS) : MatchingType.CLASS; } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java index 2d6887fff6d..dcde8d8263c 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java @@ -19,6 +19,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -30,8 +31,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.logging.Level; import java.util.logging.Logger; @@ -61,7 +64,6 @@ import javax.inject.Qualifier; import javax.inject.Singleton; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HEAD; @@ -69,8 +71,6 @@ import javax.ws.rs.PATCH; import javax.ws.rs.POST; import javax.ws.rs.PUT; -import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.container.Suspended; import io.helidon.common.Errors; import io.helidon.common.context.Contexts; @@ -104,7 +104,6 @@ import static io.helidon.microprofile.metrics.MetricUtil.LookupResult; import static io.helidon.microprofile.metrics.MetricUtil.getMetricName; -import static io.helidon.microprofile.metrics.MetricUtil.lookupAnnotation; import static javax.interceptor.Interceptor.Priority.LIBRARY_BEFORE; /** @@ -134,6 +133,8 @@ public class MetricsCdiExtension implements Extension { .notReusable() .build(); + private boolean restEndpointsMetricsEnabled = REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE; + private final Map, AnnotatedMember> producers = new HashMap<>(); private final Map> annotatedGaugeSites = new HashMap<>(); @@ -145,7 +146,8 @@ public class MetricsCdiExtension implements Extension { private final Map, Set> methodsWithSyntheticSimpleTimer = new HashMap<>(); private final Set> syntheticSimpleTimerClassesProcessed = new HashSet<>(); private final Set syntheticSimpleTimersToRegister = new HashSet<>(); - private final Map asyncSyntheticSimpleTimerInfo = new HashMap<>(); + + private final Map> interceptInfo = new HashMap<>(); @SuppressWarnings("unchecked") private static T getReference(BeanManager bm, Type type, Bean bean) { @@ -164,9 +166,18 @@ private static T getReference(BeanManager bm, Type type, Bean bean) { @Deprecated public static void registerMetric(E element, Class clazz, LookupResult lookupResult) { + registerMetricInternal(element, clazz, lookupResult); + } + + static MetricInfo registerMetricInternal(E element, Class clazz, + LookupResult lookupResult) { MetricRegistry registry = getMetricRegistry(); Annotation annotation = lookupResult.getAnnotation(); + String savedMetricName = null; + org.eclipse.microprofile.metrics.Metric metric = null; + Tag[] tags = null; + if (annotation instanceof Counted) { Counted counted = (Counted) annotation; String metricName = getMetricName(element, clazz, lookupResult.getType(), counted.name().trim(), @@ -180,7 +191,9 @@ void registerMetric(E element, Class clazz, LookupResult "Registered counter " + metricName); } else if (annotation instanceof Metered) { Metered metered = (Metered) annotation; @@ -195,7 +208,9 @@ void registerMetric(E element, Class clazz, LookupResult "Registered meter " + metricName); } else if (annotation instanceof Timed) { Timed timed = (Timed) annotation; @@ -210,7 +225,10 @@ void registerMetric(E element, Class clazz, LookupResult "Registered timer " + metricName); } else if (annotation instanceof ConcurrentGauge) { ConcurrentGauge concurrentGauge = (ConcurrentGauge) annotation; @@ -225,7 +243,9 @@ void registerMetric(E element, Class clazz, LookupResult "Registered concurrent gauge " + metricName); } else if (annotation instanceof SimplyTimed) { SimplyTimed simplyTimed = (SimplyTimed) annotation; @@ -240,9 +260,16 @@ void registerMetric(E element, Class clazz, LookupResult "Registered simple timer " + metricName); } + if (savedMetricName == null || metric == null || tags == null) { + throw new IllegalArgumentException(String.format("Cannot map annotation %s on %s to metric type", + annotation.annotationType().getSimpleName(), element)); + } + return new MetricInfo<>(new MetricID(savedMetricName, tags), metric); } private static Tag[] tags(String[] tagStrings) { @@ -258,12 +285,8 @@ private static Tag[] tags(String[] tagStrings) { return result.toArray(new Tag[result.size()]); } - static String[] tags(Tag[] tags) { - final List result = new ArrayList<>(); - for (int i = 0; i < tags.length; i++) { - result.add(tags[i].getTagName() + "=" + tags[i].getTagValue()); - } - return result.toArray(new String[0]); + InterceptInfo interceptInfo(Executable executable) { + return interceptInfo.get(executable); } /** @@ -313,15 +336,7 @@ void before(@Observes BeforeBeanDiscovery discovery) { discovery.addAnnotatedType(InterceptorSyntheticSimplyTimed.class, InterceptorSyntheticSimplyTimed.class.getSimpleName()); discovery.addAnnotatedType(SyntheticSimplyTimed.class, SyntheticSimplyTimed.class.getSimpleName()); - // Config might disable the MP synthetic SimpleTimer feature for JAX-RS endpoints. - // For efficiency, prepare to consult config only once rather than from each interceptor instance. - discovery.addAnnotatedType(RestEndpointMetricsInfo.class, RestEndpointMetricsInfo.class.getSimpleName()); - - asyncSyntheticSimpleTimerInfo.clear(); - } - - Map asyncResponseInfo() { - return asyncSyntheticSimpleTimerInfo; + restEndpointsMetricsEnabled = restEndpointsMetricsEnabled(); } private void clearAnnotationInfo(@Observes AfterDeploymentValidation adv) { @@ -374,7 +389,7 @@ private boolean checkCandidateMetricClass(ProcessAnnotatedType pat) { + (annotatedType.isAnnotationPresent(Interceptor.class) ? "interceptor " : "")); return false; } - LOGGER.log(Level.FINE, () -> "Accepting " + clazz.getName() + " for later bean processing"); + LOGGER.log(Level.FINE, () -> "Accepting annotated type " + clazz.getName() + " for later bean processing"); return true; } @@ -416,37 +431,39 @@ private void registerMetrics(@Observes ProcessManagedBean pmb) { // Process methods keeping non-private declared on this class for (AnnotatedMethod annotatedMethod : type.getMethods()) { + Method method = annotatedMethod.getJavaMember(); + if (Modifier.isPrivate(annotatedMethod.getJavaMember().getModifiers())) { continue; } - METRIC_ANNOTATIONS.forEach(annotation -> { - for (LookupResult lookupResult : MetricUtil.lookupAnnotations( - type, annotatedMethod, annotation)) { - // For methods, register the metric only on the declaring - // class, not subclasses per the MP Metrics 2.0 TCK - // VisibilityTimedMethodBeanTest. - if (lookupResult.getType() != MetricUtil.MatchingType.METHOD - || clazz.equals(annotatedMethod.getJavaMember() - .getDeclaringClass())) { - registerMetric(annotatedMethod.getJavaMember(), clazz, lookupResult); - } - } - }); + METRIC_ANNOTATIONS.forEach(annotation -> + MetricUtil.lookupAnnotations(type, annotatedMethod, annotation).forEach(lookupResult -> { + // For methods, register the metric only on the declaring + // class, not subclasses per the MP Metrics 2.0 TCK + // VisibilityTimedMethodBeanTest. + if (lookupResult.getType() != MetricUtil.MatchingType.METHOD + || clazz.equals(method.getDeclaringClass())) { + InterceptInfo info = interceptInfo.computeIfAbsent(method, + m -> InterceptInfo.create(method)); + MetricInfo metricInfo = registerMetricInternal(method, clazz, lookupResult); + info.addWorkItem(annotation, MetricWorkItem.create(metricInfo.metricID, metricInfo.metric)); + } + })); } // Process constructors for (AnnotatedConstructor annotatedConstructor : type.getConstructors()) { - Constructor c = annotatedConstructor.getJavaMember(); + Constructor c = annotatedConstructor.getJavaMember(); if (Modifier.isPrivate(c.getModifiers())) { continue; } - METRIC_ANNOTATIONS.forEach(annotation -> { - LookupResult lookupResult - = lookupAnnotation(c, annotation, clazz); - if (lookupResult != null) { - registerMetric(c, clazz, lookupResult); - } - }); + METRIC_ANNOTATIONS.forEach(annotation -> + MetricUtil.lookupAnnotations(type, annotatedConstructor, annotation).forEach(lookupResult -> { + InterceptInfo info = interceptInfo.computeIfAbsent(c, + InterceptInfo::create); + MetricInfo metricInfo = registerMetricInternal(c, clazz, lookupResult); + info.addWorkItem(annotation, MetricWorkItem.create(metricInfo.metricID, metricInfo.metric)); + })); } } @@ -472,12 +489,12 @@ private void recordSimplyTimedForRestResources(@Observes /// Ignore abstract classes or interceptors. Make sure synthetic SimpleTimer creation is enabled, and if so record the // class and JAX-RS methods to use in later bean processing. if (!checkCandidateMetricClass(pat) - || !restEndpointsMetricEnabledFromConfig()) { + || !restEndpointsMetricsEnabled) { return; } LOGGER.log(Level.FINE, - () -> "Processing SyntheticSimplyTimed annotation for " + pat.getAnnotatedType() + () -> "Processing @SyntheticSimplyTimed annotation for " + pat.getAnnotatedType() .getJavaClass() .getName()); @@ -490,19 +507,18 @@ private void recordSimplyTimedForRestResources(@Observes // Process methods keeping non-private declared on this class configurator.filterMethods(method -> !Modifier.isPrivate(method.getJavaMember() .getModifiers())) - .forEach(method -> + .forEach(annotatedMethodConfigurator -> JAX_RS_ANNOTATIONS.forEach(jaxRsAnnotation -> { - AnnotatedMethod annotatedMethod = method.getAnnotated(); + AnnotatedMethod annotatedMethod = annotatedMethodConfigurator.getAnnotated(); if (annotatedMethod.isAnnotationPresent(jaxRsAnnotation)) { Method m = annotatedMethod.getJavaMember(); // For methods, add the SyntheticSimplyTimed annotation only on the declaring // class, not subclasses. if (clazz.equals(m.getDeclaringClass())) { - LOGGER.log(Level.FINE, () -> String.format("Adding @SyntheticSimplyTimed to %s#%s", clazz.getName(), - m.getName())); - // Add the synthetic annotation to this method's configurator and record this Java method. - method.add(LiteralSyntheticSimplyTimed.getInstance()); + LOGGER.log(Level.FINE, () -> String.format("Adding @SyntheticSimplyTimed to %s", + m.toString())); + annotatedMethodConfigurator.add(SyntheticSimplyTimed.Literal.getInstance()); methodsToRecord.add(m); } } @@ -527,23 +543,12 @@ static SimpleTimer syntheticSimpleTimer(Method method) { .simpleTimer(SYNTHETIC_SIMPLE_TIMER_METADATA, syntheticSimpleTimerMetricTags(method)); } - private SimpleTimer registerAndSaveAsyncSyntheticSimpleTimer(Method method) { - SimpleTimer result = syntheticSimpleTimer(method); - asyncSyntheticSimpleTimerInfo.computeIfAbsent(method, this::asyncResponse); - return result; - } + private void registerAndSaveSyntheticSimpleTimer(Method method) { + InterceptInfo info = interceptInfo.computeIfAbsent(method, m -> InterceptInfo.create(method)); - private AsyncResponseInfo asyncResponse(Method m) { - int candidateAsyncResponseParameterSlot = 0; - - for (Parameter p : m.getParameters()) { - if (AsyncResponse.class.isAssignableFrom(p.getType()) && p.getAnnotation(Suspended.class) != null) { - return new AsyncResponseInfo(candidateAsyncResponseParameterSlot); - } - candidateAsyncResponseParameterSlot++; - - } - return null; + info.addWorkItem(SyntheticSimplyTimed.class, + MetricWorkItem.create(SYNTHETIC_SIMPLE_TIMER_METADATA, syntheticSimpleTimer(method), + syntheticSimpleTimerMetricTags(method))); } /** @@ -684,8 +689,12 @@ private void collectSyntheticSimpleTimerMetric(@Observes ProcessManagedBean p syntheticSimpleTimersToRegister.addAll(methodsWithSyntheticSimpleTimer.get(clazz)); } - private void registerSyntheticSimpleTimerMetrics(@Observes @RuntimeStart Object event) { - syntheticSimpleTimersToRegister.forEach(this::registerAndSaveAsyncSyntheticSimpleTimer); + private void runtimeStart(@Observes @RuntimeStart Object event) { + registerSyntheticSimpleTimerMetrics(); + } + + private void registerSyntheticSimpleTimerMetrics() { + syntheticSimpleTimersToRegister.forEach(this::registerAndSaveSyntheticSimpleTimer); if (LOGGER.isLoggable(Level.FINE)) { Set> syntheticSimpleTimerAnnotatedClassesIgnored = new HashSet<>(methodsWithSyntheticSimpleTimer.keySet()); syntheticSimpleTimerAnnotatedClassesIgnored.removeAll(syntheticSimpleTimerClassesProcessed); @@ -699,12 +708,10 @@ private void registerSyntheticSimpleTimerMetrics(@Observes @RuntimeStart Object syntheticSimpleTimersToRegister.clear(); } - boolean restEndpointsMetricEnabledFromConfig() { + boolean restEndpointsMetricsEnabled() { try { - return ((Config) (ConfigProvider.getConfig())) - .get("metrics") - .get(REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME) - .asBoolean().orElse(REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE); + return chooseRestEndpointsSetting(((Config) (ConfigProvider.getConfig())) + .get("metrics")); } catch (Throwable t) { LOGGER.log(Level.WARNING, "Error looking up config setting for enabling REST endpoints SimpleTimer metrics;" + " reporting 'false'", t); @@ -761,11 +768,11 @@ private static boolean chooseRestEndpointsSetting(Config metricsConfig) { boolean result = explicitRestEndpointsSetting.orElse(REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE); if (explicitRestEndpointsSetting.isPresent()) { LOGGER.log(Level.FINE, () -> String.format( - "Support for MP REST.reqeust metric and annotation handling explicitly set to %b in configuration", + "Support for MP REST.request metric and annotation handling explicitly set to %b in configuration", explicitRestEndpointsSetting.get())); } else { LOGGER.log(Level.FINE, () -> String.format( - "Support for MP REST.reqeust metric and annotation handling explicitly defaulted to %b", + "Support for MP REST.request metric and annotation handling defaulted to %b", REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE)); } return result; @@ -792,7 +799,7 @@ private void recordAnnotatedGaugeSite(@Observes ProcessManagedBean pmb) { AnnotatedType type = pmb.getAnnotatedBeanClass(); Class clazz = type.getJavaClass(); - LOGGER.log(Level.FINE, () -> "recordAnnoatedGaugeSite for class " + clazz); + LOGGER.log(Level.FINE, () -> "recordAnnotatedGaugeSite for class " + clazz); LOGGER.log(Level.FINE, () -> "Processing annotations for " + clazz.getName()); // Register metrics based on annotations @@ -964,27 +971,64 @@ public boolean isSynthetic() { } } - /** - * A {@code AsyncResponse} parameter annotated with {@code Suspended} in a JAX-RS method subject to inferred - * {@code SimplyTimed} behavior. - */ - static class AsyncResponseInfo { + static class MetricWorkItem { + + private final MetricID metricID; + private final org.eclipse.microprofile.metrics.Metric metric; + + static MetricWorkItem create( + Metadata metadata, org.eclipse.microprofile.metrics.Metric metric, Tag... tags) { + MetricID metricID = new MetricID(metadata.getName(), tags); + return new MetricWorkItem(metricID, metric); + } + + static MetricWorkItem create(MetricID metricID, + org.eclipse.microprofile.metrics.Metric metric) { + return new MetricWorkItem(metricID, metric); + } + + private MetricWorkItem(MetricID metricID, org.eclipse.microprofile.metrics.Metric metric) { + this.metricID = metricID; + this.metric = metric; + } + + MetricID metricID() { + return metricID; + } + + org.eclipse.microprofile.metrics.Metric metric() { + return metric; + } - // which parameter slot in the method the AsyncResponse is - private final int parameterSlot; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MetricWorkItem that = (MetricWorkItem) o; + return metricID.equals(that.metricID) && metric.equals(that.metric); + } - AsyncResponseInfo(int parameterSlot) { - this.parameterSlot = parameterSlot; + @Override + public int hashCode() { + return Objects.hash(metricID, metric); } - /** - * Returns the {@code AsyncResponse} argument object in the given invocation. - * - * @param context the {@code InvocationContext} representing the call with an {@code AsyncResponse} parameter - * @return the {@code AsyncResponse} instance - */ - AsyncResponse asyncResponse(InvocationContext context) { - return AsyncResponse.class.cast(context.getParameters()[parameterSlot]); + @Override + public String toString() { + return new StringJoiner(", " + System.lineSeparator(), MetricWorkItem.class.getSimpleName() + "[", "]") + .add("metricID=" + metricID) + .add("metric=" + metric) + .toString(); + } + } + + private static class MetricInfo { + private final MetricID metricID; + private final T metric; + + MetricInfo(MetricID metricID, T metric) { + this.metricID = metricID; + this.metric = metric; } } } From 6f922a420a057dac4746c378900eeeeee5c84a36 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 20 Mar 2021 12:21:16 -0500 Subject: [PATCH 04/74] Convert the interceptors to work with the new approach; introduce an inheritance hierarchy among them --- .../microprofile/metrics/InterceptorBase.java | 324 ++++-------------- .../metrics/InterceptorConcurrentGauge.java | 39 +-- .../metrics/InterceptorCounted.java | 25 +- .../metrics/InterceptorMetered.java | 23 +- .../metrics/InterceptorSimplyTimed.java | 23 +- .../metrics/InterceptorSimplyTimedBase.java | 42 +++ .../InterceptorSyntheticSimplyTimed.java | 82 +---- .../metrics/InterceptorTimed.java | 24 +- .../metrics/InterceptorTimedBase.java | 49 +++ .../metrics/InterceptorWithPostInvoke.java | 69 ++++ 10 files changed, 272 insertions(+), 428 deletions(-) create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimedBase.java create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java index b4f4bdbe8b0..25750ebf635 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,305 +12,107 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * */ - package io.helidon.microprofile.metrics; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Member; -import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; -import javax.enterprise.context.Dependent; +import javax.inject.Inject; import javax.interceptor.AroundConstruct; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; -import io.helidon.metrics.Registry; +import io.helidon.microprofile.metrics.MetricsCdiExtension.MetricWorkItem; import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricID; import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.Tag; - -import static io.helidon.microprofile.metrics.MetricUtil.MatchingType; -import static io.helidon.microprofile.metrics.MetricUtil.getMetricName; -import static io.helidon.microprofile.metrics.MetricUtil.lookupAnnotation; -import static io.helidon.microprofile.metrics.MetricUtil.tags; /** - * Common methods for interceptors. + * Basic interceptor implementation which supports pre-invoke updates to metrics. *

- * Concrete subclasses implement: - *

    - *
  • {@code @Inject}ed constructor which accepts a {@code MetricRegistry} and - * invokes the constructor for this class, passing: - *
      - *
    • the registry, - *
    • the {@code Class} object for the specific metrics annotation it handles, - *
    • a {@code Function} that accepts an instance of the annotation and returns - * the name for that instance of the annotation, - *
    • a {@code Function} that accepts an instance of the annotation and returns - * the array of tag values for that instance of the annotation,
    • - *
    • a {@code Function} that accepts an instance of the annotation and returns - * whether the name is absolute or not, - *
    • the simple metric type name, and - *
    • the {@code Class} for the metric type. - *
    - * For example, the constructor for the implementation that handles - * {@code Metered} might use this: - *
    - * {@code
    - *     super(registry,
    - *             Metered.class,
    - *             Metered::name,
    - *             Metered::tags,
    - *             Metered::absolute,
    - *             metricTypeName,
    - *             metricTypeClass);
    - * }
    - * 
    - *
  • {@link #prepareAndInvoke} to perform any steps before invoking the intercepted - * method and then invoke that method, - *
  • and, optionally, {@link #postInvoke} to perform any steps after invoking - * the intercepted method. - *
+ * Concrete subclasses implement {@link #preInvoke(Metric)} which takes metric-specific action on a metric before the + * intercepted constructor or method runs. + *

+ * @param type of metrics the interceptor handles */ -@Dependent -abstract class InterceptorBase { +abstract class InterceptorBase { - private final MetricRegistry registry; - private final Registry hRegistry; - private final Class
annotationClass; - private final Function nameFunction; - private final Function tagsFunction; - private final Function isAbsoluteFunction; - private final Map elementMetricMap = new ConcurrentHashMap<>(); - private final String metricTypeName; - private final Class metricClass; - private final Map universalTags; // Get global and app tags for later + static final Logger LOGGER = Logger.getLogger(InterceptorBase.class.getPackageName() + ".Interceptor*"); - InterceptorBase(MetricRegistry registry, - Class annotationClass, - Function nameFunction, - Function tagsFunction, - Function isAbsoluteFunction, - String metricTypeName, - Class metricClass) { - this.registry = registry; - hRegistry = Registry.class.cast(registry); - this.annotationClass = annotationClass; - this.nameFunction = nameFunction; - this.tagsFunction = tagsFunction; - this.isAbsoluteFunction = isAbsoluteFunction; - this.metricTypeName = metricTypeName; - this.metricClass = metricClass; - universalTags = new MetricID("base").getTags(); - } + private final Class annotationType; + private final Class metricType; - protected Optional getMetric(Map metricMap, MetricID metricID) { - return Optional.ofNullable(metricMap.get(metricID)); - } + @Inject + private MetricsCdiExtension extension; - @AroundConstruct - private Object aroundConstructor(InvocationContext context) throws Exception { - return called(context, context.getConstructor()); - } + @Inject + private MetricRegistry registry; - @AroundInvoke - private Object aroundMethod(InvocationContext context) throws Exception { - return called(context, context.getMethod()); - } + private Map metricsForVerification; - /** - * Returns class for this context. There is no target for constructors. - * - * @param context The context. - * @param element Method or constructor. - * @param Method or constructor type. - * @return The class. - */ - protected Class getClass(InvocationContext context, E element) { - return context.getTarget() != null ? MetricsCdiExtension.getRealClass(context.getTarget()) : element.getDeclaringClass(); - } + enum ActionType { + PREINVOKE("preinvoke"), COMPLETE("complete"); - private Object called(InvocationContext context, E element) throws Exception { - MetricUtil.LookupResult lookupResult = lookupAnnotation(element, annotationClass, getClass(context, element)); - if (lookupResult != null) { - T metricInstance = getMetricForElement(element, getClass(context, element), lookupResult); + private final String label; - Exception ex = null; - A annot = lookupResult.getAnnotation(); - try { - return prepareAndInvoke(metricInstance, annot, context); - } catch (Exception e) { - ex = e; - throw e; - } finally { - postInvoke(metricInstance, annot, context, ex); - } + ActionType(String label) { + this.label = label; } - return context.proceed(); - } - /** - * Performs any logic to be run before the intercepted method is invoked and - * then invokes {@code context.proceed()}, returning the value returned by - * from {@code context.proceed()}. - * - * @param metricInstance metric being accessed - * @param annotation annotation instance for the metric - * @param context invocation context for the intercepted method call - * @return return value from invoking the intercepted method - * @throws Exception in case of errors invoking the intercepted method or - * performing the pre-invoke processing - */ - protected abstract Object prepareAndInvoke(T metricInstance, - A annotation, - InvocationContext context) throws Exception; - - /** - * Performs any logic to be run after the intercepted method has run. - *

- * This method is invoked regardless of whether the intercepted method threw an exception. - * - * @param metricInstance metric being accessed - * @param annotation annotation instance for the metric - * @param context invocation context for the intercepted method call - * @param ex any exception caught when invoking {@code prepareAndInvoke}; - * null if that method threw no exception - * @throws Exception in case of errors performing the post-call logic - */ - protected void postInvoke(T metricInstance, - A annotation, - InvocationContext context, - Exception ex) throws Exception { + public String toString() { + return label; + } } - /** - * Return the metric for the given site, either by returning the cached one associated with the site or by looking up the - * metric in the registry. - * - * @param element the annotated element being invoked - * @param clazz the type of metric - * @param lookupResult the combination of the annotation and the matching type - * @param specific type of element - * @return the metric to be updated for the annotated element being invoked - */ - private T getMetricForElement(E element, Class clazz, - MetricUtil.LookupResult lookupResult) { - - T metric = elementMetricMap.computeIfAbsent(element, e -> createMetricForElement(element, clazz, lookupResult)); - return metric; + InterceptorBase(Class annotationType, Class metricType) { + this.annotationType = annotationType; + this.metricType = metricType; } - /** - * Retrieves from the registry -- and stores in the site-to-metric map -- the metric coresponding to the specified site. - * - * @param element the annotated element being invoked - * @param clazz the type of metric - * @param lookupResult the combinatino of the annotation and the matching type - * @param specific type of element - * @return the metric retrieved from the registry and added to the site-to-metric map - */ - private T createMetricForElement(E element, Class clazz, - MetricUtil.LookupResult lookupResult) { - - /* - * Build the metric name that should exist for this annotation site and look up all metric IDs associated with that name. - * (This is very efficient in the registry.) - */ - A annot = lookupResult.getAnnotation(); - MatchingType matchingType = lookupResult.getType(); - - String metricName = getMetricName(element, clazz, matchingType, nameFunction.apply(annot), - isAbsoluteFunction.apply(annot)); - Optional>> matchingEntry = hRegistry.getOptionalMetricWithIDsEntry(metricName); - if (!matchingEntry.isPresent()) { - throw new IllegalStateException(String.format("No %s with name %s found in registry [%s]", metricTypeName, - metricName, registry)); - } - - /* - * Build a simple (no config use) metric ID for the metric corresponding to this annotation site. - */ - Tag[] tagsFromAnnotation = tags(tagsFunction.apply(annot)); - SimpleMetricID simpleMetricID = new SimpleMetricID(metricName, universalTags, tagsFromAnnotation); - - /* - * Find the metric ID associated with the metric name that matches on name and tags. - */ - MetricID metricID = matchingEntry.get().getValue().stream() - .filter(simpleMetricID::matches) - .findFirst() - .orElseThrow(() -> new IllegalStateException(String.format( - "No %s with name %s and matching tags %s found in registry", metricTypeName, metricName, - tagsFromAnnotation))); - - /* - * Retrieve the metric corresponding to the matching metric ID. - */ - Metric metric = registry.getMetrics().get(metricID); - return metricClass.cast(metric); + Class annotationType() { + return annotationType; } - /** - * A near-replica of the MP MetricID, but one that does not repeatedly access config to get global and app-level tags. - * Instead, those come from a single MP MetricID which we instantiate and keep for future reference. - */ - private static class SimpleMetricID { - - private final String name; - private final Map tags; - - private SimpleMetricID(String name, Map univTags, Tag... tagExprs) { - this.name = name; - tags = computeTags(univTags, tagExprs); - } - - private static Map computeTags(Map univTags, Tag... tags) { - if (univTags.isEmpty() && (tags == null || tags.length == 0)) { - return Collections.emptyMap(); - } - Map result = new TreeMap<>(univTags); - if (tags != null && tags.length > 0) { - for (Tag tag : tags) { - result.put(tag.getTagName(), tag.getTagValue()); - } - } - return result; + Map metricsForVerification() { + if (metricsForVerification == null) { + metricsForVerification = registry.getMetrics(); } + return metricsForVerification; + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - SimpleMetricID that = (SimpleMetricID) o; - return equals(that.name, that.tags); - } + @AroundConstruct + Object aroundConstruct(InvocationContext context) throws Exception { + InterceptInfo interceptInfo = extension.interceptInfo(context.getConstructor()); + return interceptInfo.runner().run(context, interceptInfo.workItems(annotationType), this::preInvoke); + } - private boolean matches(MetricID metricID) { - return equals(metricID.getName(), metricID.getTags()); - } + @AroundInvoke + Object aroundInvoke(InvocationContext context) throws Exception { + InterceptInfo interceptInfo = extension.interceptInfo(context.getMethod()); + return interceptInfo.runner().run(context, interceptInfo.workItems(annotationType), this::preInvoke); + } - private boolean equals(String otherName, Map otherTags) { - return name.equals(otherName) && tags.equals(otherTags); - } + void preInvoke(InvocationContext context, MetricWorkItem workItem) { + invokeVerifiedAction(context, workItem, this::preInvoke, ActionType.PREINVOKE); + } - @Override - public int hashCode() { - return Objects.hash(name, tags); + void invokeVerifiedAction(InvocationContext context, MetricWorkItem workItem, Consumer action, ActionType actionType) { + if (!metricsForVerification().containsKey(workItem.metricID())) { + throw new IllegalStateException("Attempt to use previously-removed metric" + workItem.metricID()); } + Metric metric = workItem.metric(); + LOGGER.log(Level.FINEST, () -> String.format( + "%s (%s) is accepting %s %s for processing on %s triggered by @%s", + getClass().getSimpleName(), actionType, workItem.metric().getClass().getSimpleName(), workItem.metricID(), + context.getMethod() != null ? context.getMethod() : context.getConstructor(), annotationType.getSimpleName())); + action.accept(metricType.cast(metric)); } + + abstract void preInvoke(M metric); } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java index 6cecd8f0e64..e30a6b5d7a1 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,45 +17,30 @@ package io.helidon.microprofile.metrics; import javax.annotation.Priority; -import javax.inject.Inject; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.annotation.ConcurrentGauge; - -import static org.eclipse.microprofile.metrics.MetricType.CONCURRENT_GAUGE; +import org.eclipse.microprofile.metrics.ConcurrentGauge; /** * Interceptor for {@link ConcurrentGauge} annotation. */ -@ConcurrentGauge +@org.eclipse.microprofile.metrics.annotation.ConcurrentGauge @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 11) -final class InterceptorConcurrentGauge - extends InterceptorBase { - - @Inject - InterceptorConcurrentGauge(MetricRegistry registry) { - super(registry, - ConcurrentGauge.class, - ConcurrentGauge::name, - ConcurrentGauge::tags, - ConcurrentGauge::absolute, - CONCURRENT_GAUGE.toString(), - org.eclipse.microprofile.metrics.ConcurrentGauge.class); +final class InterceptorConcurrentGauge extends InterceptorWithPostInvoke { + + + InterceptorConcurrentGauge() { + super(org.eclipse.microprofile.metrics.annotation.ConcurrentGauge.class, ConcurrentGauge.class); } @Override - protected Object prepareAndInvoke(org.eclipse.microprofile.metrics.ConcurrentGauge concurrentGauge, - ConcurrentGauge annotation, InvocationContext context) throws Exception { - concurrentGauge.inc(); - return context.proceed(); + void preInvoke(ConcurrentGauge metric) { + metric.inc(); } @Override - protected void postInvoke(org.eclipse.microprofile.metrics.ConcurrentGauge concurrentGauge, - ConcurrentGauge annotation, InvocationContext context, Exception ex) throws Exception { - concurrentGauge.dec(); + void postComplete(ConcurrentGauge metric) { + metric.dec(); } } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorCounted.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorCounted.java index 0accffadfa3..cf31d252ebc 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorCounted.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorCounted.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,9 @@ package io.helidon.microprofile.metrics; import javax.annotation.Priority; -import javax.inject.Inject; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.annotation.Counted; /** @@ -31,24 +28,14 @@ @Counted @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 8) -final class InterceptorCounted extends InterceptorBase { +final class InterceptorCounted extends InterceptorBase { - @Inject - InterceptorCounted(MetricRegistry registry) { - super(registry, - Counted.class, - Counted::name, - Counted::tags, - Counted::absolute, - "counter", - Counter.class); + InterceptorCounted() { + super(Counted.class, Counter.class); } @Override - protected Object prepareAndInvoke(Counter counter, - Counted annot, - InvocationContext context) throws Exception { - counter.inc(); - return context.proceed(); + void preInvoke(Counter metric) { + metric.inc(); } } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java index c23ea294ac9..c81e51a2845 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,9 @@ package io.helidon.microprofile.metrics; import javax.annotation.Priority; -import javax.inject.Inject; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.metrics.Meter; -import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.annotation.Metered; /** @@ -31,23 +28,15 @@ @Metered @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 9) -final class InterceptorMetered extends InterceptorBase { +final class InterceptorMetered extends InterceptorBase { - @Inject - InterceptorMetered(MetricRegistry registry) { - super(registry, - Metered.class, - Metered::name, - Metered::tags, - Metered::absolute, - "meter", - org.eclipse.microprofile.metrics.Meter.class); + InterceptorMetered() { + super(Metered.class, Meter.class); } @Override - protected Object prepareAndInvoke(Meter meter, Metered annotation, InvocationContext context) throws Exception { - meter.mark(); - return context.proceed(); + void preInvoke(Meter metric) { + metric.mark(); } } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java index 5e4d2277dae..b01afaf1976 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,8 @@ package io.helidon.microprofile.metrics; import javax.annotation.Priority; -import javax.inject.Inject; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.SimpleTimer; import org.eclipse.microprofile.metrics.annotation.SimplyTimed; /** @@ -36,21 +32,10 @@ @SimplyTimed @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 10) -final class InterceptorSimplyTimed extends InterceptorBase { +final class InterceptorSimplyTimed extends InterceptorSimplyTimedBase { - @Inject - InterceptorSimplyTimed(MetricRegistry registry) { - super(registry, - SimplyTimed.class, - SimplyTimed::name, - SimplyTimed::tags, - SimplyTimed::absolute, - "simpletimer", - SimpleTimer.class); + InterceptorSimplyTimed() { + super(SimplyTimed.class); } - @Override - protected Object prepareAndInvoke(SimpleTimer simpleTimer, SimplyTimed annotation, InvocationContext context) throws Exception { - return simpleTimer.time(context::proceed); - } } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimedBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimedBase.java new file mode 100644 index 00000000000..36a18e0c11f --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimedBase.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.lang.annotation.Annotation; + +import org.eclipse.microprofile.metrics.SimpleTimer; + +/** + * Abstracts the (tiny) common behavior between the {@code InterceptorSimplyTimed} and {@code InterceptorSyntheticSimplyTimed} + * interceptors. + *

+ * Having {@code InterceptorSyntheticSimplyTimed} extend {@code InterceptorSimplyTimed} would cause problems with the + * subclass taking on not only its own interceptor binding but also that of the superclass. + *

+ */ +class InterceptorSimplyTimedBase extends InterceptorTimedBase { + + InterceptorSimplyTimedBase(Class annotationType) { + super(annotationType, SimpleTimer.class); + } + + @Override + void postComplete(SimpleTimer metric) { + metric.update(duration()); + } + +} diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticSimplyTimed.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticSimplyTimed.java index 77da63cc625..f70bac3553a 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticSimplyTimed.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticSimplyTimed.java @@ -16,20 +16,16 @@ package io.helidon.microprofile.metrics; -import java.lang.reflect.Method; -import java.time.Duration; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Map; import javax.annotation.Priority; import javax.inject.Inject; -import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.container.CompletionCallback; -import org.eclipse.microprofile.metrics.SimpleTimer; +import org.eclipse.microprofile.metrics.Metric; +import org.eclipse.microprofile.metrics.MetricID; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.annotation.RegistryType; /** * Interceptor for synthetic {@link SyntheticSimplyTimed} annotations. @@ -41,70 +37,18 @@ @SyntheticSimplyTimed @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 10) -final class InterceptorSyntheticSimplyTimed { +final class InterceptorSyntheticSimplyTimed extends InterceptorSimplyTimedBase { - private static final Logger LOGGER = Logger.getLogger(InterceptorSyntheticSimplyTimed.class.getName()); - - private final boolean isEnabled; - private final RestEndpointMetricsInfo restEndpointMetricsInfo; + private final Map metricsForVerification; @Inject - InterceptorSyntheticSimplyTimed(RestEndpointMetricsInfo restEndpointMetricsInfo) { - this.restEndpointMetricsInfo = restEndpointMetricsInfo; - isEnabled = restEndpointMetricsInfo.isEnabled(); - } - - /** - * Intercepts a call to bean method annotated by a JAX-RS annotation. - * - * @param context invocation context - * @return the intercepted method's return value - * @throws Throwable in case any error occurs - */ - @AroundInvoke - public Object interceptRestEndpoint(InvocationContext context) throws Throwable { - if (!isEnabled) { - return context.proceed(); - } - long startNanos = System.nanoTime(); - try { - LOGGER.fine("Interceptor of SyntheticSimplyTimed called for '" + context.getTarget().getClass() - + "::" + context.getMethod().getName() + "'"); - - Method timedMethod = context.getMethod(); - SimpleTimer simpleTimer = MetricsCdiExtension.syntheticSimpleTimer(timedMethod); - AsyncResponse asyncResponse = restEndpointMetricsInfo.asyncResponse(context); - if (asyncResponse != null) { - asyncResponse.register(new FinishCallback(startNanos, simpleTimer)); - return context.proceed(); - } - return simpleTimer.time(context::proceed); - } catch (Throwable t) { - LOGGER.log(Level.FINE, "Throwable caught by interceptor", t); - throw t; - } + InterceptorSyntheticSimplyTimed(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry baseRegistry) { + super(SyntheticSimplyTimed.class); + metricsForVerification = baseRegistry.getMetrics(); } - /** - * Async callback which updates a {@code SimpleTimer} associated with the REST endpoint. - */ - static class FinishCallback implements CompletionCallback { - - private final long startTimeNanos; - private final SimpleTimer simpleTimer; - - private FinishCallback(long startTimeNanos, SimpleTimer simpleTimer) { - this.simpleTimer = simpleTimer; - this.startTimeNanos = startTimeNanos; - } - - @Override - public void onComplete(Throwable throwable) { - long nowNanos = System.nanoTime(); - simpleTimer.update(Duration.ofNanos(nowNanos - startTimeNanos)); - if (throwable != null) { - LOGGER.log(Level.FINE, "Throwable detected by interceptor callback", throwable); - } - } + @Override + Map metricsForVerification() { + return metricsForVerification; } } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimed.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimed.java index 70a6ad30406..a2c72ac87a2 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimed.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimed.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,11 @@ package io.helidon.microprofile.metrics; +import java.util.concurrent.TimeUnit; + import javax.annotation.Priority; -import javax.inject.Inject; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.Timer; import org.eclipse.microprofile.metrics.annotation.Timed; @@ -31,21 +30,14 @@ @Timed @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 10) -final class InterceptorTimed extends InterceptorBase { +final class InterceptorTimed extends InterceptorTimedBase { - @Inject - InterceptorTimed(MetricRegistry registry) { - super(registry, - Timed.class, - Timed::name, - Timed::tags, - Timed::absolute, - "timer", - Timer.class); + InterceptorTimed() { + super(Timed.class, Timer.class); } @Override - protected Object prepareAndInvoke(Timer timer, Timed annotation, InvocationContext context) throws Exception { - return timer.time(context::proceed); + void postComplete(Timer metric) { + metric.update(durationNanoseconds(), TimeUnit.NANOSECONDS); } } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java new file mode 100644 index 00000000000..e6fe1d7b46c --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.lang.annotation.Annotation; +import java.time.Duration; + +import org.eclipse.microprofile.metrics.Metric; + +/** + * Common behavior for interceptors that handle timing, specifically ones which need to capture a start time before we + * invoke the intercepted method. + * + * @param type of the metric the interceptor updates + */ +abstract class InterceptorTimedBase extends InterceptorWithPostInvoke { + + private long startNanos; + + InterceptorTimedBase(Class annotationType, Class metricType) { + super(annotationType, metricType); + } + + void preInvoke(T metric) { + startNanos = System.nanoTime(); + } + + Duration duration() { + return Duration.ofNanos(durationNanoseconds()); + } + + long durationNanoseconds() { + return System.nanoTime() - startNanos; + } +} diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java new file mode 100644 index 00000000000..c7496b8e6de --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.lang.annotation.Annotation; + +import javax.inject.Inject; +import javax.interceptor.AroundConstruct; +import javax.interceptor.AroundInvoke; +import javax.interceptor.InvocationContext; + +import io.helidon.microprofile.metrics.MetricsCdiExtension.MetricWorkItem; + +import org.eclipse.microprofile.metrics.Metric; + +/** + * Basic metric interceptor which adds post-completion action on the associated metric. + *

+ * Concrete subclasses implement {@link #postComplete(Metric)} which takes metric-specific action after the intercepted + * constructor or method has completed. For async methods this occurs after completion (not immediately after the start) of + * the method invocation. + *

+ * @param type of metrics the interceptor handles + */ +abstract class InterceptorWithPostInvoke extends InterceptorBase { + + @Inject + private MetricsCdiExtension extension; + + InterceptorWithPostInvoke(Class annotationType, Class metricType) { + super(annotationType, metricType); + } + + @AroundConstruct + Object aroundConstruct(InvocationContext context) throws Exception { + InterceptInfo interceptInfo = extension.interceptInfo(context.getConstructor()); + return interceptInfo.runner().run(context, interceptInfo.workItems(annotationType()), this::preInvoke, + this::postComplete); + } + + + @Override + @AroundInvoke + Object aroundInvoke(InvocationContext context) throws Exception { + InterceptInfo interceptInfo = extension.interceptInfo(context.getMethod()); + return interceptInfo.runner().run(context, interceptInfo.workItems(annotationType()), this::preInvoke, + this::postComplete); + } + + void postComplete(InvocationContext context, MetricWorkItem workItem) { + invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); + } + + abstract void postComplete(T metric); +} From e2e5c35068ae52264fb7cb329632552fbd4a4f75 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 20 Mar 2021 12:21:53 -0500 Subject: [PATCH 05/74] Remove a now-unused class --- .../metrics/RestEndpointMetricsInfo.java | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/RestEndpointMetricsInfo.java diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/RestEndpointMetricsInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/RestEndpointMetricsInfo.java deleted file mode 100644 index e2e2cdbf508..00000000000 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/RestEndpointMetricsInfo.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package io.helidon.microprofile.metrics; - -import java.lang.reflect.Method; -import java.util.Map; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.spi.BeanManager; -import javax.inject.Inject; -import javax.interceptor.InvocationContext; -import javax.ws.rs.container.AsyncResponse; - -import io.helidon.microprofile.metrics.MetricsCdiExtension.AsyncResponseInfo; - -/** - * Captures information about REST endpoint synthetic annotations so interceptors can be quicker. Includes: - *
    - *
  • whether configuration enables or disables synthetic {@code SimplyMetric} annotation behavior
  • - *
  • which JAX-RS endpoint methods (if any) are asynchronous
  • - *
- */ -@ApplicationScoped -class RestEndpointMetricsInfo { - - private boolean isEnabled; - private Map asyncResponseInfo; - - @Inject - RestEndpointMetricsInfo(BeanManager beanManager) { - MetricsCdiExtension metricsCdiExtension = beanManager.getExtension(MetricsCdiExtension.class); - isEnabled = metricsCdiExtension.restEndpointsMetricEnabledFromConfig(); - asyncResponseInfo = metricsCdiExtension.asyncResponseInfo(); - } - - public boolean isEnabled() { - return isEnabled; - } - - public AsyncResponse asyncResponse(InvocationContext context) { - AsyncResponseInfo info = asyncResponseInfo.get(context.getMethod()); - return info == null ? null : info.asyncResponse(context); - } -} From 4d71b978d2b6956ca70695ec0a83f8ba01581a2d Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 20 Mar 2021 12:24:13 -0500 Subject: [PATCH 06/74] Expand and add tests; add logging.properties with useful pre-commented entries --- .../microprofile/metrics/CountedBean.java | 11 +- .../metrics/CountedConstructorTest.java | 52 +++++++++ .../metrics/CountedConstructorTestBean.java | 35 ++++++ .../metrics/HelloWorldAsyncResponseTest.java | 99 +++++++++++++--- .../metrics/HelloWorldResource.java | 57 ++++++++- ...ldRestEndpointSimpleTimerDisabledTest.java | 13 ++- .../microprofile/metrics/HelloWorldTest.java | 110 ++++++++++++------ .../microprofile/metrics/MetricsBaseTest.java | 24 +++- .../metrics/MetricsMpServiceTest.java | 17 ++- .../microprofile/metrics/MetricsTest.java | 61 ++++++---- .../microprofile/metrics/SimplyTimedBean.java | 6 +- .../metrics/TestRemovedInterceptorMetric.java | 58 +++++++++ .../src/test/resources/logging.properties | 45 +++++++ 13 files changed, 494 insertions(+), 94 deletions(-) create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTest.java create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTestBean.java create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestRemovedInterceptorMetric.java create mode 100644 microprofile/metrics/src/test/resources/logging.properties diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedBean.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedBean.java index 94dcb86dd4f..7cfbbf95885 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedBean.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ @Counted public class CountedBean { + static final String DOOMED_COUNTER = "doomedCounter"; + @Counted public void method1() { } @@ -34,4 +36,11 @@ public void method1() { // Inherits annotations from class public void method2() { } + + public void method3() { + } + + @Counted(name = DOOMED_COUNTER, absolute = true) + public void method4() { + } } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTest.java new file mode 100644 index 00000000000..ef0375d8a8f --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.util.Map; +import java.util.stream.IntStream; + +import io.helidon.microprofile.tests.junit5.AddBean; +import io.helidon.microprofile.tests.junit5.HelidonTest; +import org.eclipse.microprofile.metrics.Counter; +import org.eclipse.microprofile.metrics.MetricID; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +@HelidonTest +@AddBean(CountedConstructorTestBean.class) +public class CountedConstructorTest extends MetricsBaseTest { + + @Test + public void checkConstructorMetric() { + String expectedName = CountedConstructorTestBean.class.getName() + "." + CountedConstructorTestBean.CONSTRUCTOR_COUNTER; + + CountedConstructorTestBean bean = newBean(CountedConstructorTestBean.class); + bean.inc(); + + MetricID metricID = new MetricID(expectedName); + Map counters = getMetricRegistry().getCounters(); + + assertThat("Counters in registry", counters, hasKey(metricID)); + Counter counter = counters.get(metricID); + assertThat("Matching counter", counter, is(notNullValue())); + assertThat("Invocations of constructor", counter.getCount(), is(1L)); + } +} diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTestBean.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTestBean.java new file mode 100644 index 00000000000..fa2bb0408ed --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/CountedConstructorTestBean.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import org.eclipse.microprofile.metrics.annotation.Counted; + +public class CountedConstructorTestBean { + + static final String CONSTRUCTOR_COUNTER = "ctorCounted"; + + private int count = 0; + + @Counted(name = CONSTRUCTOR_COUNTER) + public CountedConstructorTestBean() { + + } + + public void inc() { + count++; + } +} diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java index b13eaae093b..29788b4d8f8 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java @@ -16,51 +16,118 @@ */ package io.helidon.microprofile.metrics; + import java.time.Duration; import java.util.Map; -import java.util.concurrent.ExecutionException; +import java.util.SortedMap; +import java.util.stream.LongStream; -import javax.ws.rs.client.AsyncInvoker; -import javax.ws.rs.client.InvocationCallback; +import javax.inject.Inject; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.core.MediaType; -import io.helidon.microprofile.metrics.InterceptorSyntheticSimplyTimed.FinishCallback; +import io.helidon.microprofile.tests.junit5.AddConfig; import io.helidon.microprofile.tests.junit5.HelidonTest; 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.junit.jupiter.api.Disabled; +import org.eclipse.microprofile.metrics.Timer; +import org.eclipse.microprofile.metrics.annotation.RegistryType; import org.junit.jupiter.api.Test; +import static io.helidon.microprofile.metrics.HelloWorldResource.SLOW_MESSAGE_SIMPLE_TIMER; +import static io.helidon.microprofile.metrics.HelloWorldResource.SLOW_MESSAGE_TIMER; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; @HelidonTest -public class HelloWorldAsyncResponseTest extends HelloWorldTest { +@AddConfig(key = "metrics." + MetricsCdiExtension.REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME, value = "true") +public class HelloWorldAsyncResponseTest { + + @Inject + WebTarget webTarget; + + @Inject + MetricRegistry registry; + + @Inject + @RegistryType(type = MetricRegistry.Type.BASE) + private MetricRegistry syntheticSimpleTimerRegistry; @Test - public void test() { + public void test() throws NoSuchMethodException { String result = webTarget .path("helloworld/slow") .request() .accept(MediaType.TEXT_PLAIN) .get(String.class); + /* + * We test simple timers (explicit and the implicit REST.request one) and timers on the async method. + * + * We don't test a ConcurrentGauge, which is the other metric that has a post-invoke update, because it reports data + * for the preceding whole minute. We don't want to deal with the timing issues to make sure that updates to the + * metric fall within one minute and that we check it in the next minute. That's done, + * though not for the JAX-RS async case, in the SE metrics tests. Because the async completion mechanism is independent + * of the specific type of metric, we're not missing much by excluding a ConcurrentGauge from the async method. + */ assertThat("Mismatched string result", result, is(HelloWorldResource.SLOW_RESPONSE)); - Tag[] tags = { - new Tag("class", HelloWorldResource.class.getName()), - new Tag("method", "slowMessage_" + AsyncResponse.class.getName()) - }; + MetricID metricID = MetricsCdiExtension.syntheticSimpleTimerMetricID(HelloWorldResource.class.getMethod("slowMessage", + AsyncResponse.class)); - SimpleTimer simpleTimer = syntheticSimpleTimerRegistry().simpleTimer("REST.request", tags); - assertThat(simpleTimer, is(notNullValue())); Duration minDuration = Duration.ofSeconds(HelloWorldResource.SLOW_DELAY_SECS); - assertThat(simpleTimer.getElapsedTime().compareTo(minDuration), is(greaterThan(0))); + + SortedMap simpleTimers = registry.getSimpleTimers(); + SimpleTimer explicitSimpleTimer = simpleTimers.get(new MetricID(SLOW_MESSAGE_SIMPLE_TIMER)); + assertThat("SimpleTimer for explicit annotation", explicitSimpleTimer, is(notNullValue())); + assertThat("Count for explicit SimpleTimer", explicitSimpleTimer.getCount(), is(1L)); + assertThat("Elapsed time for explicit SimpleTimer", explicitSimpleTimer.getElapsedTime().compareTo(minDuration), + is(greaterThan(0))); + + simpleTimers = syntheticSimpleTimerRegistry.getSimpleTimers(); + SimpleTimer simpleTimer = simpleTimers.get(metricID); + assertThat("Synthetic SimpleTimer for the endpoint", simpleTimer, is(notNullValue())); + assertThat("Synthetic SimpleTimer elapsed time", simpleTimer.getElapsedTime().compareTo(minDuration), is(greaterThan(0))); + + Map timers = registry.getTimers(); + Timer timer = timers.get(new MetricID(SLOW_MESSAGE_TIMER)); + assertThat("Timer", timer, is(notNullValue())); + assertThat("Timer count", timer.getCount(), is(1L)); + assertThat("Timer mean rate", timer.getMeanRate(), is(greaterThan(0.0))); + } + + @Test + public void testAsyncWithArg() { + LongStream.range(0, 3).forEach( + i -> webTarget + .path("helloworld/slowWithArg/Johan") + .request(MediaType.TEXT_PLAIN_TYPE) + .get(String.class)); + + SimpleTimer syntheticSimpleTimer = getSyntheticSimpleTimer(); + assertThat("Synthetic SimpleTimer count", syntheticSimpleTimer.getCount(), is(3L)); + } + + SimpleTimer getSyntheticSimpleTimer() { + MetricID metricID = null; + try { + metricID = MetricsCdiExtension.syntheticSimpleTimerMetricID(HelloWorldResource.class.getMethod("slowMessageWithArg", + String.class, AsyncResponse.class)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + SortedMap simpleTimers = syntheticSimpleTimerRegistry.getSimpleTimers(); + SimpleTimer syntheticSimpleTimer = simpleTimers.get(metricID); + assertThat("Synthetic simple timer " + + MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME, + syntheticSimpleTimer, is(notNullValue())); + return syntheticSimpleTimer; } } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldResource.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldResource.java index 1faefa9317e..981eb9505c7 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldResource.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldResource.java @@ -17,17 +17,29 @@ package io.helidon.microprofile.metrics; import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.annotation.ConcurrentGauge; +import org.eclipse.microprofile.metrics.annotation.Counted; +import org.eclipse.microprofile.metrics.annotation.SimplyTimed; +import org.eclipse.microprofile.metrics.annotation.Timed; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.Collections; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -38,6 +50,7 @@ */ @Path("helloworld") @RequestScoped +@Counted public class HelloWorldResource { static final String SLOW_RESPONSE = "At last"; @@ -45,11 +58,22 @@ public class HelloWorldResource { // In case pipeline runs need a different time static final int SLOW_DELAY_SECS = Integer.getInteger("helidon.asyncSimplyTimedDelaySeconds", 2); + static final String MESSAGE_SIMPLE_TIMER = "messageSimpleTimer"; + static final String SLOW_MESSAGE_TIMER = "slowMessageTimer"; + static final String SLOW_MESSAGE_SIMPLE_TIMER = "slowMessageSimpleTimer"; + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + private static ExecutorService executorService = Executors.newSingleThreadExecutor(); @Inject MetricRegistry metricRegistry; + public HelloWorldResource() { + + } + + // Do not add other metrics annotations to this method! @GET @Produces(MediaType.TEXT_PLAIN) public String message() { @@ -57,17 +81,19 @@ public String message() { return "Hello World"; } - @PUT - @Path("withArg") + @GET + @SimplyTimed(name = MESSAGE_SIMPLE_TIMER, absolute = true) + @Path("/withArg/{name}") @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.TEXT_PLAIN) - public String messageWithArg(String input){ + public String messageWithArg(@PathParam("name") String input){ return "Hello World, " + input; } @GET @Path("/slow") @Produces(MediaType.TEXT_PLAIN) + @SimplyTimed(name = SLOW_MESSAGE_SIMPLE_TIMER, absolute = true) + @Timed(name = SLOW_MESSAGE_TIMER, absolute = true) public void slowMessage(@Suspended AsyncResponse ar) { executorService.execute(() -> { try { @@ -78,4 +104,27 @@ public void slowMessage(@Suspended AsyncResponse ar) { ar.resume(SLOW_RESPONSE); }); } + + @GET + @Path("/slowWithArg/{name}") + @Produces(MediaType.TEXT_PLAIN) + @SimplyTimed(name = SLOW_MESSAGE_SIMPLE_TIMER, absolute = true) + @Timed(name = SLOW_MESSAGE_TIMER, absolute = true) + public void slowMessageWithArg(@PathParam("name") String input, @Suspended AsyncResponse ar) { + executorService.execute(() -> { + try { + TimeUnit.SECONDS.sleep(SLOW_DELAY_SECS); + } catch (InterruptedException e) { + // absorb silently + } + ar.resume(SLOW_RESPONSE + " " + input); + }); + } + + @GET + @Path("/testDeletedMetric") + @Produces(MediaType.TEXT_PLAIN) + public String testDeletedMetric() { + return "Hello there"; + } } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java index 313e26de402..b69eba082e1 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.microprofile.metrics; +import io.helidon.microprofile.tests.junit5.AddConfig; import io.helidon.microprofile.tests.junit5.HelidonTest; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.annotation.RegistryType; @@ -31,11 +32,17 @@ * the config disables that feature. */ @HelidonTest -public class HelloWorldRestEndpointSimpleTimerDisabledTest extends HelloWorldTest { +public class HelloWorldRestEndpointSimpleTimerDisabledTest { @Inject @RegistryType(type = MetricRegistry.Type.BASE) - MetricRegistry baseRegistry; + MetricRegistry syntheticSimpleTimerRegistry; + + boolean isSyntheticSimpleTimerPresent() { + return !syntheticSimpleTimerRegistry.getSimpleTimers((metricID, metric) -> + metricID.equals(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME)) + .isEmpty(); + } @Test public void testSyntheticSimpleTimer() { diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java index 72818e37515..be1be47b968 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,42 +16,55 @@ package io.helidon.microprofile.metrics; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.Callable; import java.util.stream.IntStream; +import java.util.stream.LongStream; import javax.inject.Inject; import javax.json.JsonObject; import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; -import io.helidon.metrics.RegistryFactory; import io.helidon.microprofile.tests.junit5.AddConfig; import io.helidon.microprofile.tests.junit5.HelidonTest; -import org.eclipse.microprofile.metrics.Metric; -import org.eclipse.microprofile.metrics.MetricFilter; + +import org.eclipse.microprofile.metrics.Counter; 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.hamcrest.core.Is; -import org.junit.jupiter.api.AfterEach; +import org.eclipse.microprofile.metrics.annotation.RegistryType; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.hamcrest.CoreMatchers.is; +import static io.helidon.microprofile.metrics.HelloWorldResource.MESSAGE_SIMPLE_TIMER; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Class HelloWorldTest. */ @HelidonTest @AddConfig(key = "metrics." + MetricsCdiExtension.REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME, value = "true") -public class HelloWorldTest extends MetricsMpServiceTest { +public class HelloWorldTest { @Inject WebTarget webTarget; + @Inject + MetricRegistry registry; + + @Inject + @RegistryType(type = MetricRegistry.Type.BASE) + MetricRegistry syntheticSimpleTimerRegistry; + @BeforeAll public static void initialize() { System.setProperty("jersey.config.server.logging.verbosity", "FINE"); @@ -60,59 +73,88 @@ public static void initialize() { @BeforeEach public void registerCounter() { - registerCounter("helloCounter"); + MetricsMpServiceTest.registerCounter(registry, "helloCounter"); } @Test public void testMetrics() { - IntStream.range(0, 5).forEach( + final int iterations = 1; + IntStream.range(0, iterations).forEach( i -> webTarget .path("helloworld") .request() .accept(MediaType.TEXT_PLAIN_TYPE) .get(String.class)); - assertThat(getCounter("helloCounter").getCount(), is(5L)); + assertThat("Value of explicitly-updated counter", registry.counter("helloCounter").getCount(), + is((long) iterations)); + + Counter classLevelCounterForConstructor = + registry.getCounters().get(new MetricID(HelloWorldResource.class.getName() + "." + HelloWorldResource.class.getSimpleName())); + assertThat("Class-declared counter metric for constructor", classLevelCounterForConstructor, is(notNullValue())); + assertThat("Value of interceptor-updated class-level counter for constructor", classLevelCounterForConstructor.getCount(), + is((long) iterations)); + + Counter classLevelCounterForMethod = + registry.getCounters().get(new MetricID(HelloWorldResource.class.getName() + ".message")); + assertThat("Class-declared counter metric for constructor", classLevelCounterForMethod, is(notNullValue())); + assertThat("Value of interceptor-updated class-level counter for method", classLevelCounterForMethod.getCount(), + is((long) iterations)); + + SimpleTimer simpleTimer = getSyntheticSimpleTimer("message"); + assertThat("Synthetic simple timer", simpleTimer, is(notNullValue())); + assertThat("Synthetic simple timer count value", simpleTimer.getCount(), is((long) iterations)); + + checkMetricsUrl(iterations); } @Test public void testSyntheticSimpleTimer() { - testSyntheticSimpleTimer(6L); + testSyntheticSimpleTimer(1L); } void testSyntheticSimpleTimer(long expectedSyntheticSimpleTimerCount) { - IntStream.range(0, 6).forEach( + IntStream.range(0, (int) expectedSyntheticSimpleTimerCount).forEach( i -> webTarget - .path("helloworld/withArg") + .path("helloworld/withArg/Joe") .request(MediaType.TEXT_PLAIN_TYPE) - .put(Entity.text("Joe")) - .readEntity(String.class)); + .get(String.class)); + + SimpleTimer explicitSimpleTimer = registry.simpleTimer(MESSAGE_SIMPLE_TIMER); + assertThat("SimpleTimer from explicit @SimplyTimed", explicitSimpleTimer, is(notNullValue())); + assertThat("SimpleTimer from explicit @SimpleTimed count", explicitSimpleTimer.getCount(), + is(expectedSyntheticSimpleTimerCount)); + + SimpleTimer syntheticSimpleTimer = getSyntheticSimpleTimer("messageWithArg", String.class); + assertThat("SimpleTimer from @SyntheticSimplyTimed", syntheticSimpleTimer, is(notNullValue())); + assertThat("SimpleTimer from @SyntheticSimplyTimed count", syntheticSimpleTimer.getCount(), is(expectedSyntheticSimpleTimerCount)); + } - SimpleTimer syntheticSimpleTimer = getSyntheticSimpleTimer(); - assertThat(syntheticSimpleTimer.getCount(), Is.is(expectedSyntheticSimpleTimerCount)); + + SimpleTimer getSyntheticSimpleTimer(String methodName, Class... paramTypes) { + try { + return getSyntheticSimpleTimer(HelloWorldResource.class.getMethod(methodName, paramTypes)); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex); + } + } + + SimpleTimer getSyntheticSimpleTimer(Method method) { + return getSyntheticSimpleTimer(MetricsCdiExtension.syntheticSimpleTimerMetricID(method)); } - SimpleTimer getSyntheticSimpleTimer() { - Tag[] tags = new Tag[] {new Tag("class", HelloWorldResource.class.getName()), - new Tag("method", "messageWithArg_java.lang.String")}; - assertThat("Synthetic simple timer " - + MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME - + " should appear in registry but does not", - syntheticSimpleTimerRegistry().getSimpleTimers((metricID, metric) -> - metricID.getName().equals(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME)).isEmpty(), - is(false)); - SimpleTimer syntheticSimpleTimer = syntheticSimpleTimerRegistry().simpleTimer( - MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METADATA, tags); - return syntheticSimpleTimer; + SimpleTimer getSyntheticSimpleTimer(MetricID metricID) { + + Map simpleTimers = syntheticSimpleTimerRegistry.getSimpleTimers(); + return simpleTimers.get(metricID); } - @AfterEach - public void checkMetricsUrl() { + void checkMetricsUrl(int iterations) { JsonObject app = webTarget .path("metrics") .request() .accept(MediaType.APPLICATION_JSON_TYPE) .get(JsonObject.class) .getJsonObject("application"); - assertThat(app.getJsonNumber("helloCounter").intValue(), is(5)); + assertThat(app.getJsonNumber("helloCounter").intValue(), is(iterations)); } } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsBaseTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsBaseTest.java index d32d6c3a940..34996fd2c12 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsBaseTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsBaseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,14 @@ import javax.inject.Inject; import io.helidon.microprofile.tests.junit5.HelidonTest; + import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricID; import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.SimpleTimer; +import org.eclipse.microprofile.metrics.annotation.RegistryType; + +import java.lang.reflect.Method; /** * Class MetricsBaseTest. @@ -35,16 +40,29 @@ public class MetricsBaseTest { @Inject private MetricRegistry metricRegistry; + @Inject + @RegistryType(type = MetricRegistry.Type.BASE) + private MetricRegistry baseRegistry; + MetricRegistry getMetricRegistry() { return metricRegistry; } - @SuppressWarnings("unchecked") T getMetric(Object bean, String name) { + return getMetric(getMetricRegistry(), bean, name); + } + + @SuppressWarnings("unchecked") + T getMetric(MetricRegistry registry, Object bean, String name) { MetricID metricName = new MetricID(String.format(METRIC_NAME_TEMPLATE, MetricsCdiExtension.getRealClass(bean).getName(), // CDI proxies name)); - return (T) getMetricRegistry().getMetrics().get(metricName); + return (T)registry.getMetrics().get(metricName); + } + + SimpleTimer getSyntheticSimpleTimer(Method method) { + MetricID metricID = MetricsCdiExtension.syntheticSimpleTimerMetricID(method); + return baseRegistry.getSimpleTimers().get(metricID); } T newBean(Class beanClass) { diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java index ea12b3503ac..4eb1f21a2ac 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,11 +43,10 @@ public static void initTest() { static MetricRegistry initSyntheticSimpleTimerRegistry() { MetricRegistry result = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.BASE); - result.remove(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME); +// result.remove(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME); return result; } - @Inject private MetricRegistry registry; @@ -63,13 +62,7 @@ MetricRegistry registry() { return registry; } - boolean isSyntheticSimpleTimerPresent() { - return !syntheticSimpleTimerRegistry().getSimpleTimers((metricID, metric) -> - metricID.equals(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME)) - .isEmpty(); - } - - protected void registerCounter(String name) { + protected static void registerCounter(MetricRegistry registry, String name) { Metadata meta = Metadata.builder() .withName(name) .withDisplayName(name) @@ -80,6 +73,10 @@ protected void registerCounter(String name) { registry.counter(meta); } + protected void registerCounter(String name) { + registerCounter(registry, name); + } + protected Counter getCounter(String name) { return registry.counter(name); } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java index d94c217c238..6e14dfd3178 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,17 @@ package io.helidon.microprofile.metrics; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.Watchable; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -27,16 +38,17 @@ import org.eclipse.microprofile.metrics.Meter; import org.eclipse.microprofile.metrics.MetricID; import org.eclipse.microprofile.metrics.SimpleTimer; -import org.eclipse.microprofile.metrics.Tag; import org.eclipse.microprofile.metrics.Timer; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; +import static org.hamcrest.Matchers.is; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.hamcrest.number.OrderingComparison.lessThan; @@ -48,7 +60,7 @@ public class MetricsTest extends MetricsBaseTest { private static final String PERF_TEST_PROP_PREFIX = "helidon.microprofile.metrics.perfTest."; private static final int PERF_TEST_COUNT = Integer.getInteger(PERF_TEST_PROP_PREFIX + "count", 10000); private static final long PERF_TEST_FAILURE_THRESHOLD_NS = Integer.getInteger( - PERF_TEST_PROP_PREFIX + ".failureThresholdNS", 150 * 1000 * 1000); // roughly double informal expc + PERF_TEST_PROP_PREFIX + ".failureThresholdNS", 200 * 1000 * 1000); // roughly double informal expc @Test public void testCounted1() { @@ -59,18 +71,20 @@ public void testCounted1() { } @Test - public void testCounted2() { + public void testCounted3() { CountedBean bean = newBean(CountedBean.class); - IntStream.range(0, 10).forEach(i -> bean.method2()); - Counter counter = getMetric(bean, "method1"); - assertThat(counter.getCount(), is(10L)); + IntStream.range(0, 8).forEach(i -> bean.method3()); + Counter counter = getMetric(bean, "method3"); + assertThat(counter.getCount(), is(8L)); } + + @Test @DisabledIfSystemProperty(named = PERF_TEST_PROP_PREFIX + "enabled", matches = "false") public void testCounted2Perf() { /* - * Informal experience shows that, without the performance fix in InterceptorBase, this test measures more than 1 s + * Informal experience shows that, without the performance fix in MetricsInterceptorBase, this test measures more than 1 s * to perform 10000 intercepted calls to the bean. With the fix, the time is around .06-.08 seconds. */ CountedBean bean = newBean(CountedBean.class); @@ -78,61 +92,64 @@ public void testCounted2Perf() { IntStream.range(0, PERF_TEST_COUNT).forEach(i -> bean.method2()); long end = System.nanoTime(); Counter counter = getMetric(bean, "method2"); + System.err.printf("Elapsed time for test (ms): %f%n", (end - start) / 1000.0 / 1000.0); assertThat(counter.getCount(), is((long) PERF_TEST_COUNT)); - assertThat(end - start, is(lessThan(PERF_TEST_FAILURE_THRESHOLD_NS))); + assertThat(String.format("Elapsed time of %d tests (ms)", PERF_TEST_COUNT), + (end - start) / 1000.0 / 1000.0, + is(lessThan(PERF_TEST_FAILURE_THRESHOLD_NS / 1000.0 / 1000.0))); } @Test public void testMetered1() { MeteredBean bean = newBean(MeteredBean.class); - IntStream.range(0, 10).forEach(i -> bean.method1()); + IntStream.range(0, 9).forEach(i -> bean.method1()); Meter meter = getMetric(bean, "method1"); - assertThat(meter.getCount(), is(10L)); + assertThat(meter.getCount(), is(9L)); assertThat(meter.getMeanRate(), is(greaterThan(0.0))); } @Test public void testMetered2() { MeteredBean bean = newBean(MeteredBean.class); - IntStream.range(0, 10).forEach(i -> bean.method2()); + IntStream.range(0, 12).forEach(i -> bean.method2()); Meter meter = getMetric(bean, "method2"); - assertThat(meter.getCount(), is(10L)); + assertThat(meter.getCount(), is(12L)); assertThat(meter.getMeanRate(), is(greaterThan(0.0))); } @Test public void testTimed1() { TimedBean bean = newBean(TimedBean.class); - IntStream.range(0, 10).forEach(i -> bean.method1()); + IntStream.range(0, 11).forEach(i -> bean.method1()); Timer timer = getMetric(bean, "method1"); - assertThat(timer.getCount(), is(10L)); + assertThat(timer.getCount(), is(11L)); assertThat(timer.getMeanRate(), is(greaterThan(0.0))); } @Test public void testTimed2() { TimedBean bean = newBean(TimedBean.class); - IntStream.range(0, 10).forEach(i -> bean.method2()); + IntStream.range(0, 14).forEach(i -> bean.method2()); Timer timer = getMetric(bean, "method2"); - assertThat(timer.getCount(), is(10L)); + assertThat(timer.getCount(), is(14L)); assertThat(timer.getMeanRate(), is(greaterThan(0.0))); } @Test public void testSimplyTimed1() { SimplyTimedBean bean = newBean(SimplyTimedBean.class); - IntStream.range(0, 10).forEach(i -> bean.method1()); + IntStream.range(0, 7).forEach(i -> bean.method1()); SimpleTimer simpleTimer = getMetric(bean, "method1"); - assertThat(simpleTimer.getCount(), is(10L)); + assertThat(simpleTimer.getCount(), is(7L)); assertThat(simpleTimer.getElapsedTime().toNanos(), is(greaterThan(0L))); } @Test public void testSimplyTimed2() { SimplyTimedBean bean = newBean(SimplyTimedBean.class); - IntStream.range(0, 10).forEach(i -> bean.method2()); + IntStream.range(0, 15).forEach(i -> bean.method2()); SimpleTimer simpleTimer = getMetric(bean, "method2"); - assertThat(simpleTimer.getCount(), is(10L)); + assertThat(simpleTimer.getCount(), is(15L)); assertThat(simpleTimer.getElapsedTime().toNanos(), is(greaterThan(0L))); } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/SimplyTimedBean.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/SimplyTimedBean.java index 7721bc966bf..706b264773f 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/SimplyTimedBean.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/SimplyTimedBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,4 +31,8 @@ public void method1() { // Inherits annotations from class public void method2() { } + + // Used to make sure that deleted metrics used in synthetic annotation interceptor are rejected + public void method3() { + } } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestRemovedInterceptorMetric.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestRemovedInterceptorMetric.java new file mode 100644 index 00000000000..9537993261c --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestRemovedInterceptorMetric.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import io.helidon.microprofile.tests.junit5.AddBean; +import io.helidon.microprofile.tests.junit5.HelidonTest; +import org.eclipse.microprofile.metrics.Counter; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import static org.junit.jupiter.api.Assertions.fail; + +@HelidonTest +public class TestRemovedInterceptorMetric extends MetricsBaseTest { + + @Inject + private MetricRegistry registry; + + @Test + public void ensureExceptionThrown() { + CountedBean bean = newBean(CountedBean.class); + + Counter counter = registry.counter(CountedBean.DOOMED_COUNTER); + bean.method4(); + assertThat(counter.getCount(), is(1L)); + assertThat("Could not remove bean during test", registry.remove(CountedBean.DOOMED_COUNTER), is(true)); + try { + bean.method4(); + } catch (RuntimeException cause) { + assertThat(cause, is(instanceOf(IllegalStateException.class))); + // Make sure that the simpleTimer hasn't been called + assertThat("Counted count is incorrect", counter.getCount(), is(1L)); + return; + } + fail("Use of removed bean from interceptor was incorrectly permitted"); + } +} diff --git a/microprofile/metrics/src/test/resources/logging.properties b/microprofile/metrics/src/test/resources/logging.properties new file mode 100644 index 00000000000..545234c7d62 --- /dev/null +++ b/microprofile/metrics/src/test/resources/logging.properties @@ -0,0 +1,45 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.common.HelidonConsoleHandler,java.util.logging.FileHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO +#io.helidon.microprofile.metrics.MetricsCdiExtension.level=FINEST +#io.helidon.microprofile.metrics.Interceptor*.level=FINEST + +#java.util.logging.FileHandler.level=FINEST +#java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter +#io.helidon.common.HelidonConsoleHandler.level=FINEST + +org.jboss.weld.Bootstrap.level = FINEST +#org.jboss.weld.Bean.level=FINEST +#org.jboss.weld.Context.level=FINEST +#org.jboss.weld.Utilties.level=FINEST From a679bc2d5a75815269bf0859f9efabc677799112 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 24 Mar 2021 08:44:30 -0500 Subject: [PATCH 07/74] Remove leftover detailed logging setting from tests --- microprofile/metrics/src/test/resources/logging.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microprofile/metrics/src/test/resources/logging.properties b/microprofile/metrics/src/test/resources/logging.properties index 545234c7d62..03baed28ef5 100644 --- a/microprofile/metrics/src/test/resources/logging.properties +++ b/microprofile/metrics/src/test/resources/logging.properties @@ -39,7 +39,7 @@ java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$ #java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter #io.helidon.common.HelidonConsoleHandler.level=FINEST -org.jboss.weld.Bootstrap.level = FINEST +#org.jboss.weld.Bootstrap.level = FINEST #org.jboss.weld.Bean.level=FINEST #org.jboss.weld.Context.level=FINEST #org.jboss.weld.Utilties.level=FINEST From 30da0ccd738ee8c5179e233388a045ec913e97db Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 24 Mar 2021 13:19:01 -0500 Subject: [PATCH 08/74] Review: fix unusual logging name --- .../java/io/helidon/microprofile/metrics/InterceptorBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java index 25750ebf635..6aad1ad1c48 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java @@ -43,7 +43,7 @@ */ abstract class InterceptorBase { - static final Logger LOGGER = Logger.getLogger(InterceptorBase.class.getPackageName() + ".Interceptor*"); + private static final Logger LOGGER = Logger.getLogger(InterceptorBase.class.getName()); private final Class annotationType; private final Class metricType; From 3a510be5f29e309b5a69eedf7ff443dc507da1f1 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 24 Mar 2021 13:19:52 -0500 Subject: [PATCH 09/74] Review: fix method javadoc comment, parameter name --- .../io/helidon/microprofile/metrics/InterceptInfo.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java index a370e039129..7c4ca895ab5 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptInfo.java @@ -70,16 +70,16 @@ Supplier> workItems(Class annotationType) { } /** - * Adds a work item to this info, identifying the interceptor type that will update this work item. - * @param annotationClass type of the interceptor + * Adds a work item to this info, identifying the annotation type that led to this work item. + * @param annotationType type of the interceptor * @param workItem the newly-created workItem */ - void addWorkItem(Class annotationClass, T workItem) { + void addWorkItem(Class annotationType, T workItem) { // Using a set for the actual collection subtly handles the case where a class-level and a method- or constructor-level // annotation both indicate the same workItem. We do not want to update the same workItem twice in that case. - Collection workItems = workItemsByAnnotationType.computeIfAbsent(annotationClass, c -> new HashSet<>()); + Collection workItems = workItemsByAnnotationType.computeIfAbsent(annotationType, c -> new HashSet<>()); workItems.add(workItem); } } From 39b4fc7ded39158c5aea4870a2c986632101c558 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 24 Mar 2021 14:30:50 -0500 Subject: [PATCH 10/74] Review: some renaming; no need for Supplier, per Laird --- .../microprofile/metrics/InterceptRunner.java | 9 +++---- .../metrics/InterceptRunnerImpl.java | 27 ++++++++----------- ...tInfo.java => InterceptionTargetInfo.java} | 17 ++++++------ .../microprofile/metrics/InterceptorBase.java | 8 +++--- .../metrics/InterceptorWithPostInvoke.java | 8 +++--- .../metrics/MetricsCdiExtension.java | 15 ++++++----- 6 files changed, 39 insertions(+), 45 deletions(-) rename microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/{InterceptInfo.java => InterceptionTargetInfo.java} (81%) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java index 23ce17f9579..3099d4cc3a5 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptRunner.java @@ -17,7 +17,6 @@ package io.helidon.microprofile.metrics; import java.util.function.BiConsumer; -import java.util.function.Supplier; import javax.interceptor.InvocationContext; @@ -30,7 +29,7 @@ *
- *

*

* The runner *

    @@ -61,13 +59,12 @@ *
  1. (if provided) invokes the post-completion consumer for all work * items.
  2. *
- *

*

* The interface requires a {@code Iterable<>} for work items because, in the before-and-after case, the runner * might need to process the work items twice. In those cases, the {@code Iterable} can furnish two {@code Iterators}. *

*/ -interface InterceptionRunner { +public interface InterceptionRunner { /** * Invokes the intercepted executable represented by the {@code InvocationContext}, performing the pre-invocation diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptionRunnerImpl.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java similarity index 99% rename from microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptionRunnerImpl.java rename to service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java index 387d77aa007..b6d854ee113 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptionRunnerImpl.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.microprofile.metrics; +package io.helidon.servicecommon.restcdi; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptionTargetInfo.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java similarity index 79% rename from microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptionTargetInfo.java rename to service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java index 54565e29961..cff42928115 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptionTargetInfo.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.microprofile.metrics; +package io.helidon.servicecommon.restcdi; import java.lang.annotation.Annotation; import java.lang.reflect.Executable; @@ -23,9 +23,6 @@ import java.util.HashSet; import java.util.Map; -import io.helidon.servicecommon.restcdi.InterceptRunner; -import io.helidon.servicecommon.restcdi.InterceptRunnerFactory; - /** * Records information about an intercepted executable. *

@@ -35,11 +32,10 @@ * gave rise to each work item; and *

  • the {@code InterceptionRunner} to use in updating the work items and invoking the method or constructor.
  • * - *

    * * @param base type of the work items handled by the interceptor represented by this instance */ -class InterceptionTargetInfo { +public class InterceptionTargetInfo { private final InterceptionRunner runner; @@ -49,9 +45,10 @@ class InterceptionTargetInfo { * Creates a new instance based on the provided {@code Executable}. * * @param executable the constructor or method subject to interception + * @param the type of work items associated with the target info * @return the new instance */ - static InterceptionTargetInfo create(Executable executable) { + public static InterceptionTargetInfo create(Executable executable) { return new InterceptionTargetInfo<>(InterceptRunnerFactory.create(executable)); } @@ -59,11 +56,21 @@ private InterceptionTargetInfo(InterceptionRunner runner) { this.runner = runner; } - InterceptionRunner runner() { + /** + * + * @return the {@code InterceptionRunner} for this instance + */ + public InterceptionRunner runner() { return runner; } - Iterable workItems(Class annotationType) { + /** + * Returns the work items for the given annotation type. + * + * @param annotationType type of annotation for which work items are requested + * @return the work items + */ + public Iterable workItems(Class annotationType) { /* * Build a supplier of the iterable, because before-and-after runners will need to process the work items twice so we need * to give the runner a supplier. @@ -76,7 +83,7 @@ Iterable workItems(Class annotationType) { * @param annotationType type of the interceptor * @param workItem the newly-created workItem */ - void addWorkItem(Class annotationType, T workItem) { + public void addWorkItem(Class annotationType, T workItem) { // Using a set for the actual collection subtly handles the case where a class-level and a method- or constructor-level // annotation both indicate the same workItem. We do not want to update the same workItem twice in that case. From a960e8ab896d05747d356c0f510801d6ad8cdb8b Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 09:54:00 -0500 Subject: [PATCH 26/74] Some more renaming --- ...rceptRunnerFactory.java => InterceptionRunnerFactory.java} | 4 ++-- .../helidon/servicecommon/restcdi/InterceptionTargetInfo.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/{InterceptRunnerFactory.java => InterceptionRunnerFactory.java} (93%) diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptRunnerFactory.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java similarity index 93% rename from service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptRunnerFactory.java rename to service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java index e38bfb6a77b..3c067716ef6 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptRunnerFactory.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java @@ -21,7 +21,7 @@ /** * Creates appropriate {@link InterceptionRunner}. */ -public class InterceptRunnerFactory { +public class InterceptionRunnerFactory { /** * Returns the appropriate {@code InterceptRunner}, depending on the {@code Executable} provider. @@ -33,6 +33,6 @@ public static InterceptionRunner create(Executable executable) { return InterceptionRunnerImpl.create(executable); } - private InterceptRunnerFactory() { + private InterceptionRunnerFactory() { } } diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java index cff42928115..915b33d688d 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java @@ -49,7 +49,7 @@ public class InterceptionTargetInfo { * @return the new instance */ public static InterceptionTargetInfo create(Executable executable) { - return new InterceptionTargetInfo<>(InterceptRunnerFactory.create(executable)); + return new InterceptionTargetInfo<>(InterceptionRunnerFactory.create(executable)); } private InterceptionTargetInfo(InterceptionRunner runner) { From 15aaf64214738a9e44e78aae2a3c1d63984d19cb Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 10:45:58 -0500 Subject: [PATCH 27/74] Typo --- .../helidon/servicecommon/restcdi/InterceptionRunner.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java index 83a89f4faca..1d35ead3309 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java @@ -61,7 +61,7 @@ * *

    * The interface requires a {@code Iterable<>} for work items because, in the before-and-after case, the runner - * might need to process the work items twice. In those cases, the {@code Iterable} can furnish two {@code Iterators}. + * might need to process the work items twice. In those cases, the {@code Iterable} can furnish two {@code Iterator}s. *

    */ public interface InterceptionRunner { @@ -72,7 +72,7 @@ public interface InterceptionRunner { * * @param context {@code InvocationContext} for the intercepted invocation * @param workItems the work items the interceptor will operate on - * @param preInvocationHandler the pre-invoke operation to perform on each work item + * @param preInvocationHandler the pre-invocation operation to perform on each work item * @param type of the work items * @return the return value from the invoked executable * @throws Exception for any error thrown by the {@code Iterable} of work items or the invoked executable itself @@ -88,7 +88,7 @@ Object run( * * @param context {@code InvocationContext} for the intercepted invocation * @param workItems the work items the interceptor will operate on - * @param preInvocationHandler the pre-invoke operation to perform on each work item + * @param preInvocationHandler the pre-invocation operation to perform on each work item * @param postCompletionHandler the post-completion operation to perform on each work item * @param type of the work items * @return the return value from the invoked executable From 231054c66532b66b09404e54a4dab6ce94043cb5 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 11:34:42 -0500 Subject: [PATCH 28/74] Split Micrometer integrations into two modules --- bom/pom.xml | 5 +++ .../io/helidon/common/FeatureCatalog.java | 9 +++++ .../micrometer/cdi}/InterceptorCounted.java | 2 +- .../micrometer/cdi}/InterceptorTimed.java | 4 +- .../micrometer/cdi}/MeterProducer.java | 2 +- .../cdi}/MeterRegistryProducer.java | 4 +- .../cdi}/MicrometerCdiExtension.java | 14 ++++++- .../cdi}/MicrometerInterceptorBase.java | 4 +- .../micrometer/cdi/package-info.java | 21 ++++++++++ .../{ => cdi}/src/main/java/module-info.java | 11 ++++-- .../micrometer/{ => micrometer}/pom.xml | 2 +- .../micrometer/MeterRegistryFactory.java | 0 .../MicrometerBuiltInRegistrySupport.java | 0 .../MicrometerPrometheusRegistrySupport.java | 0 .../micrometer/MicrometerSupport.java | 0 .../integrations/micrometer/package-info.java | 0 .../micrometer/src/main/java/module-info.java | 38 +++++++++++++++++++ .../javax.enterprise.inject.spi.Extension | 2 +- .../micrometer/MicrometerEndpointTests.java | 0 .../MicrometerSimplePrometheusTest.java | 0 .../MicrometerSupportBuilderTest.java | 0 .../MicrometerTestAddingMeters.java | 0 .../micrometer/MicrometerTestUtil.java | 0 .../test/resources/micrometerTestData.json | 0 24 files changed, 104 insertions(+), 14 deletions(-) rename integrations/micrometer/{src/main/java/io/helidon/integrations/micrometer => cdi/src/main/java/io/helidon/integrations/micrometer/cdi}/InterceptorCounted.java (96%) rename integrations/micrometer/{src/main/java/io/helidon/integrations/micrometer => cdi/src/main/java/io/helidon/integrations/micrometer/cdi}/InterceptorTimed.java (92%) rename integrations/micrometer/{src/main/java/io/helidon/integrations/micrometer => cdi/src/main/java/io/helidon/integrations/micrometer/cdi}/MeterProducer.java (98%) rename integrations/micrometer/{src/main/java/io/helidon/integrations/micrometer => cdi/src/main/java/io/helidon/integrations/micrometer/cdi}/MeterRegistryProducer.java (92%) rename integrations/micrometer/{src/main/java/io/helidon/integrations/micrometer => cdi/src/main/java/io/helidon/integrations/micrometer/cdi}/MicrometerCdiExtension.java (93%) rename integrations/micrometer/{src/main/java/io/helidon/integrations/micrometer => cdi/src/main/java/io/helidon/integrations/micrometer/cdi}/MicrometerInterceptorBase.java (97%) create mode 100644 integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/package-info.java rename integrations/micrometer/{ => cdi}/src/main/java/module-info.java (78%) rename integrations/micrometer/{ => micrometer}/pom.xml (98%) rename integrations/micrometer/{ => micrometer}/src/main/java/io/helidon/integrations/micrometer/MeterRegistryFactory.java (100%) rename integrations/micrometer/{ => micrometer}/src/main/java/io/helidon/integrations/micrometer/MicrometerBuiltInRegistrySupport.java (100%) rename integrations/micrometer/{ => micrometer}/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java (100%) rename integrations/micrometer/{ => micrometer}/src/main/java/io/helidon/integrations/micrometer/MicrometerSupport.java (100%) rename integrations/micrometer/{ => micrometer}/src/main/java/io/helidon/integrations/micrometer/package-info.java (100%) create mode 100644 integrations/micrometer/micrometer/src/main/java/module-info.java rename integrations/micrometer/{ => micrometer}/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension (90%) rename integrations/micrometer/{ => micrometer}/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java (100%) rename integrations/micrometer/{ => micrometer}/src/test/java/io/helidon/integrations/micrometer/MicrometerSimplePrometheusTest.java (100%) rename integrations/micrometer/{ => micrometer}/src/test/java/io/helidon/integrations/micrometer/MicrometerSupportBuilderTest.java (100%) rename integrations/micrometer/{ => micrometer}/src/test/java/io/helidon/integrations/micrometer/MicrometerTestAddingMeters.java (100%) rename integrations/micrometer/{ => micrometer}/src/test/java/io/helidon/integrations/micrometer/MicrometerTestUtil.java (100%) rename integrations/micrometer/{ => micrometer}/src/test/resources/micrometerTestData.json (100%) diff --git a/bom/pom.xml b/bom/pom.xml index d27d1b2f076..2e51dc5c2aa 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -859,6 +859,11 @@ helidon-integrations-micrometer ${helidon.version} + + io.helidon.integrations + helidon-integrations-micrometer-cdi + ${helidon.version} + io.helidon.openapi diff --git a/common/common/src/main/java/io/helidon/common/FeatureCatalog.java b/common/common/src/main/java/io/helidon/common/FeatureCatalog.java index a2a5e02f97d..2e5110272e2 100644 --- a/common/common/src/main/java/io/helidon/common/FeatureCatalog.java +++ b/common/common/src/main/java/io/helidon/common/FeatureCatalog.java @@ -326,6 +326,15 @@ final class FeatureCatalog { .experimental(true) ); + addSe("io.helidon.integrations.micrometer", + "Micrometer", + "Micrometer integration" + "Micrometer"); + addMp("io.helidon.integrations.micrometer.cdi", + "Micrometer CDI", + "Micrometer CDI integration", + "Micrometer-CDI"); + /* * Common modules */ diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/InterceptorCounted.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java similarity index 96% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/InterceptorCounted.java rename to integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java index 11111a458be..c733563bafd 100644 --- a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/InterceptorCounted.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.integrations.micrometer; +package io.helidon.integrations.micrometer.cdi; import javax.annotation.Priority; import javax.inject.Inject; diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/InterceptorTimed.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java similarity index 92% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/InterceptorTimed.java rename to integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java index 749602f400c..07ad362acbd 100644 --- a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/InterceptorTimed.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.integrations.micrometer; +package io.helidon.integrations.micrometer.cdi; import java.time.Duration; @@ -22,7 +22,7 @@ import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; -import io.helidon.integrations.micrometer.MicrometerCdiExtension.MeterWorkItem; +import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension.MeterWorkItem; import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.Timer; diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterProducer.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MeterProducer.java similarity index 98% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterProducer.java rename to integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MeterProducer.java index a3cd42262a2..c6f9aa534ad 100644 --- a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterProducer.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MeterProducer.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.integrations.micrometer; +package io.helidon.integrations.micrometer.cdi; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryProducer.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MeterRegistryProducer.java similarity index 92% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryProducer.java rename to integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MeterRegistryProducer.java index a6340dc02c4..2d6225b2533 100644 --- a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryProducer.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MeterRegistryProducer.java @@ -14,13 +14,15 @@ * limitations under the License. * */ -package io.helidon.integrations.micrometer; +package io.helidon.integrations.micrometer.cdi; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; import io.helidon.common.LazyValue; import io.helidon.config.Config; +import io.helidon.integrations.micrometer.MeterRegistryFactory; +import io.helidon.integrations.micrometer.MicrometerSupport; import io.micrometer.core.instrument.MeterRegistry; diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerCdiExtension.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java similarity index 93% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerCdiExtension.java rename to integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java index 7766395b51a..50521a2b5c1 100644 --- a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerCdiExtension.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.integrations.micrometer; +package io.helidon.integrations.micrometer.cdi; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; @@ -38,6 +38,7 @@ import javax.enterprise.util.Nonbinding; import javax.interceptor.InterceptorBinding; +import io.helidon.integrations.micrometer.MicrometerSupport; import io.helidon.servicecommon.restcdi.AnnotationLookupResult; import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension; import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; @@ -81,21 +82,32 @@ public MicrometerCdiExtension() { void register(E element, Class clazz, AnnotationLookupResult lookupResult) { Annotation annotation = lookupResult.annotation(); + Meter newMeter = null; + if (annotation instanceof Counted) { Counter counter = MeterProducer.produceCounter(meterRegistry, (Counted) annotation); LOGGER.log(Level.FINE, () -> "Registered counter " + counter.getId().toString()); + newMeter = counter; } else if (annotation instanceof Timed) { Timed timed = (Timed) annotation; if (timed.longTask()) { LongTaskTimer longTaskTimer = MeterProducer.produceLongTaskTimer(meterRegistry, timed); LOGGER.log(Level.FINE, () -> "Registered long task timer " + longTaskTimer.getId() .toString()); + newMeter = longTaskTimer; } else { Timer timer = MeterProducer.produceTimer(meterRegistry, timed); LOGGER.log(Level.FINE, () -> "Registered timer " + timer.getId() .toString()); + newMeter = timer; } } + if (element instanceof Executable) { + InterceptionTargetInfo info = interceptionTargetInfo.computeIfAbsent((Executable) element, + InterceptionTargetInfo::create); + info.addWorkItem(lookupResult.annotation() + .annotationType(), MeterWorkItem.create(newMeter)); + } } /** diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerInterceptorBase.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java similarity index 97% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerInterceptorBase.java rename to integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java index b2e0bb6f2c4..7cdcdbd043b 100644 --- a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerInterceptorBase.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.helidon.integrations.micrometer; +package io.helidon.integrations.micrometer.cdi; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; @@ -31,7 +31,7 @@ import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; -import io.helidon.integrations.micrometer.MicrometerCdiExtension.MeterWorkItem; +import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension.MeterWorkItem; import io.helidon.servicecommon.restcdi.HelidonInterceptor; import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/package-info.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/package-info.java new file mode 100644 index 00000000000..7f3b0dca730 --- /dev/null +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Support for Micrometer in Helidon MP. + */ +package io.helidon.integrations.micrometer.cdi; diff --git a/integrations/micrometer/src/main/java/module-info.java b/integrations/micrometer/cdi/src/main/java/module-info.java similarity index 78% rename from integrations/micrometer/src/main/java/module-info.java rename to integrations/micrometer/cdi/src/main/java/module-info.java index 9ba404414f5..60d9d6c330f 100644 --- a/integrations/micrometer/src/main/java/module-info.java +++ b/integrations/micrometer/cdi/src/main/java/module-info.java @@ -15,7 +15,9 @@ * */ -module io.helidon.integrations.micrometer { +import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension; + +module io.helidon.integrations.micrometer.cdi { requires java.logging; @@ -28,18 +30,19 @@ requires io.helidon.common.http; requires io.helidon.servicecommon.rest; - requires static io.helidon.servicecommon.restcdi; + requires io.helidon.servicecommon.restcdi; requires io.helidon.config; requires io.helidon.webserver.cors; + requires io.helidon.integrations.micrometer; requires micrometer.core; requires micrometer.registry.prometheus; requires simpleclient; - exports io.helidon.integrations.micrometer; + exports io.helidon.integrations.micrometer.cdi; // this is needed for CDI extensions that use non-public observer methods opens io.helidon.integrations.micrometer to weld.core.impl, io.helidon.microprofile.cdi; - provides javax.enterprise.inject.spi.Extension with io.helidon.integrations.micrometer.MicrometerCdiExtension; + provides javax.enterprise.inject.spi.Extension with MicrometerCdiExtension; } diff --git a/integrations/micrometer/pom.xml b/integrations/micrometer/micrometer/pom.xml similarity index 98% rename from integrations/micrometer/pom.xml rename to integrations/micrometer/micrometer/pom.xml index 8252c51010b..71e36a45810 100644 --- a/integrations/micrometer/pom.xml +++ b/integrations/micrometer/micrometer/pom.xml @@ -23,7 +23,7 @@ 4.0.0 io.helidon.integrations - helidon-integrations-project + helidon-integrations-micrometer-project 2.3.0-SNAPSHOT helidon-integrations-micrometer diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryFactory.java b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryFactory.java similarity index 100% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryFactory.java rename to integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MeterRegistryFactory.java diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerBuiltInRegistrySupport.java b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerBuiltInRegistrySupport.java similarity index 100% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerBuiltInRegistrySupport.java rename to integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerBuiltInRegistrySupport.java diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java similarity index 100% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java rename to integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerSupport.java b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerSupport.java similarity index 100% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerSupport.java rename to integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerSupport.java diff --git a/integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/package-info.java b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/package-info.java similarity index 100% rename from integrations/micrometer/src/main/java/io/helidon/integrations/micrometer/package-info.java rename to integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/package-info.java diff --git a/integrations/micrometer/micrometer/src/main/java/module-info.java b/integrations/micrometer/micrometer/src/main/java/module-info.java new file mode 100644 index 00000000000..1225755d062 --- /dev/null +++ b/integrations/micrometer/micrometer/src/main/java/module-info.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +module io.helidon.integrations.micrometer { + + requires java.logging; + + requires static java.annotation; + + requires io.helidon.common.http; + requires io.helidon.servicecommon.rest; + requires io.helidon.config; + requires io.helidon.webserver.cors; + + requires micrometer.core; + requires micrometer.registry.prometheus; + requires simpleclient; + + exports io.helidon.integrations.micrometer; + + // this is needed for CDI extensions that use non-public observer methods + opens io.helidon.integrations.micrometer to weld.core.impl, io.helidon.microprofile.cdi; + +} diff --git a/integrations/micrometer/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/integrations/micrometer/micrometer/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension similarity index 90% rename from integrations/micrometer/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension rename to integrations/micrometer/micrometer/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension index ff23db2310b..ac62bd6f9cc 100644 --- a/integrations/micrometer/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension +++ b/integrations/micrometer/micrometer/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.integrations.micrometer.MicrometerCdiExtension +io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension diff --git a/integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java similarity index 100% rename from integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java rename to integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java diff --git a/integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSimplePrometheusTest.java b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSimplePrometheusTest.java similarity index 100% rename from integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSimplePrometheusTest.java rename to integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSimplePrometheusTest.java diff --git a/integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSupportBuilderTest.java b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSupportBuilderTest.java similarity index 100% rename from integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSupportBuilderTest.java rename to integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerSupportBuilderTest.java diff --git a/integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestAddingMeters.java b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestAddingMeters.java similarity index 100% rename from integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestAddingMeters.java rename to integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestAddingMeters.java diff --git a/integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestUtil.java b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestUtil.java similarity index 100% rename from integrations/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestUtil.java rename to integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerTestUtil.java diff --git a/integrations/micrometer/src/test/resources/micrometerTestData.json b/integrations/micrometer/micrometer/src/test/resources/micrometerTestData.json similarity index 100% rename from integrations/micrometer/src/test/resources/micrometerTestData.json rename to integrations/micrometer/micrometer/src/test/resources/micrometerTestData.json From 81ba3581ad576946335932e76283a2d546900224 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 11:43:52 -0500 Subject: [PATCH 29/74] Typo --- .../common/src/main/java/io/helidon/common/FeatureCatalog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/common/src/main/java/io/helidon/common/FeatureCatalog.java b/common/common/src/main/java/io/helidon/common/FeatureCatalog.java index 2e5110272e2..468a84c659d 100644 --- a/common/common/src/main/java/io/helidon/common/FeatureCatalog.java +++ b/common/common/src/main/java/io/helidon/common/FeatureCatalog.java @@ -328,7 +328,7 @@ final class FeatureCatalog { addSe("io.helidon.integrations.micrometer", "Micrometer", - "Micrometer integration" + "Micrometer integration", "Micrometer"); addMp("io.helidon.integrations.micrometer.cdi", "Micrometer CDI", From 264c4f37a02e5832d8a8b56882d351891b340a80 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 13:45:21 -0500 Subject: [PATCH 30/74] Support multiple annotations of same type on annotated element --- .../servicecommon/restcdi/AnnotationLookupResult.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/AnnotationLookupResult.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/AnnotationLookupResult.java index 8899b0a88c2..ff5ff66fcd2 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/AnnotationLookupResult.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/AnnotationLookupResult.java @@ -74,11 +74,8 @@ AnnotationLookupResult
    lookupAnnotation(E element, Class annotCl */ public static Collection> lookupAnnotations(Annotated annotated, Class annotClass) { - // We have to filter by annotation class ourselves, because annotatedMethod.getAnnotations(Class) delegates - // to the Java method. That would bypass any annotations that had been added dynamically to the configurator. - return annotated.getAnnotations() + return annotated.getAnnotations(annotClass) .stream() - .filter(annotClass::isInstance) .map(annotation -> new AnnotationLookupResult<>(AnnotationSiteType.matchingType(annotated), annotClass.cast(annotation))) .collect(Collectors.toList()); From f4c7da4590ef397a73c730bed9a5c9c5e22b0713 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 13:47:59 -0500 Subject: [PATCH 31/74] Store in context data any exception thrown by the intercepted executable --- .../io/helidon/servicecommon/restcdi/InterceptionRunner.java | 2 ++ .../helidon/servicecommon/restcdi/InterceptionRunnerImpl.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java index 1d35ead3309..9285b014e0d 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java @@ -66,6 +66,8 @@ */ public interface InterceptionRunner { + String EXCEPTION = InterceptionRunner.class.getPackageName() + ".Exception"; + /** * Invokes the intercepted executable represented by the {@code InvocationContext}, performing the pre-invocation * operation on each work item. diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java index b6d854ee113..95c2a7f0d7b 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java @@ -80,6 +80,9 @@ public Object run( workItems.forEach(workItem -> preInvocationHandler.accept(context, workItem)); try { return context.proceed(); + } catch (Exception e) { + context.getContextData().put(EXCEPTION, e); + throw e; } finally { workItems.forEach(workItem -> postCompletionHandler.accept(context, workItem)); } @@ -148,6 +151,7 @@ public void onComplete(Throwable throwable) { workItems.forEach(workItem -> postCompletionHandler.accept(context, workItem)); if (throwable != null) { LOGGER.log(Level.FINE, "Throwable detected by interceptor async callback", throwable); + context.getContextData().put(EXCEPTION, throwable); } } } From 673dfeba64924ecbc091a54a7d9ad2152d18543f Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 13:50:22 -0500 Subject: [PATCH 32/74] Convert all interceptors to before-and-after to support update-only-on-exception Micrometer behavior --- .../micrometer/cdi/InterceptorCounted.java | 4 +- .../micrometer/cdi/InterceptorTimed.java | 8 +-- .../cdi/MicrometerCdiExtension.java | 16 ++++-- .../cdi/MicrometerInterceptorBase.java | 51 ++++++++----------- 4 files changed, 37 insertions(+), 42 deletions(-) diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java index c733563bafd..5dd347c50c8 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorCounted.java @@ -17,7 +17,6 @@ package io.helidon.integrations.micrometer.cdi; import javax.annotation.Priority; -import javax.inject.Inject; import javax.interceptor.Interceptor; import io.micrometer.core.annotation.Counted; @@ -27,14 +26,13 @@ @Priority(Interceptor.Priority.PLATFORM_BEFORE + 8) final class InterceptorCounted extends MicrometerInterceptorBase { - @Inject InterceptorCounted() { super(Counted.class, Counter.class); } @Override - void preInvoke(Counter counter) { + void postComplete(Counter counter) { counter.increment(); } } diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java index 07ad362acbd..84f28c54558 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java @@ -29,23 +29,19 @@ @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 8) -final class InterceptorTimed extends MicrometerInterceptorBase.WithPostComplete { +final class InterceptorTimed extends MicrometerInterceptorBase { private long startTimeNanos; InterceptorTimed() { super(Timed.class, Timer.class); } + @Override protected void preInvoke(Timer timer) { startTimeNanos = System.nanoTime(); } - @Override - public void postComplete(InvocationContext context, MeterWorkItem workItem) { - invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); - } - public void postComplete(Timer timer) { timer.record(Duration.ofNanos(System.nanoTime() - startTimeNanos)); } diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java index 50521a2b5c1..f13d330b3e5 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java @@ -83,11 +83,13 @@ void register(E element, Class clazz, AnnotationLookupResult "Registered counter " + counter.getId().toString()); newMeter = counter; + isOnlyOnException = ((Counted) annotation).recordFailuresOnly(); } else if (annotation instanceof Timed) { Timed timed = (Timed) annotation; if (timed.longTask()) { @@ -106,7 +108,7 @@ void register(E element, Class clazz, AnnotationLookupResult info = interceptionTargetInfo.computeIfAbsent((Executable) element, InterceptionTargetInfo::create); info.addWorkItem(lookupResult.annotation() - .annotationType(), MeterWorkItem.create(newMeter)); + .annotationType(), MeterWorkItem.create(newMeter, isOnlyOnException)); } } @@ -143,18 +145,24 @@ InterceptionTargetInfo interceptionTargetInfo(Executable executab static class MeterWorkItem { private final Meter meter; + private final boolean isOnlyOnException; - static MeterWorkItem create(M meter) { - return new MeterWorkItem(meter); + static MeterWorkItem create(M meter, boolean isOnlyOnException) { + return new MeterWorkItem(meter, isOnlyOnException); } - private MeterWorkItem(Meter meter) { + private MeterWorkItem(Meter meter, boolean isOnlyOnException) { this.meter = meter; + this.isOnlyOnException = isOnlyOnException; } Meter meter() { return meter; } + + boolean isOnlyOnException() { + return isOnlyOnException; + } } /** diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java index 7cdcdbd043b..74cdb28b58c 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java @@ -33,13 +33,22 @@ import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension.MeterWorkItem; import io.helidon.servicecommon.restcdi.HelidonInterceptor; +import io.helidon.servicecommon.restcdi.InterceptionRunner; import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; +/** + * Base implementation for all Micrometer interceptors. + *

    + * All use the with-post-completion" semantics because some metrics are updated only when the invoked executable throws an + * exception. + *

    + * @param + */ @Dependent -abstract class MicrometerInterceptorBase implements HelidonInterceptor { +abstract class MicrometerInterceptorBase implements HelidonInterceptor.WithPostComplete { private static final Logger LOGGER = Logger.getLogger(MicrometerInterceptorBase.class.getPackageName() + ".Interceptor*"); @@ -102,10 +111,17 @@ public InterceptionTargetInfo interceptionTargetInfo(Executable e @Override public void preInvoke(InvocationContext context, MeterWorkItem workItem) { - invokeVerifiedAction(context, workItem, this::preInvoke, ActionType.PREINVOKE); + verifyAction(context, workItem, this::preInvoke, ActionType.PREINVOKE); } - void invokeVerifiedAction(InvocationContext context, MeterWorkItem workItem, Consumer action, ActionType actionType) { + @Override + public void postComplete(InvocationContext context, MeterWorkItem workItem) { + if (!workItem.isOnlyOnException() || context.getContextData().get(InterceptionRunner.EXCEPTION) != null) { + verifyAction(context, workItem, this::postComplete, ActionType.COMPLETE); + } + } + + private void verifyAction(InvocationContext context, MeterWorkItem workItem, Consumer action, ActionType actionType) { Meter meter = workItem.meter(); if (registry .find(meter.getId().getName()) @@ -120,32 +136,9 @@ void invokeVerifiedAction(InvocationContext context, MeterWorkItem workItem, Con action.accept(meterType.cast(meter)); } - abstract void preInvoke(M meter); - - abstract static class WithPostComplete extends MicrometerInterceptorBase - implements HelidonInterceptor.WithPostComplete { - - WithPostComplete(Class annotationType, Class meterType) { - super(annotationType, meterType); - } - - @AroundConstruct - @Override - public Object aroundConstruct(InvocationContext context) throws Exception { - return aroundConstructBase(context); - } + void preInvoke(M meter) { + }; - @AroundInvoke - @Override - public Object aroundInvoke(InvocationContext context) throws Exception { - return aroundInvokeBase(context); - } + abstract void postComplete(M meter); - @Override - public void postComplete(InvocationContext context, MeterWorkItem workItem) { - invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); - } - - abstract void postComplete(M meter); - } } From 46dca87ceba0e03802aa9d48cc026d904c1960bf Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Fri, 26 Mar 2021 16:08:19 -0500 Subject: [PATCH 33/74] Clean up metrics extension, delegating to new superclass --- .../metrics/MetricsCdiExtension.java | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java index 4bb0688f97b..fa060bc3cb9 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java @@ -633,31 +633,16 @@ boolean restEndpointsMetricsEnabled() { // register metrics with server after security and when // application scope is initialized - void registerMetrics(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) Object adv, - BeanManager bm) { + @Override + protected Routing.Builder registerService(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) Object adv, + BeanManager bm, ServerCdiExtension server) { Set vendorMetricsAdded = new HashSet<>(); Config config = ((Config) ConfigProvider.getConfig()).get("metrics"); - MetricsSupport metricsSupport = MetricsSupport.create(config); - - ServerCdiExtension server = bm.getExtension(ServerCdiExtension.class); - - ConfigValue routingNameConfig = config.get("routing").asString(); - Routing.Builder defaultRouting = server.serverRoutingBuilder(); - - Routing.Builder endpointRouting = defaultRouting; + Routing.Builder defaultRouting = super.registerService(adv, bm, server); + MetricsSupport metricsSupport = serviceSupport(); - if (routingNameConfig.isPresent()) { - String routingName = routingNameConfig.get(); - // support for overriding this back to default routing using config - if (!"@default".equals(routingName)) { - endpointRouting = server.serverNamedRoutingBuilder(routingName); - } - } - - metricsSupport.configureVendorMetrics(null, defaultRouting); vendorMetricsAdded.add("@default"); - metricsSupport.configureEndpoint(endpointRouting); // now we may have additional sockets we want to add vendor metrics to config.get("vendor-metrics-routings") @@ -672,6 +657,8 @@ void registerMetrics(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(Appli // registry factory is available in global Contexts.globalContext().register(RegistryFactory.getInstance()); + + return defaultRouting; } private static boolean chooseRestEndpointsSetting(Config metricsConfig) { From 26e6ea38ee7323d33c5ec6f092d2c754329b79f3 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 17:18:29 -0500 Subject: [PATCH 34/74] Formalize pre-invocation and post-completion handlers with their own interfaces; add ability to pass throwable to post-completion handlers --- .../rest-cdi/etc/spotbugs/exclude.xml | 33 ++++ service-common/rest-cdi/pom.xml | 4 + .../restcdi/HelidonInterceptor.java | 4 +- .../restcdi/InterceptionRunner.java | 47 +++++- .../restcdi/InterceptionRunnerImpl.java | 145 ++++++++++++++++-- 5 files changed, 207 insertions(+), 26 deletions(-) create mode 100644 service-common/rest-cdi/etc/spotbugs/exclude.xml diff --git a/service-common/rest-cdi/etc/spotbugs/exclude.xml b/service-common/rest-cdi/etc/spotbugs/exclude.xml new file mode 100644 index 00000000000..65a28acf0bd --- /dev/null +++ b/service-common/rest-cdi/etc/spotbugs/exclude.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/service-common/rest-cdi/pom.xml b/service-common/rest-cdi/pom.xml index db0ac1cb056..1a45611137e 100644 --- a/service-common/rest-cdi/pom.xml +++ b/service-common/rest-cdi/pom.xml @@ -26,6 +26,10 @@ helidon-service-common-rest-cdi + + etc/spotbugs/exclude.xml + + io.helidon.service-common diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java index c2d98265581..20792f56434 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java @@ -163,9 +163,9 @@ default Object aroundInvokeBase(InvocationContext context) throws Exception { * Performs whatever post-completion work is needed for the given context, applied to the specified work item. * * @param context {@code InvocationContext} for the execution being intercepted + * @param throwable throwable from the intercepted method; null if the method returned normally * @param workItem the work item on which to operate */ - - void postComplete(InvocationContext context, W workItem); + void postComplete(InvocationContext context, Throwable throwable, W workItem); } } diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java index 9285b014e0d..15c63524e27 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java @@ -16,8 +16,6 @@ */ package io.helidon.servicecommon.restcdi; -import java.util.function.BiConsumer; - import javax.interceptor.InvocationContext; /** @@ -29,7 +27,7 @@ *
      *
    • Create an instance of a class which implements {@code InterceptionRunner}.
    • *
    • From the interceptor's {@code @AroundConstruct} and {@code @AroundInvoke} methods, invoke one of the variants of - * the runner's {@link #run(InvocationContext, Iterable, BiConsumer) run} method. Which variant depends on whether the + * the runner's {@link #run(InvocationContext, Iterable, PreInvocationHandler) run} method. Which variant depends on whether the * specific interceptor needs to operate on the work items *
        *
      • only before (e.g., to increment a counter metric), or
      • @@ -47,7 +45,7 @@ * intercepted invocation runs, and *
      • an post-completion {@code Consumer} of work item which performs an action on each work item after the * intercepted invocation has finished, only for the "before-and-after" - * {@link #run(InvocationContext, Iterable, BiConsumer, BiConsumer) run} variant.
      • + * {@link #run(InvocationContext, Iterable, PreInvocationHandler, PostCompletionHandler) run} variant. *
      *
    • *
    @@ -66,7 +64,40 @@ */ public interface InterceptionRunner { - String EXCEPTION = InterceptionRunner.class.getPackageName() + ".Exception"; + /** + * Processing before an intercepted executable is invoked. + * + * @param type of the work item for the handler to process + */ + @FunctionalInterface + interface PreInvocationHandler { + + /** + * Processing before an intercepted executable is invoked. + * + * @param context {@code InvocationContext} for calling an intercepted executable + * @param workItem work item for the handler to process + */ + void accept(InvocationContext context, T workItem); + } + + /** + * Processing after an intercepted executable has completed, successfully or not. + * + * @param type of the work item for the handler to process + */ + @FunctionalInterface + interface PostCompletionHandler { + + /** + * Processes a work item after completion (successful or failed) for calling an intercepted executable. + * + * @param context {@code InvocationContext} for the intercepted executable + * @param throwable any problem running the executable; null if the invocation succeeded + * @param workItem the work item for the handler to process + */ + void accept(InvocationContext context, Throwable throwable, T workItem); + } /** * Invokes the intercepted executable represented by the {@code InvocationContext}, performing the pre-invocation @@ -82,7 +113,7 @@ public interface InterceptionRunner { Object run( InvocationContext context, Iterable workItems, - BiConsumer preInvocationHandler) throws Exception; + PreInvocationHandler preInvocationHandler) throws Exception; /** * Invokes the intercepted executable represented by the {@code InvocationContext}, performing the pre-invocation @@ -99,6 +130,6 @@ Object run( Object run( InvocationContext context, Iterable workItems, - BiConsumer preInvocationHandler, - BiConsumer postCompletionHandler) throws Exception; + PreInvocationHandler preInvocationHandler, + PostCompletionHandler postCompletionHandler) throws Exception; } diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java index 95c2a7f0d7b..3bcb0e5560a 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerImpl.java @@ -20,9 +20,12 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.Collection; +import java.util.Date; +import java.util.Map; import java.util.Objects; import java.util.StringJoiner; -import java.util.function.BiConsumer; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -30,6 +33,7 @@ import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.CompletionCallback; import javax.ws.rs.container.Suspended; +import javax.ws.rs.container.TimeoutHandler; /** * A general-purpose implementation of {@link InterceptionRunner}, supporting asynchronous JAX-RS endpoints as indicated by the @@ -66,7 +70,7 @@ static InterceptionRunner create(Executable executable) { public Object run( InvocationContext context, Iterable workItems, - BiConsumer preInvocationHandler) throws Exception { + PreInvocationHandler preInvocationHandler) throws Exception { workItems.forEach(workItem -> preInvocationHandler.accept(context, workItem)); return context.proceed(); } @@ -75,16 +79,19 @@ public Object run( public Object run( InvocationContext context, Iterable workItems, - BiConsumer preInvocationHandler, - BiConsumer postCompletionHandler) throws Exception { + PreInvocationHandler preInvocationHandler, + PostCompletionHandler postCompletionHandler) throws Exception { workItems.forEach(workItem -> preInvocationHandler.accept(context, workItem)); + Throwable t = null; try { return context.proceed(); } catch (Exception e) { - context.getContextData().put(EXCEPTION, e); + t = e; throw e; } finally { - workItems.forEach(workItem -> postCompletionHandler.accept(context, workItem)); + for (T workItem : workItems) { + postCompletionHandler.accept(context, t, workItem); + } } } @@ -106,16 +113,23 @@ private AsyncMethodRunnerImpl(int asyncResponseSlot) { public Object run( InvocationContext context, Iterable workItems, - BiConsumer preInvocationHandler, - BiConsumer postCompletionHandler) throws Exception { + PreInvocationHandler preInvocationHandler, + PostCompletionHandler postCompletionHandler) throws Exception { // Check the post-completion handler now because we don't want an NPE thrown from some other call stack when we try to // use it in the completion callback. Any other null argument would trigger an NPE from the current call stack. Objects.requireNonNull(postCompletionHandler, "postCompletionHandler"); workItems.forEach(workItem -> preInvocationHandler.accept(context, workItem)); + + Object[] params = context.getParameters(); AsyncResponse asyncResponse = AsyncResponse.class.cast(context.getParameters()[asyncResponseSlot]); - asyncResponse.register(FinishCallback.create(context, postCompletionHandler, workItems)); + ThrowableCapturingAsyncResponse throwableCapturingAsyncResponse = new ThrowableCapturingAsyncResponse(asyncResponse); + params[asyncResponseSlot] = throwableCapturingAsyncResponse; + context.setParameters(params); + + throwableCapturingAsyncResponse.register( + FinishCallback.create(context, throwableCapturingAsyncResponse, postCompletionHandler, workItems)); return context.proceed(); } @@ -127,31 +141,130 @@ public String toString() { } } + /** + * Delegating implementation of {@code AsyncResponse} which captures any exception the intercepted method passes to + * {@code AsyncResponse.resume}. + */ + private static class ThrowableCapturingAsyncResponse implements AsyncResponse { + + private final AsyncResponse delegate; + private Throwable throwable = null; + + private ThrowableCapturingAsyncResponse(AsyncResponse delegate) { + this.delegate = delegate; + } + + Throwable throwable() { + return throwable; + } + + @Override + public boolean resume(Object response) { + return delegate.resume(response); + } + + @Override + public boolean resume(Throwable response) { + throwable = response; + return delegate.resume(response); + } + + @Override + public boolean cancel() { + return delegate.cancel(); + } + + @Override + public boolean cancel(int retryAfter) { + return delegate.cancel(retryAfter); + } + + @Override + public boolean cancel(Date retryAfter) { + return delegate.cancel(retryAfter); + } + + @Override + public boolean isSuspended() { + return delegate.isSuspended(); + } + + @Override + public boolean isCancelled() { + return delegate.isCancelled(); + } + + @Override + public boolean isDone() { + return delegate.isDone(); + } + + @Override + public boolean setTimeout(long time, TimeUnit unit) { + return delegate.setTimeout(time, unit); + } + + @Override + public void setTimeoutHandler(TimeoutHandler handler) { + delegate.setTimeoutHandler(handler); + } + + @Override + public Collection> register(Class callback) { + return delegate.register(callback); + } + + @Override + public Map, Collection>> register(Class callback, Class... callbacks) { + return delegate.register(callback, callbacks); + } + + @Override + public Collection> register(Object callback) { + return delegate.register(callback); + } + + @Override + public Map, Collection>> register(Object callback, Object... callbacks) { + return delegate.register(callback, callbacks); + } + } + private static class FinishCallback implements CompletionCallback { private static final Logger LOGGER = Logger.getLogger(FinishCallback.class.getName()); private final InvocationContext context; - private final BiConsumer postCompletionHandler; + private final ThrowableCapturingAsyncResponse throwableCapturingAsyncResponse; + private final PostCompletionHandler postCompletionHandler; private final Iterable workItems; - static FinishCallback create(InvocationContext context, BiConsumer postCompletionHandler, + static FinishCallback create(InvocationContext context, + ThrowableCapturingAsyncResponse throwableCapturingAsyncResponse, + PostCompletionHandler postCompletionHandler, Iterable workItems) { - return new FinishCallback<>(context, postCompletionHandler, workItems); + return new FinishCallback<>(context, throwableCapturingAsyncResponse, postCompletionHandler, workItems); } - private FinishCallback(InvocationContext context, BiConsumer postCompletionHandler, + private FinishCallback(InvocationContext context, + ThrowableCapturingAsyncResponse throwableCapturingAsyncResponse, + PostCompletionHandler postCompletionHandler, Iterable workItems) { this.context = context; + this.throwableCapturingAsyncResponse = throwableCapturingAsyncResponse; this.postCompletionHandler = postCompletionHandler; this.workItems = workItems; } @Override public void onComplete(Throwable throwable) { - workItems.forEach(workItem -> postCompletionHandler.accept(context, workItem)); if (throwable != null) { - LOGGER.log(Level.FINE, "Throwable detected by interceptor async callback", throwable); - context.getContextData().put(EXCEPTION, throwable); + LOGGER.log(Level.FINE, "Unmapped throwable detected by interceptor async callback", throwable); + } else if (throwableCapturingAsyncResponse.throwable() != null) { + throwable = throwableCapturingAsyncResponse.throwable(); + LOGGER.log(Level.FINE, "Mapped throwable detected by interceptor async callback", throwable); + } + for (T workItem : workItems) { + postCompletionHandler.accept(context, throwable, workItem); } } } From 2dd1bf88b8c5dbf2eaadaf6e8258a0e14e06dfec Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 17:20:18 -0500 Subject: [PATCH 35/74] Adapt MP metrics to addition of Throwable to post-completion handler method --- .../microprofile/metrics/InterceptorWithPostInvoke.java | 2 +- .../helidon/microprofile/metrics/MetricsCdiExtension.java | 6 ++++-- .../microprofile/metrics/MetricsInterceptorBase.java | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java index 1d281e81a0a..9f8d1e70c71 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java @@ -62,7 +62,7 @@ Object aroundInvoke(InvocationContext context) throws Exception { this::postComplete); } - void postComplete(InvocationContext context, MetricWorkItem workItem) { + void postComplete(InvocationContext context, Throwable t, MetricWorkItem workItem) { invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java index fa060bc3cb9..38742835ccc 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java @@ -634,8 +634,10 @@ boolean restEndpointsMetricsEnabled() { // register metrics with server after security and when // application scope is initialized @Override - protected Routing.Builder registerService(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) Object adv, - BeanManager bm, ServerCdiExtension server) { + protected Routing.Builder registerService(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) + Object adv, + BeanManager bm, + ServerCdiExtension server) { Set vendorMetricsAdded = new HashSet<>(); Config config = ((Config) ConfigProvider.getConfig()).get("metrics"); diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java index f4439ead194..b8fde8232d7 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java @@ -157,7 +157,7 @@ public Object aroundInvoke(InvocationContext context) throws Exception { } @Override - public void postComplete(InvocationContext context, MetricWorkItem workItem) { + public void postComplete(InvocationContext context, Throwable throwable, MetricWorkItem workItem) { invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); } From d6cfb0973b902905a9f168710773f8fad9a21af3 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 17:21:22 -0500 Subject: [PATCH 36/74] Straggler pom --- integrations/micrometer/cdi/pom.xml | 139 ++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 integrations/micrometer/cdi/pom.xml diff --git a/integrations/micrometer/cdi/pom.xml b/integrations/micrometer/cdi/pom.xml new file mode 100644 index 00000000000..111abdfc744 --- /dev/null +++ b/integrations/micrometer/cdi/pom.xml @@ -0,0 +1,139 @@ + + + + + 4.0.0 + + io.helidon.integrations + helidon-integrations-micrometer-project + 2.3.0-SNAPSHOT + + helidon-integrations-micrometer-cdi + Helidon Integrations Micrometer CDI + + + Helidon Micrometer Integration CDI + + + + etc/spotbugs/exclude.xml + + + + + io.helidon.common + helidon-common + + + io.helidon.config + helidon-config-mp + + + io.helidon.integrations + helidon-integrations-micrometer + + + io.micrometer + micrometer-registry-prometheus + + + io.prometheus + simpleclient + + + io.helidon.webserver + helidon-webserver + + + io.helidon.webserver + helidon-webserver-cors + + + io.helidon.config + helidon-config + + + io.helidon.service-common + helidon-service-common-rest + + + io.helidon.common + helidon-common-http + + + io.helidon.service-common + helidon-service-common-rest-cdi + provided + true + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + true + + + jakarta.activation + jakarta.activation-api + provided + true + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.webclient + helidon-webclient + test + + + io.helidon.config + helidon-config-hocon + test + + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -Xlint:-requires-automatic + + + + + + From 62f598744884c547dcf09643d63f55ba301b6d4f Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 17:22:18 -0500 Subject: [PATCH 37/74] Adapt to addition of Throwable to completion handler; update timer in interceptor according to only-on-failure annotation setting --- .../integrations/micrometer/cdi/InterceptorTimed.java | 3 --- .../micrometer/cdi/MicrometerInterceptorBase.java | 7 +++---- integrations/micrometer/cdi/src/main/java/module-info.java | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java index 84f28c54558..4dd227cdab6 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/InterceptorTimed.java @@ -20,9 +20,6 @@ import javax.annotation.Priority; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; - -import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension.MeterWorkItem; import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.Timer; diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java index 74cdb28b58c..03ec3a2ab5b 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java @@ -33,7 +33,6 @@ import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension.MeterWorkItem; import io.helidon.servicecommon.restcdi.HelidonInterceptor; -import io.helidon.servicecommon.restcdi.InterceptionRunner; import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; import io.micrometer.core.instrument.Meter; @@ -115,8 +114,8 @@ public void preInvoke(InvocationContext context, MeterWorkItem workItem) { } @Override - public void postComplete(InvocationContext context, MeterWorkItem workItem) { - if (!workItem.isOnlyOnException() || context.getContextData().get(InterceptionRunner.EXCEPTION) != null) { + public void postComplete(InvocationContext context, Throwable throwable, MeterWorkItem workItem) { + if (!workItem.isOnlyOnException() || throwable != null) { verifyAction(context, workItem, this::postComplete, ActionType.COMPLETE); } } @@ -137,7 +136,7 @@ private void verifyAction(InvocationContext context, MeterWorkItem workItem, Con } void preInvoke(M meter) { - }; + } abstract void postComplete(M meter); diff --git a/integrations/micrometer/cdi/src/main/java/module-info.java b/integrations/micrometer/cdi/src/main/java/module-info.java index 60d9d6c330f..c7aa875d325 100644 --- a/integrations/micrometer/cdi/src/main/java/module-info.java +++ b/integrations/micrometer/cdi/src/main/java/module-info.java @@ -42,7 +42,7 @@ exports io.helidon.integrations.micrometer.cdi; // this is needed for CDI extensions that use non-public observer methods - opens io.helidon.integrations.micrometer to weld.core.impl, io.helidon.microprofile.cdi; + opens io.helidon.integrations.micrometer.cdi to weld.core.impl, io.helidon.microprofile.cdi; provides javax.enterprise.inject.spi.Extension with MicrometerCdiExtension; } From 0bcde702c12d6b04bbbed47d4f53986f8f895f4f Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 17:25:53 -0500 Subject: [PATCH 38/74] Remove incorrectly-preserved spotbugs exclusion property (never belonged here) --- integrations/micrometer/cdi/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/integrations/micrometer/cdi/pom.xml b/integrations/micrometer/cdi/pom.xml index 111abdfc744..aa0c187cb08 100644 --- a/integrations/micrometer/cdi/pom.xml +++ b/integrations/micrometer/cdi/pom.xml @@ -33,10 +33,6 @@ Helidon Micrometer Integration CDI - - etc/spotbugs/exclude.xml - - io.helidon.common From 8ef29e4bee4ba4297df4d024d2ee86ed91fbb10b Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 17:28:14 -0500 Subject: [PATCH 39/74] Straggler pom; tests --- .../micrometer/cdi/HelloWorldResource.java | 179 ++++++++++++++++++ .../micrometer/cdi/HelloWorldTest.java | 167 ++++++++++++++++ .../micrometer/cdi/MeteredBean.java | 49 +++++ .../micrometer/cdi/MeteredBeanTest.java | 75 ++++++++ .../cdi/src/test/resources/logging.properties | 45 +++++ integrations/micrometer/pom.xml | 42 ++++ 6 files changed, 557 insertions(+) create mode 100644 integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldResource.java create mode 100644 integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldTest.java create mode 100644 integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBean.java create mode 100644 integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBeanTest.java create mode 100644 integrations/micrometer/cdi/src/test/resources/logging.properties create mode 100644 integrations/micrometer/pom.xml diff --git a/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldResource.java b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldResource.java new file mode 100644 index 00000000000..03c4da817ba --- /dev/null +++ b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldResource.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.micrometer.cdi; + +import java.util.Collections; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import javax.enterprise.context.RequestScoped; +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.CompletionCallback; +import javax.ws.rs.container.Suspended; +import javax.ws.rs.core.MediaType; + +import io.micrometer.core.annotation.Counted; +import io.micrometer.core.annotation.Timed; + +/** + * HelloWorldResource class. + */ +@Path("helloworld") +@RequestScoped +public class HelloWorldResource { + + static final String SLOW_RESPONSE = "At last"; + + // In case pipeline runs need a different time + static final int SLOW_DELAY_SECS = Integer.getInteger("helidon.asyncSimplyTimedDelaySeconds", 2); + static final int NOT_SO_SLOW_DELAY_MS = 250; + + static final String MESSAGE_RESPONSE = "Hello World"; + static final String MESSAGE_COUNTER = "messageCounter"; + static final String MESSAGE_TIMER = "messageTimer"; + static final String MESSAGE_TIMER_2 = "messageTimer2"; + static final String SLOW_MESSAGE_TIMER = "slowMessageTimer"; + static final String SLOW_MESSAGE_COUNTER = "slowMessageCounter"; + static final String SLOW_MESSAGE_FAIL_COUNTER = "slowMessageFailCounter"; + static final String DOOMED_COUNTER = "doomedCounter"; + static final String FAST_MESSAGE_COUNTER = "fastMessageCounter"; + static final String FAIL = "FAIL"; + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + private static ExecutorService executorService = Executors.newSingleThreadExecutor(); + + public HelloWorldResource() { + } + + @Counted(MESSAGE_COUNTER) + @GET + @Produces(MediaType.TEXT_PLAIN) + public String message() { + return MESSAGE_RESPONSE; + } + + @GET + @Timed(MESSAGE_TIMER) + @Timed(MESSAGE_TIMER_2) + @Path("/withArg/{name}") + @Produces(MediaType.TEXT_PLAIN) + public String messageWithArg(@PathParam("name") String input){ + return "Hello World, " + input; + } + + @GET + @Counted(value = FAST_MESSAGE_COUNTER, recordFailuresOnly = true) + @Path("/fail/{fail}") + @Produces(MediaType.TEXT_PLAIN) + public String sometimesFail(@PathParam("fail") boolean fail) { + if (fail) { + throw new IllegalStateException("Failure on request"); + } + return "Hi"; + } + + @GET + @Path("/slow") + @Produces(MediaType.TEXT_PLAIN) + @Timed(SLOW_MESSAGE_TIMER) + public void slowMessage(@Suspended AsyncResponse ar) { + executorService.execute(() -> { + try { + TimeUnit.SECONDS.sleep(SLOW_DELAY_SECS); + } catch (InterruptedException e) { + // absorb silently + } + ar.resume(SLOW_RESPONSE); + }); + } + + @GET + @Path("/slowWithArg/{name}") + @Produces(MediaType.TEXT_PLAIN) + @Counted(SLOW_MESSAGE_COUNTER) + public void slowMessageWithArg(@PathParam("name") String input, @Suspended AsyncResponse ar) { + executorService.execute(() -> { + try { + TimeUnit.SECONDS.sleep(SLOW_DELAY_SECS); + } catch (InterruptedException e) { + // absorb silently + } finally { + ar.resume(SLOW_RESPONSE + " " + input); + } + }); + } + + @GET + @Path("/slowWithFail/{fail}") + @Produces(MediaType.TEXT_PLAIN) + @Counted(value = SLOW_MESSAGE_FAIL_COUNTER, recordFailuresOnly = true) + public void slowMessageWithFail(@PathParam("fail") boolean fail, @Suspended AsyncResponse ar) { + executorService.execute(() -> { + try { + TimeUnit.MILLISECONDS.sleep(NOT_SO_SLOW_DELAY_MS); + if (fail) { + throw new IllegalStateException("Fail on request"); + } else { + ar.resume(SLOW_RESPONSE); + } + } catch (Exception e) { + ar.resume(e); + } + }); + } + + @GET + @Path("/slowFailNoCounter") + @Produces(MediaType.TEXT_PLAIN) + public void slowFailNoCounter(@Suspended AsyncResponse ar) { + ar.register(new CompletionCallback() { + + @Override + public void onComplete(Throwable throwable) { + if (throwable == null) { + System.out.println("OK"); + } else { + System.out.println("No good"); + } + } + }); + executorService.execute(() -> { + try { + TimeUnit.MILLISECONDS.sleep(NOT_SO_SLOW_DELAY_MS); + } catch (InterruptedException e) { + // Don't care for testing. + } + ar.resume(new IllegalStateException("Fail on request")); + }); + } + + @GET + @Counted(DOOMED_COUNTER) + @Path("/testDeletedMetric") + @Produces(MediaType.TEXT_PLAIN) + public String testDeletedMetric() { + return "Hello there"; + } +} diff --git a/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldTest.java b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldTest.java new file mode 100644 index 00000000000..f70c3ce2647 --- /dev/null +++ b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/HelloWorldTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.micrometer.cdi; + +import io.helidon.microprofile.tests.junit5.AddBean; +import io.helidon.microprofile.tests.junit5.HelidonTest; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.json.JsonObject; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.IntStream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +/** + * Class HelloWorldTest. + */ +@HelidonTest +@AddBean(HelloWorldResource.class) +public class HelloWorldTest { + + @Inject + WebTarget webTarget; + + @Inject + MeterRegistry registry; + + @Test + public void testMetrics() { + final int iterations = 2; + IntStream.range(0, iterations).forEach( + i -> webTarget + .path("helloworld") + .request() + .accept(MediaType.TEXT_PLAIN_TYPE) + .get(String.class)); + assertThat("Value of explicitly-updated counter", registry.counter(HelloWorldResource.MESSAGE_COUNTER).count(), + is((double) iterations)); + + checkMicrometerURL(iterations); + } + + @Test + void testTimer() { + int exp = 4; + IntStream.range(0, exp).forEach( + i -> webTarget + .path("helloworld/withArg/Joe") + .request(MediaType.TEXT_PLAIN_TYPE) + .get(String.class)); + + Timer timer = registry.timer(HelloWorldResource.MESSAGE_TIMER); + assertThat("Timer ", timer, is(notNullValue())); + assertThat("Timer count", timer.count(), is((long) exp)); + + Timer timer2 = registry.timer(HelloWorldResource.MESSAGE_TIMER_2); + assertThat("Timer 2", timer2, is(notNullValue())); + assertThat("Timer 2 count", timer2.count(), + is((long) exp)); + } + + @Test + public void testSlowTimer() { + int exp = 3; + AtomicReference result = new AtomicReference<>(); + IntStream.range(0, exp).forEach( + i -> result.set(webTarget + .path("helloworld/slow") + .request() + .accept(MediaType.TEXT_PLAIN_TYPE) + .get(String.class))); + assertThat("Returned from HTTP request", result.get(), is(HelloWorldResource.SLOW_RESPONSE)); + Timer slowTimer = registry.timer(HelloWorldResource.SLOW_MESSAGE_TIMER); + assertThat("Slow message timer", slowTimer, is(notNullValue())); + assertThat("Slow message timer count", slowTimer.count(), is((long) exp)); + } + + @Test + public void testFastFailCounter() { + int exp = 8; + IntStream.range(0, exp).forEach( + i -> { + try { + webTarget + .path("helloworld/fail/" + (i % 2 == 0 ? "false" : "true")) + .request() + .accept(MediaType.TEXT_PLAIN_TYPE) + .get(String.class); + } catch (InternalServerErrorException ex) { + // expected half the time + } + }); + + Counter counter = registry.counter(HelloWorldResource.FAST_MESSAGE_COUNTER); + assertThat("Failed message counter", counter, is(notNullValue())); + assertThat("Failed message counter count", counter.count(), is((double) exp / 2)); + } + + @Test + public void testSlowFailNoCounter() { + try { + webTarget + .path("helloworld/slowFailNoCounter") + .request() + .accept(MediaType.TEXT_PLAIN_TYPE) + .get(String.class); + } catch (InternalServerErrorException ex) { + // expected + } + System.out.println("Finished slowFailNoCounter test"); + } + + @Test + public void testSlowFailCounter() { + int exp = 6; + IntStream.range(0, exp).forEach( + i -> { + try { + webTarget + .path("helloworld/slowWithFail/" + (i % 2 == 0 ? "false" : "true")) + .request() + .accept(MediaType.TEXT_PLAIN_TYPE) + .get(String.class); + } catch (InternalServerErrorException ex) { + // expected half the time + } + }); + + Counter counter = registry.counter(HelloWorldResource.SLOW_MESSAGE_FAIL_COUNTER); + assertThat("Failed message counter", counter, is(notNullValue())); + assertThat("Failed message counter count", counter.count(), is((double) 3)); + } + + void checkMicrometerURL(int iterations) { + String result = webTarget + .path("micrometer") + .request() + .accept(MediaType.TEXT_PLAIN_TYPE) + .get(String.class); + assertThat(HelloWorldResource.MESSAGE_COUNTER + " present in /micrometer output", + result.contains(HelloWorldResource.MESSAGE_COUNTER), is(true)); + } +} diff --git a/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBean.java b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBean.java new file mode 100644 index 00000000000..e1e05b2ca66 --- /dev/null +++ b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBean.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.integrations.micrometer.cdi; + +import io.micrometer.core.annotation.Counted; +import io.micrometer.core.annotation.Timed; + +public class MeteredBean { + + static final String COUNTED = "counted"; + static final String TIMED_1 = "timed-1"; + static final String TIMED_A = "timed-a"; + static final String TIMED_B = "timed-b"; + static final String COUNTED_ONLY_FOR_FAILURE = "counted-only-for-failure"; + + @Counted(COUNTED) + public void count() { + } + + @Timed(TIMED_1) + public void timed() { + } + + @Timed(TIMED_A) + @Timed(TIMED_B) + public void timedA() { + } + + @Counted(value = COUNTED_ONLY_FOR_FAILURE, recordFailuresOnly = true) + public void failable(boolean shouldFail) { + if (shouldFail) { + throw new RuntimeException("shouldFail"); + } + } +} diff --git a/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBeanTest.java b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBeanTest.java new file mode 100644 index 00000000000..88c55697d06 --- /dev/null +++ b/integrations/micrometer/cdi/src/test/java/io/helidon/integrations/micrometer/cdi/MeteredBeanTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.integrations.micrometer.cdi; + +import java.util.stream.IntStream; + +import javax.inject.Inject; + +import io.helidon.microprofile.tests.junit5.AddBean; +import io.helidon.microprofile.tests.junit5.HelidonTest; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@HelidonTest +@AddBean(MeteredBean.class) +public class MeteredBeanTest { + + @Inject + MeteredBean meteredBean; + + @Inject + MeterRegistry registry; + + @Test + public void testSimpleCounted() { + int exp = 3; + IntStream.range(0, exp).forEach(i -> meteredBean.count()); + assertThat("Value from simple counted meter", registry.counter(MeteredBean.COUNTED).count(), is((double) exp)); + } + + @Test + public void testSinglyTimed() { + int exp = 4; + IntStream.range(0, exp).forEach(i -> meteredBean.timed()); + assertThat("Count from singly-timed meter", registry.timer(MeteredBean.TIMED_1).count(), is((long) exp)); + } + + @Test + public void testDoublyTimed() { + int exp = 5; + IntStream.range(0, exp).forEach(i -> meteredBean.timedA()); + assertThat("Count from doubly-timed meter (A)", registry.timer(MeteredBean.TIMED_A).count(), is((long) exp)); + assertThat("Count from doubly-timed meter (B)", registry.timer(MeteredBean.TIMED_B).count(), is((long) exp)); + } + + @Test + public void testFailable() { + int OKCalls = 2; + int expFailed = 3; + IntStream.range(0, OKCalls).forEach(i -> meteredBean.failable(false)); + IntStream.range(0, expFailed).forEach(i -> assertThrows(RuntimeException.class, + () -> meteredBean.failable(true))); + assertThat("Count from failed calls", registry.counter(MeteredBean.COUNTED_ONLY_FOR_FAILURE).count(), + is((double) expFailed)); + } +} diff --git a/integrations/micrometer/cdi/src/test/resources/logging.properties b/integrations/micrometer/cdi/src/test/resources/logging.properties new file mode 100644 index 00000000000..03baed28ef5 --- /dev/null +++ b/integrations/micrometer/cdi/src/test/resources/logging.properties @@ -0,0 +1,45 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.common.HelidonConsoleHandler,java.util.logging.FileHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO +#io.helidon.microprofile.metrics.MetricsCdiExtension.level=FINEST +#io.helidon.microprofile.metrics.Interceptor*.level=FINEST + +#java.util.logging.FileHandler.level=FINEST +#java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter +#io.helidon.common.HelidonConsoleHandler.level=FINEST + +#org.jboss.weld.Bootstrap.level = FINEST +#org.jboss.weld.Bean.level=FINEST +#org.jboss.weld.Context.level=FINEST +#org.jboss.weld.Utilties.level=FINEST diff --git a/integrations/micrometer/pom.xml b/integrations/micrometer/pom.xml new file mode 100644 index 00000000000..221bdabf84a --- /dev/null +++ b/integrations/micrometer/pom.xml @@ -0,0 +1,42 @@ + + + + + 4.0.0 + + io.helidon.integrations + helidon-integrations-project + 2.3.0-SNAPSHOT + + helidon-integrations-micrometer-project + Helidon Integrations Micrometer Project + pom + + + micrometer + cdi + + + + Helidon Micrometer Integration + + + From e351acdeb4ce403de2c3f50e8976146c403510d8 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sat, 27 Mar 2021 23:29:58 -0500 Subject: [PATCH 40/74] Copyright --- docs/se/metrics/03_prometheus.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/se/metrics/03_prometheus.adoc b/docs/se/metrics/03_prometheus.adoc index 36af06e8944..1731b17baa4 100644 --- a/docs/se/metrics/03_prometheus.adoc +++ b/docs/se/metrics/03_prometheus.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2019, 2020 Oracle and/or its affiliates. + Copyright (c) 2019, 2021 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From d4812562ccf5550bc775b1771d6dda6e81c528ae Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sun, 28 Mar 2021 08:13:02 -0500 Subject: [PATCH 41/74] Further refine reusable API, reducing public surface area --- .../restcdi/HelidonInterceptor.java | 150 ++++++++++-------- .../restcdi/HelidonRestCdiExtension.java | 33 ++++ .../restcdi/InterceptionRunner.java | 2 +- .../restcdi/InterceptionRunnerFactory.java | 38 ----- .../restcdi/InterceptionTargetInfo.java | 94 ----------- 5 files changed, 119 insertions(+), 198 deletions(-) delete mode 100644 service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java delete mode 100644 service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java index 20792f56434..ccb52289300 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java @@ -16,22 +16,20 @@ */ package io.helidon.servicecommon.restcdi; -import java.lang.annotation.Annotation; import java.lang.reflect.Executable; +import javax.interceptor.AroundConstruct; +import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; /** - * Common behavior for many interceptors, invoking a {@linkplain #preInvoke(InvocationContext, Object)} method before - * running the intercepted constructor or method. + * Common behavior for interceptors, invoking a {@linkplain #preInvocation(InvocationContext, Object) preInvocation} method + * before running an intercepted {@code Executable}. *

    - * See also the {@link WithPostComplete} interface. + * Implementing classes typically extend {@link HelidonInterceptor.Base}. *

    *

    - * Concrete classes typically implement the required {@linkplain #aroundConstruct(InvocationContext)} method with the - * {@code @AroundConstruct} annotation and it delegates to {@linkplain #aroundConstructBase(InvocationContext)}. Similarly, - * the required {@linkplain #aroundInvoke(InvocationContext)} method has the {@code @AroundInvoke} annotation and delegates to - * {@linkplain #aroundInvokeBase(InvocationContext)}. + * See also the {@link WithPostCompletion} interface. *

    * * @param type of the work item processed by the interceptor @@ -39,26 +37,27 @@ public interface HelidonInterceptor { /** - * Invokes the implementation's {@code preInvoke} logic for a constructor, once for each work item associated with this + * Invokes the implementation's {@code preInvocation} logic for a constructor, once for each work item associated with this * constructor and the annotation type this interceptor handles. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} * @throws Exception when the intercepted code throws an exception */ - default Object aroundConstructBase(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = interceptionTargetInfo(context.getConstructor()); - return interceptionTargetInfo.runner().run( - context, - interceptionTargetInfo.workItems(annotationType()), - this::preInvoke); + default Object aroundConstruction(InvocationContext context) throws Exception { + return InterceptionRunnerImpl.create(context.getConstructor()) + .run( + context, + workItems(context.getConstructor()), + this::preInvocation); } /** * Invoked during the intercepted constructor invocation. *

    - * Typically, annotate the implementation with {@code @AroundConstruct} and simply delegate to {@linkplain - * #aroundConstructBase}. + * Typically, concrete implementations bear the {@code @AroundConstruct} annotation and simply delegate to + * {@linkplain #aroundConstruction(InvocationContext) aroundConstructBase}. (Annotating the interface method is not + * sufficient.) *

    * @param context the invocation context for the intercepted call * @return the value returned from the intercepted constructor @@ -67,46 +66,40 @@ default Object aroundConstructBase(InvocationContext context) throws Exception { Object aroundConstruct(InvocationContext context) throws Exception; /** - * Invoked during the intercepted method invocation. - *

    - * Typically, annotated the implementation with {@code @AroundInvoke} and simply delegate to {@code aroundInvokeBase}. - *

    - * @param context the invocation context for the intercepted call - * @return the value returned from the intercepted method - * @throws Exception if the invoked method throws an exception - */ - Object aroundInvoke(InvocationContext context) throws Exception; - - /** - * Invokes the implementation's {@code preInvoke} logic for a method, once for each work item associated with this - * constructor and the annotation type this interceptor handles. + * Invokes the implementation's {@linkplain #preInvocation(InvocationContext, Object) preInvocation} logic for a method, once + * for each work item associated with this constructor and the annotation type this interceptor handles. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} * @throws Exception when the intercepted code throws an exception */ - default Object aroundInvokeBase(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = interceptionTargetInfo(context.getMethod()); - return interceptionTargetInfo.runner().run( + default Object aroundInvocation(InvocationContext context) throws Exception { + return InterceptionRunnerImpl.create(context.getMethod()).run( context, - interceptionTargetInfo.workItems(annotationType()), - this::preInvoke); + workItems(context.getMethod()), + this::preInvocation); } /** - * Returns the type of the annotation this interceptor handles. - * - * @return type of the annotation + * Invoked during the intercepted method invocation. + *

    + * Typically, concrete implementations bear the {@code @AroundInvoke} annotation and simply delegate to + * {@linkplain #aroundInvocation(InvocationContext) aroundInvokeBase}. (Annotating the interface method is not + * sufficient.) + *

    + * @param context the invocation context for the intercepted call + * @return the value returned from the intercepted method + * @throws Exception if the invoked method throws an exception */ - Class annotationType(); + Object aroundInvoke(InvocationContext context) throws Exception; /** - * Returns the correct {@link InterceptionTargetInfo} for the given {@code Executable}. + * Returns the work items the specific interceptor instance should process. * - * @param executable the constructor or method for which the {@code InterceptInfo} is needed - * @return the appropriate {@code InterceptInfo} + * @param executable the specific {@code Executable} being intercepted + * @return the work items of this interceptor's type that are pertinent to the specified {@code Executable} */ - InterceptionTargetInfo interceptionTargetInfo(Executable executable); + Iterable workItems(Executable executable); /** * Performs whatever pre-invocation work is needed for the given context, applied to the specified work item. @@ -114,49 +107,76 @@ default Object aroundInvokeBase(InvocationContext context) throws Exception { * @param context {@code InvocationContext} for the execution being intercepted * @param workItem the work item on which to operate */ - void preInvoke(InvocationContext context, W workItem); + void preInvocation(InvocationContext context, W workItem); + + /** + * {@code HelidonInterceptor} implementation providing as much logic as possible. + *

    + * The two methods implemented here cannot be {@code default} methods on the interface because the + * {@code @AroundConstruct} and {@code @AroundInvoke} annotations are not recognized on {@code default} interface methods. + *

    + * @param type of work items processed by the interceptor implementation + */ + abstract class Base implements HelidonInterceptor { + + @AroundConstruct + @Override + public Object aroundConstruct(InvocationContext context) throws Exception { + return aroundConstruction(context); + } + + @AroundInvoke + @Override + public Object aroundInvoke(InvocationContext context) throws Exception { + return aroundInvocation(context); + } + } /** - * Common behavior among interceptors with both pre- and post-invoke behavior. + * Common behavior among interceptors with both pre-invocation and post-completion behavior. + *

    + * Implementing classes can extend {@link Base} for symmetry with {@code HelidonInterceptor} (it provides no + * concrete logic) or implement this interface directly. + *

    * * @param type of the work item processed during interception */ - interface WithPostComplete extends HelidonInterceptor { + interface WithPostCompletion extends HelidonInterceptor { /** - * Invokes the implementation's {@code preInvoke} and {@code postInvoke} logic for a constructor, once for each work item - * associated with this constructor and the annotation type this interceptor handles. + * Invokes the implementation's {@code preInvocation} and {@code postCompletion} logic for a constructor, once for each + * work item associated with this constructor and the annotation type this interceptor handles. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} * @throws Exception when the intercepted code throws an exception */ @Override - default Object aroundConstructBase(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = interceptionTargetInfo(context.getConstructor()); - return interceptionTargetInfo.runner().run( - context, - interceptionTargetInfo.workItems(annotationType()), - this::preInvoke, - this::postComplete); + default Object aroundConstruction(InvocationContext context) throws Exception { + return InterceptionRunnerImpl.create(context.getConstructor()) + .run( + context, + workItems(context.getConstructor()), + this::preInvocation, + this::postCompletion); } /** - * Invokes the implementation's {@code preInvoke} and {@code postInvoke} logic for a constructor, once for each work item - * associated with this constructor and the annotation type this interceptor handles. + * Invokes the implementation's {@code preInvocation} and {@code postCompletion} logic for a constructor, once for each + * work item associated with this constructor and the annotation type this interceptor handles. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} * @throws Exception when the intercepted code throws an exception */ @Override - default Object aroundInvokeBase(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = interceptionTargetInfo(context.getMethod()); - return interceptionTargetInfo.runner().run( - context, - interceptionTargetInfo.workItems(annotationType()), - this::preInvoke, - this::postComplete); + default Object aroundInvocation(InvocationContext context) throws Exception { + return InterceptionRunnerImpl.create(context.getMethod()) + .run( + context, + workItems(context.getMethod()), + this::preInvocation, + this::postCompletion); } /** @@ -166,6 +186,6 @@ default Object aroundInvokeBase(InvocationContext context) throws Exception { * @param throwable throwable from the intercepted method; null if the method returned normally * @param workItem the work item on which to operate */ - void postComplete(InvocationContext context, Throwable throwable, W workItem); + void postCompletion(InvocationContext context, Throwable throwable, W workItem); } } diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonRestCdiExtension.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonRestCdiExtension.java index 925d3e548ae..bec16145811 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonRestCdiExtension.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonRestCdiExtension.java @@ -19,8 +19,10 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Modifier; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -325,6 +327,37 @@ protected T serviceSupport() { return serviceSupport; } + /** + * Manages a very simple multi-map of {@code Executable} to {@code Class} to a {@code Set} of typed + * work items. + * + * @param type of work items managed + */ + protected static class WorkItemsManager { + + public static WorkItemsManager create() { + return new WorkItemsManager<>(); + } + + private WorkItemsManager() { + } + + private final Map, Set>> workItemsByExecutable = new HashMap<>(); + + public void put(Executable executable, Class annotationType, W workItem) { + workItemsByExecutable + .computeIfAbsent(executable, e -> new HashMap<>()) + .computeIfAbsent(annotationType, t -> new HashSet<>()) + .add(workItem); + } + + public Iterable workItems(Executable executable, Class annotationType) { + return workItemsByExecutable + .getOrDefault(executable, Collections.emptyMap()) + .getOrDefault(annotationType, Collections.emptySet()); + } + } + /** * Records producer fields and methods defined by the application. Ignores producers with non-default qualifiers and * library producers. diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java index 15c63524e27..21d2e40c2e7 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java @@ -62,7 +62,7 @@ * might need to process the work items twice. In those cases, the {@code Iterable} can furnish two {@code Iterator}s. *

    */ -public interface InterceptionRunner { +interface InterceptionRunner { /** * Processing before an intercepted executable is invoked. diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java deleted file mode 100644 index 3c067716ef6..00000000000 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunnerFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package io.helidon.servicecommon.restcdi; - -import java.lang.reflect.Executable; - -/** - * Creates appropriate {@link InterceptionRunner}. - */ -public class InterceptionRunnerFactory { - - /** - * Returns the appropriate {@code InterceptRunner}, depending on the {@code Executable} provider. - * - * @param executable the constructor or method - * @return the {@code IntercepRunner} - */ - public static InterceptionRunner create(Executable executable) { - return InterceptionRunnerImpl.create(executable); - } - - private InterceptionRunnerFactory() { - } -} diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java deleted file mode 100644 index 915b33d688d..00000000000 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionTargetInfo.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package io.helidon.servicecommon.restcdi; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Executable; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - -/** - * Records information about an intercepted executable. - *

    - * Specifically: - *

      - *
    • the work items to be updated when the corresponding executable is intercepted, organized by the annotation class that - * gave rise to each work item; and
    • - *
    • the {@code InterceptionRunner} to use in updating the work items and invoking the method or constructor.
    • - *
    - * - * @param base type of the work items handled by the interceptor represented by this instance - */ -public class InterceptionTargetInfo { - - private final InterceptionRunner runner; - - private final Map, Collection> workItemsByAnnotationType = new HashMap<>(); - - /** - * Creates a new instance based on the provided {@code Executable}. - * - * @param executable the constructor or method subject to interception - * @param the type of work items associated with the target info - * @return the new instance - */ - public static InterceptionTargetInfo create(Executable executable) { - return new InterceptionTargetInfo<>(InterceptionRunnerFactory.create(executable)); - } - - private InterceptionTargetInfo(InterceptionRunner runner) { - this.runner = runner; - } - - /** - * - * @return the {@code InterceptionRunner} for this instance - */ - public InterceptionRunner runner() { - return runner; - } - - /** - * Returns the work items for the given annotation type. - * - * @param annotationType type of annotation for which work items are requested - * @return the work items - */ - public Iterable workItems(Class annotationType) { - /* - * Build a supplier of the iterable, because before-and-after runners will need to process the work items twice so we need - * to give the runner a supplier. - */ - return workItemsByAnnotationType.get(annotationType); - } - - /** - * Adds a work item to this info, identifying the annotation type that led to this work item. - * @param annotationType type of the interceptor - * @param workItem the newly-created workItem - */ - public void addWorkItem(Class annotationType, T workItem) { - - // Using a set for the actual collection subtly handles the case where a class-level and a method- or constructor-level - // annotation both indicate the same workItem. We do not want to update the same workItem twice in that case. - - Collection workItems = workItemsByAnnotationType.computeIfAbsent(annotationType, c -> new HashSet<>()); - workItems.add(workItem); - } -} From 3a2df8279a132ffaaf5abd0318b8330d5e5ab6f7 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sun, 28 Mar 2021 08:13:41 -0500 Subject: [PATCH 42/74] Migrate MP metrics to newly-refined reusable bits --- .../microprofile/metrics/InterceptorBase.java | 21 +++----------- .../metrics/InterceptorConcurrentGauge.java | 2 +- .../metrics/InterceptorTimedBase.java | 2 +- .../metrics/InterceptorWithPostInvoke.java | 24 +++------------- .../metrics/MetricsCdiExtension.java | 20 +++++-------- .../metrics/MetricsInterceptorBase.java | 28 ++++++++----------- 6 files changed, 28 insertions(+), 69 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java index c6a52c9f076..06bc1126949 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorBase.java @@ -23,12 +23,10 @@ import java.util.logging.Logger; import javax.inject.Inject; -import javax.interceptor.AroundConstruct; -import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import io.helidon.microprofile.metrics.MetricsCdiExtension.MetricWorkItem; -import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; +import io.helidon.servicecommon.restcdi.HelidonInterceptor; import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricID; @@ -42,7 +40,7 @@ *

    * @param type of metrics the interceptor handles */ -abstract class InterceptorBase { +abstract class InterceptorBase extends HelidonInterceptor.Base { private static final Logger LOGGER = Logger.getLogger(InterceptorBase.class.getName()); @@ -87,19 +85,8 @@ Map metricsForVerification() { return metricsForVerification; } - @AroundConstruct - Object aroundConstruct(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = extension.interceptInfo(context.getConstructor()); - return interceptionTargetInfo.runner().run(context, interceptionTargetInfo.workItems(annotationType), this::preInvoke); - } - - @AroundInvoke - Object aroundInvoke(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = extension.interceptInfo(context.getMethod()); - return interceptionTargetInfo.runner().run(context, interceptionTargetInfo.workItems(annotationType), this::preInvoke); - } - - void preInvoke(InvocationContext context, MetricWorkItem workItem) { + @Override + public void preInvocation(InvocationContext context, MetricWorkItem workItem) { invokeVerifiedAction(context, workItem, this::preInvoke, ActionType.PREINVOKE); } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java index 4a3b301802b..f2828ccd304 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java @@ -27,7 +27,7 @@ @org.eclipse.microprofile.metrics.annotation.ConcurrentGauge @Interceptor @Priority(Interceptor.Priority.PLATFORM_BEFORE + 11) -final class InterceptorConcurrentGauge extends MetricsInterceptorBase.WithPostComplete { +final class InterceptorConcurrentGauge extends MetricsInterceptorBase.WithPostCompletion { InterceptorConcurrentGauge() { diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java index ea33d40e738..74590051e72 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorTimedBase.java @@ -27,7 +27,7 @@ * * @param type of the metric the interceptor updates */ -abstract class InterceptorTimedBase extends MetricsInterceptorBase.WithPostComplete { +abstract class InterceptorTimedBase extends MetricsInterceptorBase.WithPostCompletion { private long startNanos; diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java index 9f8d1e70c71..6311d992522 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorWithPostInvoke.java @@ -19,12 +19,10 @@ import java.lang.annotation.Annotation; import javax.inject.Inject; -import javax.interceptor.AroundConstruct; -import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import io.helidon.microprofile.metrics.MetricsCdiExtension.MetricWorkItem; -import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; +import io.helidon.servicecommon.restcdi.HelidonInterceptor; import org.eclipse.microprofile.metrics.Metric; @@ -37,7 +35,8 @@ *

    * @param type of metrics the interceptor handles */ -abstract class InterceptorWithPostInvoke extends InterceptorBase { +abstract class InterceptorWithPostInvoke extends InterceptorBase + implements HelidonInterceptor.WithPostCompletion { @Inject private MetricsCdiExtension extension; @@ -46,23 +45,8 @@ abstract class InterceptorWithPostInvoke extends InterceptorBa super(annotationType, metricType); } - @AroundConstruct - Object aroundConstruct(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = extension.interceptInfo(context.getConstructor()); - return interceptionTargetInfo.runner().run(context, interceptionTargetInfo.workItems(annotationType()), this::preInvoke, - this::postComplete); - } - - @Override - @AroundInvoke - Object aroundInvoke(InvocationContext context) throws Exception { - InterceptionTargetInfo interceptionTargetInfo = extension.interceptInfo(context.getMethod()); - return interceptionTargetInfo.runner().run(context, interceptionTargetInfo.workItems(annotationType()), this::preInvoke, - this::postComplete); - } - - void postComplete(InvocationContext context, Throwable t, MetricWorkItem workItem) { + public void postCompletion(InvocationContext context, Throwable t, MetricWorkItem workItem) { invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java index 38742835ccc..2be6e6c94f3 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java @@ -78,7 +78,6 @@ import io.helidon.microprofile.server.ServerCdiExtension; import io.helidon.servicecommon.restcdi.AnnotationLookupResult; import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension; -import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; import io.helidon.webserver.Routing; import org.eclipse.microprofile.config.ConfigProvider; @@ -142,11 +141,11 @@ public class MetricsCdiExtension extends HelidonRestCdiExtension> syntheticSimpleTimerClassesProcessed = new HashSet<>(); private final Set syntheticSimpleTimersToRegister = new HashSet<>(); - private final Map> interceptInfo = new HashMap<>(); - private final AtomicReference config = new AtomicReference<>(); private final AtomicReference metricsConfig = new AtomicReference<>(); + private final WorkItemsManager workItemsManager = WorkItemsManager.create(); + @SuppressWarnings("unchecked") private static T getReference(BeanManager bm, Type type, Bean bean) { return (T) bm.getReference(bean, type, bm.createCreationalContext(bean)); @@ -287,10 +286,8 @@ protected void register(E element, Class lookupResult) { MetricInfo metricInfo = registerMetricInternal(element, clazz, lookupResult); if (element instanceof Executable) { - InterceptionTargetInfo info = interceptInfo.computeIfAbsent((Executable) element, - InterceptionTargetInfo::create); - info.addWorkItem(lookupResult.annotation() - .annotationType(), MetricWorkItem.create(metricInfo.metricID, metricInfo.metric)); + workItemsManager.put((Executable) element, lookupResult.annotation().annotationType(), + MetricWorkItem.create(metricInfo.metricID, metricInfo.metric)); } } @@ -307,8 +304,8 @@ private static Tag[] tags(String[] tagStrings) { return result.toArray(new Tag[result.size()]); } - InterceptionTargetInfo interceptInfo(Executable executable) { - return interceptInfo.get(executable); + Iterable workItems(Executable executable, Class annotationType) { + return workItemsManager.workItems(executable, annotationType); } /** @@ -483,10 +480,7 @@ static SimpleTimer syntheticSimpleTimer(Method method) { } private void registerAndSaveSyntheticSimpleTimer(Method method) { - InterceptionTargetInfo info = interceptInfo.computeIfAbsent(method, - m -> InterceptionTargetInfo.create(method)); - - info.addWorkItem(SyntheticSimplyTimed.class, + workItemsManager.put(method, SyntheticSimplyTimed.class, MetricWorkItem.create(SYNTHETIC_SIMPLE_TIMER_METADATA, syntheticSimpleTimer(method), syntheticSimpleTimerMetricTags(method))); } diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java index b8fde8232d7..d254d868cb7 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsInterceptorBase.java @@ -31,7 +31,6 @@ import io.helidon.microprofile.metrics.MetricsCdiExtension.MetricWorkItem; import io.helidon.servicecommon.restcdi.HelidonInterceptor; -import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricID; @@ -83,18 +82,13 @@ public String toString() { @AroundConstruct @Override public Object aroundConstruct(InvocationContext context) throws Exception { - return aroundConstructBase(context); + return aroundConstruction(context); } @AroundInvoke @Override public Object aroundInvoke(InvocationContext context) throws Exception { - return aroundInvokeBase(context); - } - - @Override - public Class annotationType() { - return annotationType; + return aroundInvocation(context); } Map metricsForVerification() { @@ -105,12 +99,12 @@ Map metricsForVerification() { } @Override - public InterceptionTargetInfo interceptionTargetInfo(Executable executable) { - return extension.interceptInfo(executable); + public Iterable workItems(Executable executable) { + return extension.workItems(executable, annotationType); } @Override - public void preInvoke(InvocationContext context, MetricWorkItem workItem) { + public void preInvocation(InvocationContext context, MetricWorkItem workItem) { invokeVerifiedAction(context, workItem, this::preInvoke, ActionType.PREINVOKE); } @@ -137,27 +131,27 @@ void invokeVerifiedAction(InvocationContext context, MetricWorkItem workItem, Co *

    * @param type of metrics the interceptor handles */ - abstract static class WithPostComplete extends MetricsInterceptorBase - implements HelidonInterceptor.WithPostComplete { + abstract static class WithPostCompletion extends MetricsInterceptorBase + implements HelidonInterceptor.WithPostCompletion { - WithPostComplete(Class annotationType, Class metricType) { + WithPostCompletion(Class annotationType, Class metricType) { super(annotationType, metricType); } @AroundConstruct @Override public Object aroundConstruct(InvocationContext context) throws Exception { - return aroundConstructBase(context); + return aroundConstruction(context); } @AroundInvoke @Override public Object aroundInvoke(InvocationContext context) throws Exception { - return aroundInvokeBase(context); + return aroundInvocation(context); } @Override - public void postComplete(InvocationContext context, Throwable throwable, MetricWorkItem workItem) { + public void postCompletion(InvocationContext context, Throwable throwable, MetricWorkItem workItem) { invokeVerifiedAction(context, workItem, this::postComplete, ActionType.COMPLETE); } From 0508912af753c098ae30041f3452ff30d5d536b2 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sun, 28 Mar 2021 08:14:06 -0500 Subject: [PATCH 43/74] Migrate Micrometer to newly-refined reusable bits --- .../cdi/MicrometerCdiExtension.java | 13 ++----- .../cdi/MicrometerInterceptorBase.java | 38 +++++-------------- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java index f13d330b3e5..3be9573ffdb 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerCdiExtension.java @@ -21,9 +21,7 @@ import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -41,7 +39,6 @@ import io.helidon.integrations.micrometer.MicrometerSupport; import io.helidon.servicecommon.restcdi.AnnotationLookupResult; import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension; -import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; import io.micrometer.core.annotation.Counted; import io.micrometer.core.annotation.Timed; @@ -65,7 +62,7 @@ public class MicrometerCdiExtension extends HelidonRestCdiExtension< private final MeterRegistry meterRegistry; - private final Map> interceptionTargetInfo = new HashMap<>(); + private final WorkItemsManager workItemsManager = WorkItemsManager.create(); /** * Creates new extension instance. @@ -105,9 +102,7 @@ void register(E element, Class clazz, AnnotationLookupResult info = interceptionTargetInfo.computeIfAbsent((Executable) element, - InterceptionTargetInfo::create); - info.addWorkItem(lookupResult.annotation() + workItemsManager.put((Executable) element, lookupResult.annotation() .annotationType(), MeterWorkItem.create(newMeter, isOnlyOnException)); } } @@ -139,8 +134,8 @@ protected void recordProducerMethods(@Observes ProcessProducerMethod interceptionTargetInfo(Executable executable) { - return interceptionTargetInfo.get(executable); + Iterable workItems(Executable executable, Class annotationType) { + return workItemsManager.workItems(executable, annotationType); } static class MeterWorkItem { diff --git a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java index 03ec3a2ab5b..0e55ce5a722 100644 --- a/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java +++ b/integrations/micrometer/cdi/src/main/java/io/helidon/integrations/micrometer/cdi/MicrometerInterceptorBase.java @@ -27,13 +27,10 @@ import javax.enterprise.context.Dependent; import javax.inject.Inject; -import javax.interceptor.AroundConstruct; -import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import io.helidon.integrations.micrometer.cdi.MicrometerCdiExtension.MeterWorkItem; import io.helidon.servicecommon.restcdi.HelidonInterceptor; -import io.helidon.servicecommon.restcdi.InterceptionTargetInfo; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; @@ -41,13 +38,15 @@ /** * Base implementation for all Micrometer interceptors. *

    - * All use the with-post-completion" semantics because some metrics are updated only when the invoked executable throws an - * exception. + * Both interceptors use the with-post-completion" semantics; {@code Timed} needs to update the metric only after the + * method has completed, and {@code Counted} might use update-only-on-failure, which of course it does not know until + * completion. *

    * @param */ @Dependent -abstract class MicrometerInterceptorBase implements HelidonInterceptor.WithPostComplete { +abstract class MicrometerInterceptorBase extends HelidonInterceptor.Base + implements HelidonInterceptor.WithPostCompletion { private static final Logger LOGGER = Logger.getLogger(MicrometerInterceptorBase.class.getPackageName() + ".Interceptor*"); @@ -87,34 +86,17 @@ interface MeterLookup { } @Override - public Class annotationType() { - return annotationType; + public Iterable workItems(Executable executable) { + return extension.workItems(executable, annotationType); } - @AroundConstruct @Override - public Object aroundConstruct(InvocationContext context) throws Exception { - return aroundConstructBase(context); - } - - @AroundInvoke - @Override - public Object aroundInvoke(InvocationContext context) throws Exception { - return aroundInvokeBase(context); - } - - @Override - public InterceptionTargetInfo interceptionTargetInfo(Executable executable) { - return extension.interceptionTargetInfo(executable); - } - - @Override - public void preInvoke(InvocationContext context, MeterWorkItem workItem) { + public void preInvocation(InvocationContext context, MeterWorkItem workItem) { verifyAction(context, workItem, this::preInvoke, ActionType.PREINVOKE); } @Override - public void postComplete(InvocationContext context, Throwable throwable, MeterWorkItem workItem) { + public void postCompletion(InvocationContext context, Throwable throwable, MeterWorkItem workItem) { if (!workItem.isOnlyOnException() || throwable != null) { verifyAction(context, workItem, this::postComplete, ActionType.COMPLETE); } @@ -131,7 +113,7 @@ private void verifyAction(InvocationContext context, MeterWorkItem workItem, Con LOGGER.log(Level.FINEST, () -> String.format( "%s (%s) is accepting %s %s for processing on %s triggered by @%s", getClass().getSimpleName(), actionType, workItem.meter().getClass().getSimpleName(), workItem.meter().getId(), - context.getMethod() != null ? context.getMethod() : context.getConstructor(), annotationType().getSimpleName())); + context.getMethod() != null ? context.getMethod() : context.getConstructor(), annotationType.getSimpleName())); action.accept(meterType.cast(meter)); } From 0d2b70c48ad5c8d13e16603473c9536b69690d74 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Sun, 28 Mar 2021 08:35:17 -0500 Subject: [PATCH 44/74] Clean up some comments --- .../restcdi/HelidonInterceptor.java | 32 +++++++++---------- .../restcdi/InterceptionRunner.java | 5 +-- .../restcdi/InterceptionRunnerImpl.java | 4 +-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java index ccb52289300..55168462bdf 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/HelidonInterceptor.java @@ -37,8 +37,8 @@ public interface HelidonInterceptor { /** - * Invokes the implementation's {@code preInvocation} logic for a constructor, once for each work item associated with this - * constructor and the annotation type this interceptor handles. + * Invokes the implementation's {@code preInvocation} logic for a constructor, once for each work item associated with the + * constructor. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} @@ -55,9 +55,9 @@ default Object aroundConstruction(InvocationContext context) throws Exception { /** * Invoked during the intercepted constructor invocation. *

    - * Typically, concrete implementations bear the {@code @AroundConstruct} annotation and simply delegate to - * {@linkplain #aroundConstruction(InvocationContext) aroundConstructBase}. (Annotating the interface method is not - * sufficient.) + * Typically, concrete implementations should extend {@link Base} which implements this method with + * {@code @AroundConstruct}. Annotation processing for {@code @AroundConstruct} does not recognize the annotation on a + * {@code default} method implementation defined on the interface. *

    * @param context the invocation context for the intercepted call * @return the value returned from the intercepted constructor @@ -67,7 +67,7 @@ default Object aroundConstruction(InvocationContext context) throws Exception { /** * Invokes the implementation's {@linkplain #preInvocation(InvocationContext, Object) preInvocation} logic for a method, once - * for each work item associated with this constructor and the annotation type this interceptor handles. + * for each work item associated with the method. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} @@ -83,9 +83,9 @@ default Object aroundInvocation(InvocationContext context) throws Exception { /** * Invoked during the intercepted method invocation. *

    - * Typically, concrete implementations bear the {@code @AroundInvoke} annotation and simply delegate to - * {@linkplain #aroundInvocation(InvocationContext) aroundInvokeBase}. (Annotating the interface method is not - * sufficient.) + * Typically, concrete implementations should extend {@link Base} which implements this method with + * {@code @AroundInvoke}. Annotation processing for {@code @AroundInvoke} does not recognize the annotation on a + * {@code default} method implementation defined on the interface. *

    * @param context the invocation context for the intercepted call * @return the value returned from the intercepted method @@ -97,7 +97,7 @@ default Object aroundInvocation(InvocationContext context) throws Exception { * Returns the work items the specific interceptor instance should process. * * @param executable the specific {@code Executable} being intercepted - * @return the work items of this interceptor's type that are pertinent to the specified {@code Executable} + * @return the work items pertinent to the specified {@code Executable} */ Iterable workItems(Executable executable); @@ -112,8 +112,9 @@ default Object aroundInvocation(InvocationContext context) throws Exception { /** * {@code HelidonInterceptor} implementation providing as much logic as possible. *

    - * The two methods implemented here cannot be {@code default} methods on the interface because the - * {@code @AroundConstruct} and {@code @AroundInvoke} annotations are not recognized on {@code default} interface methods. + * The two methods implemented here cannot be {@code default} methods on the interface because annotation processing + * for {@code @AroundConstruct} and {@code @AroundInvoke} does not recognize their placement on {@code default} interface + * methods. *

    * @param type of work items processed by the interceptor implementation */ @@ -135,8 +136,7 @@ public Object aroundInvoke(InvocationContext context) throws Exception { /** * Common behavior among interceptors with both pre-invocation and post-completion behavior. *

    - * Implementing classes can extend {@link Base} for symmetry with {@code HelidonInterceptor} (it provides no - * concrete logic) or implement this interface directly. + * Implementing classes should extend {@link HelidonInterceptor} to inherit the provided behavior. *

    * * @param type of the work item processed during interception @@ -145,7 +145,7 @@ interface WithPostCompletion extends HelidonInterceptor { /** * Invokes the implementation's {@code preInvocation} and {@code postCompletion} logic for a constructor, once for each - * work item associated with this constructor and the annotation type this interceptor handles. + * work item associated with the constructor. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} @@ -163,7 +163,7 @@ default Object aroundConstruction(InvocationContext context) throws Exception { /** * Invokes the implementation's {@code preInvocation} and {@code postCompletion} logic for a constructor, once for each - * work item associated with this constructor and the annotation type this interceptor handles. + * work item associated with the method. * * @param context {@code InvocationContext} * @return any value returned by the intercepted {@code Executable} diff --git a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java index 21d2e40c2e7..d96d0faef1d 100644 --- a/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java +++ b/service-common/rest-cdi/src/main/java/io/helidon/servicecommon/restcdi/InterceptionRunner.java @@ -40,7 +40,8 @@ *

    * The interceptor passes the {@code run} method: *

    - *

    * * @param concrete type of {@code HelidonRestServiceSupport} used * @param Builder for the concrete type of {@code }HelidonRestServiceSupport} @@ -99,7 +92,7 @@ public abstract class HelidonRestCdiExtension< private final Set> annotatedClasses = new HashSet<>(); private final Set> annotatedClassesProcessed = new HashSet<>(); - private final Set> annotations; + private final Set> annotationTypes; private final Logger logger; private final Class ownProducer; @@ -112,19 +105,19 @@ public abstract class HelidonRestCdiExtension< * Common initialization for concrete implementations. * * @param logger Logger instance to use for logging messages - * @param annotations set of annotations this extension handles + * @param annotationTypes set of annotation types this extension handles * @param ownProducer type of producer class used in creating beans needed by the extension * @param serviceSupportFactory function from config to the corresponding SE-style service support object * @param configPrefix prefix for retrieving config related to this extension */ protected HelidonRestCdiExtension( Logger logger, - Set> annotations, + Set> annotationTypes, Class ownProducer, Function serviceSupportFactory, String configPrefix) { this.logger = logger; - this.annotations = annotations; + this.annotationTypes = annotationTypes; this.ownProducer = ownProducer; // class containing producers provided by this module this.serviceSupportFactory = serviceSupportFactory; this.configPrefix = configPrefix; @@ -182,59 +175,20 @@ protected void registerObjects(@Observes ProcessManagedBean pmb) { logger.log(Level.FINE, () -> "Processing annotations for " + clazz.getName()); - // Process methods keeping non-private declared on this class - for (AnnotatedMethod annotatedMethod : type.getMethods()) { - if (Modifier.isPrivate(annotatedMethod.getJavaMember() - .getModifiers())) { - continue; - } - annotations.forEach(annotation -> { - for (AnnotationLookupResult lookupResult : AnnotationLookupResult.lookupAnnotations( - type, annotatedMethod, annotation)) { - // For methods, register the object only on the declaring - // class, not subclasses per the MP Metrics 2.0 TCK - // VisibilityTimedMethodBeanTest. - if (lookupResult.siteType() != AnnotationSiteType.METHOD - || clazz.equals(annotatedMethod.getJavaMember() - .getDeclaringClass())) { - register(annotatedMethod.getJavaMember(), clazz, lookupResult); - } - } - }); - } - - // Process constructors - for (AnnotatedConstructor annotatedConstructor : type.getConstructors()) { - Constructor c = annotatedConstructor.getJavaMember(); - if (Modifier.isPrivate(c.getModifiers())) { - continue; - } - annotations.forEach(annotation -> { - AnnotationLookupResult lookupResult - = lookupAnnotation(c, annotation, clazz); - if (lookupResult != null) { - register(c, clazz, lookupResult); - } - }); - } - } + processManagedBean(pmb); + } /** - * Registers an object based on an annotation site. + * Registers a managed bean that survived vetoing. *

    * The meaning of "register" varies among the concrete implementations. At this point, this base implementation has managed * the annotation processing in a general way (e.g., only non-vetoed beans survive) and now delegates to the concrete - * implementations to actually respond appropriately to the annotation site. The implementation can return a - * value for later use in the concrete class. + * implementations to actually respond appropriately to the bean and whichever of its members are annotated. *

    * - * @param element the Element hosting the annotation - * @param clazz the class on which the hosting Element appears - * @param lookupResult result of looking up an annotation on an element, its class, and its ancestor classes - * @param type of method or field or constructor + * @param processManagedBean the managed bean, with at least one annotation of interest to the extension */ - protected abstract - void register(E element, Class clazz, AnnotationLookupResult lookupResult); + protected abstract void processManagedBean(ProcessManagedBean processManagedBean); /** * Checks to make sure the annotated type is not abstract and is not an interceptor. From f5c76fee1b0979daa647374dd5d5f2448778fe37 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 08:58:53 -0500 Subject: [PATCH 52/74] Remove commented bit in method declaration --- .../main/java/io/helidon/microprofile/metrics/MetricUtil.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java index a3bea8475b5..0dd8cae1d8c 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricUtil.java @@ -354,8 +354,7 @@ private static MetricRegistry getMetricRegistry() { return RegistryProducer.getDefaultRegistry(); } - static - String getElementName(E element, Class clazz) { + static String getElementName(Member element, Class clazz) { return element instanceof Constructor ? clazz.getSimpleName() : element.getName(); } From fbb0cd4ba16876ca10c8dbdea8ada6d9d9c0b3e6 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 11:50:38 -0500 Subject: [PATCH 53/74] Simplify MP metrics registration of metrics in response to finding metrics annotations (data-driven vs. repeated near-identical code to access annotation attributes) --- .../metrics/MetricAnnotationInfo.java | 283 ++++++++++++++++++ .../metrics/MetricsCdiExtension.java | 105 +------ .../metrics/TestMetricTypeCoverage.java | 43 +++ 3 files changed, 330 insertions(+), 101 deletions(-) create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java new file mode 100644 index 00000000000..e28f0b570df --- /dev/null +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Member; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import io.helidon.microprofile.metrics.MetricUtil.MatchingType; + +import org.eclipse.microprofile.metrics.Metadata; +import org.eclipse.microprofile.metrics.Metric; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.MetricType; +import org.eclipse.microprofile.metrics.Tag; +import org.eclipse.microprofile.metrics.annotation.ConcurrentGauge; +import org.eclipse.microprofile.metrics.annotation.Counted; +import org.eclipse.microprofile.metrics.annotation.Metered; +import org.eclipse.microprofile.metrics.annotation.SimplyTimed; +import org.eclipse.microprofile.metrics.annotation.Timed; + +/** + * Captures information and logic for dealing with the various metrics annotations. This allows the metrics handling to be + * largely data-driven rather than requiring separate code for each type of metric. + * + * @param
    the type of the specific metrics annotation + */ +class MetricAnnotationInfo { + + /** + * Encapulates information for preparing for a metric registration based on an annotation and an annotated element. + * + * @param type of the metric to be registered + */ + static class RegistrationPrep { + private final String metricName; + private final Metadata metadata; + private final Tag[] tags; + private final Registration registration; + + static + RegistrationPrep create(A annotation, + E annotatedElement, + Class clazz, + MatchingType matchingType) { + MetricAnnotationInfo info = ANNOTATION_TYPE_TO_INFO.get(annotation.annotationType()); + if (info != null && info.annotationClass().isInstance(annotation)) { + MetricAnnotationInfo typedInfo = (MetricAnnotationInfo) info; + + String metricName = MetricUtil.getMetricName(annotatedElement, clazz, matchingType, typedInfo.name(annotation), + typedInfo.absolute(annotation)); + String candidateDisplayName = typedInfo.displayName(annotation); + Metadata metadata = Metadata.builder() + .withName(metricName) + .withDisplayName(candidateDisplayName.isEmpty() ? metricName : candidateDisplayName) + .withDescription(typedInfo.description(annotation) + .trim()) + .withType(ANNOTATION_TYPE_TO_METRIC_TYPE.get(annotation.annotationType())) + .withUnit(typedInfo.unit(annotation) + .trim()) + .reusable(typedInfo.reusable(annotation)) + .build(); + return new RegistrationPrep<>(metricName, metadata, typedInfo.tags(annotation), typedInfo.registerFunction); + } + return null; + } + + private RegistrationPrep(String metricName, Metadata metadata, Tag[] tags, Registration registration) { + this.metricName = metricName; + this.metadata = metadata; + this.tags = tags; + this.registration = registration; + } + + String metricName() { + return metricName; + } + + Tag[] tags() { + return tags; + } + + T register(MetricRegistry registry) { + return registration.register(registry, metadata, tags); + } + } + + static final Map, MetricType> ANNOTATION_TYPE_TO_METRIC_TYPE = + Map.of(ConcurrentGauge.class, MetricType.CONCURRENT_GAUGE, + Counted.class, MetricType.COUNTER, + Metered.class, MetricType.METERED, + SimplyTimed.class, MetricType.SIMPLE_TIMER, + Timed.class, MetricType.TIMER); + + static final Map, MetricAnnotationInfo> ANNOTATION_TYPE_TO_INFO = Map.of( + Counted.class, new MetricAnnotationInfo<>( + Counted.class, + Counted::name, + Counted::absolute, + Counted::description, + Counted::displayName, + Counted::reusable, + Counted::unit, + Counted::tags, + MetricRegistry::counter), + Metered.class, new MetricAnnotationInfo<>( + Metered.class, + Metered::name, + Metered::absolute, + Metered::description, + Metered::displayName, + Metered::reusable, + Metered::unit, + Metered::tags, + MetricRegistry::meter), + Timed.class, new MetricAnnotationInfo<>( + Timed.class, + Timed::name, + Timed::absolute, + Timed::description, + Timed::displayName, + Timed::reusable, + Timed::unit, + Timed::tags, + MetricRegistry::timer), + ConcurrentGauge.class, new MetricAnnotationInfo<>( + ConcurrentGauge.class, + ConcurrentGauge::name, + ConcurrentGauge::absolute, + ConcurrentGauge::description, + ConcurrentGauge::displayName, + ConcurrentGauge::reusable, + ConcurrentGauge::unit, + ConcurrentGauge::tags, + MetricRegistry::concurrentGauge), + SimplyTimed.class, new MetricAnnotationInfo<>( + SimplyTimed.class, + SimplyTimed::name, + SimplyTimed::absolute, + SimplyTimed::description, + SimplyTimed::displayName, + SimplyTimed::reusable, + SimplyTimed::unit, + SimplyTimed::tags, + MetricRegistry::simpleTimer) + ); + + private final Class annotationClass; + private final Function annotationNameFunction; + private final Function annotationAbsoluteFunction; + private final Function annotationDescriptorFunction; + private final Function annotationDisplayNameFunction; + private final Function annotationReusableFunction; + private final Function annotationUnitsFunction; + private final Function annotationTagsFunction; + private final Registration registerFunction; + + + MetricAnnotationInfo( + Class annotationClass, + Function annotationNameFunction, + Function annotationAbsoluteFunction, + Function annotationDescriptorFunction, + Function annotationDisplayNameFunction, + Function annotationReusableFunction, + Function annotationUnitsFunction, + Function annotationTagsFunction, + Registration registerFunction) { + this.annotationClass = annotationClass; + this.annotationNameFunction = annotationNameFunction; + this.annotationAbsoluteFunction = annotationAbsoluteFunction; + this.annotationDescriptorFunction = annotationDescriptorFunction; + this.annotationDisplayNameFunction = annotationDisplayNameFunction; + this.annotationReusableFunction = annotationReusableFunction; + this.annotationUnitsFunction = annotationUnitsFunction; + this.annotationTagsFunction = annotationTagsFunction; + this.registerFunction = registerFunction; + } + + static Tag[] tags(String[] tagStrings) { + final List result = new ArrayList<>(); + for (String tagString : tagStrings) { + final int eq = tagString.indexOf("="); + if (eq > 0) { + final String tagName = tagString.substring(0, eq); + final String tagValue = tagString.substring(eq + 1); + result.add(new Tag(tagName, tagValue)); + } + } + return result.toArray(new Tag[0]); + } + + Class annotationClass() { + return annotationClass; + } + + A annotationOnMethod(AnnotatedElement ae) { + return ae.getAnnotation(annotationClass); + } + +// String name(AnnotatedElement ae) { +// return name(ae.getAnnotation(annotationClass)); +// } + + String name(A a) { + return annotationNameFunction.apply(a); + } + + boolean absolute(A a) { + return annotationAbsoluteFunction.apply(a); + } + + String displayName(A a) { + return annotationDisplayNameFunction.apply(a); + } + + String description(A a) { + return annotationDescriptorFunction.apply(a); + } + + boolean reusable(A a) { + return annotationReusableFunction.apply(a); + } + + String unit(A a) { + return annotationUnitsFunction.apply(a); + } + + Tag[] tags(A a) { + return tags(annotationTagsFunction.apply(a)); + } + +// boolean absolute(AnnotatedElement ae) { +// return absolute(ae.getAnnotation(annotationClass)); +// } +// +// String displayName(AnnotatedElement ae) { +// return annotationDisplayNameFunction.apply(ae.getAnnotation(annotationClass)); +// } + +// String description(AnnotatedElement ae) { +// return annotationDescriptorFunction.apply(ae.getAnnotation(annotationClass)); +// } + +// boolean reusable(AnnotatedElement ae) { +// return annotationReusableFunction.apply(ae.getAnnotation(annotationClass)); +// } +// +// String unit(AnnotatedElement ae) { +// return annotationUnitsFunction.apply(ae.getAnnotation(annotationClass)); +// } +// +// Tag[] tags(AnnotatedElement ae) { +// return tags(annotationTagsFunction.apply(ae.getAnnotation(annotationClass))); +// } + +// T register(MetricRegistry registry, Metadata metadata, AnnotatedElement ae) { +// return registerFunction.register(registry, metadata, tags(ae)); +// } + + @FunctionalInterface + interface Registration { + T register(MetricRegistry registry, Metadata metadata, Tag... tags); + } +} diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java index cb0e0b2505b..da0de8d2004 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java @@ -76,6 +76,7 @@ import io.helidon.metrics.MetricsSupport; import io.helidon.metrics.RegistryFactory; import io.helidon.microprofile.cdi.RuntimeStart; +import io.helidon.microprofile.metrics.MetricAnnotationInfo.RegistrationPrep; import io.helidon.microprofile.metrics.MetricUtil.LookupResult; import io.helidon.microprofile.server.ServerCdiExtension; import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension; @@ -179,107 +180,9 @@ static MetricInfo registerMetricInterna MetricRegistry registry = getMetricRegistry(); Annotation annotation = lookupResult.getAnnotation(); - String savedMetricName = null; - org.eclipse.microprofile.metrics.Metric metric = null; - Tag[] tags = null; - - if (annotation instanceof Counted) { - Counted counted = (Counted) annotation; - String metricName = getMetricName(element, clazz, lookupResult.getType(), counted.name().trim(), - counted.absolute()); - String displayName = counted.displayName().trim(); - Metadata meta = Metadata.builder() - .withName(metricName) - .withDisplayName(displayName.isEmpty() ? metricName : displayName) - .withDescription(counted.description().trim()) - .withType(MetricType.COUNTER) - .withUnit(counted.unit().trim()) - .reusable(counted.reusable()) - .build(); - registry.counter(meta, tags(counted.tags())); - savedMetricName = metricName; - tags = tags(counted.tags()); - metric = registry.counter(meta, tags); - LOGGER.log(Level.FINE, () -> "Registered counter " + metricName); - } else if (annotation instanceof Metered) { - Metered metered = (Metered) annotation; - String metricName = getMetricName(element, clazz, lookupResult.getType(), metered.name().trim(), - metered.absolute()); - String displayName = metered.displayName().trim(); - Metadata meta = Metadata.builder() - .withName(metricName) - .withDisplayName(displayName.isEmpty() ? metricName : displayName) - .withDescription(metered.description().trim()) - .withType(MetricType.METERED) - .withUnit(metered.unit().trim()) - .reusable(metered.reusable()) - .build(); - registry.meter(meta, tags(metered.tags())); - savedMetricName = metricName; - tags = tags(metered.tags()); - metric = registry.meter(meta, tags); - LOGGER.log(Level.FINE, () -> "Registered meter " + metricName); - } else if (annotation instanceof Timed) { - Timed timed = (Timed) annotation; - String metricName = getMetricName(element, clazz, lookupResult.getType(), timed.name().trim(), - timed.absolute()); - String displayName = timed.displayName().trim(); - Metadata meta = Metadata.builder() - .withName(metricName) - .withDisplayName(displayName.isEmpty() ? metricName : displayName) - .withDescription(timed.description().trim()) - .withType(MetricType.TIMER) - .withUnit(timed.unit().trim()) - .reusable(timed.reusable()) - .build(); - registry.timer(meta, tags(timed.tags())); - - savedMetricName = metricName; - tags = tags(timed.tags()); - metric = registry.timer(meta, tags); - LOGGER.log(Level.FINE, () -> "Registered timer " + metricName); - } else if (annotation instanceof ConcurrentGauge) { - ConcurrentGauge concurrentGauge = (ConcurrentGauge) annotation; - String metricName = getMetricName(element, clazz, lookupResult.getType(), concurrentGauge.name().trim(), - concurrentGauge.absolute()); - String displayName = concurrentGauge.displayName().trim(); - Metadata meta = Metadata.builder() - .withName(metricName) - .withDisplayName(displayName.isEmpty() ? metricName : displayName) - .withDescription(concurrentGauge.description().trim()) - .withType(MetricType.CONCURRENT_GAUGE) - .withUnit(concurrentGauge.unit().trim()) - .reusable(concurrentGauge.reusable()) - .build(); - registry.concurrentGauge(meta, tags(concurrentGauge.tags())); - savedMetricName = metricName; - tags = tags(concurrentGauge.tags()); - metric = registry.concurrentGauge(meta, tags); - LOGGER.log(Level.FINE, () -> "Registered concurrent gauge " + metricName); - } else if (annotation instanceof SimplyTimed) { - SimplyTimed simplyTimed = (SimplyTimed) annotation; - String metricName = getMetricName(element, clazz, lookupResult.getType(), simplyTimed.name().trim(), - simplyTimed.absolute()); - String displayName = simplyTimed.displayName().trim(); - Metadata meta = Metadata.builder() - .withName(metricName) - .withDisplayName(displayName.isEmpty() ? metricName : displayName) - .withDescription(simplyTimed.description().trim()) - .withType(MetricType.SIMPLE_TIMER) - .withUnit(simplyTimed.unit().trim()) - .reusable(simplyTimed.reusable()) - .build(); - registry.simpleTimer(meta, tags(simplyTimed.tags())); - savedMetricName = metricName; - tags = tags(simplyTimed.tags()); - metric = registry.simpleTimer(meta, tags); - LOGGER.log(Level.FINE, () -> "Registered simple timer " + metricName); - } - if (savedMetricName == null || metric == null || tags == null) { - throw new IllegalArgumentException(String.format("Cannot map annotation %s on %s to metric type", - annotation.annotationType().getSimpleName(), element)); - } - return new MetricInfo<>(new MetricID(savedMetricName, tags), metric); + RegistrationPrep registrationPrep = RegistrationPrep.create(annotation, element, clazz, lookupResult.getType()); + org.eclipse.microprofile.metrics.Metric metric = registrationPrep.register(registry); + return new MetricInfo<>(new MetricID(registrationPrep.metricName(), registrationPrep.tags()), metric); } @Override diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java new file mode 100644 index 00000000000..363227013d4 --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.metrics; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.microprofile.metrics.MetricType; +import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; + + +public class TestMetricTypeCoverage { + + @Test + public void ensureAllMetricTypesHandled() { + + Set missing = new HashSet<>(); + + for (MetricType type : MetricType.values()) { + if (!MetricAnnotationInfo.ANNOTATION_TYPE_TO_INFO.containsKey(type)) { + missing.add(type); + } + } + assertThat("MetricTypes not handled", missing, is(empty())); + } +} From 0a5d040b042b066b005d105ce0a7416de85df235 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 11:55:34 -0500 Subject: [PATCH 54/74] Fix unchecked casting --- .../metrics/MetricAnnotationInfo.java | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java index e28f0b570df..4076a156855 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java @@ -62,25 +62,24 @@ RegistrationPrep create(A annotation, Class clazz, MatchingType matchingType) { MetricAnnotationInfo info = ANNOTATION_TYPE_TO_INFO.get(annotation.annotationType()); - if (info != null && info.annotationClass().isInstance(annotation)) { - MetricAnnotationInfo typedInfo = (MetricAnnotationInfo) info; - - String metricName = MetricUtil.getMetricName(annotatedElement, clazz, matchingType, typedInfo.name(annotation), - typedInfo.absolute(annotation)); - String candidateDisplayName = typedInfo.displayName(annotation); - Metadata metadata = Metadata.builder() - .withName(metricName) - .withDisplayName(candidateDisplayName.isEmpty() ? metricName : candidateDisplayName) - .withDescription(typedInfo.description(annotation) - .trim()) - .withType(ANNOTATION_TYPE_TO_METRIC_TYPE.get(annotation.annotationType())) - .withUnit(typedInfo.unit(annotation) - .trim()) - .reusable(typedInfo.reusable(annotation)) - .build(); - return new RegistrationPrep<>(metricName, metadata, typedInfo.tags(annotation), typedInfo.registerFunction); + if (info == null || !info.annotationClass().isInstance(annotation)) { + return null; } - return null; + + String metricName = MetricUtil.getMetricName(annotatedElement, clazz, matchingType, info.name(annotation), + info.absolute(annotation)); + String candidateDisplayName = info.displayName(annotation); + Metadata metadata = Metadata.builder() + .withName(metricName) + .withDisplayName(candidateDisplayName.isEmpty() ? metricName : candidateDisplayName) + .withDescription(info.description(annotation) + .trim()) + .withType(ANNOTATION_TYPE_TO_METRIC_TYPE.get(annotation.annotationType())) + .withUnit(info.unit(annotation) + .trim()) + .reusable(info.reusable(annotation)) + .build(); + return new RegistrationPrep<>(metricName, metadata, info.tags(annotation), info.registerFunction); } private RegistrationPrep(String metricName, Metadata metadata, Tag[] tags, Registration registration) { @@ -220,32 +219,32 @@ A annotationOnMethod(AnnotatedElement ae) { // return name(ae.getAnnotation(annotationClass)); // } - String name(A a) { - return annotationNameFunction.apply(a); + String name(Annotation a) { + return annotationNameFunction.apply(annotationClass.cast(a)); } - boolean absolute(A a) { - return annotationAbsoluteFunction.apply(a); + boolean absolute(Annotation a) { + return annotationAbsoluteFunction.apply(annotationClass.cast(a)); } - String displayName(A a) { - return annotationDisplayNameFunction.apply(a); + String displayName(Annotation a) { + return annotationDisplayNameFunction.apply(annotationClass.cast(a)); } - String description(A a) { - return annotationDescriptorFunction.apply(a); + String description(Annotation a) { + return annotationDescriptorFunction.apply(annotationClass.cast(a)); } - boolean reusable(A a) { - return annotationReusableFunction.apply(a); + boolean reusable(Annotation a) { + return annotationReusableFunction.apply(annotationClass.cast(a)); } - String unit(A a) { - return annotationUnitsFunction.apply(a); + String unit(Annotation a) { + return annotationUnitsFunction.apply(annotationClass.cast(a)); } - Tag[] tags(A a) { - return tags(annotationTagsFunction.apply(a)); + Tag[] tags(Annotation a) { + return tags(annotationTagsFunction.apply(annotationClass.cast(a))); } // boolean absolute(AnnotatedElement ae) { From ac45245a86c740eced2b8b6118482874658a2871 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 12:05:06 -0500 Subject: [PATCH 55/74] Fix check that all metrics types are handled in anno processing --- .../metrics/TestMetricTypeCoverage.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java index 363227013d4..c9d3c795896 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java @@ -16,6 +16,7 @@ */ package io.helidon.microprofile.metrics; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -31,13 +32,18 @@ public class TestMetricTypeCoverage { @Test public void ensureAllMetricTypesHandled() { - Set missing = new HashSet<>(); + Set found = new HashSet<>(); + Set typesToCheck = new HashSet<>(Arrays.asList(MetricType.values())); + typesToCheck.remove(MetricType.INVALID); + typesToCheck.remove(MetricType.GAUGE); - for (MetricType type : MetricType.values()) { + + for (MetricType type : typesToCheck) { if (!MetricAnnotationInfo.ANNOTATION_TYPE_TO_INFO.containsKey(type)) { - missing.add(type); + found.add(type); } } - assertThat("MetricTypes not handled", missing, is(empty())); + typesToCheck.removeAll(found); + assertThat("MetricTypes not handled", typesToCheck, is(empty())); } } From f9c815b6c48faef58742325bd97997bb1d17d9d3 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 12:27:56 -0500 Subject: [PATCH 56/74] Fix the metric type coverage testing again --- .../metrics/MetricAnnotationInfo.java | 50 +++++++------------ .../metrics/TestMetricTypeCoverage.java | 12 +++-- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java index 4076a156855..c2cb74b49c9 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java @@ -119,7 +119,8 @@ T register(MetricRegistry registry) { Counted::reusable, Counted::unit, Counted::tags, - MetricRegistry::counter), + MetricRegistry::counter, + MetricType.COUNTER), Metered.class, new MetricAnnotationInfo<>( Metered.class, Metered::name, @@ -129,7 +130,8 @@ T register(MetricRegistry registry) { Metered::reusable, Metered::unit, Metered::tags, - MetricRegistry::meter), + MetricRegistry::meter, + MetricType.METERED), Timed.class, new MetricAnnotationInfo<>( Timed.class, Timed::name, @@ -139,7 +141,8 @@ T register(MetricRegistry registry) { Timed::reusable, Timed::unit, Timed::tags, - MetricRegistry::timer), + MetricRegistry::timer, + MetricType.TIMER), ConcurrentGauge.class, new MetricAnnotationInfo<>( ConcurrentGauge.class, ConcurrentGauge::name, @@ -149,7 +152,8 @@ T register(MetricRegistry registry) { ConcurrentGauge::reusable, ConcurrentGauge::unit, ConcurrentGauge::tags, - MetricRegistry::concurrentGauge), + MetricRegistry::concurrentGauge, + MetricType.CONCURRENT_GAUGE), SimplyTimed.class, new MetricAnnotationInfo<>( SimplyTimed.class, SimplyTimed::name, @@ -159,7 +163,8 @@ T register(MetricRegistry registry) { SimplyTimed::reusable, SimplyTimed::unit, SimplyTimed::tags, - MetricRegistry::simpleTimer) + MetricRegistry::simpleTimer, + MetricType.SIMPLE_TIMER) ); private final Class annotationClass; @@ -171,6 +176,7 @@ T register(MetricRegistry registry) { private final Function annotationUnitsFunction; private final Function annotationTagsFunction; private final Registration registerFunction; + private final MetricType metricType; MetricAnnotationInfo( @@ -182,7 +188,8 @@ T register(MetricRegistry registry) { Function annotationReusableFunction, Function annotationUnitsFunction, Function annotationTagsFunction, - Registration registerFunction) { + Registration registerFunction, + MetricType metricType) { this.annotationClass = annotationClass; this.annotationNameFunction = annotationNameFunction; this.annotationAbsoluteFunction = annotationAbsoluteFunction; @@ -192,6 +199,7 @@ T register(MetricRegistry registry) { this.annotationUnitsFunction = annotationUnitsFunction; this.annotationTagsFunction = annotationTagsFunction; this.registerFunction = registerFunction; + this.metricType = metricType; } static Tag[] tags(String[] tagStrings) { @@ -247,33 +255,9 @@ Tag[] tags(Annotation a) { return tags(annotationTagsFunction.apply(annotationClass.cast(a))); } -// boolean absolute(AnnotatedElement ae) { -// return absolute(ae.getAnnotation(annotationClass)); -// } -// -// String displayName(AnnotatedElement ae) { -// return annotationDisplayNameFunction.apply(ae.getAnnotation(annotationClass)); -// } - -// String description(AnnotatedElement ae) { -// return annotationDescriptorFunction.apply(ae.getAnnotation(annotationClass)); -// } - -// boolean reusable(AnnotatedElement ae) { -// return annotationReusableFunction.apply(ae.getAnnotation(annotationClass)); -// } -// -// String unit(AnnotatedElement ae) { -// return annotationUnitsFunction.apply(ae.getAnnotation(annotationClass)); -// } -// -// Tag[] tags(AnnotatedElement ae) { -// return tags(annotationTagsFunction.apply(ae.getAnnotation(annotationClass))); -// } - -// T register(MetricRegistry registry, Metadata metadata, AnnotatedElement ae) { -// return registerFunction.register(registry, metadata, tags(ae)); -// } + MetricType metricType() { + return metricType; + } @FunctionalInterface interface Registration { diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java index c9d3c795896..7db060a3784 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricTypeCoverage.java @@ -34,13 +34,17 @@ public void ensureAllMetricTypesHandled() { Set found = new HashSet<>(); Set typesToCheck = new HashSet<>(Arrays.asList(MetricType.values())); - typesToCheck.remove(MetricType.INVALID); - typesToCheck.remove(MetricType.GAUGE); + + + // We do not use the general anno processing for gauges. There is no annotation for histogram. + typesToCheck.removeAll(Set.of(MetricType.INVALID, MetricType.GAUGE, MetricType.HISTOGRAM)); for (MetricType type : typesToCheck) { - if (!MetricAnnotationInfo.ANNOTATION_TYPE_TO_INFO.containsKey(type)) { - found.add(type); + for (MetricAnnotationInfo info : MetricAnnotationInfo.ANNOTATION_TYPE_TO_INFO.values()) { + if (info.metricType().equals(type)) { + found.add(type); + } } } typesToCheck.removeAll(found); From 7269a9154ff9d05b6b4a54a4997c0cd631874b58 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 13:04:23 -0500 Subject: [PATCH 57/74] Remove unused commented method --- .../io/helidon/microprofile/metrics/MetricAnnotationInfo.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java index c2cb74b49c9..a7b923647c6 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java @@ -223,10 +223,6 @@ A annotationOnMethod(AnnotatedElement ae) { return ae.getAnnotation(annotationClass); } -// String name(AnnotatedElement ae) { -// return name(ae.getAnnotation(annotationClass)); -// } - String name(Annotation a) { return annotationNameFunction.apply(annotationClass.cast(a)); } From fb5cfb2c349310e3caf5c5e35f7849e10a7e8c68 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 14:27:57 -0500 Subject: [PATCH 58/74] Add examples --- examples/integrations/micrometer/mp/README.md | 75 +++++++++ examples/integrations/micrometer/mp/pom.xml | 96 +++++++++++ .../micrometer/mp/GreetResource.java | 155 ++++++++++++++++++ .../micrometer/mp/GreetingProvider.java | 49 ++++++ .../micrometer/mp/package-info.java | 21 +++ .../mp/src/main/resources/META-INF/beans.xml | 26 +++ .../META-INF/microprofile-config.properties | 22 +++ .../mp/src/main/resources/application.yaml | 20 +++ .../mp/src/main/resources/logging.properties | 37 +++++ .../micrometer/mp/TestEndpoint.java | 71 ++++++++ examples/integrations/micrometer/pom.xml | 42 +++++ examples/integrations/micrometer/se/README.md | 89 ++++++++++ examples/integrations/micrometer/se/pom.xml | 88 ++++++++++ .../examples/micrometer/se/GreetService.java | 141 ++++++++++++++++ .../micrometer/se/GreetingMessage.java | 88 ++++++++++ .../helidon/examples/micrometer/se/Main.java | 112 +++++++++++++ .../examples/micrometer/se/package-info.java | 24 +++ .../se/src/main/resources/application.yaml | 24 +++ .../examples/micrometer/se/MainTest.java | 129 +++++++++++++++ 19 files changed, 1309 insertions(+) create mode 100644 examples/integrations/micrometer/mp/README.md create mode 100644 examples/integrations/micrometer/mp/pom.xml create mode 100644 examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java create mode 100644 examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java create mode 100644 examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java create mode 100644 examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml create mode 100644 examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties create mode 100644 examples/integrations/micrometer/mp/src/main/resources/application.yaml create mode 100644 examples/integrations/micrometer/mp/src/main/resources/logging.properties create mode 100644 examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java create mode 100644 examples/integrations/micrometer/pom.xml create mode 100644 examples/integrations/micrometer/se/README.md create mode 100644 examples/integrations/micrometer/se/pom.xml create mode 100644 examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetService.java create mode 100644 examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetingMessage.java create mode 100644 examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/Main.java create mode 100644 examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/package-info.java create mode 100644 examples/integrations/micrometer/se/src/main/resources/application.yaml create mode 100644 examples/integrations/micrometer/se/src/test/java/io/helidon/examples/micrometer/se/MainTest.java diff --git a/examples/integrations/micrometer/mp/README.md b/examples/integrations/micrometer/mp/README.md new file mode 100644 index 00000000000..d185c870b64 --- /dev/null +++ b/examples/integrations/micrometer/mp/README.md @@ -0,0 +1,75 @@ +# Helidon MP Micrometer Example + +This example shows a simple greeting application, similar to the one from the Helidon MP +QuickStart, but enhanced with Helidon MP Micrometer support to +* time all accesses to the two `GET` endpoints, and + +* count the accesses to the `GET` endpoint which returns a personalized + greeting. + +The example is similar to the one from the Helidon MP QuickStart with these differences: +* The `pom.xml` file contains this additional dependency on the Helidon Micrometer integration + module: +```xml + + io.helidon.integrations + helidon-integrations-micrometer + +``` +* The `GreetingService` includes additional annotations: + * `@Timed` on the two `GET` methods. + * `@Counted` on the `GET` method that returns a personalized greeting. + +## Build and run + +With JDK11+ +```bash +mvn package +java -jar target/helidon-examples-integrations-micrometer-mp.jar +``` + +## Using the app endpoints as with the "classic" greeting app + +These normal greeting app endpoints work just as in the original greeting app: + +```bash +curl -X GET http://localhost:8080/greet +{"message":"Hello World!"} + +curl -X GET http://localhost:8080/greet/Joe +{"message":"Hello Joe!"} + +curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting + +curl -X GET http://localhost:8080/greet/Jose +{"message":"Hola Jose!"} +``` + +## Using Micrometer + +Access the `/micrometer` endpoint which reports the newly-added timer and counter. + +```bash +curl http://localhost:8080/micrometer +``` +Because the `@Timer` annotation specifies a histogram, +the actual timer output includes a lengthy histogram (only part of which is shown below). +Your output might show the `personalizedGets` output before the `allGets` output, +rather than after as shown here. + +``` +curl http://localhost:8080/micrometer +# HELP allGets_seconds_max Tracks all GET operations +# TYPE allGets_seconds_max gauge +allGets_seconds_max 0.004840005 +# HELP allGets_seconds Tracks all GET operations +# TYPE allGets_seconds histogram +allGets_seconds_bucket{le="0.001",} 2.0 +allGets_seconds_bucket{le="30.0",} 3.0 +allGets_seconds_bucket{le="+Inf",} 3.0 +allGets_seconds_count 3.0 +allGets_seconds_sum 0.005098119 +# HELP personalizedGets_total Counts personalized GET operations +# TYPE personalizedGets_total counter +personalizedGets_total 2.0 +``` diff --git a/examples/integrations/micrometer/mp/pom.xml b/examples/integrations/micrometer/mp/pom.xml new file mode 100644 index 00000000000..e9acfe9cffc --- /dev/null +++ b/examples/integrations/micrometer/mp/pom.xml @@ -0,0 +1,96 @@ + + + + + io.helidon.applications + helidon-mp + 2.3.0-SNAPSHOT + ../../../../applications/mp/pom.xml + + 4.0.0 + + Helidon MP Examples Micrometer + + + Basic illustration of Micrometer integration in Helidon MP + + + io.helidon.examples.integrations.micrometer + helidon-examples-integrations-micrometer-mp + + + + io.helidon.microprofile.bundles + helidon-microprofile-core + + + io.helidon.integrations + helidon-integrations-micrometer-cdi + + + org.eclipse.microprofile.openapi + microprofile-openapi-api + + + io.helidon.media + helidon-media-jsonp + + + org.junit.jupiter + junit-jupiter-api + test + + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + + + io.helidon.webclient + helidon-webclient + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.jboss.jandex + jandex-maven-plugin + + + make-index + + + + + + + \ No newline at end of file diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java new file mode 100644 index 00000000000..f13ca347829 --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.micrometer.mp; + +import java.util.Collections; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.micrometer.core.annotation.Counted; +import io.micrometer.core.annotation.Timed; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + +/** + * A simple JAX-RS resource to greet you. Examples: + * + * Get default greeting message: + * curl -X GET http://localhost:8080/greet + * + * Get greeting message for Joe: + * curl -X GET http://localhost:8080/greet/Joe + * + * Change greeting + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting + * + * The message is returned as a JSON object. + */ +@Path("/greet") +@RequestScoped +public class GreetResource { + + static final String PERSONALIZED_GETS_COUNTER_NAME = "personalizedGets"; + private static final String PERSONALIZED_GETS_COUNTER_DESCRIPTION = "Counts personalized GET operations"; + static final String GETS_TIMER_NAME = "allGets"; + private static final String GETS_TIMER_DESCRIPTION = "Tracks all GET operations"; + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + /** + * The greeting message provider. + */ + private final GreetingProvider greetingProvider; + + /** + * Using constructor injection to get a configuration property. + * By default this gets the value from META-INF/microprofile-config + * + * @param greetingConfig the configured greeting message + */ + @Inject + public GreetResource(GreetingProvider greetingConfig) { + this.greetingProvider = greetingConfig; + } + + /** + * Return a worldly greeting message. + * + * @return {@link JsonObject} + */ + @SuppressWarnings("checkstyle:designforextension") + @GET + @Produces(MediaType.APPLICATION_JSON) + @Timed(value = GETS_TIMER_NAME, description = GETS_TIMER_DESCRIPTION, histogram = true) + public JsonObject getDefaultMessage() { + return createResponse("World"); + } + + /** + * Return a greeting message using the name that was provided. + * + * @param name the name to greet + * @return {@link JsonObject} + */ + @SuppressWarnings("checkstyle:designforextension") + @Path("/{name}") + @GET + @Produces(MediaType.APPLICATION_JSON) + @Counted(value = PERSONALIZED_GETS_COUNTER_NAME, description = PERSONALIZED_GETS_COUNTER_DESCRIPTION) + @Timed(value = GETS_TIMER_NAME, description = GETS_TIMER_DESCRIPTION, histogram = true) + public JsonObject getMessage(@PathParam("name") String name) { + return createResponse(name); + } + + /** + * Set the greeting to use in future messages. + * + * @param jsonObject JSON containing the new greeting + * @return {@link Response} + */ + @SuppressWarnings("checkstyle:designforextension") + @Path("/greeting") + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @RequestBody(name = "greeting", + required = true, + content = @Content(mediaType = "application/json", + schema = @Schema(type = SchemaType.STRING, example = "{\"greeting\" : \"Hola\"}"))) + @APIResponses({ + @APIResponse(name = "normal", responseCode = "204", description = "Greeting updated"), + @APIResponse(name = "missing 'greeting'", responseCode = "400", + description = "JSON did not contain setting for 'greeting'")}) + public Response updateGreeting(JsonObject jsonObject) { + + if (!jsonObject.containsKey("greeting")) { + JsonObject entity = JSON.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + return Response.status(Response.Status.BAD_REQUEST).entity(entity).build(); + } + + String newGreeting = jsonObject.getString("greeting"); + + greetingProvider.setMessage(newGreeting); + return Response.status(Response.Status.NO_CONTENT).build(); + } + + private JsonObject createResponse(String who) { + String msg = String.format("%s %s!", greetingProvider.getMessage(), who); + + return JSON.createObjectBuilder() + .add("message", msg) + .build(); + } +} diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java new file mode 100644 index 00000000000..ba376237837 --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.examples.integrations.micrometer.mp; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +/** + * Provider for greeting message. + */ +@ApplicationScoped +public class GreetingProvider { + private final AtomicReference message = new AtomicReference<>(); + + /** + * Create a new greeting provider, reading the message from configuration. + * + * @param message greeting to use + */ + @Inject + public GreetingProvider(@ConfigProperty(name = "app.greeting") String message) { + this.message.set(message); + } + + String getMessage() { + return message.get(); + } + + void setMessage(String message) { + this.message.set(message); + } +} diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java new file mode 100644 index 00000000000..d6a5a600b1c --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Example for Micrometer integration. + */ +package io.helidon.examples.integrations.micrometer.mp; diff --git a/examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml b/examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..55cdbd8de65 --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..0ee81e03e99 --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Application properties. This is the default greeting +app.greeting=Hello + +# Microprofile server properties +server.port=8080 +server.host=0.0.0.0 diff --git a/examples/integrations/micrometer/mp/src/main/resources/application.yaml b/examples/integrations/micrometer/mp/src/main/resources/application.yaml new file mode 100644 index 00000000000..60a20726fd9 --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/resources/application.yaml @@ -0,0 +1,20 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +micrometer: + builtin-registries: + - type: prometheus + prefix: myPrefix \ No newline at end of file diff --git a/examples/integrations/micrometer/mp/src/main/resources/logging.properties b/examples/integrations/micrometer/mp/src/main/resources/logging.properties new file mode 100644 index 00000000000..5a64db181bb --- /dev/null +++ b/examples/integrations/micrometer/mp/src/main/resources/logging.properties @@ -0,0 +1,37 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.common.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.microprofile.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO +#org.glassfish.jersey.level=INFO +#org.jboss.weld=INFO diff --git a/examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java b/examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java new file mode 100644 index 00000000000..9e14b07f5e7 --- /dev/null +++ b/examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.examples.integrations.micrometer.mp; + +import javax.inject.Inject; +import javax.json.JsonObject; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; + +import io.helidon.microprofile.tests.junit5.HelidonTest; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; + +import org.junit.jupiter.api.Test; + +import static io.helidon.examples.integrations.micrometer.mp.GreetResource.PERSONALIZED_GETS_COUNTER_NAME; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@HelidonTest +public class TestEndpoint { + + @Inject + private WebTarget webTarget; + + @Inject + private MeterRegistry registry; + + @Test + public void pingGreet() { + + JsonObject jsonObject = webTarget + .path("/greet/Joe") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(JsonObject.class); + + String responseString = jsonObject.getString("message"); + + assertEquals("Hello Joe!", responseString, "Response string"); + Counter counter = registry.counter(PERSONALIZED_GETS_COUNTER_NAME); + double before = counter.count(); + + jsonObject = webTarget + .path("/greet/Jose") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(JsonObject.class); + + responseString = jsonObject.getString("message"); + + assertEquals("Hello Jose!", responseString, "Response string"); + double after = counter.count(); + assertEquals(1d, after - before, "Difference in personalized greeting counter between successive calls"); + + } +} diff --git a/examples/integrations/micrometer/pom.xml b/examples/integrations/micrometer/pom.xml new file mode 100644 index 00000000000..d600fbdf0f2 --- /dev/null +++ b/examples/integrations/micrometer/pom.xml @@ -0,0 +1,42 @@ + + + + + 4.0.0 + + io.helidon.examples.integrations + helidon-examples-integrations-project + 2.3.0-SNAPSHOT + + + helidon-examples-micrometer-project + Helidon Micrometer Examples + + pom + + + se + mp + + + + Basic illustrations of Micrometer integration in Helidon + + + diff --git a/examples/integrations/micrometer/se/README.md b/examples/integrations/micrometer/se/README.md new file mode 100644 index 00000000000..aecdb160992 --- /dev/null +++ b/examples/integrations/micrometer/se/README.md @@ -0,0 +1,89 @@ +# Helidon SE Micrometer Example + +This example shows a simple greeting application, similar to the one from the +Helidon SE QuickStart, but enhanced with Micrometer support to: + +* time all accesses to the two `GET` endpoints, and + +* count the accesses to the `GET` endpoint which returns a personalized + greeting. + +The Helidon Micrometer integration creates a Micrometer `MeterRegistry` automatically for you. +The `registry()` instance method on `MicrometerSupport` returns that meter registry. + +The `Main` class +1. Uses `MicrometerSupport` to obtain the Micrometer `MeterRegistry` which Helidon SE + automatically provides. + +1. Uses the `MeterRegistry` to: + * Create a Micrometer `Timer` for recording accesses to all `GET` endpoints. + * Create a Micrometer `Counter` for counting accesses to the `GET` endpoint that + returns a personalized greeting. + +1. Registers the built-in support for the `/micrometer` endpoint. + +1. Passes the `Timer` and `Counter` to the `GreetingService` constructor. + +The `GreetingService` class +1. Accepts in the constructor the `Timer` and `Counter` and saves them. +1. Adds routing rules to: + * Update the `Timer` with every `GET` access. + * Increment `Counter` (in addition to returning a personalized greeting) for every + personalized `GET` access. + + +## Build and run + +With JDK11+ +```bash +mvn package +java -jar target/helidon-examples-integrations-micrometer-se.jar +``` + +## Using the app endpoints as with the "classic" greeting app + +These normal greeting app endpoints work just as in the original greeting app: + +```bash +curl -X GET http://localhost:8080/greet +{"message":"Hello World!"} + +curl -X GET http://localhost:8080/greet/Joe +{"message":"Hello Joe!"} + +curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting + +curl -X GET http://localhost:8080/greet/Jose +{"message":"Hola Jose!"} +``` + +## Using Micrometer + +Access the `/micrometer` endpoint which reports the newly-added timer and counter. +```bash +curl http://localhost:8080/micrometer +``` + +Because we created the `Timer` with a histogram, +the actual timer output includes a lengthy histogram (only part of which is shown below). +Your output might show the `personalizedGets` output before the `allGets` output, +rather than after as shown here. + +``` +curl http://localhost:8080/micrometer +# HELP allGets_seconds_max +# TYPE allGets_seconds_max gauge +allGets_seconds_max 0.04341847 +# HELP allGets_seconds +# TYPE allGets_seconds histogram +allGets_seconds_bucket{le="0.001",} 0.0 +... +allGets_seconds_bucket{le="30.0",} 3.0 +allGets_seconds_bucket{le="+Inf",} 3.0 +allGets_seconds_count 3.0 +allGets_seconds_sum 0.049222592 +# HELP personalizedGets_total +# TYPE personalizedGets_total counter +personalizedGets_total 2.0 + +``` \ No newline at end of file diff --git a/examples/integrations/micrometer/se/pom.xml b/examples/integrations/micrometer/se/pom.xml new file mode 100644 index 00000000000..0dd1c177ea9 --- /dev/null +++ b/examples/integrations/micrometer/se/pom.xml @@ -0,0 +1,88 @@ + + + + + 4.0.0 + + io.helidon.applications + helidon-se + 2.3.0-SNAPSHOT + ../../../../applications/se/pom.xml + + + io.helidon.examples.integrations.micrometer-project + helidon-examples-integrations-micrometer-se + + Helidon SE Examples Micrometer + + + Basic illustration of Micrometer integration in Helidon SE + + + + io.helidon.examples.micrometer.se.Main + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + + + + + io.helidon.webserver + helidon-webserver + + + io.helidon.config + helidon-config-yaml + + + io.helidon.webserver + helidon-webserver-cors + + + io.helidon.integrations + helidon-integrations-micrometer + + + io.helidon.media + helidon-media-jsonp + + + org.junit.jupiter + junit-jupiter-api + test + + + io.helidon.webclient + helidon-webclient + test + + + diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetService.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetService.java new file mode 100644 index 00000000000..09951b5d642 --- /dev/null +++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetService.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.micrometer.se; + +import java.util.Collections; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; + +import io.helidon.common.http.Http; +import io.helidon.config.Config; +import io.helidon.webserver.Routing; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Timer; + +/** + * A simple service to greet you. + *

    + * Examples: + *

    {@code
    + * Get default greeting message:
    + * curl -X GET http://localhost:8080/greet
    + *
    + * Get greeting message for Joe:
    + * curl -X GET http://localhost:8080/greet/Joe
    + *
    + * Change greeting
    + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
    + *
    + * }
    + * The greeting message is returned as a JSON object. + * + *

    + */ + +public class GreetService implements Service { + + /** + * The config value for the key {@code greeting}. + */ + private String greeting; + + private final Timer getTimer; + private final Counter personalizedGetCounter; + + private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap()); + + GreetService(Config config, Timer getTimer, Counter personalizedGetCounter) { + this.greeting = config.get("app.greeting").asString().orElse("Ciao"); + this.getTimer = getTimer; + this.personalizedGetCounter = personalizedGetCounter; + } + + /** + * A service registers itself by updating the routine rules. + * @param rules the routing rules. + */ + @Override + public void update(Routing.Rules rules) { + rules + .get((req, resp) -> getTimer.record((Runnable) req::next)) // Update the timer with every GET. + .get("/", this::getDefaultMessageHandler) + .get("/{name}", + (req, resp) -> { + personalizedGetCounter.increment(); + req.next(); + }, // Count personalized GETs... + this::getMessageHandler) // ...and process them. + .put("/greeting", this::updateGreetingHandler); + } + + /** + * Return a worldly greeting message. + * @param request the server request + * @param response the server response + */ + private void getDefaultMessageHandler(ServerRequest request, + ServerResponse response) { + sendResponse(response, "World"); + } + + /** + * Return a greeting message using the name that was provided. + * @param request the server request + * @param response the server response + */ + private void getMessageHandler(ServerRequest request, + ServerResponse response) { + String name = request.path().param("name"); + sendResponse(response, name); + } + + private void sendResponse(ServerResponse response, String name) { + GreetingMessage msg = new GreetingMessage(String.format("%s %s!", greeting, name)); + response.send(msg.forRest()); + } + + private void updateGreetingFromJson(JsonObject jo, ServerResponse response) { + + if (!jo.containsKey(GreetingMessage.JSON_LABEL)) { + JsonObject jsonErrorObject = JSON_BF.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + response.status(Http.Status.BAD_REQUEST_400) + .send(jsonErrorObject); + return; + } + + greeting = GreetingMessage.fromRest(jo).getMessage(); + response.status(Http.Status.NO_CONTENT_204).send(); + } + + /** + * Set the greeting to use in future messages. + * @param request the server request + * @param response the server response + */ + private void updateGreetingHandler(ServerRequest request, + ServerResponse response) { + request.content().as(JsonObject.class).thenAccept(jo -> updateGreetingFromJson(jo, response)); + } +} diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetingMessage.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetingMessage.java new file mode 100644 index 00000000000..f3f03770da5 --- /dev/null +++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/GreetingMessage.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.examples.micrometer.se; + +import java.util.Collections; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +/** + * POJO for the greeting message exchanged between the server and the client. + */ +public class GreetingMessage { + + /** + * Label for tagging a {@code GreetingMessage} instance in JSON. + */ + public static final String JSON_LABEL = "greeting"; + + private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap()); + + private String message; + + /** + * Create a new greeting with the specified message content. + * + * @param message the message to store in the greeting + */ + public GreetingMessage(String message) { + this.message = message; + } + + /** + * Returns the message value. + * + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * Sets the message value. + * + * @param message value to be set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Converts a JSON object (typically read from the request payload) + * into a {@code GreetingMessage}. + * + * @param jsonObject the {@link JsonObject} to convert. + * @return {@code GreetingMessage} set according to the provided object + */ + public static GreetingMessage fromRest(JsonObject jsonObject) { + return new GreetingMessage(jsonObject.getString(JSON_LABEL)); + } + + /** + * Prepares a {@link JsonObject} corresponding to this instance. + * + * @return {@code JsonObject} representing this {@code GreetingMessage} instance + */ + public JsonObject forRest() { + JsonObjectBuilder builder = JSON_BF.createObjectBuilder(); + return builder.add(JSON_LABEL, message) + .build(); + } +} diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/Main.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/Main.java new file mode 100644 index 00000000000..c8c7a65e8b2 --- /dev/null +++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/Main.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.micrometer.se; + +import java.io.IOException; + +import io.helidon.common.LogConfig; +import io.helidon.config.Config; +import io.helidon.integrations.micrometer.MicrometerSupport; +import io.helidon.media.jsonp.JsonpSupport; +import io.helidon.webserver.Routing; +import io.helidon.webserver.WebServer; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Timer; + +/** + * Simple Hello World rest application. + */ +public final class Main { + + /** + * Cannot be instantiated. + */ + private Main() { + } + + /** + * Application main entry point. + * @param args command line arguments. + * @throws IOException if there are problems reading logging properties + */ + public static void main(final String[] args) throws IOException { + startServer(); + } + + /** + * Start the server. + * @return the created {@link WebServer} instance + * @throws IOException if there are problems reading logging properties + */ + static WebServer startServer() throws IOException { + + // load logging configuration + LogConfig.configureRuntime(); + + // By default this will pick up application.yaml from the classpath + Config config = Config.create(); + + // Get webserver config from the "server" section of application.yaml + WebServer server = WebServer.builder(createRouting(config)) + .config(config.get("server")) + .addMediaSupport(JsonpSupport.create()) + .build(); + + // Try to start the server. If successful, print some info and arrange to + // print a message at shutdown. If unsuccessful, print the exception. + server.start() + .thenAccept(ws -> { + System.out.println( + "WEB server is up! http://localhost:" + ws.port() + "/greet"); + ws.whenShutdown().thenRun(() + -> System.out.println("WEB server is DOWN. Good bye!")); + }) + .exceptionally(t -> { + System.err.println("Startup failed: " + t.getMessage()); + t.printStackTrace(System.err); + return null; + }); + + // Server threads are not daemon. No need to block. Just react. + + return server; + } + + /** + * Creates new {@link Routing}. + * + * @return routing configured with JSON support, Micrometer metrics, and the greeting service + * @param config configuration of this server + */ + private static Routing createRouting(Config config) { + + MicrometerSupport micrometerSupport = MicrometerSupport.create(); + Counter personalizedGetCounter = micrometerSupport.registry() + .counter("personalizedGets"); + Timer getTimer = Timer.builder("allGets") + .publishPercentileHistogram() + .register(micrometerSupport.registry()); + + GreetService greetService = new GreetService(config, getTimer, personalizedGetCounter); + + return Routing.builder() + .register(micrometerSupport) // Micrometer support at "/micrometer" + .register("/greet", greetService) + .build(); + } +} diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/package-info.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/package-info.java new file mode 100644 index 00000000000..eb46268faa5 --- /dev/null +++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/micrometer/se/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Example application showing Micrometer support in Helidon SE + *

    + * Start with {@link io.helidon.examples.micrometer.se.Main} class. + * + * @see io.helidon.examples.micrometer.se.Main + */ +package io.helidon.examples.micrometer.se; diff --git a/examples/integrations/micrometer/se/src/main/resources/application.yaml b/examples/integrations/micrometer/se/src/main/resources/application.yaml new file mode 100644 index 00000000000..12336d5f138 --- /dev/null +++ b/examples/integrations/micrometer/se/src/main/resources/application.yaml @@ -0,0 +1,24 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +app: + greeting: "Hello" + +server: + port: 8080 + host: 0.0.0.0 + + diff --git a/examples/integrations/micrometer/se/src/test/java/io/helidon/examples/micrometer/se/MainTest.java b/examples/integrations/micrometer/se/src/test/java/io/helidon/examples/micrometer/se/MainTest.java new file mode 100644 index 00000000000..b2056e1de64 --- /dev/null +++ b/examples/integrations/micrometer/se/src/test/java/io/helidon/examples/micrometer/se/MainTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.examples.micrometer.se; + +import java.util.Collections; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; + +import io.helidon.media.jsonp.JsonpSupport; +import io.helidon.webclient.WebClient; +import io.helidon.webserver.WebServer; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class MainTest { + + private static WebServer webServer; + private static WebClient webClient; + + private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap()); + private static final JsonObject TEST_JSON_OBJECT; + + static { + TEST_JSON_OBJECT = JSON_BF.createObjectBuilder() + .add("greeting", "Hola") + .build(); + } + + @BeforeAll + public static void startTheServer() throws Exception { + webServer = Main.startServer(); + + long timeout = 2000; // 2 seconds should be enough to start the server + long now = System.currentTimeMillis(); + + while (!webServer.isRunning()) { + Thread.sleep(100); + if ((System.currentTimeMillis() - now) > timeout) { + Assertions.fail("Failed to start webserver"); + } + } + + webClient = WebClient.builder() + .baseUri("http://localhost:" + webServer.port()) + .addMediaSupport(JsonpSupport.create()) + .build(); + } + + @AfterAll + public static void stopServer() throws Exception { + if (webServer != null) { + webServer.shutdown() + .toCompletableFuture() + .get(10, TimeUnit.SECONDS); + } + } + + @Test + public void testHelloWorld() throws Exception { + webClient.get() + .path("/greet") + .request(JsonObject.class) + .thenAccept(jsonObject -> Assertions.assertEquals("Hello World!", jsonObject.getString("greeting"))) + .toCompletableFuture() + .get(); + + webClient.get() + .path("/greet/Joe") + .request(JsonObject.class) + .thenAccept(jsonObject -> Assertions.assertEquals("Hello Joe!", jsonObject.getString("greeting"))) + .toCompletableFuture() + .get(); + + webClient.put() + .path("/greet/greeting") + .submit(TEST_JSON_OBJECT) + .thenAccept(response -> Assertions.assertEquals(204, response.status().code())) + .thenCompose(nothing -> webClient.get() + .path("/greet/Joe") + .request(JsonObject.class)) + .thenAccept(jsonObject -> Assertions.assertEquals("Hola Joe!", jsonObject.getString("greeting"))) + .toCompletableFuture() + .get(); + + webClient.get() + .path("/micrometer") + .request() + .thenAccept(response -> { + Assertions.assertEquals(200, response.status() + .code()); + try { + String output = response.content() + .as(String.class) + .get(); + Assertions.assertTrue(output.contains("get_seconds_count 2.0"), + "Unable to find expected all-gets timer count 2.0"); // 2 gets; the put is not counted + Assertions.assertTrue(output.contains("get_seconds_sum"), + "Unable to find expected all-gets timer sum"); + Assertions.assertTrue(output.contains("personalizedGets_total 1.0"), + "Unable to find expected counter result 1.0"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + response.close(); + }); + } +} From 710c354bf106aeaa3f72a0caab9e9496ffe52ed8 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 30 Mar 2021 14:56:05 -0500 Subject: [PATCH 59/74] Add Micrometer examples module to examples --- examples/integrations/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/integrations/pom.xml b/examples/integrations/pom.xml index cc4d15d6373..6b521f33e70 100644 --- a/examples/integrations/pom.xml +++ b/examples/integrations/pom.xml @@ -1,7 +1,7 @@