Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zero gc labels lookup #486

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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,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();
}
}
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) {
final List<String> labels;
if (labelValues.length > 0) {
labels = getPooledLabels();
for (String label : labelValues) {
labels.add(label);
}
} else {
labels = Collections.emptyList();
}
return getOrCreateChild(labels);
}

public Child labels(String l1) {
ArrayList<String> pooledLabels = getPooledLabels();
pooledLabels.add(l1);
return getOrCreateChild(pooledLabels);
}
List<String> key = Arrays.asList(labelValues);
Child c = children.get(key);
if (c != null) {
return c;
public Child labels(String l1, String l2) {
ArrayList<String> pooledLabels = getPooledLabels();
pooledLabels.add(l1);
pooledLabels.add(l2);
return getOrCreateChild(pooledLabels);
}
public Child labels(String l1, String l2, String l3) {
ArrayList<String> 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<String> pooledLabels = getPooledLabels();
pooledLabels.add(l1);
pooledLabels.add(l2);
pooledLabels.add(l3);
pooledLabels.add(l4);
return getOrCreateChild(pooledLabels);
}

private ArrayList<String> getPooledLabels() {
final ThreadLocal<ArrayList<String>> labelNamesPool = this.labelNamesPool;
ArrayList<String> pooledLabels = labelNamesPool.get();
if (pooledLabels == null) {
pooledLabels = new LabelNames(10);
labelNamesPool.set(pooledLabels);
} else {
pooledLabels.clear();
}
return pooledLabels;
}

private Child getOrCreateChild(List<String> labels) {
Child c = children.get(labels);
if (c != null) {
return c;
}
return tryCreateChild(labels);
}

private Child tryCreateChild(List<String> 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
labelNamesPool.set(null);
return c2;
} else {
return tmp;
}
}

private void validateLabels(List<String> labelValues) {
if (labelValues.size() != labelNames.size()) {
throw new IllegalArgumentException("Incorrect number of labels.");
}
for (String label : labelValues) {
if (label == null) {
throw new IllegalArgumentException("Label cannot be null.");
}
}
}
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