diff --git a/simpleclient/src/main/java/io/prometheus/client/ConcurrentChildMap.java b/simpleclient/src/main/java/io/prometheus/client/ConcurrentChildMap.java
new file mode 100644
index 000000000..904c22caf
--- /dev/null
+++ b/simpleclient/src/main/java/io/prometheus/client/ConcurrentChildMap.java
@@ -0,0 +1,94 @@
+package io.prometheus.client;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Abstraction for the internal labels-to-child concurrent map.
+ *
+ * The default implementation is based on a {@link ConcurrentHashMap} but users who are
+ * interested in performance can provide an optimized implementation (which could be
+ * garbage-free for example).
+ *
+ * Implementations must have a no-arg or default constructor.
+ */
+public interface ConcurrentChildMap extends ConcurrentMap, Child> {
+
+ interface ChildFactory {
+ Child newChild(String[] labels);
+ }
+
+ Child labels(ChildFactory childFactory, String... labelValues);
+
+ Child labels(ChildFactory childFactory, String v1);
+
+ Child labels(ChildFactory childFactory, String v1, String v2);
+
+ Child labels(ChildFactory childFactory, String v1, String v2, String v3);
+
+ Child labels(ChildFactory childFactory, String v1, String v2, String v3, String v4);
+
+ void setChild(Child child, String... labelValues);
+
+ void remove(String... labelValues);
+
+ /**
+ * The default {@link ConcurrentHashMap}-based implementation.
+ */
+ static class ConcurrentChildHashMap extends ConcurrentHashMap, Child>
+ implements ConcurrentChildMap {
+
+ @Override
+ public Child labels(ChildFactory childFactory, String... labelValues) {
+ List key = Arrays.asList(labelValues);
+ Child c = get(key);
+ if (c != null) {
+ return c;
+ }
+ for (String label: labelValues) {
+ if (label == null) {
+ throw new IllegalArgumentException("Label cannot be null.");
+ }
+ }
+ Child c2 = childFactory.newChild(labelValues);
+ Child tmp = putIfAbsent(key, c2);
+ return tmp == null ? c2 : tmp;
+ }
+
+ @Override
+ public Child labels(ChildFactory childFactory, String v1) {
+ return labels(childFactory, arr(v1));
+ }
+
+ @Override
+ public Child labels(ChildFactory childFactory, String v1, String v2) {
+ return labels(childFactory, arr(v1, v2));
+ }
+
+ @Override
+ public Child labels(ChildFactory childFactory, String v1, String v2, String v3) {
+ return labels(childFactory, arr(v1, v2, v3));
+ }
+
+ @Override
+ public Child labels(ChildFactory childFactory, String v1, String v2, String v3, String v4) {
+ return labels(childFactory, arr(v1, v2, v3, v4));
+ }
+
+ @Override
+ public void setChild(Child child, String... labelValues) {
+ put(Arrays.asList(labelValues), child);
+ }
+
+ @Override
+ public void remove(String... labelValues) {
+ remove(Arrays.asList(labelValues));
+ }
+
+ private static String[] arr(String... arr) {
+ return arr;
+ }
+ }
+}
diff --git a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java
index a70d5d4e7..7b6861cb4 100644
--- a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java
+++ b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java
@@ -1,10 +1,9 @@
package io.prometheus.client;
import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.Arrays;
import java.util.List;
+import io.prometheus.client.ConcurrentChildMap.ConcurrentChildHashMap;
/**
* Common functionality for {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}.
@@ -46,12 +45,12 @@
* by each. If the cardinality is in the hundreds, you may wish to consider removing the breakout
* by one of the dimensions altogether.
*/
-public abstract class SimpleCollector extends Collector {
+public abstract class SimpleCollector extends Collector implements ConcurrentChildMap.ChildFactory {
protected final String fullname;
protected final String help;
protected final List labelNames;
- protected final ConcurrentMap, Child> children = new ConcurrentHashMap, Child>();
+ protected final ConcurrentChildMap children;
protected Child noLabelsChild;
/**
@@ -60,22 +59,39 @@ public abstract class SimpleCollector extends Collector {
* Must be passed the same number of labels are were passed to {@link #labelNames}.
*/
public Child labels(String... labelValues) {
- if (labelValues.length != labelNames.size()) {
+ validateCount(labelValues.length);
+ return children.labels(this, labelValues);
+ }
+
+ public Child labels() {
+ validateCount(0);
+ return noLabelsChild;
+ }
+
+ public Child labels(String lv1) {
+ validateCount(1);
+ return children.labels(this, lv1);
+ }
+
+ public Child labels(String lv1, String lv2) {
+ validateCount(2);
+ return children.labels(this, lv1, lv2);
+ }
+
+ public Child labels(String lv1, String lv2, String lv3) {
+ validateCount(3);
+ return children.labels(this, lv1, lv2, lv3);
+ }
+
+ public Child labels(String lv1, String lv2, String lv3, String lv4) {
+ validateCount(4);
+ return children.labels(this, lv1, lv2, lv3, lv4);
+ }
+
+ private void validateCount(int count) {
+ if (count != labelNames.size()) {
throw new IllegalArgumentException("Incorrect number of labels.");
}
- for (String label: labelValues) {
- if (label == null) {
- throw new IllegalArgumentException("Label cannot be null.");
- }
- }
- List key = Arrays.asList(labelValues);
- Child c = children.get(key);
- if (c != null) {
- return c;
- }
- Child c2 = newChild();
- Child tmp = children.putIfAbsent(key, c2);
- return tmp == null ? c2 : tmp;
}
/**
@@ -84,7 +100,7 @@ public Child labels(String... labelValues) {
* Any references to the Child are invalidated.
*/
public void remove(String... labelValues) {
- children.remove(Arrays.asList(labelValues));
+ children.remove(labelValues);
initializeNoLabelsChild();
}
@@ -104,7 +120,7 @@ public void clear() {
protected void initializeNoLabelsChild() {
// Initialize metric if it has no labels.
if (labelNames.size() == 0) {
- noLabelsChild = labels();
+ noLabelsChild = labels(new String[0]);
}
}
@@ -132,10 +148,8 @@ protected void initializeNoLabelsChild() {
* A metric should be either all callbacks, or none.
*/
public T setChild(Child child, String... labelValues) {
- if (labelValues.length != labelNames.size()) {
- throw new IllegalArgumentException("Incorrect number of labels.");
- }
- children.put(Arrays.asList(labelValues), child);
+ validateCount(labelValues.length);
+ children.setChild(child, labelValues);
return (T)this;
}
@@ -144,6 +158,11 @@ public T setChild(Child child, String... labelValues) {
*/
protected abstract Child newChild();
+ @Override
+ public Child newChild(String[] labels) {
+ return newChild();
+ }
+
protected List familySamplesList(Collector.Type type, List samples) {
MetricFamilySamples mfs = new MetricFamilySamples(fullname, type, help, samples);
List mfsList = new ArrayList(1);
@@ -152,6 +171,11 @@ protected List familySamplesList(Collector.Type type, List<
}
protected SimpleCollector(Builder b) {
+ try {
+ children = (ConcurrentChildMap) b.childMapClass.newInstance();
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Error instantiating child map class", e);
+ }
if (b.name.isEmpty()) throw new IllegalStateException("Name hasn't been set.");
String name = b.name;
if (!b.subsystem.isEmpty()) {
@@ -187,6 +211,15 @@ public abstract static class Builder, C extends SimpleCo
String[] labelNames = new String[]{};
// Some metrics require additional setup before the initialization can be done.
boolean dontInitializeNoLabelsChild;
+ Class extends ConcurrentChildMap> childMapClass = ConcurrentChildHashMap.class;
+
+ /**
+ * Set a custom implementation for the internal labels-to-Child map.
+ */
+ public B childMap(Class extends ConcurrentChildMap> childMapClass) {
+ this.childMapClass = childMapClass;
+ return (B)this;
+ }
/**
* Set the name of the metric. Required.