diff --git a/implementation/pom.xml b/implementation/pom.xml
index e4c948fb..1e42e897 100644
--- a/implementation/pom.xml
+++ b/implementation/pom.xml
@@ -85,5 +85,13 @@
assertj-core
test
+
+
+ org.glassfish
+ javax.json
+ 1.0.4
+ test
+
+
diff --git a/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusExporter.java b/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusExporter.java
index caba8f8e..9b04e053 100644
--- a/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusExporter.java
+++ b/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusExporter.java
@@ -176,14 +176,14 @@ private void writeTimerValues(StringBuffer sb, MetricRegistry.Type scope, TimerI
writeMeterRateValues(sb, scope, timer.getMeter(), md);
Snapshot snapshot = timer.getSnapshot();
- writeSnapshotBasics(sb, scope, md, snapshot, theUnit);
+ writeSnapshotBasics(sb, scope, md, snapshot, theUnit, true);
String suffix = USCORE + PrometheusUnit.getBaseUnitAsPrometheusString(md.getUnit());
writeHelpLine(sb, scope, md.getName(), md, suffix);
writeTypeLine(sb,scope,md.getName(),md, suffix,SUMMARY);
writeValueLine(sb,scope,suffix + "_count",timer.getCount(),md, null, false);
- writeSnapshotQuantiles(sb, scope, md, snapshot, theUnit);
+ writeSnapshotQuantiles(sb, scope, md, snapshot, theUnit, true);
}
private void writeHistogramValues(StringBuffer sb, MetricRegistry.Type scope, HistogramImpl histogram, Metadata md) {
@@ -195,47 +195,47 @@ private void writeHistogramValues(StringBuffer sb, MetricRegistry.Type scope, Hi
String theUnit = unit.equals("none") ? "" : USCORE + unit;
writeHelpLine(sb, scope, md.getName(), md, SUMMARY);
- writeSnapshotBasics(sb, scope, md, snapshot, theUnit);
+ writeSnapshotBasics(sb, scope, md, snapshot, theUnit, false);
writeTypeLine(sb,scope,md.getName(),md, theUnit,SUMMARY);
writeValueLine(sb,scope,theUnit + "_count",histogram.getCount(),md, null, false);
- writeSnapshotQuantiles(sb, scope, md, snapshot, theUnit);
+ writeSnapshotQuantiles(sb, scope, md, snapshot, theUnit, false);
}
- private void writeSnapshotBasics(StringBuffer sb, MetricRegistry.Type scope, Metadata md, Snapshot snapshot, String unit) {
+ private void writeSnapshotBasics(StringBuffer sb, MetricRegistry.Type scope, Metadata md, Snapshot snapshot, String unit, boolean performScaling) {
- writeTypeAndValue(sb, scope, "_min" + unit, snapshot.getMin(), GAUGE, md);
- writeTypeAndValue(sb, scope, "_max" + unit, snapshot.getMax(), GAUGE, md);
- writeTypeAndValue(sb, scope, "_mean" + unit, snapshot.getMean(), GAUGE, md);
- writeTypeAndValue(sb, scope, "_stddev" + unit, snapshot.getStdDev(), GAUGE, md);
+ writeTypeAndValue(sb, scope, "_min" + unit, snapshot.getMin(), GAUGE, md, performScaling);
+ writeTypeAndValue(sb, scope, "_max" + unit, snapshot.getMax(), GAUGE, md, performScaling);
+ writeTypeAndValue(sb, scope, "_mean" + unit, snapshot.getMean(), GAUGE, md, performScaling);
+ writeTypeAndValue(sb, scope, "_stddev" + unit, snapshot.getStdDev(), GAUGE, md, performScaling);
}
- private void writeSnapshotQuantiles(StringBuffer sb, MetricRegistry.Type scope, Metadata md, Snapshot snapshot, String unit) {
- writeValueLine(sb, scope, unit, snapshot.getMedian(), md, new Tag(QUANTILE, "0.5"));
- writeValueLine(sb, scope, unit, snapshot.get75thPercentile(), md, new Tag(QUANTILE, "0.75"));
- writeValueLine(sb, scope, unit, snapshot.get95thPercentile(), md, new Tag(QUANTILE, "0.95"));
- writeValueLine(sb, scope, unit, snapshot.get98thPercentile(), md, new Tag(QUANTILE, "0.98"));
- writeValueLine(sb, scope, unit, snapshot.get99thPercentile(), md, new Tag(QUANTILE, "0.99"));
- writeValueLine(sb, scope, unit, snapshot.get999thPercentile(), md, new Tag(QUANTILE, "0.999"));
+ private void writeSnapshotQuantiles(StringBuffer sb, MetricRegistry.Type scope, Metadata md, Snapshot snapshot, String unit, boolean performScaling) {
+ writeValueLine(sb, scope, unit, snapshot.getMedian(), md, new Tag(QUANTILE, "0.5"), performScaling);
+ writeValueLine(sb, scope, unit, snapshot.get75thPercentile(), md, new Tag(QUANTILE, "0.75"), performScaling);
+ writeValueLine(sb, scope, unit, snapshot.get95thPercentile(), md, new Tag(QUANTILE, "0.95"), performScaling);
+ writeValueLine(sb, scope, unit, snapshot.get98thPercentile(), md, new Tag(QUANTILE, "0.98"), performScaling);
+ writeValueLine(sb, scope, unit, snapshot.get99thPercentile(), md, new Tag(QUANTILE, "0.99"), performScaling);
+ writeValueLine(sb, scope, unit, snapshot.get999thPercentile(), md, new Tag(QUANTILE, "0.999"), performScaling);
}
private void writeMeterValues(StringBuffer sb, MetricRegistry.Type scope, Metered metric, Metadata md) {
writeHelpLine(sb, scope, md.getName(), md, "_total");
- writeTypeAndValue(sb, scope, "_total", metric.getCount(), COUNTER, md);
+ writeTypeAndValue(sb, scope, "_total", metric.getCount(), COUNTER, md, false);
writeMeterRateValues(sb, scope, metric, md);
}
private void writeMeterRateValues(StringBuffer sb, MetricRegistry.Type scope, Metered metric, Metadata md) {
- writeTypeAndValue(sb, scope, "_rate_per_second", metric.getMeanRate(), GAUGE, md);
- writeTypeAndValue(sb, scope, "_one_min_rate_per_second", metric.getOneMinuteRate(), GAUGE, md);
- writeTypeAndValue(sb, scope, "_five_min_rate_per_second", metric.getFiveMinuteRate(), GAUGE, md);
- writeTypeAndValue(sb, scope, "_fifteen_min_rate_per_second", metric.getFifteenMinuteRate(), GAUGE, md);
+ writeTypeAndValue(sb, scope, "_rate_per_second", metric.getMeanRate(), GAUGE, md, false);
+ writeTypeAndValue(sb, scope, "_one_min_rate_per_second", metric.getOneMinuteRate(), GAUGE, md, false);
+ writeTypeAndValue(sb, scope, "_five_min_rate_per_second", metric.getFiveMinuteRate(), GAUGE, md, false);
+ writeTypeAndValue(sb, scope, "_fifteen_min_rate_per_second", metric.getFifteenMinuteRate(), GAUGE, md, false);
}
- private void writeTypeAndValue(StringBuffer sb, MetricRegistry.Type scope, String suffix, double valueRaw, String type, Metadata md) {
+ private void writeTypeAndValue(StringBuffer sb, MetricRegistry.Type scope, String suffix, double valueRaw, String type, Metadata md, boolean performScaling) {
String key = md.getName();
writeTypeLine(sb, scope, key, md, suffix, type);
- writeValueLine(sb, scope, suffix, valueRaw, md);
+ writeValueLine(sb, scope, suffix, valueRaw, md, null, performScaling);
}
private void writeValueLine(StringBuffer sb, MetricRegistry.Type scope, String suffix, double valueRaw, Metadata md) {
@@ -252,7 +252,7 @@ private void writeValueLine(StringBuffer sb,
double valueRaw,
Metadata md,
Tag extraTag,
- boolean scaled) {
+ boolean performScaling) {
String name = md.getName();
name = getPrometheusMetricName(name);
fillBaseName(sb, scope, name);
@@ -270,7 +270,7 @@ private void writeValueLine(StringBuffer sb,
}
sb.append(SPACE);
- Double value = scaled ? PrometheusUnit.scaleToBase(md.getUnit(), valueRaw) : valueRaw;
+ Double value = performScaling ? PrometheusUnit.scaleToBase("nanoseconds", valueRaw) : valueRaw;
sb.append(value).append(LF);
}
diff --git a/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusUnit.java b/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusUnit.java
index 4f5c7b2f..69c61dbd 100644
--- a/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusUnit.java
+++ b/implementation/src/main/java/io/smallrye/metrics/exporters/PrometheusUnit.java
@@ -36,6 +36,13 @@ private PrometheusUnit() {
}
+ /**
+ * Determines the basic unit to be used by Prometheus exporter based on the input unit from parameter.
+ * That is:
+ * - for memory size units, returns "bytes"
+ * - for time units, returns "seconds"
+ * - for any other unit, returns the input unit itself
+ */
public static String getBaseUnitAsPrometheusString(String unit) {
String out;
@@ -88,11 +95,18 @@ public static String getBaseUnitAsPrometheusString(String unit) {
return out;
}
- public static Double scaleToBase(String unit, Double value) {
+ /**
+ * Scales the value (time or memory size) interpreted using inputUnit to the base unit for Prometheus exporter
+ * That means:
+ * - values for memory size units are scaled to bytes
+ * - values for time units are scaled to seconds
+ * - values for other units are returned unchanged
+ */
+ public static Double scaleToBase(String inputUnit, Double value) {
Double out;
- switch (unit) {
+ switch (inputUnit) {
case MetricUnits.BITS:
out = value / 8;
diff --git a/implementation/src/test/java/io/smallrye/metrics/exporters/ExportersMetricScalingTest.java b/implementation/src/test/java/io/smallrye/metrics/exporters/ExportersMetricScalingTest.java
new file mode 100644
index 00000000..d0a4e1b8
--- /dev/null
+++ b/implementation/src/test/java/io/smallrye/metrics/exporters/ExportersMetricScalingTest.java
@@ -0,0 +1,287 @@
+package io.smallrye.metrics.exporters;
+
+import io.smallrye.metrics.MetricRegistries;
+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.MetricFilter;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.MetricType;
+import org.eclipse.microprofile.metrics.MetricUnits;
+import org.eclipse.microprofile.metrics.Timer;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+
+public class ExportersMetricScalingTest {
+
+ @After
+ public void cleanup() {
+ MetricRegistries.get(MetricRegistry.Type.APPLICATION).removeMatching(MetricFilter.ALL);
+ }
+
+ /**
+ * Given a Timer with unit=MINUTES,
+ * check that the statistics from PrometheusExporter will be correctly converted to SECONDS.
+ */
+ @Test
+ public void timer_prometheus() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("timer1", MetricType.TIMER, MetricUnits.MINUTES);
+ Timer metric = registry.timer(metadata);
+ metric.update(1, TimeUnit.HOURS);
+ metric.update(2, TimeUnit.HOURS);
+ metric.update(3, TimeUnit.HOURS);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "timer1").toString();
+
+ Assert.assertThat(exported, containsString("application:timer1_seconds{quantile=\"0.5\"} 7200.0"));
+ Assert.assertThat(exported, containsString("application:timer1_mean_seconds 7200.0"));
+ Assert.assertThat(exported, containsString("application:timer1_min_seconds 3600.0"));
+ Assert.assertThat(exported, containsString("application:timer1_max_seconds 10800.0"));
+ }
+
+ /**
+ * Given a Timer with unit=MINUTES,
+ * check that the statistics from JsonExporter will be presented in MINUTES.
+ */
+ @Test
+ public void timer_json() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("timer1", MetricType.TIMER, MetricUnits.MINUTES);
+ Timer metric = registry.timer(metadata);
+ metric.update(1, TimeUnit.HOURS);
+ metric.update(2, TimeUnit.HOURS);
+ metric.update(3, TimeUnit.HOURS);
+
+ JsonExporter exporter = new JsonExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "timer1").toString();
+
+ JsonObject json = Json.createReader(new StringReader(exported)).read().asJsonObject().getJsonObject("timer1");
+ assertEquals(120.0, json.getJsonNumber("p50").doubleValue(), 0.001);
+ assertEquals(120.0, json.getJsonNumber("mean").doubleValue(), 0.001);
+ assertEquals(60.0, json.getJsonNumber("min").doubleValue(), 0.001);
+ assertEquals(180.0, json.getJsonNumber("max").doubleValue(), 0.001);
+ }
+
+ /**
+ * Given a Histogram with unit=MINUTES,
+ * check that the statistics from JsonExporter will be presented in MINUTES.
+ */
+ @Test
+ public void histogram_prometheus() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("histogram1", MetricType.HISTOGRAM, MetricUnits.MINUTES);
+ Histogram metric = registry.histogram(metadata);
+ metric.update(30);
+ metric.update(40);
+ metric.update(50);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "histogram1").toString();
+
+ Assert.assertThat(exported, containsString("application:histogram1_min_seconds 30.0"));
+ Assert.assertThat(exported, containsString("application:histogram1_max_seconds 50.0"));
+ Assert.assertThat(exported, containsString("application:histogram1_mean_seconds 40.0"));
+ Assert.assertThat(exported, containsString("application:histogram1_seconds{quantile=\"0.5\"} 40.0"));
+ }
+
+ /**
+ * Given a Histogram with unit=dollars (custom unit),
+ * check that the statistics from JsonExporter will be presented in dollars.
+ */
+ @Test
+ public void histogram_customunit_prometheus() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("histogram1", MetricType.HISTOGRAM, "dollars");
+ Histogram metric = registry.histogram(metadata);
+ metric.update(30);
+ metric.update(40);
+ metric.update(50);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "histogram1").toString();
+
+ Assert.assertThat(exported, containsString("application:histogram1_min_dollars 30.0"));
+ Assert.assertThat(exported, containsString("application:histogram1_max_dollars 50.0"));
+ Assert.assertThat(exported, containsString("application:histogram1_mean_dollars 40.0"));
+ Assert.assertThat(exported, containsString("application:histogram1_dollars{quantile=\"0.5\"} 40.0"));
+ }
+
+ /**
+ * Given a Histogram with unit=MINUTES,
+ * check that the statistics from JsonExporter will be presented in MINUTES.
+ */
+ @Test
+ public void histogram_json() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("timer1", MetricType.TIMER, MetricUnits.MINUTES);
+ Timer metric = registry.timer(metadata);
+ metric.update(1, TimeUnit.HOURS);
+ metric.update(2, TimeUnit.HOURS);
+ metric.update(3, TimeUnit.HOURS);
+
+ JsonExporter exporter = new JsonExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "timer1").toString();
+
+
+ JsonObject json = Json.createReader(new StringReader(exported)).read().asJsonObject().getJsonObject("timer1");
+ assertEquals(120.0, json.getJsonNumber("p50").doubleValue(), 0.001);
+ assertEquals(120.0, json.getJsonNumber("mean").doubleValue(), 0.001);
+ assertEquals(60.0, json.getJsonNumber("min").doubleValue(), 0.001);
+ assertEquals(180.0, json.getJsonNumber("max").doubleValue(), 0.001);
+ }
+
+ /**
+ * Given a Counter,
+ * check that the statistics from PrometheusExporter will not be scaled in any way.
+ */
+ @Test
+ public void counter_prometheus() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("counter1", MetricType.COUNTER);
+ Counter metric = registry.counter(metadata);
+ metric.inc(30);
+ metric.inc(40);
+ metric.inc(50);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "counter1").toString();
+
+ Assert.assertThat(exported, containsString("application:counter1 120.0"));
+ }
+
+ /**
+ * Given a Counter,
+ * check that the statistics from JsonExporter will not be scaled in any way.
+ */
+ @Test
+ public void counter_json() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("counter1", MetricType.COUNTER);
+ Counter metric = registry.counter(metadata);
+ metric.inc(10);
+ metric.inc(20);
+ metric.inc(30);
+
+ JsonExporter exporter = new JsonExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "counter1").toString();
+
+ JsonObject json = Json.createReader(new StringReader(exported)).read().asJsonObject();
+ assertEquals(60, json.getInt("counter1"));
+ }
+
+ /**
+ * Given a Meter,
+ * check that the statistics from PrometheusExporter will be presented as per_second.
+ */
+ @Test
+ public void meter_prometheus() throws InterruptedException {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("meter1", MetricType.METERED);
+ Meter metric = registry.meter(metadata);
+ metric.mark(10);
+ TimeUnit.SECONDS.sleep(1);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "meter1").toString();
+
+ System.out.println(exported);
+ Assert.assertThat(exported, containsString("application:meter1_total 10.0"));
+ double ratePerSecond = Double.parseDouble(Arrays.stream(exported.split("\\n"))
+ .filter(line -> line.contains("application:meter1_rate_per_second"))
+ .filter(line -> !line.contains("TYPE") && !line.contains("HELP"))
+ .findFirst()
+ .get()
+ .split(" ")[1]);
+ Assert.assertTrue("Rate per second should be between 1 and 10 but is " + ratePerSecond,
+ ratePerSecond > 1 && ratePerSecond < 10);
+ }
+
+ /**
+ * Given a Meter,
+ * check that the statistics from JsonExporter will be presented as per_second.
+ */
+ @Test
+ public void meter_json() throws InterruptedException {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("meter1", MetricType.METERED);
+ Meter metric = registry.meter(metadata);
+ metric.mark(10);
+ TimeUnit.SECONDS.sleep(1);
+
+ JsonExporter exporter = new JsonExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "meter1").toString();
+
+ JsonObject json = Json.createReader(new StringReader(exported)).read().asJsonObject().getJsonObject("meter1");
+ assertEquals(10, json.getInt("count"));
+ double meanRate = json.getJsonNumber("meanRate").doubleValue();
+ Assert.assertTrue("meanRate should be between 1 and 10 but is " + meanRate,
+ meanRate > 1 && meanRate < 10);
+ }
+
+ /**
+ * Given a Gauge with unit=MINUTES,
+ * check that the statistics from PrometheusExporter will be presented in SECONDS.
+ */
+ @Test
+ public void gauge_prometheus() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("gauge1", MetricType.GAUGE, MetricUnits.MINUTES);
+ Gauge gaugeInstance = () -> 3L;
+ registry.register("gauge1", gaugeInstance, metadata);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "gauge1").toString();
+
+ Assert.assertThat(exported, containsString("application:gauge1_seconds 180.0"));
+ }
+
+ /**
+ * Given a Gauge with unit=dollars (custom unit),
+ * check that the statistics from PrometheusExporter will be presented in dollars.
+ */
+ @Test
+ public void gauge_customUnit_prometheus() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("gauge1", MetricType.GAUGE, "dollars");
+ Gauge gaugeInstance = () -> 3L;
+ registry.register("gauge1", gaugeInstance, metadata);
+
+ PrometheusExporter exporter = new PrometheusExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "gauge1").toString();
+
+ Assert.assertThat(exported, containsString("application:gauge1_dollars 3.0"));
+ }
+
+ /**
+ * Given a Gauge with unit=MINUTES,
+ * check that the statistics from PrometheusExporter will be presented in MINUTES.
+ */
+ @Test
+ public void gauge_json() {
+ MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.APPLICATION);
+ Metadata metadata = new Metadata("gauge1", MetricType.GAUGE, MetricUnits.MINUTES);
+ Gauge gaugeInstance = () -> 3L;
+ registry.register("gauge1", gaugeInstance, metadata);
+
+ JsonExporter exporter = new JsonExporter();
+ String exported = exporter.exportOneMetric(MetricRegistry.Type.APPLICATION, "gauge1").toString();
+
+ JsonObject json = Json.createReader(new StringReader(exported)).read().asJsonObject();
+ assertEquals(3, json.getInt("gauge1"));
+ }
+
+}