better tag handling for all metric types
diff --git a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/RegistryImpl.java b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/RegistryImpl.java
index 61cb4eb..469af63 100644
--- a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/RegistryImpl.java
+++ b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/RegistryImpl.java
@@ -244,7 +244,7 @@
@Override
public Histogram histogram(final String name, final Tag... tags) {
- return histogram(Metadata.builder().withName(name).withType(MetricType.HISTOGRAM).build());
+ return histogram(Metadata.builder().withName(name).withType(MetricType.HISTOGRAM).build(), tags);
}
@Override
@@ -254,7 +254,7 @@
@Override
public Meter meter(final String name, final Tag... tags) {
- return meter(Metadata.builder().withName(name).withType(MetricType.METERED).build());
+ return meter(Metadata.builder().withName(name).withType(MetricType.METERED).build(), tags);
}
@Override
@@ -264,7 +264,7 @@
@Override
public Timer timer(final String name, final Tag... tags) {
- return timer(Metadata.builder().withName(name).withType(MetricType.TIMER).build());
+ return timer(Metadata.builder().withName(name).withType(MetricType.TIMER).build(), tags);
}
@Override
diff --git a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java
index 7aec70a..47f0c40 100644
--- a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java
+++ b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java
@@ -24,6 +24,7 @@
import static java.util.stream.Collectors.toMap;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -41,12 +42,18 @@
import javax.ws.rs.core.UriInfo;
import org.apache.geronimo.microprofile.metrics.common.prometheus.PrometheusFormatter;
+import org.eclipse.microprofile.metrics.ConcurrentGauge;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Meter;
+import org.eclipse.microprofile.metrics.Metered;
import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.Snapshot;
+import org.eclipse.microprofile.metrics.Timer;
@Path("metrics")
public class MetricsEndpoints {
@@ -89,7 +96,7 @@
securityValidator.checkSecurity(securityContext, uriInfo);
return Stream.of(MetricRegistry.Type.values())
.collect(toMap(MetricRegistry.Type::getName, it -> findRegistry(it.getName()).getMetrics().entrySet().stream()
- .collect(toMap(Map.Entry::getKey, m -> map(m.getValue())))));
+ .collect(toMap(this::getKey, m -> toJson(map(m.getValue()), formatTags(m.getKey())), (a, b) -> a))));
}
@GET
@@ -114,7 +121,7 @@
@Context final UriInfo uriInfo) {
securityValidator.checkSecurity(securityContext, uriInfo);
return findRegistry(registry).getMetrics().entrySet().stream()
- .collect(toMap(Map.Entry::getKey, it -> map(it.getValue())));
+ .collect(toMap(this::getKey, it -> toJson(map(it.getValue()), formatTags(it.getKey())), (a, b) -> a));
}
@GET
@@ -174,12 +181,12 @@
@Context final UriInfo uriInfo) {
securityValidator.checkSecurity(securityContext, uriInfo);
return findRegistry(registry).getMetadata().entrySet().stream()
- .collect(toMap(Map.Entry::getKey, e -> mapMeta(e.getValue(), new MetricID(e.getKey()))));
+ .collect(toMap(Map.Entry::getKey, e -> mapMeta(e.getValue(), new MetricID(e.getKey())), (a, b) -> a));
}
private Map<String, Metric> metrics(final MetricRegistry metricRegistry) {
return metricRegistry.getMetrics().entrySet().stream()
- .collect(toMap(e -> e.getKey().getName(), Map.Entry::getValue));
+ .collect(toMap(this::getKey, Map.Entry::getValue, (a, b) -> a));
}
private <T> Map<String, T> singleEntry(final String id, final MetricRegistry metricRegistry,
@@ -203,6 +210,69 @@
return metric;
}
+ private String getKey(final Map.Entry<MetricID, Metric> e) {
+ if (Counter.class.isInstance(e.getValue()) || Gauge.class.isInstance(e.getValue())) {
+ return e.getKey().getName() + formatTags(e.getKey());
+ }
+ return e.getKey().getName();
+ }
+
+ // https://github.com/eclipse/microprofile-metrics/issues/508
+ private Object toJson(final Object metric, final String nameSuffix) {
+ if (Timer.class.isInstance(metric)) {
+ final Timer meter = Timer.class.cast(metric);
+ final Map<Object, Object> map = new HashMap<>(15);
+ map.putAll(snapshot(meter.getSnapshot(), nameSuffix));
+ map.putAll(meter(meter, nameSuffix));
+ return map;
+ }
+ if (Meter.class.isInstance(metric)) {
+ return meter(Meter.class.cast(metric), nameSuffix);
+ }
+ if (Histogram.class.isInstance(metric)) {
+ final Histogram histogram = Histogram.class.cast(metric);
+ final Map<Object, Object> map = new HashMap<>(11);
+ map.putAll(snapshot(histogram.getSnapshot(), nameSuffix));
+ map.put("count" + nameSuffix, histogram.getCount());
+ return map;
+ }
+ if (ConcurrentGauge.class.isInstance(metric)) {
+ final ConcurrentGauge concurrentGauge = ConcurrentGauge.class.cast(metric);
+ final Map<Object, Object> map = new HashMap<>(3);
+ map.put("min" + nameSuffix, concurrentGauge.getMin());
+ map.put("current" + nameSuffix, concurrentGauge.getCount());
+ map.put("max" + nameSuffix, concurrentGauge.getMax());
+ return map;
+ }
+ // counters and gauges are unwrapped so skip it
+ return metric;
+ }
+
+ private Map<String, Object> meter(final Metered metered, final String nameSuffix) {
+ final Map<String, Object> map = new HashMap<>(5);
+ map.put("count" + nameSuffix, metered.getCount());
+ map.put("meanRate" + nameSuffix, metered.getMeanRate());
+ map.put("oneMinRate" + nameSuffix, metered.getOneMinuteRate());
+ map.put("fiveMinRate" + nameSuffix, metered.getFiveMinuteRate());
+ map.put("fifteenMinRate" + nameSuffix, metered.getFifteenMinuteRate());
+ return map;
+ }
+
+ private Map<String, Object> snapshot(final Snapshot snapshot, final String nameSuffix) {
+ final Map<String, Object> map = new HashMap<>(10);
+ map.put("p50" + nameSuffix, snapshot.getMedian());
+ map.put("p75" + nameSuffix, snapshot.get75thPercentile());
+ map.put("p95" + nameSuffix, snapshot.get95thPercentile());
+ map.put("p98" + nameSuffix, snapshot.get98thPercentile());
+ map.put("p99" + nameSuffix, snapshot.get99thPercentile());
+ map.put("p999" + nameSuffix, snapshot.get999thPercentile());
+ map.put("min" + nameSuffix, snapshot.getMin());
+ map.put("mean" + nameSuffix, snapshot.getMean());
+ map.put("max" + nameSuffix, snapshot.getMax());
+ map.put("stddev" + nameSuffix, snapshot.getStdDev());
+ return map;
+ }
+
private MetricRegistry findRegistry(final String registry) {
switch (Stream.of(MetricRegistry.Type.values()).filter(it -> it.getName().equals(registry)).findFirst()
.orElseThrow(() -> new WebApplicationException(Response.Status.NOT_FOUND))) {
@@ -215,6 +285,12 @@
}
}
+ private String formatTags(final MetricID id) {
+ return id.getTags().isEmpty() ? "" : (';' + id.getTags().entrySet().stream()
+ .map(e -> e.getKey() + "=" + e.getValue())
+ .collect(joining(",")));
+ }
+
public static class Meta {
private final Metadata value;
private final MetricID metricID;
diff --git a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java
index c0a7598..0bf382d 100644
--- a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java
+++ b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java
@@ -32,13 +32,13 @@
import java.util.function.Predicate;
import java.util.stream.Stream;
+import org.eclipse.microprofile.metrics.ConcurrentGauge;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.Meter;
import org.eclipse.microprofile.metrics.Metric;
-import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Snapshot;
@@ -96,16 +96,28 @@
final Map<String, Metric> entries) {
final Map<String, Metadata> metadatas = registry.getMetadata();
return entries.entrySet().stream()
- .map(it -> new Entry(metadatas.get(it.getKey()), registryKey + ':' + toPrometheusKey(metadatas.get(it.getKey())), it.getValue()))
+ .map(it -> {
+ String key = it.getKey();
+ final int tagSep = key.indexOf(';');
+ if (tagSep > 0) {
+ key = key.substring(0, tagSep);
+ }
+ final Metadata metadata = metadatas.get(key);
+ return new Entry(metadata, registryKey + ':' + toPrometheusKey(metadata), it.getValue());
+ })
.filter(it -> prefixFilter == null || prefixFilter.test(it.prometheusKey))
.map(entry -> {
switch (entry.metadata.getTypeRaw()) {
- case COUNTER:
- case CONCURRENT_GAUGE: {
+ case COUNTER: {
final String key = toPrometheusKey(entry.metadata);
return new StringBuilder()
.append(value(registryKey, key, Counter.class.cast(entry.metric).getCount(), entry.metadata));
}
+ case CONCURRENT_GAUGE: {
+ final String key = toPrometheusKey(entry.metadata);
+ return new StringBuilder()
+ .append(value(registryKey, key, ConcurrentGauge.class.cast(entry.metric).getCount(), entry.metadata));
+ }
case GAUGE: {
final Object val = Gauge.class.cast(entry.metric).getValue();
if (Number.class.isInstance(val)) {
@@ -183,7 +195,7 @@
}
private String toUnitSuffix(final Metadata metadata) {
- return metadata.getUnit().orElse("").equalsIgnoreCase("none") ?
+ return metadata.getUnit().orElse("none").equalsIgnoreCase("none") ?
"" : ("_" + toPrometheusUnit(metadata.getUnit().orElse("")));
}
@@ -290,8 +302,8 @@
}
}
- private String toPrometheus(final Metadata metadata) {
- return metadata.getName()
+ private String toPrometheus(final Metadata id) {
+ return id.getName()
.replaceAll("[^\\w]+", "_")
.replaceAll("(.)(\\p{Upper})", "$1_$2")
.replace("__", "_")
diff --git a/geronimo-metrics/src/main/java/org/apache/geronimo/microprofile/metrics/cdi/MetricsExtension.java b/geronimo-metrics/src/main/java/org/apache/geronimo/microprofile/metrics/cdi/MetricsExtension.java
index b5fe0de..cb50015 100644
--- a/geronimo-metrics/src/main/java/org/apache/geronimo/microprofile/metrics/cdi/MetricsExtension.java
+++ b/geronimo-metrics/src/main/java/org/apache/geronimo/microprofile/metrics/cdi/MetricsExtension.java
@@ -60,6 +60,7 @@
import org.apache.geronimo.microprofile.metrics.common.BaseMetrics;
import org.apache.geronimo.microprofile.metrics.common.GaugeImpl;
import org.apache.geronimo.microprofile.metrics.common.RegistryImpl;
+import org.apache.geronimo.microprofile.metrics.common.jaxrs.MetricsEndpoints;
import org.apache.geronimo.microprofile.metrics.jaxrs.CdiMetricsEndpoints;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
@@ -86,11 +87,13 @@
private final MetricRegistry vendorRegistry = new RegistryImpl();
private final Map<MetricID, Metadata> registrations = new HashMap<>();
- private final Map<String, Function<BeanManager, Gauge<?>>> gaugeFactories = new HashMap<>();
+ private final Map<MetricID, Function<BeanManager, Gauge<?>>> gaugeFactories = new HashMap<>();
private final Collection<Runnable> producersRegistrations = new ArrayList<>();
private final Collection<CreationalContext<?>> creationalContexts = new ArrayList<>();
- void letOtherExtensionsUseRegistries(@Observes final ProcessAnnotatedType<CdiMetricsEndpoints> processAnnotatedType) {
+ private Map<String, String> environmentalTags;
+
+ void vetoEndpointIfNotActivated(@Observes final ProcessAnnotatedType<CdiMetricsEndpoints> processAnnotatedType) {
if ("false".equalsIgnoreCase(System.getProperty("geronimo.metrics.jaxrs.activated"))) { // default is secured so deploy
processAnnotatedType.veto();
}
@@ -101,6 +104,13 @@
processAnnotatedType.veto();
}
+ // can happen in shades
+ void vetoNonCdiEndpoint(@Observes final ProcessAnnotatedType<MetricsEndpoints> processAnnotatedType) {
+ if (processAnnotatedType.getAnnotatedType().getJavaClass() == MetricsEndpoints.class) { // not subclasses
+ processAnnotatedType.veto();
+ }
+ }
+
void letOtherExtensionsUseRegistries(@Observes final BeforeBeanDiscovery beforeBeanDiscovery, final BeanManager beanManager) {
beforeBeanDiscovery.addQualifier(RegistryType.class);
beanManager.fireEvent(applicationRegistry);
@@ -114,6 +124,9 @@
final String name = method.getAnnotated().getJavaMember().getName();
return name.equals("name") || name.equals("tags");
}).forEach(method -> method.remove(a -> a.annotationType() == Nonbinding.class));
+
+ // we must update @Metrics with that if it exists for the cdi resolution ot match since we make it binding
+ environmentalTags = new MetricID("foo").getTags();
}
void onMetric(@Observes final ProcessProducerField<? extends Metric, ?> processProducerField, final BeanManager beanManager) {
@@ -132,7 +145,7 @@
}
void onMetric(@Observes ProcessProducerMethod<? extends Metric, ?> processProducerMethod,
- final BeanManager beanManager) {
+ final BeanManager beanManager) {
final org.eclipse.microprofile.metrics.annotation.Metric config = processProducerMethod.getAnnotated()
.getAnnotation(org.eclipse.microprofile.metrics.annotation.Metric.class);
if (config == null) {
@@ -160,7 +173,7 @@
final MetricType type = findType(clazz);
if (config != null) {
- String name = Names.findName(injectionPoint.getMember().getDeclaringClass(), injectionPoint.getMember(),
+ final String name = Names.findName(injectionPoint.getMember().getDeclaringClass(), injectionPoint.getMember(),
of(config.name()).filter(it -> !it.isEmpty()).orElseGet(injectionPoint.getMember()::getName), config.absolute(),
"");
final Metadata metadata = Metadata.builder()
@@ -173,7 +186,7 @@
final MetricID id = new MetricID(name, createTags(config.tags()));
addRegistration(metadata, id, true);
- if (!name.equals(config.name())) {
+ if (!name.equals(config.name()) || !environmentalTags.isEmpty()) {
final Annotation[] newQualifiers = Stream.concat(metricInjectionPointProcessEvent.getInjectionPoint().getQualifiers().stream()
.filter(it -> it.annotationType() != org.eclipse.microprofile.metrics.annotation.Metric.class),
Stream.of(new MetricImpl(metadata, id)))
@@ -317,7 +330,7 @@
.build();
final MetricID metricID = new MetricID(name, createTags(gauge.tags()));
addRegistration(metadata, metricID, false);
- gaugeFactories.put(name, beanManager -> {
+ gaugeFactories.put(metricID, beanManager -> {
final Object reference = getInstance(javaClass, beanManager);
final Method mtd = Method.class.cast(javaMember);
return new GaugeImpl<>(reference, mtd);
@@ -378,11 +391,11 @@
void afterDeploymentValidation(@Observes final AfterDeploymentValidation afterDeploymentValidation,
final BeanManager beanManager) {
- registrations.values().stream()
- .filter(m -> m.getTypeRaw() == MetricType.GAUGE)
- .forEach(metadata -> {
- final Gauge<?> gauge = gaugeFactories.get(metadata.getName()).apply(beanManager);
- applicationRegistry.register(metadata, gauge);
+ registrations.entrySet().stream()
+ .filter(e -> e.getValue().getTypeRaw() == MetricType.GAUGE)
+ .forEach(entry -> {
+ final Gauge<?> gauge = gaugeFactories.get(entry.getKey()).apply(beanManager);
+ applicationRegistry.register(entry.getValue(), gauge, entry.getKey().getTagsAsList().toArray(NO_TAG));
});
producersRegistrations.forEach(Runnable::run);
@@ -409,7 +422,9 @@
beanClass = javaMember.getDeclaringClass();
}
final Metadata metadata = createMetadata(config, clazz, javaMember, beanClass);
- applicationRegistry.register(metadata, Metric.class.cast(getInstance(clazz, beanManager, bean)));
+ applicationRegistry.register(
+ metadata, Metric.class.cast(getInstance(clazz, beanManager, bean)),
+ createTags(config.tags()));
}
private Metadata createMetadata(final org.eclipse.microprofile.metrics.annotation.Metric config,
diff --git a/geronimo-metrics/src/test/java/org/apache/geronimo/microprofile/metrics/test/ArquillianSetup.java b/geronimo-metrics/src/test/java/org/apache/geronimo/microprofile/metrics/test/ArquillianSetup.java
index 7b8ab56..2e86ae3 100644
--- a/geronimo-metrics/src/test/java/org/apache/geronimo/microprofile/metrics/test/ArquillianSetup.java
+++ b/geronimo-metrics/src/test/java/org/apache/geronimo/microprofile/metrics/test/ArquillianSetup.java
@@ -23,10 +23,14 @@
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
+import javax.enterprise.util.AnnotationLiteral;
import org.apache.catalina.Context;
import org.apache.meecrowave.Meecrowave;
import org.apache.meecrowave.arquillian.MeecrowaveContainer;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.annotation.Metric;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.context.annotation.ContainerScoped;
import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped;
@@ -132,9 +136,9 @@
try {
final CDI<Object> cdi = CDI.current();
final Annotation[] qualifiers = Stream.of(p.getAnnotations()).filter(it -> cdi.getBeanManager().isQualifier(it.annotationType())).toArray(Annotation[]::new);
- return cdi.select(p.getType(), qualifiers).get();
+ return cdi.select(p.getType(), fixQualifiers(qualifiers)).get();
} catch (final RuntimeException re) {
- re.printStackTrace();
+ re.printStackTrace(); // easier to debug when some test fail since TCK inject metrics as params
return null;
} finally {
thread.setContextClassLoader(classLoader);
@@ -142,6 +146,61 @@
})
.toArray();
}
+
+ private Annotation[] fixQualifiers(final Annotation[] qualifiers) {
+ return Stream.of(qualifiers)
+ .map(it -> {
+ if (Metric.class == it.annotationType()) { // we make tags and name binding so ensure it uses the right values
+ final Metric delegate = Metric.class.cast(it);
+ return new MetricLiteral(delegate, new MetricID(delegate.name(), Stream.of(delegate.tags()).filter(tag -> tag.contains("=")).map(tag -> {
+ final int sep = tag.indexOf("=");
+ return new Tag(tag.substring(0, sep), tag.substring(sep + 1));
+ }).toArray(Tag[]::new)).getTagsAsList().stream().map(t -> t.getTagName() + '=' + t.getTagValue()).toArray(String[]::new));
+ }
+ return it;
+ })
+ .toArray(Annotation[]::new);
+ }
+ }
+
+ private static class MetricLiteral extends AnnotationLiteral<Metric> implements Metric {
+ private final Metric delegate;
+ private final String[] tags;
+
+ private MetricLiteral(final Metric delegate, final String[] tags) {
+ this.delegate = delegate;
+ this.tags = tags;
+ }
+
+ @Override
+ public String name() {
+ return delegate.name();
+ }
+
+ @Override
+ public String[] tags() {
+ return tags;
+ }
+
+ @Override
+ public boolean absolute() {
+ return delegate.absolute();
+ }
+
+ @Override
+ public String displayName() {
+ return delegate.displayName();
+ }
+
+ @Override
+ public String description() {
+ return delegate.description();
+ }
+
+ @Override
+ public String unit() {
+ return delegate.unit();
+ }
}
public static class ForceWarProtocol extends LocalProtocol {
diff --git a/pom.xml b/pom.xml
index eeebdb2..e33d4c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
<dependency>
<groupId>org.apache.geronimo.config</groupId>
<artifactId>geronimo-config-impl</artifactId>
- <version>1.1</version>
+ <version>1.2.2</version>
<scope>test</scope>
</dependency>