diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/BuiltInMeterNameFormat.java b/metrics/api/src/main/java/io/helidon/metrics/api/BuiltInMeterNameFormat.java new file mode 100644 index 00000000000..e038adcd2e4 --- /dev/null +++ b/metrics/api/src/main/java/io/helidon/metrics/api/BuiltInMeterNameFormat.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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.metrics.api; + +/** + * Choices for the output format for built-in meter names. + */ +public enum BuiltInMeterNameFormat { + /** + * Snake-case. + */ + SNAKE, + + /** + * Camel-case (which is compatible with the MicroProfile Metrics spec). + */ + CAMEL; + + static final String DEFAULT = "CAMEL"; +} diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java index dd0dd035788..4b4531b6165 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java @@ -219,6 +219,18 @@ static List createTags(String pairs) { @Option.Default("COUNTER") GcTimeType gcTimeType(); + /** + * Output format for built-in meter names. + *

+ * {@link BuiltInMeterNameFormat#SNAKE} selects "snake_case" which does not conform to the MicroProfile + * Metrics specification. + * + * @return the output format for built-in meter names + */ + @Option.Configured + @Option.Default(BuiltInMeterNameFormat.DEFAULT) + BuiltInMeterNameFormat builtInMeterNameFormat(); + /** * Reports whether the specified scope is enabled, according to any scope configuration that * is part of this metrics configuration. diff --git a/metrics/system-meters/pom.xml b/metrics/system-meters/pom.xml index 0da00734d31..6da0b7ce30c 100644 --- a/metrics/system-meters/pom.xml +++ b/metrics/system-meters/pom.xml @@ -46,6 +46,17 @@ io.helidon.common helidon-common + + io.helidon.config + helidon-config + test + + + + io.helidon.metrics.providers + helidon-metrics-providers-micrometer + test + io.helidon.common.testing helidon-common-testing-junit5 diff --git a/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java index 9f70e7bfc01..a90c79efe0e 100644 --- a/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java +++ b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java @@ -26,9 +26,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; +import io.helidon.metrics.api.BuiltInMeterNameFormat; import io.helidon.metrics.api.Meter; import io.helidon.metrics.api.MetricsFactory; import io.helidon.metrics.api.Tag; @@ -42,12 +45,15 @@ public class SystemMetersProvider implements MetersProvider { private static final String BYTES = "bytes"; private static final String SECONDS = "seconds"; private static final String SCOPE = Meter.Scope.BASE; - private static final Metadata MEMORY_USED_HEAP = Metadata.builder() + + // Maps camelCase names to snake_case, but only for those names that are actually different in the two cases. + private static final Map CAMEL_TO_SNAKE_CASE_METER_NAMES = initMeterNames(); + + private static final Metadata.Builder MEMORY_USED_HEAP = Metadata.builder() .withName("memory.usedHeap") .withDescription("Displays the amount of used heap memory in bytes.") - .withUnit(BYTES) - .build(); - private static final Metadata MEMORY_COMMITTED_HEAP = Metadata.builder() + .withUnit(BYTES); + private static final Metadata.Builder MEMORY_COMMITTED_HEAP = Metadata.builder() .withName("memory.committedHeap") .withDescription( "Displays the amount of memory in bytes that is " @@ -55,9 +61,8 @@ public class SystemMetersProvider implements MetersProvider { + "machine to use. This amount of memory is " + "guaranteed for the Java virtual " + "machine to use.") - .withUnit(BYTES) - .build(); - private static final Metadata MEMORY_MAX_HEAP = Metadata.builder() + .withUnit(BYTES); + private static final Metadata.Builder MEMORY_MAX_HEAP = Metadata.builder() .withName("memory.maxHeap") .withDescription( "Displays the maximum amount of heap memory in bytes that can" @@ -72,9 +77,8 @@ public class SystemMetersProvider implements MetersProvider { + " to allocate memory " + "even if the amount of used memory does not exceed " + "this maximum size.") - .withUnit(BYTES) - .build(); - private static final Metadata JVM_UPTIME = Metadata.builder() + .withUnit(BYTES); + private static final Metadata.Builder JVM_UPTIME = Metadata.builder() .withName("jvm.uptime") .withDescription( "Displays the start time of the Java virtual machine in " @@ -82,47 +86,39 @@ public class SystemMetersProvider implements MetersProvider { + "attribute displays the approximate time when the Java " + "virtual machine " + "started.") - .withUnit(SECONDS) - .build(); - private static final Metadata THREAD_COUNT = Metadata.builder() + .withUnit(SECONDS); + private static final Metadata.Builder THREAD_COUNT = Metadata.builder() .withName("thread.count") .withDescription("Displays the current number of live threads including both " - + "daemon and nondaemon threads") - .build(); - private static final Metadata THREAD_DAEMON_COUNT = Metadata.builder() + + "daemon and nondaemon threads"); + private static final Metadata.Builder THREAD_DAEMON_COUNT = Metadata.builder() .withName("thread.daemon.count") - .withDescription("Displays the current number of live daemon threads.") - .build(); - private static final Metadata THREAD_MAX_COUNT = Metadata.builder() + .withDescription("Displays the current number of live daemon threads."); + private static final Metadata.Builder THREAD_MAX_COUNT = Metadata.builder() .withName("thread.max.count") .withDescription("Displays the peak live thread count since the Java " + "virtual machine started or " + "peak was reset. This includes daemon and " - + "non-daemon threads.") - .build(); - private static final Metadata CL_LOADED_COUNT = Metadata.builder() + + "non-daemon threads."); + private static final Metadata.Builder CL_LOADED_COUNT = Metadata.builder() .withName("classloader.loadedClasses.count") .withDescription("Displays the number of classes that are currently loaded in " - + "the Java virtual machine.") - .build(); - private static final Metadata CL_LOADED_TOTAL = Metadata.builder() + + "the Java virtual machine."); + private static final Metadata.Builder CL_LOADED_TOTAL = Metadata.builder() .withName("classloader.loadedClasses.total") .withDescription("Displays the total number of classes that have been loaded " - + "since the Java virtual machine has started execution.") - .build(); - private static final Metadata CL_UNLOADED_COUNT = Metadata.builder() + + "since the Java virtual machine has started execution."); + private static final Metadata.Builder CL_UNLOADED_COUNT = Metadata.builder() .withName("classloader.unloadedClasses.total") .withDescription("Displays the total number of classes unloaded since the Java " - + "virtual machine has started execution.") - .build(); - private static final Metadata OS_AVAILABLE_CPU = Metadata.builder() + + "virtual machine has started execution."); + private static final Metadata.Builder OS_AVAILABLE_CPU = Metadata.builder() .withName("cpu.availableProcessors") .withDescription("Displays the number of processors available to the Java " + "virtual machine. This " + "value may change during a particular invocation of" - + " the virtual machine.") - .build(); - private static final Metadata OS_LOAD_AVERAGE = Metadata.builder() + + " the virtual machine."); + private static final Metadata.Builder OS_LOAD_AVERAGE = Metadata.builder() .withName("cpu.systemLoadAverage") .withDescription("Displays the system load average for the last minute. The " + "system load average " @@ -141,9 +137,8 @@ public class SystemMetersProvider implements MetersProvider { + "and may be queried frequently. The load average may" + " be unavailable on some " + "platforms where it is expensive to implement this " - + "method.") - .build(); - private static final Metadata GC_TIME = Metadata.builder() + + "method."); + private static final Metadata.Builder GC_TIME = Metadata.builder() .withName("gc.time") .withDescription( "Displays the approximate accumulated collection elapsed time in seconds. " @@ -152,14 +147,12 @@ public class SystemMetersProvider implements MetersProvider { + "timer to measure the elapsed time. This attribute may display the same value " + "even if the collection count has been incremented if the collection elapsed " + "time is very short.") - .withUnit("seconds") - .build(); - private static final Metadata GC_COUNT = Metadata.builder() + .withUnit("seconds"); + private static final Metadata.Builder GC_COUNT = Metadata.builder() .withName("gc.total") .withDescription( "Displays the total number of collections that have occurred. This attribute lists " - + "-1 if the collection count is undefined for this collector.") - .build(); + + "-1 if the collection count is undefined for this collector."); private MetricsFactory metricsFactory; @@ -172,6 +165,19 @@ public class SystemMetersProvider implements MetersProvider { public SystemMetersProvider() { } + private static Map initMeterNames() { + Map result = new HashMap<>(); + result.put("memory.usedHeap", "memory.used_heap"); + result.put("memory.committedHeap", "memory.committed_heap"); + result.put("memory.maxHeap", "memory.max_heap"); + result.put("classloader.loadedClasses.count", "classloader.loaded_classes.count"); + result.put("classloader.loadedClasses.total", "classloader.loaded_classes.total"); + result.put("classloader.unloadedClasses.total", "classloader.unloaded_classes.total"); + result.put("cpu.availableProcessors", "cpu.available_processors"); + result.put("cpu.systemLoadAverage", "cpu.system_load_average"); + return result; + } + @Override public Collection> meterBuilders(MetricsFactory metricsFactory) { this.metricsFactory = metricsFactory; // save at the instance level for ease of access @@ -193,6 +199,18 @@ private static Function typedFn(Function ge return main -> valueFn.apply(getSubBeanFn.apply(main)); } + private Metadata metadata(Metadata.Builder metadataBuilderWithCamelCaseName) { + String camelCaseName = metadataBuilderWithCamelCaseName.name; + Metadata.Builder builder = metadataBuilderWithCamelCaseName; + if (metricsFactory.metricsConfig().builtInMeterNameFormat() == BuiltInMeterNameFormat.SNAKE) { + builder = Metadata.builder() + .withName(CAMEL_TO_SNAKE_CASE_METER_NAMES.getOrDefault(camelCaseName, camelCaseName)) + .withDescription(metadataBuilderWithCamelCaseName.description) + .withUnit(metadataBuilderWithCamelCaseName.baseUnit); + } + return builder.build(); + } + private , M extends Meter> Collection> prepareMeterBuilders() { @@ -202,40 +220,43 @@ private static Function typedFn(Function ge // load all base metrics registerGauge(result, - MEMORY_USED_HEAP, + metadata(MEMORY_USED_HEAP), memoryBean, typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getUsed)); registerGauge(result, - MEMORY_COMMITTED_HEAP, + metadata(MEMORY_COMMITTED_HEAP), memoryBean, typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getCommitted)); registerGauge(result, - MEMORY_MAX_HEAP, + metadata(MEMORY_MAX_HEAP), memoryBean, typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getMax)); RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); - registerGauge(result, JVM_UPTIME, runtimeBean, rtBean -> rtBean.getUptime() / 1000.0D); + registerGauge(result, + metadata(JVM_UPTIME), + runtimeBean, + rtBean -> rtBean.getUptime() / 1000.0D); ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); - registerGauge(result, THREAD_COUNT, threadBean, ThreadMXBean::getThreadCount); - registerGauge(result, THREAD_DAEMON_COUNT, threadBean, ThreadMXBean::getDaemonThreadCount); - registerGauge(result, THREAD_MAX_COUNT, threadBean, ThreadMXBean::getPeakThreadCount); + registerGauge(result, metadata(THREAD_COUNT), threadBean, ThreadMXBean::getThreadCount); + registerGauge(result, metadata(THREAD_DAEMON_COUNT), threadBean, ThreadMXBean::getDaemonThreadCount); + registerGauge(result, metadata(THREAD_MAX_COUNT), threadBean, ThreadMXBean::getPeakThreadCount); ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean(); - registerGauge(result, CL_LOADED_COUNT, clBean, ClassLoadingMXBean::getLoadedClassCount); - registerFunctionalCounter(result, CL_LOADED_TOTAL, clBean, ClassLoadingMXBean::getTotalLoadedClassCount); - registerFunctionalCounter(result, CL_UNLOADED_COUNT, clBean, ClassLoadingMXBean::getUnloadedClassCount); + registerGauge(result, metadata(CL_LOADED_COUNT), clBean, ClassLoadingMXBean::getLoadedClassCount); + registerFunctionalCounter(result, metadata(CL_LOADED_TOTAL), clBean, ClassLoadingMXBean::getTotalLoadedClassCount); + registerFunctionalCounter(result, metadata(CL_UNLOADED_COUNT), clBean, ClassLoadingMXBean::getUnloadedClassCount); OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - registerGauge(result, OS_AVAILABLE_CPU, osBean, OperatingSystemMXBean::getAvailableProcessors); - registerGauge(result, OS_LOAD_AVERAGE, osBean, OperatingSystemMXBean::getSystemLoadAverage); + registerGauge(result, metadata(OS_AVAILABLE_CPU), osBean, OperatingSystemMXBean::getAvailableProcessors); + registerGauge(result, metadata(OS_LOAD_AVERAGE), osBean, OperatingSystemMXBean::getSystemLoadAverage); List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); for (GarbageCollectorMXBean gcBean : gcBeans) { String poolName = gcBean.getName(); registerFunctionalCounter(result, - GC_COUNT, + metadata(GC_COUNT), gcBean, GarbageCollectorMXBean::getCollectionCount, Tag.create("name", poolName)); @@ -244,13 +265,13 @@ private static Function typedFn(Function ge // type of meter to register. if (isGcTimeGauge()) { registerGauge(result, - GC_TIME, + metadata(GC_TIME), gcBean, bean -> (long) (bean.getCollectionTime() / 1000.0D), Tag.create("name", poolName)); } else { registerFunctionalCounter(result, - GC_TIME, + metadata(GC_TIME), gcBean, bean -> (long) (bean.getCollectionTime() / 1000.0D), Tag.create("name", poolName)); diff --git a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/MeterBuilderMatcher.java b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/MeterBuilderMatcher.java new file mode 100644 index 00000000000..04709b742b4 --- /dev/null +++ b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/MeterBuilderMatcher.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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.metrics.systemmeters; + +import io.helidon.metrics.api.Meter; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Hamcrest matcher for unit tests involving {@link Meter.Builder} objects. + */ +class MeterBuilderMatcher { + + /** + * A matcher that checks the meter builder's name using the supplied string matcher. + * + * @param matcher string matcher to apply to the meter builder's name + * @return matcher applicable to a meter builder for checking the name + */ + static Matcher> withName(Matcher matcher) { + return new WithName(matcher); + } + + private static class WithName extends TypeSafeMatcher> { + + private final Matcher matcher; + + private WithName(Matcher matcher) { + this.matcher = matcher; + } + + @Override + protected boolean matchesSafely(Meter.Builder item) { + return matcher.matches(item.name()); + } + + @Override + public void describeTo(Description description) { + description.appendText("with name "); + description.appendDescriptionOf(matcher); + } + } +} diff --git a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestBuiltInMeterCaseSelection.java b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestBuiltInMeterCaseSelection.java new file mode 100644 index 00000000000..3d0ba2e1d97 --- /dev/null +++ b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestBuiltInMeterCaseSelection.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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.metrics.systemmeters; + +import java.util.Collection; +import java.util.Map; + +import io.helidon.common.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.metrics.api.Meter; +import io.helidon.metrics.api.MetricsFactory; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; + +class TestBuiltInMeterCaseSelection { + + @Test + void testDefaults() { + SystemMetersProvider provider = new SystemMetersProvider(); + MetricsFactory metricsFactory = MetricsFactory.getInstance(Config.empty()); + Collection> meterBuilders = provider.meterBuilders(metricsFactory); + + assertThat("Default used heap name", meterBuilders, allOf( + hasItem(MeterBuilderMatcher.withName(equalTo("memory.usedHeap"))), + not(hasItem(MeterBuilderMatcher.withName(equalTo("memory.used_heap")))))); + } + + @Test + void testWithSnakeConfigured() { + SystemMetersProvider provider = new SystemMetersProvider(); + Config config = io.helidon.config.Config.just(ConfigSources.create(Map.of("metrics.built-in-meter-name-format", "SNAKE"))); + MetricsFactory metricsFactory = MetricsFactory.getInstance(config.get("metrics")); + Collection> meterBuilders = provider.meterBuilders(metricsFactory); + + assertThat("Configured used heap name", meterBuilders, allOf( + hasItem(MeterBuilderMatcher.withName(equalTo("memory.used_heap"))), + not(hasItem(MeterBuilderMatcher.withName(equalTo("memory.usedHeap")))))); + } +} 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 64ab36bae81..d598f31240f 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 @@ -42,6 +42,7 @@ import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.config.ConfigValue; +import io.helidon.metrics.api.BuiltInMeterNameFormat; import io.helidon.metrics.api.MeterRegistry; import io.helidon.metrics.api.MetricsConfig; import io.helidon.metrics.api.MetricsFactory; @@ -125,13 +126,6 @@ public class MetricsCdiExtension extends HelidonRestCdiExtension { static final String SYNTHETIC_TIMER_METRIC_NAME = "REST.request"; static final String SYNTHETIC_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME = SYNTHETIC_TIMER_METRIC_NAME + ".unmappedException.total"; - static final Metadata SYNTHETIC_TIMER_UNMAPPED_EXCEPTION_METADATA = Metadata.builder() - .withName(SYNTHETIC_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME) - .withDescription(""" - The total number of unmapped exceptions that occur from this RESTful resouce method since \ - the start of the server.""") - .withUnit(MetricUnits.NONE) - .build(); static final Metadata SYNTHETIC_TIMER_METADATA = Metadata.builder() .withName(SYNTHETIC_TIMER_METRIC_NAME) .withDescription(""" @@ -155,6 +149,7 @@ public class MetricsCdiExtension extends HelidonRestCdiExtension { } }; private static final boolean REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE = false; + private static Metadata syntheticTimerUnmappedExceptionMetadata; private final Map> annotatedGaugeSites = new HashMap<>(); private final List annotatedSites = new ArrayList<>(); private final Map, Set> methodsWithRestRequestMetrics = new HashMap<>(); @@ -170,6 +165,7 @@ public class MetricsCdiExtension extends HelidonRestCdiExtension { private final Map, StereotypeMetricsInfo> stereotypeMetricsInfo = new HashMap<>(); private boolean restEndpointsMetricsEnabled = REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE; private Errors.Collector errors = Errors.collector(); + private String syntheticTimerMetricUnmappedExceptionName; /** * Creates a new extension instance. @@ -226,7 +222,7 @@ static Counter restEndpointCounter(Method method) { () -> String.format("Registering synthetic Counter for %s#%s", method.getDeclaringClass().getName(), method.getName())); return getRegistryForSyntheticRestRequestMetrics() - .counter(SYNTHETIC_TIMER_UNMAPPED_EXCEPTION_METADATA, syntheticRestRequestMetricTags(method)); + .counter(syntheticTimerUnmappedExceptionMetadata, syntheticRestRequestMetricTags(method)); } /** @@ -245,8 +241,8 @@ static MetricID restEndpointTimerMetricID(Method method) { * @param method Java method of interest * @return {@code MetricID} for the counter for this Java method */ - static MetricID restEndpointCounterMetricID(Method method) { - return new MetricID(SYNTHETIC_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME, syntheticRestRequestMetricTags(method)); + MetricID restEndpointCounterMetricID(Method method) { + return new MetricID(syntheticTimerMetricUnmappedExceptionName, syntheticRestRequestMetricTags(method)); } /** @@ -559,6 +555,17 @@ private MetricsObserver configure() { Contexts.globalContext().register(metricsFactory); MetricsConfig metricsConfig = metricsFactory.metricsConfig(); + syntheticTimerMetricUnmappedExceptionName = + metricsConfig.builtInMeterNameFormat() == BuiltInMeterNameFormat.CAMEL + ? SYNTHETIC_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME + : SYNTHETIC_TIMER_METRIC_NAME + ".unmapped_exception.total"; + syntheticTimerUnmappedExceptionMetadata = Metadata.builder() + .withName(syntheticTimerMetricUnmappedExceptionName) + .withDescription(""" + The total number of unmapped exceptions that occur from this RESTful resouce method since \ + the start of the server.""") + .withUnit(MetricUnits.NONE) + .build(); MeterRegistry meterRegistry = metricsFactory.globalRegistry(metricsConfig); return builder.metricsConfig(metricsConfig) .meterRegistry(meterRegistry) diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricIDMatcher.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricIDMatcher.java new file mode 100644 index 00000000000..34018ed3753 --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricIDMatcher.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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.MetricID; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +class MetricIDMatcher { + + static WithName withName(Matcher matcher) { + return new WithName(matcher); + } + + private static class WithName extends TypeSafeMatcher { + + private final Matcher matcher; + + private WithName(Matcher matcher) { + this.matcher = matcher; + } + + @Override + protected boolean matchesSafely(MetricID item) { + return matcher.matches(item.getName()); + } + + @Override + public void describeTo(Description description) { + description.appendText("metric ID name"); + description.appendDescriptionOf(matcher); + } + } + +} diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestConfiguredBuiltInMeterNameFormat.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestConfiguredBuiltInMeterNameFormat.java new file mode 100644 index 00000000000..6f8cf0d819d --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestConfiguredBuiltInMeterNameFormat.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.AddConfig; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.inject.Inject; +import org.eclipse.microprofile.metrics.Metric; +import org.eclipse.microprofile.metrics.MetricID; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.annotation.RegistryScope; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; + +@HelidonTest +@AddConfig(key = "metrics." + MetricsCdiExtension.REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME, value = "true") +@AddConfig(key = "metrics.built-in-meter-name-format", value = "SNAKE") +@AddBean(HelloWorldResource.class) +class TestConfiguredBuiltInMeterNameFormat { + + @Inject + @RegistryScope(scope = MetricRegistry.BASE_SCOPE) + private MetricRegistry baseRegistry; + + @Test + void checkDefaultNames() { + Map baseMetrics = baseRegistry.getMetrics(); + + assertThat("Built-in metrics", baseMetrics.keySet(), allOf( + not(hasItem(MetricIDMatcher.withName(equalTo("memory.usedHeap")))), + hasItem(MetricIDMatcher.withName(equalTo("memory.used_heap"))))); + + Map metrics = baseRegistry.getMetrics(); + assertThat("REST.request unmapped exception metric", metrics.keySet(), allOf( + not(hasItem(MetricIDMatcher.withName(equalTo(MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME)))), + hasItem(MetricIDMatcher.withName(equalTo(MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_NAME + + ".unmapped_exception.total"))))); + + } +} diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDefaultBuiltInMeterNameFormat.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDefaultBuiltInMeterNameFormat.java new file mode 100644 index 00000000000..a5adfeee1c6 --- /dev/null +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDefaultBuiltInMeterNameFormat.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.AddConfig; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.inject.Inject; +import org.eclipse.microprofile.metrics.Metric; +import org.eclipse.microprofile.metrics.MetricID; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.annotation.RegistryScope; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; + +@HelidonTest +@AddConfig(key = "metrics." + MetricsCdiExtension.REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME, value = "true") +@AddBean(HelloWorldResource.class) +class TestDefaultBuiltInMeterNameFormat { + + @Inject + @RegistryScope(scope = MetricRegistry.BASE_SCOPE) + private MetricRegistry baseRegistry; + + @Test + void checkDefaultNames() { + Map baseMetrics = baseRegistry.getMetrics(); + + assertThat("Built-in metrics", baseMetrics.keySet(), allOf( + hasItem(MetricIDMatcher.withName(equalTo("memory.usedHeap"))), + not(hasItem(MetricIDMatcher.withName(equalTo("memory.used_heap")))))); + + Map metrics = baseRegistry.getMetrics(); + assertThat("REST.request unmapped exception metric", metrics.keySet(), allOf( + hasItem(MetricIDMatcher.withName(equalTo(MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME))), + not(hasItem(MetricIDMatcher.withName(equalTo(MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_NAME + + ".unmapped_exception.total")))))); + + } + +}