Skip to content

Commit

Permalink
Allow config to set camel (default) or snake case for built-in meter …
Browse files Browse the repository at this point in the history
…names (#9429)

* Allow config to set camel (default) or snake case for built-in meter names

Signed-off-by: Tim Quinn <[email protected]>

* Add an MP test for when the SNAKE choice is selected

---------

Signed-off-by: Tim Quinn <[email protected]>
  • Loading branch information
tjquinno authored Oct 23, 2024
1 parent f253590 commit 383bd4a
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,18 @@ static List<Tag> createTags(String pairs) {
@Option.Default("COUNTER")
GcTimeType gcTimeType();

/**
* Output format for built-in meter names.
* <p>
* {@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.
Expand Down
11 changes: 11 additions & 0 deletions metrics/system-meters/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config</artifactId>
<scope>test</scope>
</dependency>
<!-- We need a full metrics implementation to test that config affects system meter names correctly. -->
<dependency>
<groupId>io.helidon.metrics.providers</groupId>
<artifactId>helidon-metrics-providers-micrometer</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.common.testing</groupId>
<artifactId>helidon-common-testing-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,22 +45,24 @@ 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<String, String> 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 "
+ "committed for the Java virtual "
+ "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"
Expand All @@ -72,57 +77,48 @@ 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 "
+ "seconds. This "
+ "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 "
Expand All @@ -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. "
Expand All @@ -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;

Expand All @@ -172,6 +165,19 @@ public class SystemMetersProvider implements MetersProvider {
public SystemMetersProvider() {
}

private static Map<String, String> initMeterNames() {
Map<String, String> 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<Meter.Builder<?, ?>> meterBuilders(MetricsFactory metricsFactory) {
this.metricsFactory = metricsFactory; // save at the instance level for ease of access
Expand All @@ -193,6 +199,18 @@ private static <M, S, N extends Number> Function<M, N> typedFn(Function<M, S> 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 <B extends Meter.Builder<B, M>,
M extends Meter>
Collection<Meter.Builder<?, ?>> prepareMeterBuilders() {
Expand All @@ -202,40 +220,43 @@ private static <M, S, N extends Number> Function<M, N> typedFn(Function<M, S> 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<GarbageCollectorMXBean> 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));
Expand All @@ -244,13 +265,13 @@ private static <M, S, N extends Number> Function<M, N> typedFn(Function<M, S> 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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Meter.Builder<?, ?>> withName(Matcher<String> matcher) {
return new WithName(matcher);
}

private static class WithName extends TypeSafeMatcher<Meter.Builder<?, ?>> {

private final Matcher<? super String> matcher;

private WithName(Matcher<? super String> 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);
}
}
}
Loading

0 comments on commit 383bd4a

Please sign in to comment.