From 03576e6540f8b8b9e6845eb75d2e3022b832e5ad Mon Sep 17 00:00:00 2001 From: Francesco Nigro Date: Thu, 13 Dec 2018 17:09:38 +0100 Subject: [PATCH 1/2] Reduced garbage produced on label lookup Introduced pooling of label Names to reduce garbage in the hot path, updated benchmarks to measure it, improved SimpleCollector creation when are used labels with no label names or with a single element. Introduced a new ArrayList implementation with faster hashCode/equals to allow faster lookups. Signed-off-by: Francesco Nigro --- benchmark/pom.xml | 4 +- .../benchmark/LabelNamesLookupBenchmark.java | 72 +++++++ .../benchmark/SummaryBenchmark.java | 37 +++- .../io/prometheus/client/SimpleCollector.java | 183 +++++++++++++++--- .../client/SimpleCollectorTest.java | 5 + 5 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java diff --git a/benchmark/pom.xml b/benchmark/pom.xml index 5a8b768af..feaeb93c1 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -28,12 +28,12 @@ org.openjdk.jmh jmh-core - 1.3.2 + 1.21 org.openjdk.jmh jmh-generator-annprocess - 1.3.2 + 1.21 diff --git a/benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java new file mode 100644 index 000000000..e2feaa9bf --- /dev/null +++ b/benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java @@ -0,0 +1,72 @@ +package io.prometheus.benchmark; + +import io.prometheus.client.Histogram; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.profile.GCProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +public class LabelNamesLookupBenchmark { + + @Param({"1","2"}) + public int labelNamesCount; + + String[] labelNames; + io.prometheus.client.Histogram prometheusSimpleHistogram; + + @Setup + public void setup() { + final String baseLabelName = "label"; + labelNames = new String[labelNamesCount]; + for (int i = 0; i< labelNamesCount; i++){ + labelNames[i] = baseLabelName + '_' + i; + } + prometheusSimpleHistogram = io.prometheus.client.Histogram.build() + .name("name") + .help("some description..") + .labelNames(labelNames).create(); + if (labelNamesCount == 1) { + prometheusSimpleHistogram.labels(labelNames[0]); + } else { + prometheusSimpleHistogram.labels(labelNames); + } + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Histogram.Child labelNamesLookupBenchmark() { + if (labelNamesCount == 1) { + return prometheusSimpleHistogram.labels(labelNames[0]); + } else { + return prometheusSimpleHistogram.labels(labelNames); + } + } + + public static void main(String[] args) throws RunnerException { + + Options opt = new OptionsBuilder() + .include(LabelNamesLookupBenchmark.class.getSimpleName()) + .jvmArgs("-XX:+UseBiasedLocking", "-XX:BiasedLockingStartupDelay=0") + .addProfiler(GCProfiler.class) + .warmupIterations(5) + .measurementIterations(4) + .threads(1) + .forks(2) + .build(); + + new Runner(opt).run(); + } +} diff --git a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java index 3a77dc5ec..5827696bf 100644 --- a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java +++ b/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java @@ -10,6 +10,7 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.profile.GCProfiler; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; @@ -29,6 +30,7 @@ public class SummaryBenchmark { io.prometheus.client.Histogram prometheusSimpleHistogram; io.prometheus.client.Histogram.Child prometheusSimpleHistogramChild; io.prometheus.client.Histogram prometheusSimpleHistogramNoLabels; + String[] labelNames; @Setup public void setup() { @@ -42,7 +44,9 @@ public void setup() { .name("name") .help("some description..") .labelNames("some", "group").create(); - prometheusSimpleSummaryChild = prometheusSimpleSummary.labels("test", "group"); + + labelNames = new String[]{"tests", "group"}; + prometheusSimpleSummaryChild = prometheusSimpleSummary.labels(labelNames); prometheusSimpleSummaryNoLabels = io.prometheus.client.Summary.build() .name("name") @@ -53,7 +57,7 @@ public void setup() { .name("name") .help("some description..") .labelNames("some", "group").create(); - prometheusSimpleHistogramChild = prometheusSimpleHistogram.labels("test", "group"); + prometheusSimpleHistogramChild = prometheusSimpleHistogram.labels(labelNames); prometheusSimpleHistogramNoLabels = io.prometheus.client.Histogram.build() .name("name") @@ -85,6 +89,13 @@ public void prometheusSimpleSummaryBenchmark() { prometheusSimpleSummary.labels("test", "group").observe(1) ; } + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleSummaryPooledLabelNamesBenchmark() { + prometheusSimpleSummary.labels(labelNames).observe(1) ; + } + @Benchmark @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -105,6 +116,26 @@ public void prometheusSimpleSummaryNoLabelsBenchmark() { public void prometheusSimpleHistogramBenchmark() { prometheusSimpleHistogram.labels("test", "group").observe(1) ; } + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHistogramPooledLabelNamesBenchmark() { + prometheusSimpleHistogram.labels(labelNames).observe(1) ; + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public double prometheusSimpleHistogramTimerBenchmark() { + return prometheusSimpleHistogram.labels("test", "group").startTimer().observeDuration(); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public double prometheusSimpleHistogramTimerPooledLabelNamesBenchmark() { + return prometheusSimpleHistogram.labels(labelNames).startTimer().observeDuration(); + } @Benchmark @BenchmarkMode({Mode.AverageTime}) @@ -131,6 +162,8 @@ public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SummaryBenchmark.class.getSimpleName()) + .jvmArgs("-XX:+UseBiasedLocking", "-XX:BiasedLockingStartupDelay=0") + .addProfiler(GCProfiler.class) .warmupIterations(5) .measurementIterations(4) .threads(4) diff --git a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java index ed8c5c8a0..75bb249a2 100644 --- a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java +++ b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java @@ -1,10 +1,12 @@ package io.prometheus.client; +import java.util.AbstractList; import java.util.ArrayList; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** * Common functionality for {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}. @@ -53,30 +55,159 @@ public abstract class SimpleCollector extends Collector { protected final ConcurrentMap, Child> children = new ConcurrentHashMap, Child>(); protected Child noLabelsChild; + private final ThreadLocal> labelNamesPool = new ThreadLocal>(); - /** - * Return the Child with the given labels, creating it if needed. - *

- * Must be passed the same number of labels are were passed to {@link #labelNames}. - */ - public Child labels(String... labelValues) { - if (labelValues.length != labelNames.size()) { - throw new IllegalArgumentException("Incorrect number of labels."); + /** + * It is just reimplementing in a more JIT-friendly way both equals/hashCode to avoid + * using Iterators like the original {@link AbstractList}. + */ + private static final class LabelNames extends ArrayList { + + public LabelNames(int capacity) { + super(capacity); + } + + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof ArrayList)) { + //what if o is a singleton list or empty? + //We can just use the common fast path + if (o instanceof List) { + if (((List) o).size() > 1) { + return super.equals(o); + } + } else { + return super.equals(o); + } + } + final int size = size(); + final List other = (List) o; + if (size != other.size()) { + return false; + } + for (int i = 0; i < size; i++) { + final Object a = get(i); + final Object b = other.get(i); + final boolean eq = (a == b) || (a != null && a.equals(b)); + if (!eq) { + return false; + } + } + return true; + } + + /** + * Returns the hash code value for this list. + * + *

This implementation uses exactly the code that is used to define the + * list hash function in the documentation for the {@link List#hashCode} + * method. + * + * @return the hash code value for this list + */ + public int hashCode() { + int hashCode = 1; + for (int i = 0, size = size(); i < size; i++) { + final Object e = get(i); + final int objHash = (e == null ? 0 : e.hashCode()); + hashCode = 31 * hashCode + objHash; + } + return hashCode; + } } - for (String label: labelValues) { - if (label == null) { - throw new IllegalArgumentException("Label cannot be null."); - } + + /** + * Return the Child with the given labels, creating it if needed. + *

+ * Must be passed the same number of labels are were passed to {@link #labelNames}. + */ + public Child labels(String... labelValues) { + validateLabels(labelValues); + final List labels; + if (labelValues.length > 0) { + labels = pooledLabelNamesOf(labelValues); + } else { + labels = Collections.emptyList(); + } + return getOrCreateChild(labels); } - List key = Arrays.asList(labelValues); - Child c = children.get(key); - if (c != null) { - return c; + + private List pooledLabelNamesOf(String... labelValues) { + final ThreadLocal> labelNamesPool = this.labelNamesPool; + ArrayList pooledLabels = labelNamesPool.get(); + final int labelValuesCount = labelValues.length; + if (pooledLabels == null) { + pooledLabels = new LabelNames(labelValuesCount); + labelNamesPool.set(pooledLabels); + } + for (String label : labelValues) { + pooledLabels.add(label); + } + return pooledLabels; + } + + private Child getOrCreateChild(List labels) { + Child c = children.get(labels); + if (c != null) { + labels.clear(); + return c; + } + return tryCreateChild(labels); + } + + private Child tryCreateChild(List labels) { + Child c2 = newChild(); + Child tmp = children.putIfAbsent(labels, c2); + if (tmp == null) { + //given that putIfAbsent return null only when a new + //labels has been added, we need to clear up + //the pool to avoid labels to be both in the pool + //and as children key + labelNamesPool.set(null); + return c2; + } else { + labels.clear(); + return tmp; + } + } + + private void validateLabels(String... labelValues) { + if (labelValues.length != labelNames.size()) { + throw new IllegalArgumentException("Incorrect number of labels."); + } + for (String label : labelValues) { + if (label == null) { + throw new IllegalArgumentException("Label cannot be null."); + } + } + } + + private void validateLabel(String labelValue) { + if (labelNames.size() != 1) { + throw new IllegalArgumentException("Incorrect number of labels."); + } + if (labelValue == null) { + throw new IllegalArgumentException("Label cannot be null."); + } + } + + /** + * Return the Child with the given labels, creating it if needed. + *

+ * Must be passed the same number of labels are were passed to {@link #labelNames}. + */ + public Child labels(String labelValue) { + validateLabel(labelValue); + final ThreadLocal> labelNamesPool = this.labelNamesPool; + ArrayList labels = labelNamesPool.get(); + if (labels == null) { + labels = new LabelNames(1); + labelNamesPool.set(labels); + } + labels.add(labelValue); + return getOrCreateChild(labels); } - Child c2 = newChild(); - Child tmp = children.putIfAbsent(key, c2); - return tmp == null ? c2 : tmp; - } /** * Remove the Child with the given labels. @@ -164,9 +295,11 @@ protected SimpleCollector(Builder b) { checkMetricName(fullname); if (b.help.isEmpty()) throw new IllegalStateException("Help hasn't been set."); help = b.help; - labelNames = Arrays.asList(b.labelNames); - - for (String n: labelNames) { + labelNames = new LabelNames(b.labelNames.length); + for (String label : b.labelNames) { + labelNames.add(label); + } + for (String n: b.labelNames) { checkMetricLabelName(n); } diff --git a/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java b/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java index d5c7e3e32..6c5a4078e 100644 --- a/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java @@ -39,6 +39,11 @@ public void testNullLabelThrows() { metric.labels(new String[]{null}); } + @Test(expected=IllegalArgumentException.class) + public void testNullLabelsThrows() { + metric.labels(new String[]{null, null}); + } + @Test(expected=IllegalArgumentException.class) public void testTooManyLabelsThrows() { metric.labels("a", "b"); From 1ef6c7813a5a18d9c1215f03c91c0497b2358882 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Wed, 19 Jun 2019 12:03:18 +0100 Subject: [PATCH 2/2] Optimise allocs, and simplify a bit. Add benchmarks from #460 --- .../benchmark/LabelNamesLookupBenchmark.java | 72 ---------------- .../LabelsToChildLookupBenchmark.java | 69 +++++++++++++++ .../benchmark/SummaryBenchmark.java | 37 +------- .../io/prometheus/client/SimpleCollector.java | 84 +++++++++---------- 4 files changed, 113 insertions(+), 149 deletions(-) delete mode 100644 benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java create mode 100644 benchmark/src/main/java/io/prometheus/benchmark/LabelsToChildLookupBenchmark.java diff --git a/benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java deleted file mode 100644 index e2feaa9bf..000000000 --- a/benchmark/src/main/java/io/prometheus/benchmark/LabelNamesLookupBenchmark.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.prometheus.benchmark; - -import io.prometheus.client.Histogram; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.profile.GCProfiler; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -import java.util.concurrent.TimeUnit; - -@State(Scope.Benchmark) -public class LabelNamesLookupBenchmark { - - @Param({"1","2"}) - public int labelNamesCount; - - String[] labelNames; - io.prometheus.client.Histogram prometheusSimpleHistogram; - - @Setup - public void setup() { - final String baseLabelName = "label"; - labelNames = new String[labelNamesCount]; - for (int i = 0; i< labelNamesCount; i++){ - labelNames[i] = baseLabelName + '_' + i; - } - prometheusSimpleHistogram = io.prometheus.client.Histogram.build() - .name("name") - .help("some description..") - .labelNames(labelNames).create(); - if (labelNamesCount == 1) { - prometheusSimpleHistogram.labels(labelNames[0]); - } else { - prometheusSimpleHistogram.labels(labelNames); - } - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public Histogram.Child labelNamesLookupBenchmark() { - if (labelNamesCount == 1) { - return prometheusSimpleHistogram.labels(labelNames[0]); - } else { - return prometheusSimpleHistogram.labels(labelNames); - } - } - - public static void main(String[] args) throws RunnerException { - - Options opt = new OptionsBuilder() - .include(LabelNamesLookupBenchmark.class.getSimpleName()) - .jvmArgs("-XX:+UseBiasedLocking", "-XX:BiasedLockingStartupDelay=0") - .addProfiler(GCProfiler.class) - .warmupIterations(5) - .measurementIterations(4) - .threads(1) - .forks(2) - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/java/io/prometheus/benchmark/LabelsToChildLookupBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/LabelsToChildLookupBenchmark.java new file mode 100644 index 000000000..14415133e --- /dev/null +++ b/benchmark/src/main/java/io/prometheus/benchmark/LabelsToChildLookupBenchmark.java @@ -0,0 +1,69 @@ +package io.prometheus.benchmark; + +import io.prometheus.client.Counter; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class LabelsToChildLookupBenchmark { + + private static final String LABEL1 = "label1", LABEL2 = "label2", LABEL3 = "label3"; + private static final String LABEL4 = "label4", LABEL5 = "label5"; + + private Counter noLabelsCollector, oneLabelCollector, twoLabelsCollector, threeLabelsCollector; + private Counter fourLabelsCollector, fiveLabelsCollector; + + @Setup + public void setup() { + Counter.Builder builder = new Counter.Builder().name("testCollector").help("testHelp"); + noLabelsCollector = builder.create(); + oneLabelCollector = builder.labelNames("name1").create(); + twoLabelsCollector = builder.labelNames("name1", "name2").create(); + threeLabelsCollector = builder.labelNames("name1", "name2", "name3").create(); + fourLabelsCollector = builder.labelNames("name1", "name2", "name3", "name4").create(); + fiveLabelsCollector = builder.labelNames("name1", "name2", "name3", "name4", "name5").create(); + } + + @Benchmark + public void baseline(LabelsToChildLookupBenchmark state) { + noLabelsCollector.inc(); + } + + @Benchmark + public void oneLabel(LabelsToChildLookupBenchmark state) { + oneLabelCollector.labels(LABEL1).inc(); + } + + @Benchmark + public void twoLabels(LabelsToChildLookupBenchmark state) { + twoLabelsCollector.labels(LABEL1, LABEL2).inc(); + } + + @Benchmark + public void threeLabels(LabelsToChildLookupBenchmark state) { + threeLabelsCollector.labels(LABEL1, LABEL2, LABEL3).inc(); + } + + @Benchmark + public void fourLabels(LabelsToChildLookupBenchmark state) { + fourLabelsCollector.labels(LABEL1, LABEL2, LABEL3, LABEL4).inc(); + } + + @Benchmark + public void fiveLabels(LabelsToChildLookupBenchmark state) { + fiveLabelsCollector.labels(LABEL1, LABEL2, LABEL3, LABEL4, LABEL5).inc(); + } + + public static void main(String[] args) throws RunnerException { + new Runner(new OptionsBuilder() + .include(LabelsToChildLookupBenchmark.class.getSimpleName()) + .build()).run(); + } +} diff --git a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java index 5827696bf..3a77dc5ec 100644 --- a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java +++ b/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java @@ -10,7 +10,6 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.profile.GCProfiler; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; @@ -30,7 +29,6 @@ public class SummaryBenchmark { io.prometheus.client.Histogram prometheusSimpleHistogram; io.prometheus.client.Histogram.Child prometheusSimpleHistogramChild; io.prometheus.client.Histogram prometheusSimpleHistogramNoLabels; - String[] labelNames; @Setup public void setup() { @@ -44,9 +42,7 @@ public void setup() { .name("name") .help("some description..") .labelNames("some", "group").create(); - - labelNames = new String[]{"tests", "group"}; - prometheusSimpleSummaryChild = prometheusSimpleSummary.labels(labelNames); + prometheusSimpleSummaryChild = prometheusSimpleSummary.labels("test", "group"); prometheusSimpleSummaryNoLabels = io.prometheus.client.Summary.build() .name("name") @@ -57,7 +53,7 @@ public void setup() { .name("name") .help("some description..") .labelNames("some", "group").create(); - prometheusSimpleHistogramChild = prometheusSimpleHistogram.labels(labelNames); + prometheusSimpleHistogramChild = prometheusSimpleHistogram.labels("test", "group"); prometheusSimpleHistogramNoLabels = io.prometheus.client.Histogram.build() .name("name") @@ -89,13 +85,6 @@ public void prometheusSimpleSummaryBenchmark() { prometheusSimpleSummary.labels("test", "group").observe(1) ; } - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleSummaryPooledLabelNamesBenchmark() { - prometheusSimpleSummary.labels(labelNames).observe(1) ; - } - @Benchmark @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -116,26 +105,6 @@ public void prometheusSimpleSummaryNoLabelsBenchmark() { public void prometheusSimpleHistogramBenchmark() { prometheusSimpleHistogram.labels("test", "group").observe(1) ; } - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHistogramPooledLabelNamesBenchmark() { - prometheusSimpleHistogram.labels(labelNames).observe(1) ; - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public double prometheusSimpleHistogramTimerBenchmark() { - return prometheusSimpleHistogram.labels("test", "group").startTimer().observeDuration(); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public double prometheusSimpleHistogramTimerPooledLabelNamesBenchmark() { - return prometheusSimpleHistogram.labels(labelNames).startTimer().observeDuration(); - } @Benchmark @BenchmarkMode({Mode.AverageTime}) @@ -162,8 +131,6 @@ public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SummaryBenchmark.class.getSimpleName()) - .jvmArgs("-XX:+UseBiasedLocking", "-XX:BiasedLockingStartupDelay=0") - .addProfiler(GCProfiler.class) .warmupIterations(5) .measurementIterations(4) .threads(4) diff --git a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java index 75bb249a2..5b857b7c5 100644 --- a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java +++ b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java @@ -123,26 +123,53 @@ public int hashCode() { * Must be passed the same number of labels are were passed to {@link #labelNames}. */ public Child labels(String... labelValues) { - validateLabels(labelValues); final List labels; if (labelValues.length > 0) { - labels = pooledLabelNamesOf(labelValues); + labels = getPooledLabels(); + for (String label : labelValues) { + labels.add(label); + } } else { labels = Collections.emptyList(); } return getOrCreateChild(labels); } - private List pooledLabelNamesOf(String... labelValues) { + public Child labels(String l1) { + ArrayList pooledLabels = getPooledLabels(); + pooledLabels.add(l1); + return getOrCreateChild(pooledLabels); + } + public Child labels(String l1, String l2) { + ArrayList pooledLabels = getPooledLabels(); + pooledLabels.add(l1); + pooledLabels.add(l2); + return getOrCreateChild(pooledLabels); + } + public Child labels(String l1, String l2, String l3) { + ArrayList pooledLabels = getPooledLabels(); + pooledLabels.add(l1); + pooledLabels.add(l2); + pooledLabels.add(l3); + return getOrCreateChild(pooledLabels); + } + public Child labels(String l1, String l2, String l3, String l4) { + ArrayList pooledLabels = getPooledLabels(); + pooledLabels.add(l1); + pooledLabels.add(l2); + pooledLabels.add(l3); + pooledLabels.add(l4); + return getOrCreateChild(pooledLabels); + } + + private ArrayList getPooledLabels() { final ThreadLocal> labelNamesPool = this.labelNamesPool; ArrayList pooledLabels = labelNamesPool.get(); - final int labelValuesCount = labelValues.length; if (pooledLabels == null) { - pooledLabels = new LabelNames(labelValuesCount); + pooledLabels = new LabelNames(10); labelNamesPool.set(pooledLabels); - } - for (String label : labelValues) { - pooledLabels.add(label); + } else { + pooledLabels.clear(); } return pooledLabels; } @@ -150,30 +177,29 @@ private List pooledLabelNamesOf(String... labelValues) { private Child getOrCreateChild(List labels) { Child c = children.get(labels); if (c != null) { - labels.clear(); return c; } return tryCreateChild(labels); } private Child tryCreateChild(List labels) { + validateLabels(labels); Child c2 = newChild(); Child tmp = children.putIfAbsent(labels, c2); if (tmp == null) { - //given that putIfAbsent return null only when a new - //labels has been added, we need to clear up - //the pool to avoid labels to be both in the pool - //and as children key + // given that putIfAbsent return null only when a new + // labels has been added, we need to clear up + // the pool to avoid labels to be both in the pool + // and as children key labelNamesPool.set(null); return c2; } else { - labels.clear(); return tmp; } } - private void validateLabels(String... labelValues) { - if (labelValues.length != labelNames.size()) { + private void validateLabels(List labelValues) { + if (labelValues.size() != labelNames.size()) { throw new IllegalArgumentException("Incorrect number of labels."); } for (String label : labelValues) { @@ -183,32 +209,6 @@ private void validateLabels(String... labelValues) { } } - private void validateLabel(String labelValue) { - if (labelNames.size() != 1) { - throw new IllegalArgumentException("Incorrect number of labels."); - } - if (labelValue == null) { - throw new IllegalArgumentException("Label cannot be null."); - } - } - - /** - * Return the Child with the given labels, creating it if needed. - *

- * Must be passed the same number of labels are were passed to {@link #labelNames}. - */ - public Child labels(String labelValue) { - validateLabel(labelValue); - final ThreadLocal> labelNamesPool = this.labelNamesPool; - ArrayList labels = labelNamesPool.get(); - if (labels == null) { - labels = new LabelNames(1); - labelNamesPool.set(labels); - } - labels.add(labelValue); - return getOrCreateChild(labels); - } - /** * Remove the Child with the given labels. *