Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benchmark/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.3.2</version>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.3.2</version>
<version>1.21</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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})
Expand All @@ -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)
Expand Down
183 changes: 158 additions & 25 deletions simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java
Original file line number Diff line number Diff line change
@@ -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}.
Expand Down Expand Up @@ -53,30 +55,159 @@ public abstract class SimpleCollector<Child> extends Collector {

protected final ConcurrentMap<List<String>, Child> children = new ConcurrentHashMap<List<String>, Child>();
protected Child noLabelsChild;
private final ThreadLocal<ArrayList<String>> labelNamesPool = new ThreadLocal<ArrayList<String>>();

/**
* Return the Child with the given labels, creating it if needed.
* <p>
* 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<String> {

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.
*
* <p>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.
* <p>
* Must be passed the same number of labels are were passed to {@link #labelNames}.
*/
public Child labels(String... labelValues) {
validateLabels(labelValues);
final List<String> labels;
if (labelValues.length > 0) {
labels = pooledLabelNamesOf(labelValues);
} else {
labels = Collections.emptyList();
}
return getOrCreateChild(labels);
}
List<String> key = Arrays.asList(labelValues);
Child c = children.get(key);
if (c != null) {
return c;

private List<String> pooledLabelNamesOf(String... labelValues) {
final ThreadLocal<ArrayList<String>> labelNamesPool = this.labelNamesPool;
ArrayList<String> 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<String> labels) {
Child c = children.get(labels);
if (c != null) {
labels.clear();
return c;
}
return tryCreateChild(labels);
}

private Child tryCreateChild(List<String> 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you throwaway the pool here?

Copy link
Author

@franz1981 franz1981 Dec 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because if labels is being put into children is better to not sharing it to others that will come later by mean of the pool: the risk is to have the same list in children and pooled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment on that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added 👍

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.
* <p>
* Must be passed the same number of labels are were passed to {@link #labelNames}.
*/
public Child labels(String labelValue) {
validateLabel(labelValue);
final ThreadLocal<ArrayList<String>> labelNamesPool = this.labelNamesPool;
ArrayList<String> 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.
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down