From fcfab36d64874031c0dc8476644387ef6b8d513c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 17 Apr 2025 08:19:49 +1200 Subject: [PATCH 1/2] Add Tags support, add Metric.ID Metric.ID is a combination of both the name and the tags, replacing just the String name. --- .../metrics/ebean/DatabaseMetricSupplier.java | 6 +- .../src/main/java/io/avaje/metrics/MId.java | 58 +++++++++++++++ .../src/main/java/io/avaje/metrics/MTags.java | 41 +++++++++++ .../main/java/io/avaje/metrics/Metric.java | 63 +++++++++++++++-- .../java/io/avaje/metrics/MetricRegistry.java | 34 +++++++++ .../src/main/java/io/avaje/metrics/Tags.java | 18 +++++ .../io/avaje/metrics/core/BaseReportName.java | 19 +++-- .../metrics/core/BucketTimerFactory.java | 13 ++-- .../io/avaje/metrics/core/CounterFactory.java | 5 +- .../io/avaje/metrics/core/DBucketTimer.java | 15 ++-- .../java/io/avaje/metrics/core/DCounter.java | 20 +++--- .../io/avaje/metrics/core/DGaugeDouble.java | 17 +++-- .../io/avaje/metrics/core/DGaugeLong.java | 47 ++++++++----- .../java/io/avaje/metrics/core/DMeter.java | 17 +++-- .../java/io/avaje/metrics/core/DTimer.java | 27 ++++--- .../metrics/core/DefaultMetricProvider.java | 67 +++++++++++++----- .../io/avaje/metrics/core/JsonWriter.java | 28 ++++++-- .../io/avaje/metrics/core/JvmCGroupCpu.java | 3 +- .../java/io/avaje/metrics/core/JvmOsLoad.java | 3 +- .../io/avaje/metrics/core/MeterFactory.java | 5 +- .../io/avaje/metrics/core/TimerFactory.java | 5 +- .../io/avaje/metrics/core/ValueCounter.java | 12 ++-- .../avaje/metrics/spi/SpiMetricBuilder.java | 7 +- .../io/avaje/metrics/stats/CounterStats.java | 13 ++-- .../avaje/metrics/stats/GaugeDoubleStats.java | 15 ++-- .../avaje/metrics/stats/GaugeLongStats.java | 15 ++-- .../io/avaje/metrics/stats/TimerStats.java | 17 +++-- .../java/io/avaje/metrics/MetricIDTest.java | 70 +++++++++++++++++++ .../java/io/avaje/metrics/MetricsTest.java | 2 +- .../test/java/io/avaje/metrics/TagsTest.java | 24 +++++++ .../avaje/metrics/core/CollectAsJsonTest.java | 19 +++++ .../avaje/metrics/core/CounterMetricTest.java | 39 +++++++++++ .../io/avaje/metrics/core/CounterTest.java | 3 +- .../avaje/metrics/core/DGaugeDoubleTest.java | 11 ++- .../java/io/avaje/metrics/core/TimerTest.java | 4 ++ .../avaje/metrics/core/ValueCounterTest.java | 5 +- 36 files changed, 623 insertions(+), 144 deletions(-) create mode 100644 metrics/src/main/java/io/avaje/metrics/MId.java create mode 100644 metrics/src/main/java/io/avaje/metrics/MTags.java create mode 100644 metrics/src/main/java/io/avaje/metrics/Tags.java create mode 100644 metrics/src/test/java/io/avaje/metrics/MetricIDTest.java create mode 100644 metrics/src/test/java/io/avaje/metrics/TagsTest.java diff --git a/metrics-ebean/src/main/java/io/avaje/metrics/ebean/DatabaseMetricSupplier.java b/metrics-ebean/src/main/java/io/avaje/metrics/ebean/DatabaseMetricSupplier.java index 6d3882f..3ee3a54 100644 --- a/metrics-ebean/src/main/java/io/avaje/metrics/ebean/DatabaseMetricSupplier.java +++ b/metrics-ebean/src/main/java/io/avaje/metrics/ebean/DatabaseMetricSupplier.java @@ -36,13 +36,13 @@ public List collectMetrics() { log.log(Level.DEBUG, dbMetrics.asJson().withHash(false).withNewLine(false).json()); } for (MetaTimedMetric timedMetric : dbMetrics.timedMetrics()) { - metrics.add(new TimerStats(timedMetric.name(), timedMetric.count(), timedMetric.total(), timedMetric.max())); + metrics.add(new TimerStats(Metric.ID.of(timedMetric.name()), timedMetric.count(), timedMetric.total(), timedMetric.max())); } for (MetaQueryMetric metric : dbMetrics.queryMetrics()) { - metrics.add(new TimerStats(metric.name(), metric.count(), metric.total(), metric.max())); + metrics.add(new TimerStats(Metric.ID.of(metric.name()), metric.count(), metric.total(), metric.max())); } for (MetaCountMetric metric : dbMetrics.countMetrics()) { - metrics.add(new CounterStats(metric.name(), metric.count())); + metrics.add(new CounterStats(Metric.ID.of(metric.name()), metric.count())); } return metrics; } diff --git a/metrics/src/main/java/io/avaje/metrics/MId.java b/metrics/src/main/java/io/avaje/metrics/MId.java new file mode 100644 index 0000000..ed92f54 --- /dev/null +++ b/metrics/src/main/java/io/avaje/metrics/MId.java @@ -0,0 +1,58 @@ +package io.avaje.metrics; + +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +final class MId implements Metric.ID { + + private final String name; + private final Tags tags; + + MId(String name, Tags tags) { + this.name = name; + this.tags = tags; + } + + public String name() { + return name; + } + + public Tags tags() { + return tags; + } + + @Override + public Metric.ID suffix(String suffix) { + return new MId(name + requireNonNull(suffix), tags); + } + + @Override + public Metric.ID withName(String otherName) { + if (name.equals(requireNonNull(otherName))) { + return this; + } + return new MId(otherName, tags); + } + + @Override + public Metric.ID withTags(Tags otherTags) { + if (tags.equals(requireNonNull(otherTags))) { + return this; + } + return new MId(name, otherTags); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (!(object instanceof MId)) return false; + MId key = (MId) object; + return Objects.equals(name, key.name) && Objects.equals(tags, key.tags); + } + + @Override + public int hashCode() { + return Objects.hash(name, tags); + } +} diff --git a/metrics/src/main/java/io/avaje/metrics/MTags.java b/metrics/src/main/java/io/avaje/metrics/MTags.java new file mode 100644 index 0000000..7abc80a --- /dev/null +++ b/metrics/src/main/java/io/avaje/metrics/MTags.java @@ -0,0 +1,41 @@ +package io.avaje.metrics; + +import java.util.Arrays; +import java.util.Objects; + +final class MTags implements Tags { + + static Tags EMPTY = new MTags(new String[]{}); + + private final String[] keyValuePairs; + + MTags(String[] keyValuePairs) { + if (keyValuePairs.length % 2 != 0) { + throw new IllegalArgumentException("Incorrect length, must be pairs of key values"); + } + this.keyValuePairs = keyValuePairs; + } + + @Override + public boolean isEmpty() { + return keyValuePairs.length == 0; + } + + @Override + public String[] array() { + return keyValuePairs; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (!(object instanceof MTags)) return false; + MTags dTags = (MTags) object; + return Objects.deepEquals(keyValuePairs, dTags.keyValuePairs); + } + + @Override + public int hashCode() { + return Arrays.hashCode(keyValuePairs); + } +} diff --git a/metrics/src/main/java/io/avaje/metrics/Metric.java b/metrics/src/main/java/io/avaje/metrics/Metric.java index 8e8e655..71c63f8 100644 --- a/metrics/src/main/java/io/avaje/metrics/Metric.java +++ b/metrics/src/main/java/io/avaje/metrics/Metric.java @@ -14,6 +14,11 @@ */ public interface Metric { + /** + * Return the Id of the metric. + */ + ID id(); + /** * Return the name of the metric. */ @@ -28,19 +33,69 @@ public interface Metric { /** * Reset the statistics resetting any internal counters etc. *

- * Typically the MetricManager takes care of resetting the statistic/counters for the metrics when - * it periodically collects and reports all the metrics and you are not expected to use this method. - *

+ * Typically, the MetricRegistry takes care of resetting the statistic/counters for the metrics when + * it periodically collects and reports all the metrics, and you are not expected to use this method. */ void reset(); + /** + * Identifier of a Metric based on both the name and tags. + */ + interface ID { + + /** + * Create an Id given a name only. + */ + static ID of(String name) { + return new MId(name, Tags.EMPTY); + } + + /** + * Create an Id given name and tags. + */ + static ID of(String name, Tags tags) { + return new MId(name, tags); + } + + /** + * Return the metric name. + */ + String name(); + + /** + * Return the tags. + */ + Tags tags(); + + /** + * Return an Id appending the suffix to the name. + */ + ID suffix(String suffix); + + /** + * Return an Id with the given name.. + */ + ID withName(String otherName); + + /** + * Return an Id with the given name.. + */ + ID withTags(Tags tags); + + } + /** * Common for statistics of all metrics. */ interface Statistics { /** - * Return the associated metric name. + * Return the metric id. + */ + ID id(); + + /** + * Return the metric name. */ String name(); diff --git a/metrics/src/main/java/io/avaje/metrics/MetricRegistry.java b/metrics/src/main/java/io/avaje/metrics/MetricRegistry.java index 1d10b66..b726444 100644 --- a/metrics/src/main/java/io/avaje/metrics/MetricRegistry.java +++ b/metrics/src/main/java/io/avaje/metrics/MetricRegistry.java @@ -15,26 +15,51 @@ public interface MetricRegistry extends JvmMetrics { */ Counter counter(String name); + /** + * Return the Counter with the given name and tags. + */ + Counter counter(String name, Tags tags); + /** * Return the Meter using the metric name. */ Meter meter(String name); + /** + * Return the Meter using the metric name and tags. + */ + Meter meter(String name, Tags tags); + /** * Create and register a gauge using the supplied double values. */ GaugeDouble gauge(String name, DoubleSupplier supplier); + /** + * Create and register a gauge using the supplied double values. + */ + GaugeDouble gauge(String name, Tags tags, DoubleSupplier supplier); + /** * Create and register a gauge using the supplied long values. */ GaugeLong gauge(String name, LongSupplier supplier); + /** + * Create and register a gauge using the supplied long values. + */ + GaugeLong gauge(String name, Tags tags, LongSupplier supplier); + /** * Return the timer using the metric name. */ Timer timer(String name); + /** + * Return the timer using the metric name and tags. + */ + Timer timer(String name, Tags tags); + /** * Return the bucket timer using the given base metric name and bucketRanges. * @@ -43,6 +68,15 @@ public interface MetricRegistry extends JvmMetrics { */ Timer timer(String name, int... bucketRanges); + /** + * Return the bucket timer using the given base metric name, tags and bucketRanges. + * + * @param name The metric name + * @param tags The metric tags + * @param bucketRanges Time in milliseconds which are used to create buckets. + */ + Timer timer(String name, Tags tags, int... bucketRanges); + /** * Return the TimerGroup using the given base metric name. */ diff --git a/metrics/src/main/java/io/avaje/metrics/Tags.java b/metrics/src/main/java/io/avaje/metrics/Tags.java new file mode 100644 index 0000000..d2730e0 --- /dev/null +++ b/metrics/src/main/java/io/avaje/metrics/Tags.java @@ -0,0 +1,18 @@ +package io.avaje.metrics; + +public interface Tags { + + Tags EMPTY = MTags.EMPTY; + + static Tags of() { + return EMPTY; + } + + static Tags of(String... keyValuePairs) { + return new MTags(keyValuePairs); + } + + String[] array(); + + boolean isEmpty(); +} diff --git a/metrics/src/main/java/io/avaje/metrics/core/BaseReportName.java b/metrics/src/main/java/io/avaje/metrics/core/BaseReportName.java index 31aac89..2e4685e 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/BaseReportName.java +++ b/metrics/src/main/java/io/avaje/metrics/core/BaseReportName.java @@ -5,16 +5,21 @@ abstract class BaseReportName { - final String name; - @Nullable String reportName; + final Metric.ID id; + volatile Metric.@Nullable ID reportId; - BaseReportName(String name) { - this.name = name; + BaseReportName(Metric.ID id) { + this.id = id; } - final String reportName(Metric.Visitor collector) { - final String tmp = collector.namingConvention().apply(name); - this.reportName = tmp; + final Metric.ID reportId(Metric.Visitor collector) { + final var id = reportId; + return id != null ? id : useNamingConvention(collector); + } + + final Metric.ID useNamingConvention(Metric.Visitor collector) { + final Metric.ID tmp = id.withName(collector.namingConvention().apply(id.name())); + this.reportId = tmp; return tmp; } diff --git a/metrics/src/main/java/io/avaje/metrics/core/BucketTimerFactory.java b/metrics/src/main/java/io/avaje/metrics/core/BucketTimerFactory.java index 12736a9..7a78229 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/BucketTimerFactory.java +++ b/metrics/src/main/java/io/avaje/metrics/core/BucketTimerFactory.java @@ -1,27 +1,28 @@ package io.avaje.metrics.core; +import io.avaje.metrics.Metric; import io.avaje.metrics.Timer; import io.avaje.metrics.spi.SpiMetricBuilder; final class BucketTimerFactory implements SpiMetricBuilder.Factory { @Override - public Timer createMetric(String name, int[] bucketRanges) { + public Timer createMetric(Metric.ID id, int[] bucketRanges) { int rangeBottom = 0; Timer[] buckets = new Timer[bucketRanges.length + 1]; for (int i = 0; i < bucketRanges.length; i++) { int rangeTop = bucketRanges[i]; - buckets[i] = createTimedMetric(name, rangeBottom, rangeTop); + buckets[i] = createTimedMetric(id, rangeBottom, rangeTop); // move the range bottom up to the last rangeTop rangeBottom = rangeTop; } - buckets[bucketRanges.length] = createTimedMetric(name, rangeBottom, 0); - return new DBucketTimer(name, bucketRanges, buckets); + buckets[bucketRanges.length] = createTimedMetric(id, rangeBottom, 0); + return new DBucketTimer(id, bucketRanges, buckets); } - private static Timer createTimedMetric(String name, int rangeBottom, int rangeTop) { + private static Timer createTimedMetric(Metric.ID id, int rangeBottom, int rangeTop) { String suffix = (rangeTop == 0) ? String.valueOf(rangeBottom) : rangeBottom + "-" + rangeTop; - return new DTimer(name, suffix); + return new DTimer(id, suffix); } } diff --git a/metrics/src/main/java/io/avaje/metrics/core/CounterFactory.java b/metrics/src/main/java/io/avaje/metrics/core/CounterFactory.java index 4f98f06..f473c5f 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/CounterFactory.java +++ b/metrics/src/main/java/io/avaje/metrics/core/CounterFactory.java @@ -1,13 +1,14 @@ package io.avaje.metrics.core; import io.avaje.metrics.Counter; +import io.avaje.metrics.Metric; import io.avaje.metrics.spi.SpiMetricBuilder; final class CounterFactory implements SpiMetricBuilder.Factory { @Override - public Counter createMetric(String name, int[] bucketRanges) { - return new DCounter(name); + public Counter createMetric(Metric.ID id, int[] bucketRanges) { + return new DCounter(id); } } diff --git a/metrics/src/main/java/io/avaje/metrics/core/DBucketTimer.java b/metrics/src/main/java/io/avaje/metrics/core/DBucketTimer.java index ecbbc0b..3e3ad55 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DBucketTimer.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DBucketTimer.java @@ -10,13 +10,13 @@ */ final class DBucketTimer implements Timer { - private final String metricName; + private final ID id; final int[] bucketRanges; final Timer[] buckets; private final int lastBucketIndex; - DBucketTimer(String metricName, int[] bucketRanges, Timer[] buckets) { - this.metricName = metricName; + DBucketTimer(ID id, int[] bucketRanges, Timer[] buckets) { + this.id = id; this.bucketRanges = bucketRanges; this.buckets = buckets; this.lastBucketIndex = bucketRanges.length; @@ -24,7 +24,7 @@ final class DBucketTimer implements Timer { @Override public String toString() { - return metricName; + return id.toString(); } @Override @@ -101,9 +101,14 @@ public void addErr(long startNanos) { addEventSince(false, startNanos); } + @Override + public ID id() { + return id; + } + @Override public String name() { - return metricName; + return id.name(); } @Override diff --git a/metrics/src/main/java/io/avaje/metrics/core/DCounter.java b/metrics/src/main/java/io/avaje/metrics/core/DCounter.java index 7bca564..5a7db27 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DCounter.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DCounter.java @@ -21,13 +21,13 @@ final class DCounter extends BaseReportName implements Counter { * The rateUnit should be chosen to 'scale' the statistics in a reasonable * manor - typically events per hour, minute or second. */ - DCounter(String name) { - super(name); + DCounter(ID id) { + super(id); } @Override public String toString() { - return name + ":" + count; + return id + ":" + count; } /** @@ -42,8 +42,8 @@ public void reset() { public void collect(Visitor collector) { final long sum = count.sumThenReset(); if (sum != 0) { - final String name = reportName != null ? reportName : reportName(collector); - collector.visit(new CounterStats(name, sum)); + final ID reportId = reportId(collector); + collector.visit(new CounterStats(reportId, sum)); } } @@ -52,12 +52,14 @@ public long count() { return count.sum(); } - /** - * Return the name of the metric. - */ + @Override + public ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } /** diff --git a/metrics/src/main/java/io/avaje/metrics/core/DGaugeDouble.java b/metrics/src/main/java/io/avaje/metrics/core/DGaugeDouble.java index d733bfe..76804a6 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DGaugeDouble.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DGaugeDouble.java @@ -12,19 +12,24 @@ final class DGaugeDouble extends BaseReportName implements GaugeDouble { private final DoubleSupplier gauge; - DGaugeDouble(String name, DoubleSupplier gauge) { - super(name); + DGaugeDouble(ID id, DoubleSupplier gauge) { + super(id); this.gauge = gauge; } + @Override + public ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } @Override public String toString() { - return name + ":" + value(); + return id + ":" + value(); } /** @@ -39,8 +44,8 @@ public double value() { public void collect(Visitor collector) { final double value = gauge.getAsDouble(); if (Double.compare(value, 0.0d) != 0) { - final String name = reportName != null ? reportName : reportName(collector); - collector.visit(new GaugeDoubleStats(name, value)); + final ID reportId = reportId(collector); + collector.visit(new GaugeDoubleStats(reportId, value)); } } diff --git a/metrics/src/main/java/io/avaje/metrics/core/DGaugeLong.java b/metrics/src/main/java/io/avaje/metrics/core/DGaugeLong.java index ac151eb..8d6eed9 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DGaugeLong.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DGaugeLong.java @@ -7,12 +7,16 @@ abstract class DGaugeLong extends BaseReportName implements GaugeLong { - static DGaugeLong of(String name, LongSupplier supplier) { - return new All(name, supplier); + static DGaugeLong of(ID id, LongSupplier supplier) { + return new All(id, supplier); } static DGaugeLong of(String name, LongSupplier supplier, boolean changesOnly) { - return changesOnly ? new ChangesOnly(name, supplier) : new All(name, supplier); + return of(ID.of(name), supplier, changesOnly); + } + + static DGaugeLong of(ID id, LongSupplier supplier, boolean changesOnly) { + return changesOnly ? new ChangesOnly(id, supplier) : new All(id, supplier); } static DGaugeLong once(String name, LongSupplier supplier) { @@ -21,19 +25,24 @@ static DGaugeLong once(String name, LongSupplier supplier) { protected final LongSupplier supplier; - protected DGaugeLong(String name, LongSupplier supplier) { - super(name); + protected DGaugeLong(ID id, LongSupplier supplier) { + super(id); this.supplier = supplier; } + @Override + public ID id() { + return id; + } + @Override public final String name() { - return name; + return id.name(); } @Override public final String toString() { - return name + ":" + supplier.getAsLong(); + return id + ":" + supplier.getAsLong(); } /** @@ -51,22 +60,22 @@ public final void reset() { static final class All extends DGaugeLong { - All(String name, LongSupplier supplier) { - super(name, supplier); + All(ID id, LongSupplier supplier) { + super(id, supplier); } @Override public void collect(Visitor collector) { - final String name = reportName != null ? reportName : reportName(collector); - collector.visit(new GaugeLongStats(name, supplier.getAsLong())); + final ID reportId = reportId(collector); + collector.visit(new GaugeLongStats(reportId, supplier.getAsLong())); } } static final class ChangesOnly extends DGaugeLong { private long lastReported; - ChangesOnly(String name, LongSupplier supplier) { - super(name, supplier); + ChangesOnly(ID id, LongSupplier supplier) { + super(id, supplier); } @Override @@ -75,8 +84,8 @@ public void collect(Visitor collector) { boolean collect = (value != 0 && value != lastReported); if (collect) { lastReported = value; - final String name = reportName != null ? reportName : reportName(collector); - collector.visit(new GaugeLongStats(name, value)); + final ID reportId = reportId(collector); + collector.visit(new GaugeLongStats(reportId, value)); } } } @@ -84,14 +93,14 @@ public void collect(Visitor collector) { static final class Once extends DGaugeLong { Once(String name, LongSupplier supplier) { - super(name, supplier); + super(ID.of(name), supplier); } @Override public void collect(Visitor collector) { - if (reportName == null) { - String name = reportName(collector); - collector.visit(new GaugeLongStats(name, supplier.getAsLong())); + if (reportId == null) { + final ID reportId = reportId(collector); + collector.visit(new GaugeLongStats(reportId, supplier.getAsLong())); } } } diff --git a/metrics/src/main/java/io/avaje/metrics/core/DMeter.java b/metrics/src/main/java/io/avaje/metrics/core/DMeter.java index f059ddc..6000e0a 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DMeter.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DMeter.java @@ -10,20 +10,20 @@ */ final class DMeter implements Metric, Meter { - private final String name; + private final ID id; private final ValueCounter values; /** * Create with a name. */ - DMeter(String name) { - this.name = name; - this.values = new ValueCounter(name); + DMeter(ID id) { + this.id = id; + this.values = new ValueCounter(id); } @Override public String toString() { - return name + ":" + values; + return id + ":" + values; } @Override @@ -39,9 +39,14 @@ public void reset() { values.reset(); } + @Override + public ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } @Override diff --git a/metrics/src/main/java/io/avaje/metrics/core/DTimer.java b/metrics/src/main/java/io/avaje/metrics/core/DTimer.java index 625962f..20c5ff0 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DTimer.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DTimer.java @@ -14,28 +14,28 @@ */ final class DTimer implements Timer { - private final String name; + private final ID id; private final @Nullable String bucketRange; private final ValueCounter successCounter; private final ValueCounter errorCounter; - DTimer(String name) { - this.name = name; + DTimer(ID id) { + this.id = id; this.bucketRange = null; - this.successCounter = new ValueCounter(name); - this.errorCounter = new ValueCounter(name + ".error"); + this.successCounter = new ValueCounter(id); + this.errorCounter = new ValueCounter(id.suffix(".error")); } - DTimer(String name, String bucketRange) { - this.name = name; + DTimer(ID id, String bucketRange) { + this.id = id; this.bucketRange = bucketRange; - this.successCounter = new ValueCounter(name, bucketRange); - this.errorCounter = new ValueCounter(name + ".error"); + this.successCounter = new ValueCounter(id, bucketRange); + this.errorCounter = new ValueCounter(id.suffix(".error")); } @Override public String toString() { - return name + ":" + successCounter + ((errorCounter.count() == 0) ? "" : " error:" + errorCounter); + return id + ":" + successCounter + ((errorCounter.count() == 0) ? "" : " error:" + errorCounter); } @Override @@ -65,8 +65,13 @@ public void collect(Visitor collector) { } } + @Override + public ID id() { + return id; + } + public String name() { - return name; + return id.name(); } @Override diff --git a/metrics/src/main/java/io/avaje/metrics/core/DefaultMetricProvider.java b/metrics/src/main/java/io/avaje/metrics/core/DefaultMetricProvider.java index 8a29f2b..6c02ce2 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/DefaultMetricProvider.java +++ b/metrics/src/main/java/io/avaje/metrics/core/DefaultMetricProvider.java @@ -18,7 +18,7 @@ */ public final class DefaultMetricProvider implements SpiMetricProvider { - private final ConcurrentHashMap metricsCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap metricsCache = new ConcurrentHashMap<>(); private final SpiMetricBuilder.Factory bucketTimerFactory; private final SpiMetricBuilder.Factory timerFactory; private final SpiMetricBuilder.Factory counterFactory; @@ -175,62 +175,93 @@ public TimerGroup timerGroup(String baseName) { @Override public Timer timer(String name) { - return (Timer) metric(name, timerFactory); + return metric(Metric.ID.of(name), timerFactory); + } + + @Override + public Timer timer(String name, Tags tags) { + return metric(Metric.ID.of(name, tags), timerFactory); } @Override public Timer timer(String name, int... bucketRanges) { - return (Timer) metric(name, bucketTimerFactory, bucketRanges); + return metric(Metric.ID.of(name), bucketTimerFactory, bucketRanges); + } + + @Override + public Timer timer(String name, Tags tags, int... bucketRanges) { + return metric(Metric.ID.of(name, tags), bucketTimerFactory, bucketRanges); } @Override public Counter counter(String name) { - return (Counter) metric(name, counterFactory); + return metric(Metric.ID.of(name), counterFactory); + } + + @Override + public Counter counter(String name, Tags tags) { + return metric(Metric.ID.of(name, tags), counterFactory); } @Override public Meter meter(String name) { - return (Meter) metric(name, meterFactory); + return metric(Metric.ID.of(name), meterFactory); + } + + @Override + public Meter meter(String name, Tags tags) { + return metric(Metric.ID.of(name, tags), meterFactory); } @Override public GaugeDouble gauge(String name, DoubleSupplier supplier) { - return put(name, new DGaugeDouble(name, supplier)); + return put(new DGaugeDouble(Metric.ID.of(name), supplier)); + } + + @Override + public GaugeDouble gauge(String name, Tags tags, DoubleSupplier supplier) { + return put(new DGaugeDouble(Metric.ID.of(name, tags), supplier)); } @Override public GaugeLong gauge(String name, LongSupplier gauge) { - return put(name, DGaugeLong.of(name, gauge)); + return put(DGaugeLong.of(Metric.ID.of(name), gauge)); } - private T put(String name, T metric) { - metricsCache.put(name, metric); + @Override + public GaugeLong gauge(String name, Tags tags, LongSupplier gauge) { + return put(DGaugeLong.of(Metric.ID.of(name, tags), gauge)); + } + + private T put(T metric) { + metricsCache.put(metric.id(), metric); return metric; } - private Metric metric(String name, SpiMetricBuilder.Factory factory) { - return metric(name, factory, null); + private T metric(Metric.ID id, SpiMetricBuilder.Factory factory) { + return metric(id, factory, null); } - private Metric metric(String name, SpiMetricBuilder.Factory factory, int[] bucketRanges) { + @SuppressWarnings("unchecked") + private T metric(Metric.ID id, SpiMetricBuilder.Factory factory, int[] bucketRanges) { // try lock free get first - Metric metric = metricsCache.get(name); + Metric metric = metricsCache.get(id); if (metric == null) { synchronized (monitor) { // use synchronized block - metric = metricsCache.get(name); + metric = metricsCache.get(id); if (metric == null) { - metric = factory.createMetric(name, bucketRanges); - metricsCache.put(name, metric); + metric = factory.createMetric(id, bucketRanges); + metricsCache.put(id, metric); } } } - return metric; + return (T) metric; } @Override public void register(Metric metric) { - metricsCache.put(metric.name(), metric); + metricsCache.put(metric.id(), metric); } @Override diff --git a/metrics/src/main/java/io/avaje/metrics/core/JsonWriter.java b/metrics/src/main/java/io/avaje/metrics/core/JsonWriter.java index 165dc9f..2896104 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/JsonWriter.java +++ b/metrics/src/main/java/io/avaje/metrics/core/JsonWriter.java @@ -44,7 +44,7 @@ private void write() { } private void writeMetricStart(Metric.Statistics metric) throws IOException { - writeMetricStart(metric.name()); + writeMetricStart(metric.id().name()); } private void writeMetricStart(String name) throws IOException { @@ -54,7 +54,21 @@ private void writeMetricStart(String name) throws IOException { buffer.append(','); } - private void writeMetricEnd() throws IOException { + private void writeMetricEnd(Metric.Statistics metric) throws IOException { + Tags tags = metric.id().tags(); + if (!tags.isEmpty()) { + buffer.append(','); + writeKey("tags"); + buffer.append('['); + String[] tagVals = tags.array(); + for (int i = 0; i < tagVals.length; i++) { + if (i > 0) { + buffer.append(','); + } + writeValue(tagVals[i]); + } + buffer.append(']'); + } buffer.append('}'); } @@ -63,7 +77,7 @@ public void visit(Timer.Stats metric) { try { writeMetricStart(metric.name()); writeSummary(metric); - writeMetricEnd(); + writeMetricEnd(metric); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -74,7 +88,7 @@ public void visit(Meter.Stats metric) { try { writeMetricStart(metric); writeSummary(metric); - writeMetricEnd(); + writeMetricEnd(metric); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -85,7 +99,7 @@ public void visit(Counter.Stats metric) { try { writeMetricStart(metric); writeKeyNumber("value", metric.count()); - writeMetricEnd(); + writeMetricEnd(metric); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -96,7 +110,7 @@ public void visit(GaugeDouble.Stats metric) { try { writeMetricStart(metric); writeKeyNumber("value", format(metric.value())); - writeMetricEnd(); + writeMetricEnd(metric); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -107,7 +121,7 @@ public void visit(GaugeLong.Stats metric) { try { writeMetricStart(metric); writeKeyNumber("value", metric.value()); - writeMetricEnd(); + writeMetricEnd(metric); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/metrics/src/main/java/io/avaje/metrics/core/JvmCGroupCpu.java b/metrics/src/main/java/io/avaje/metrics/core/JvmCGroupCpu.java index 47c85db..c4ae20e 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/JvmCGroupCpu.java +++ b/metrics/src/main/java/io/avaje/metrics/core/JvmCGroupCpu.java @@ -1,6 +1,7 @@ package io.avaje.metrics.core; import io.avaje.metrics.GaugeLong; +import io.avaje.metrics.Metric; import io.avaje.metrics.MetricRegistry; import java.math.RoundingMode; @@ -83,7 +84,7 @@ private void createCGroupCpuThrottle(MetricRegistry registry, FileLines cpuStat, } private GaugeLong gauge(String name, LongSupplier gauge, boolean reportChangesOnly) { - return DGaugeLong.of(name, gauge, reportChangesOnly); + return DGaugeLong.of(Metric.ID.of(name), gauge, reportChangesOnly); } static final class CpuUsageMicros implements LongSupplier { diff --git a/metrics/src/main/java/io/avaje/metrics/core/JvmOsLoad.java b/metrics/src/main/java/io/avaje/metrics/core/JvmOsLoad.java index 1725592..e3b892d 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/JvmOsLoad.java +++ b/metrics/src/main/java/io/avaje/metrics/core/JvmOsLoad.java @@ -1,6 +1,7 @@ package io.avaje.metrics.core; import io.avaje.metrics.GaugeLong; +import io.avaje.metrics.Metric; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; @@ -13,7 +14,7 @@ final class JvmOsLoad { private static final BigDecimal HUNDRED = BigDecimal.valueOf(100); static GaugeLong osLoadAverage() { - return DGaugeLong.of("jvm.os.loadAverage", new OsLoadGauge(ManagementFactory.getOperatingSystemMXBean())); + return DGaugeLong.of(Metric.ID.of("jvm.os.loadAverage"), new OsLoadGauge(ManagementFactory.getOperatingSystemMXBean())); } static long toLoad(double loadAverage) { diff --git a/metrics/src/main/java/io/avaje/metrics/core/MeterFactory.java b/metrics/src/main/java/io/avaje/metrics/core/MeterFactory.java index 3fce6d1..3f1870c 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/MeterFactory.java +++ b/metrics/src/main/java/io/avaje/metrics/core/MeterFactory.java @@ -1,13 +1,14 @@ package io.avaje.metrics.core; import io.avaje.metrics.Meter; +import io.avaje.metrics.Metric; import io.avaje.metrics.spi.SpiMetricBuilder; final class MeterFactory implements SpiMetricBuilder.Factory { @Override - public Meter createMetric(String name, int[] bucketRanges) { - return new DMeter(name); + public Meter createMetric(Metric.ID id, int[] bucketRanges) { + return new DMeter(id); } } diff --git a/metrics/src/main/java/io/avaje/metrics/core/TimerFactory.java b/metrics/src/main/java/io/avaje/metrics/core/TimerFactory.java index c4545f1..73800ec 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/TimerFactory.java +++ b/metrics/src/main/java/io/avaje/metrics/core/TimerFactory.java @@ -1,13 +1,14 @@ package io.avaje.metrics.core; +import io.avaje.metrics.Metric; import io.avaje.metrics.Timer; import io.avaje.metrics.spi.SpiMetricBuilder; final class TimerFactory implements SpiMetricBuilder.Factory { @Override - public Timer createMetric(String name, int[] bucketRanges) { - return new DTimer(name); + public Timer createMetric(Metric.ID id, int[] bucketRanges) { + return new DTimer(id); } } diff --git a/metrics/src/main/java/io/avaje/metrics/core/ValueCounter.java b/metrics/src/main/java/io/avaje/metrics/core/ValueCounter.java index 2d373ee..7ae2349 100644 --- a/metrics/src/main/java/io/avaje/metrics/core/ValueCounter.java +++ b/metrics/src/main/java/io/avaje/metrics/core/ValueCounter.java @@ -21,13 +21,13 @@ final class ValueCounter extends BaseReportName { private final LongAdder total = new LongAdder(); private final LongAccumulator max = new LongAccumulator(Math::max, Long.MIN_VALUE); - ValueCounter(String name) { - super(name); + ValueCounter(Metric.ID id) { + super(id); this.bucketRange = null; } - ValueCounter(String name, String bucketRange) { - super(name); + ValueCounter(Metric.ID id, String bucketRange) { + super(id); this.bucketRange = bucketRange; } @@ -56,8 +56,8 @@ void add(long value) { } else { final long maxVal = max.getThenReset(); final long totalVal = total.sumThenReset(); - final String name = reportName != null ? reportName : reportName(collector); - return new TimerStats(name, bucketRange, count, totalVal, maxVal); + final Metric.ID reportId = reportId(collector); + return new TimerStats(reportId, bucketRange, count, totalVal, maxVal); } } diff --git a/metrics/src/main/java/io/avaje/metrics/spi/SpiMetricBuilder.java b/metrics/src/main/java/io/avaje/metrics/spi/SpiMetricBuilder.java index d4803d6..f5912b1 100644 --- a/metrics/src/main/java/io/avaje/metrics/spi/SpiMetricBuilder.java +++ b/metrics/src/main/java/io/avaje/metrics/spi/SpiMetricBuilder.java @@ -1,6 +1,9 @@ package io.avaje.metrics.spi; -import io.avaje.metrics.*; +import io.avaje.metrics.Counter; +import io.avaje.metrics.Meter; +import io.avaje.metrics.Metric; +import io.avaje.metrics.Timer; public interface SpiMetricBuilder { @@ -17,6 +20,6 @@ interface Factory { /** * Create the metric. */ - T createMetric(String name, int[] bucketRanges); + T createMetric(Metric.ID id, int[] bucketRanges); } } diff --git a/metrics/src/main/java/io/avaje/metrics/stats/CounterStats.java b/metrics/src/main/java/io/avaje/metrics/stats/CounterStats.java index de54cc7..c742241 100644 --- a/metrics/src/main/java/io/avaje/metrics/stats/CounterStats.java +++ b/metrics/src/main/java/io/avaje/metrics/stats/CounterStats.java @@ -8,14 +8,14 @@ */ public final class CounterStats implements Counter.Stats { - final String name; + final Metric.ID id; final long count; /** * Construct for Counter which doesn't collect time or high watermark. */ - public CounterStats(String name, long count) { - this.name = name; + public CounterStats(Metric.ID id, long count) { + this.id = id; this.count = count; } @@ -29,9 +29,14 @@ public String toString() { return "count:" + count; } + @Override + public Metric.ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } /** diff --git a/metrics/src/main/java/io/avaje/metrics/stats/GaugeDoubleStats.java b/metrics/src/main/java/io/avaje/metrics/stats/GaugeDoubleStats.java index 0411735..a05b360 100644 --- a/metrics/src/main/java/io/avaje/metrics/stats/GaugeDoubleStats.java +++ b/metrics/src/main/java/io/avaje/metrics/stats/GaugeDoubleStats.java @@ -5,17 +5,17 @@ public final class GaugeDoubleStats implements GaugeDouble.Stats { - private final String name; + private final Metric.ID id; private final double value; - public GaugeDoubleStats(String name, double value) { - this.name = name; + public GaugeDoubleStats(Metric.ID id, double value) { + this.id = id; this.value = value; } @Override public String toString() { - return name; + return id.toString(); } @Override @@ -23,9 +23,14 @@ public void visit(Metric.Visitor visitor) { visitor.visit(this); } + @Override + public Metric.ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } @Override diff --git a/metrics/src/main/java/io/avaje/metrics/stats/GaugeLongStats.java b/metrics/src/main/java/io/avaje/metrics/stats/GaugeLongStats.java index 729f235..2ee0922 100644 --- a/metrics/src/main/java/io/avaje/metrics/stats/GaugeLongStats.java +++ b/metrics/src/main/java/io/avaje/metrics/stats/GaugeLongStats.java @@ -5,17 +5,17 @@ public final class GaugeLongStats implements GaugeLong.Stats { - private final String name; + private final Metric.ID id; private final long value; - public GaugeLongStats(String name, long value) { - this.name = name; + public GaugeLongStats(Metric.ID id, long value) { + this.id = id; this.value = value; } @Override public String toString() { - return name; + return id.toString(); } @Override @@ -23,9 +23,14 @@ public void visit(Metric.Visitor visitor) { visitor.visit(this); } + @Override + public Metric.ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } @Override diff --git a/metrics/src/main/java/io/avaje/metrics/stats/TimerStats.java b/metrics/src/main/java/io/avaje/metrics/stats/TimerStats.java index 1574d5a..636b840 100644 --- a/metrics/src/main/java/io/avaje/metrics/stats/TimerStats.java +++ b/metrics/src/main/java/io/avaje/metrics/stats/TimerStats.java @@ -9,7 +9,7 @@ */ public final class TimerStats implements Timer.Stats { - final String name; + final Metric.ID id; final @Nullable String bucketRange; final long count; final long total; @@ -18,15 +18,15 @@ public final class TimerStats implements Timer.Stats { /** * Create with no bucketRange. */ - public TimerStats(String name, long count, long total, long max) { - this(name, null, count, total, max); + public TimerStats(Metric.ID id, long count, long total, long max) { + this(id, null, count, total, max); } /** * Create with all parameters including bucketRange. */ - public TimerStats(String name, @Nullable String bucketRange, long count, long total, long max) { - this.name = name; + public TimerStats(Metric.ID id, @Nullable String bucketRange, long count, long total, long max) { + this.id = id; this.bucketRange = bucketRange; this.count = count; this.total = total; @@ -50,9 +50,14 @@ public void visit(Metric.Visitor visitor) { return bucketRange; } + @Override + public Metric.ID id() { + return id; + } + @Override public String name() { - return name; + return id.name(); } /** diff --git a/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java b/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java new file mode 100644 index 0000000..2a4fbbb --- /dev/null +++ b/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java @@ -0,0 +1,70 @@ +package io.avaje.metrics; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MetricIDTest { + + @Test + void equalToNoTag() { + var one = Metric.ID.of("one"); + assertThat(one).isEqualTo(Metric.ID.of("one")); + + assertThat(one).isNotEqualTo(Metric.ID.of("two")); + assertThat(one).isNotEqualTo(Metric.ID.of("one", Tags.of("a", "b"))); + } + + @Test + void equalToWithTag() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + assertThat(one).isEqualTo(Metric.ID.of("one", Tags.of("a", "b"))); + + assertThat(one).isNotEqualTo(Metric.ID.of("one")); + assertThat(one).isNotEqualTo(Metric.ID.of("one", Tags.EMPTY)); + assertThat(one).isNotEqualTo(Metric.ID.of("one", Tags.of("x", "y"))); + assertThat(one).isNotEqualTo(Metric.ID.of("two", Tags.of("a", "b"))); + } + + @Test + void suffix() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + Metric.ID suffix = one.suffix(".error"); + assertThat(suffix.name()).isEqualTo("one.error"); + assertThat(suffix.tags()).isEqualTo(one.tags()); + } + + @Test + void withName_sameName_expectSame() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + Metric.ID expectSame = one.withName("one"); + assertThat(expectSame).isSameAs(one); + } + + @Test + void withName_differentName() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + Metric.ID other = one.withName("other"); + assertThat(other.name()).isEqualTo("other"); + assertThat(other.tags()).isEqualTo(one.tags()); + } + + @Test + void withTags_sameTags() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + + Metric.ID other = one.withTags(Tags.of("a", "b")); + assertThat(other).isSameAs(one); + } + + @Test + void withTags_differentTags() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + + Metric.ID other = one.withTags(Tags.of("x", "y")); + assertThat(other.name()).isEqualTo(one.name()); + assertThat(other.tags()).isNotEqualTo(one.tags()); + assertThat(other.tags()).isEqualTo(Tags.of("x", "y")); + } + +} diff --git a/metrics/src/test/java/io/avaje/metrics/MetricsTest.java b/metrics/src/test/java/io/avaje/metrics/MetricsTest.java index 060431c..df2158c 100644 --- a/metrics/src/test/java/io/avaje/metrics/MetricsTest.java +++ b/metrics/src/test/java/io/avaje/metrics/MetricsTest.java @@ -17,7 +17,7 @@ void addSupplier() { Metrics.collectMetrics(); Metrics.addSupplier(() -> suppliedMetrics); - suppliedMetrics.add(new GaugeLongStats("supplied0", 42)); + suppliedMetrics.add(new GaugeLongStats(Metric.ID.of("supplied0"), 42)); List result = Metrics.collectMetrics(); assertThat(result).hasSize(1); diff --git a/metrics/src/test/java/io/avaje/metrics/TagsTest.java b/metrics/src/test/java/io/avaje/metrics/TagsTest.java new file mode 100644 index 0000000..87b5d78 --- /dev/null +++ b/metrics/src/test/java/io/avaje/metrics/TagsTest.java @@ -0,0 +1,24 @@ +package io.avaje.metrics; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class TagsTest { + + @Test + void empty() { + assertThat(Tags.of()).isEqualTo(Tags.EMPTY); + assertThat(Tags.EMPTY.array().length).isEqualTo(0); + } + + @Test + void of() { + Tags tag = Tags.of("env", "dev"); + assertThat(tag).isEqualTo(Tags.of("env", "dev")); + assertThat(tag.array()).isEqualTo(new String[]{"env","dev"}); + + assertThat(tag).isNotEqualTo(Tags.of("env", "test")); + assertThat(tag).isNotEqualTo(Tags.of("enx", "dev")); + } +} diff --git a/metrics/src/test/java/io/avaje/metrics/core/CollectAsJsonTest.java b/metrics/src/test/java/io/avaje/metrics/core/CollectAsJsonTest.java index 8d67782..291d27a 100644 --- a/metrics/src/test/java/io/avaje/metrics/core/CollectAsJsonTest.java +++ b/metrics/src/test/java/io/avaje/metrics/core/CollectAsJsonTest.java @@ -12,29 +12,48 @@ class CollectAsJsonTest { @Test void collectAsJson() { + Tags tags = Tags.of("a", "b"); long startNanos = System.nanoTime(); Timer timer = registry.timer("my.timer"); + Timer timer2 = registry.timer("my.timer", tags); + Timer timer3 = registry.timer("myBucket", tags, 400, 900); + Counter counter = registry.counter("my.count"); + Counter counter2 = registry.counter("my.count", tags); counter.inc(); counter.inc(); + counter2.inc(); + Meter meter = registry.meter("my.meter"); + Meter meter2 = registry.meter("my.meter", tags); meter.addEvent(42); meter.addEvent(44); meter.addEvent(46); + meter2.addEvent(993); timer.add(startNanos); + timer2.add(startNanos); + timer3.add(startNanos); registry.gauge("my.gauge0", () -> 142D); registry.gauge("my.gauge1", () -> 200L); + registry.gauge("my.gauge0", tags, () -> 442D); + registry.gauge("my.gauge1", tags, () -> 400L); timer.add(startNanos); String asJson = registry.collectAsJson().asJson(); assertThat(asJson).contains("{\"name\":\"my.count\",\"value\":2}"); + assertThat(asJson).contains("{\"name\":\"my.count\",\"value\":1,\"tags\":[\"a\",\"b\"]}"); assertThat(asJson).contains("{\"name\":\"my.timer\",\"count\":2,\"mean\":"); + assertThat(asJson).contains("{\"name\":\"my.timer\",\"count\":1,\"mean\":"); // with tags + assertThat(asJson).contains("{\"name\":\"myBucket\",\"count\":1,\"mean\":"); // with tags assertThat(asJson).contains("{\"name\":\"my.meter\",\"count\":3,\"mean\":44,\"max\":46,\"total\":132}"); + assertThat(asJson).contains("{\"name\":\"my.meter\",\"count\":1,\"mean\":993,\"max\":993,\"total\":993,\"tags\":[\"a\",\"b\"]}"); assertThat(asJson).contains("{\"name\":\"my.gauge0\",\"value\":142.0}"); + assertThat(asJson).contains("{\"name\":\"my.gauge0\",\"value\":442.0,\"tags\":[\"a\",\"b\"]}"); assertThat(asJson).contains("{\"name\":\"my.gauge1\",\"value\":200}"); + assertThat(asJson).contains("{\"name\":\"my.gauge1\",\"value\":400,\"tags\":[\"a\",\"b\"]}"); } } diff --git a/metrics/src/test/java/io/avaje/metrics/core/CounterMetricTest.java b/metrics/src/test/java/io/avaje/metrics/core/CounterMetricTest.java index c04a1aa..9f32afa 100644 --- a/metrics/src/test/java/io/avaje/metrics/core/CounterMetricTest.java +++ b/metrics/src/test/java/io/avaje/metrics/core/CounterMetricTest.java @@ -38,4 +38,43 @@ private List collect(Metric metric) { metric.collect(collector); return collector.list(); } + + @Test + void tags() { + MetricRegistry registry = Metrics.registry(); + Counter counter0 = registry.counter("one", Tags.of("k", "v")); + Counter counter1 = registry.counter("one", Tags.of("k", "x")); + Counter counter2 = registry.counter("one"); + + assertThat(counter0).isNotSameAs(counter1); + assertThat(counter0).isNotSameAs(counter2); + + counter0.inc(); + counter1.inc(); + counter1.inc(); + counter2.inc(); + counter2.inc(); + counter2.inc(); + + List s0 = collect(counter0); + assertThat(s0).hasSize(1); + assertThat(s0.get(0).id().tags()).isEqualTo(Tags.of("k", "v")); + assertThat(counter0.count()).isEqualTo(0); + assertThat(counter1.count()).isEqualTo(2); + assertThat(counter2.count()).isEqualTo(3); + + List s1 = collect(counter1); + assertThat(s1).hasSize(1); + assertThat(s1.get(0).id().tags()).isEqualTo(Tags.of("k", "x")); + assertThat(counter0.count()).isEqualTo(0); + assertThat(counter1.count()).isEqualTo(0); + assertThat(counter2.count()).isEqualTo(3); + + List s2 = collect(counter2); + assertThat(s2).hasSize(1); + assertThat(s2.get(0).id().tags()).isEqualTo(Tags.of()); + assertThat(counter0.count()).isEqualTo(0); + assertThat(counter1.count()).isEqualTo(0); + assertThat(counter2.count()).isEqualTo(0); + } } diff --git a/metrics/src/test/java/io/avaje/metrics/core/CounterTest.java b/metrics/src/test/java/io/avaje/metrics/core/CounterTest.java index 78f5639..3980431 100644 --- a/metrics/src/test/java/io/avaje/metrics/core/CounterTest.java +++ b/metrics/src/test/java/io/avaje/metrics/core/CounterTest.java @@ -1,5 +1,6 @@ package io.avaje.metrics.core; +import io.avaje.metrics.Metric; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -8,7 +9,7 @@ class CounterTest { @Test void test() { - DCounter counter = new DCounter(null); + DCounter counter = new DCounter(Metric.ID.of("foo")); assertEquals(0, counter.count()); counter.inc(); diff --git a/metrics/src/test/java/io/avaje/metrics/core/DGaugeDoubleTest.java b/metrics/src/test/java/io/avaje/metrics/core/DGaugeDoubleTest.java index 1194ca1..7fa3abb 100644 --- a/metrics/src/test/java/io/avaje/metrics/core/DGaugeDoubleTest.java +++ b/metrics/src/test/java/io/avaje/metrics/core/DGaugeDoubleTest.java @@ -2,6 +2,7 @@ import io.avaje.metrics.Metric; import io.avaje.metrics.NamingMatch; +import io.avaje.metrics.Tags; import org.junit.jupiter.api.Test; import java.util.List; @@ -21,7 +22,8 @@ private List collect(Metric metric) { @Test void notSkip_when_unchanged() { MyGauge myGauge = new MyGauge(); - DGaugeDouble metric = new DGaugeDouble(DGaugeLongTest.MyGauge.class.getName() + ".test", myGauge); + Metric.ID id = Metric.ID.of(MyGauge.class.getName() + ".test"); + DGaugeDouble metric = new DGaugeDouble(id, myGauge); assertEquals(0.0, metric.value()); assertThat(collect(metric)).isEmpty(); @@ -51,15 +53,18 @@ void notSkip_when_unchanged() { @Test void test() { + Metric.ID id = Metric.ID.of(MyGauge.class.getName() + ".test", Tags.of("k", "v")); MyGauge myGauge = new MyGauge(); - DGaugeDouble metric = new DGaugeDouble(MyGauge.class.getName() + ".test", myGauge); + DGaugeDouble metric = new DGaugeDouble(id, myGauge); assertEquals(0d, metric.value()); assertThat(collect(metric)).isEmpty(); myGauge.value = 100d; assertEquals(100d, metric.value()); - assertThat(collect(metric)).hasSize(1); + List collect = collect(metric); + assertThat(collect).hasSize(1); + assertThat(collect.get(0).id()).isEqualTo(id); } static class MyGauge implements DoubleSupplier { diff --git a/metrics/src/test/java/io/avaje/metrics/core/TimerTest.java b/metrics/src/test/java/io/avaje/metrics/core/TimerTest.java index 3d7e941..46a8472 100644 --- a/metrics/src/test/java/io/avaje/metrics/core/TimerTest.java +++ b/metrics/src/test/java/io/avaje/metrics/core/TimerTest.java @@ -16,6 +16,10 @@ void add() { MetricRegistry registry = Metrics.createRegistry(); Timer metric = registry.timer("org.test.mytimed"); + Timer metric2 = registry.timer("org.test.mytimed", Tags.of("a", "b")); + Timer metric3 = registry.timer("myBucket", Tags.of("a", "b"), 400, 900); + assertThat(metric2).isNotSameAs(metric); + assertThat(metric3).isNotSameAs(metric); boolean useContext = false;//metric.isRequestTiming(); long start = System.nanoTime(); diff --git a/metrics/src/test/java/io/avaje/metrics/core/ValueCounterTest.java b/metrics/src/test/java/io/avaje/metrics/core/ValueCounterTest.java index 6280377..39e5695 100644 --- a/metrics/src/test/java/io/avaje/metrics/core/ValueCounterTest.java +++ b/metrics/src/test/java/io/avaje/metrics/core/ValueCounterTest.java @@ -1,5 +1,6 @@ package io.avaje.metrics.core; +import io.avaje.metrics.Metric; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,7 +9,7 @@ class ValueCounterTest { @Test void testGetStatisticsWithNoReset() { - ValueCounter counter = new ValueCounter("junk"); + ValueCounter counter = new ValueCounter(Metric.ID.of("junk")); assertEquals(Long.MIN_VALUE, counter.max()); counter.add(100); @@ -26,7 +27,7 @@ void testGetStatisticsWithNoReset() { @Test void test() { - ValueCounter counter = new ValueCounter("junk"); + ValueCounter counter = new ValueCounter(Metric.ID.of("junk")); assertEquals(0, counter.count()); assertEquals(0, counter.total()); From 9cb5feb2981bb92b84b1f0bec30cbc85399dca1a Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 17 Apr 2025 08:32:16 +1200 Subject: [PATCH 2/2] toString() for Tags and Metric.ID --- metrics/src/main/java/io/avaje/metrics/MId.java | 7 +++++++ metrics/src/main/java/io/avaje/metrics/MTags.java | 5 +++++ metrics/src/test/java/io/avaje/metrics/MetricIDTest.java | 9 +++++++++ metrics/src/test/java/io/avaje/metrics/TagsTest.java | 7 +++++++ 4 files changed, 28 insertions(+) diff --git a/metrics/src/main/java/io/avaje/metrics/MId.java b/metrics/src/main/java/io/avaje/metrics/MId.java index ed92f54..abd48dc 100644 --- a/metrics/src/main/java/io/avaje/metrics/MId.java +++ b/metrics/src/main/java/io/avaje/metrics/MId.java @@ -14,10 +14,12 @@ final class MId implements Metric.ID { this.tags = tags; } + @Override public String name() { return name; } + @Override public Tags tags() { return tags; } @@ -55,4 +57,9 @@ public boolean equals(Object object) { public int hashCode() { return Objects.hash(name, tags); } + + @Override + public String toString() { + return tags.isEmpty() ? name : name + ' ' + tags; + } } diff --git a/metrics/src/main/java/io/avaje/metrics/MTags.java b/metrics/src/main/java/io/avaje/metrics/MTags.java index 7abc80a..b994bbd 100644 --- a/metrics/src/main/java/io/avaje/metrics/MTags.java +++ b/metrics/src/main/java/io/avaje/metrics/MTags.java @@ -38,4 +38,9 @@ public boolean equals(Object object) { public int hashCode() { return Arrays.hashCode(keyValuePairs); } + + @Override + public String toString() { + return keyValuePairs.length == 0 ? "" : "tags:" + Arrays.toString(keyValuePairs); + } } diff --git a/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java b/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java index 2a4fbbb..3ed4533 100644 --- a/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java +++ b/metrics/src/test/java/io/avaje/metrics/MetricIDTest.java @@ -34,6 +34,15 @@ void suffix() { assertThat(suffix.tags()).isEqualTo(one.tags()); } + @Test + void testToString() { + var one = Metric.ID.of("one", Tags.of("a", "b")); + assertThat(one.toString()).isEqualTo("one tags:[a, b]"); + + assertThat(Metric.ID.of("one").toString()).isEqualTo("one"); + assertThat(Metric.ID.of("one", Tags.of()).toString()).isEqualTo("one"); + } + @Test void withName_sameName_expectSame() { var one = Metric.ID.of("one", Tags.of("a", "b")); diff --git a/metrics/src/test/java/io/avaje/metrics/TagsTest.java b/metrics/src/test/java/io/avaje/metrics/TagsTest.java index 87b5d78..e441b74 100644 --- a/metrics/src/test/java/io/avaje/metrics/TagsTest.java +++ b/metrics/src/test/java/io/avaje/metrics/TagsTest.java @@ -21,4 +21,11 @@ void of() { assertThat(tag).isNotEqualTo(Tags.of("env", "test")); assertThat(tag).isNotEqualTo(Tags.of("enx", "dev")); } + + @Test + void testToString() { + assertThat(Tags.EMPTY.toString()).isEqualTo(""); + assertThat(Tags.of("env", "dev").toString()).isEqualTo("tags:[env, dev]"); + assertThat(Tags.of("a", "b", "x", "y").toString()).isEqualTo("tags:[a, b, x, y]"); + } }