,Child>[] children() {
+ return children;
+ }
+
+ /**
+ * @throws IllegalArgumentException for null label
+ */
+ private static int labelsHashCode(String... labelValues) {
+ int hashCode = 1;
+ for (String label : labelValues) {
+ hashCode = buildHashCode(hashCode, label);
+ }
+ return hashCode;
+ }
+
+ private static int hash(int hashCode, int length) {
+ return hashCode & (length - 1); // length is a power of 2
+ }
+
+ /**
+ * @throws IllegalArgumentException for null label
+ */
+ private static int buildHashCode(int hashCode, String label) {
+ if (label == null) {
+ throw new IllegalArgumentException("Label cannot be null.");
+ }
+ return 31 * hashCode + label.hashCode();
+ }
+
+ private static int nextIdx(int i, int len) {
+ return (i == len - 1) ? 0 : (i + 1);
+ }
+
+ private void validateCount(int count) {
+ if (count != labelCount) {
+ throw new IllegalArgumentException("Incorrect number of labels.");
+ }
+ }
+
/**
* 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}.
+ * Must be passed the same number of labels as were passed to {@link #labelNames}.
*/
public Child labels(String... labelValues) {
- if (labelValues.length != labelNames.size()) {
- throw new IllegalArgumentException("Incorrect number of labels.");
+ validateCount(labelValues.length);
+
+ int hashCode = labelsHashCode(labelValues); // also checks for null values
+ final ChildEntry[] arr = children;
+ int arrLen = arr.length, i = hash(hashCode, arrLen);
+ for (ChildEntry ce; (ce = arr[i]) != null; i = nextIdx(i, arrLen)) {
+ if (ce.hashCode == hashCode && Arrays.equals(ce.labels, labelValues)) {
+ return ce.child;
+ }
+ }
+ return labelsMiss(hashCode, arr, i, labelValues);
+ }
+
+ public Child labels() {
+ validateCount(0);
+ return noLabelsChild;
+ }
+
+ // Logic for the fixed-arg overloads is identical apart from number of strings
+ // used for hashcode generation and key comparison
+
+ public Child labels(String v1) {
+ validateCount(1);
+
+ int hashCode = buildHashCode(1, v1);
+ final ChildEntry[] arr = children;
+ int arrLen = arr.length, i = hash(hashCode, arrLen);
+ for (ChildEntry ce; (ce = arr[i]) != null; i = nextIdx(i, arrLen)) {
+ if (v1.equals(ce.labels[0])) {
+ return ce.child;
+ }
+ }
+ return labelsMiss(hashCode, arr, i, v1);
+ }
+
+ public Child labels(String v1, String v2) {
+ validateCount(2);
+
+ int hashCode = buildHashCode(buildHashCode(1, v1), v2);
+ final ChildEntry[] arr = children;
+ int arrLen = arr.length, i = hash(hashCode, arrLen);
+ for (ChildEntry ce; (ce = arr[i]) != null; i = nextIdx(i, arrLen)) {
+ if (ce.hashCode == hashCode) {
+ String[] ls = ce.labels;
+ if (ls[0].equals(v1) && ls[1].equals(v2)) {
+ return ce.child;
+ }
+ }
+ }
+ return labelsMiss(hashCode, arr, i, v1, v2);
+ }
+
+ public Child labels(String v1, String v2, String v3) {
+ validateCount(3);
+
+ int hashCode = buildHashCode(buildHashCode(buildHashCode(1, v1), v2), v3);
+ final ChildEntry[] arr = children;
+ int arrLen = arr.length, i = hash(hashCode, arrLen);
+ for (ChildEntry ce; (ce = arr[i]) != null; i = nextIdx(i, arrLen)) {
+ if (ce.hashCode == hashCode) {
+ String[] ls = ce.labels;
+ if (ls[0].equals(v1) && ls[1].equals(v2) && ls[2].equals(v3)) {
+ return ce.child;
+ }
+ }
}
- for (String label: labelValues) {
- if (label == null) {
- throw new IllegalArgumentException("Label cannot be null.");
+ return labelsMiss(hashCode, arr, i, v1, v2, v3);
+ }
+
+ public Child labels(String v1, String v2, String v3, String v4) {
+ validateCount(4);
+
+ int hashCode = buildHashCode(buildHashCode(buildHashCode(buildHashCode(1, v1), v2), v3), v4);
+ final ChildEntry[] arr = children;
+ int arrLen = arr.length, i = hash(hashCode, arrLen);
+ for (ChildEntry ce; (ce = arr[i]) != null; i = nextIdx(i, arrLen)) {
+ if (ce.hashCode == hashCode) {
+ String[] ls = ce.labels;
+ if (ls[0].equals(v1) && ls[1].equals(v2) && ls[2].equals(v3) && ls[3].equals(v4)) {
+ return ce.child;
+ }
}
}
- List key = Arrays.asList(labelValues);
- Child c = children.get(key);
- if (c != null) {
- return c;
+ return labelsMiss(hashCode, arr, i, v1, v2, v3, v4);
+ }
+
+ private Child labelsMiss(int hashCode, ChildEntry[] arr, int pos, String... values) {
+ return updateChild(values, hashCode, true, arr, pos,
+ new ChildEntry(values, hashCode, newChild()));
+ }
+
+ /**
+ * Multi-purpose used for set, remove and get after not found (cache miss)
+ *
+ * @param getIfPresent if true, entry won't be updated if it already exists
+ * @param pos insertion position for new entry if known (get case), otherwise -1
+ * @param newEntry null for remove, otherwise new entry to add
+ * @return new/existing Child in case of get, prior Child in case of set/remove
+ */
+ @SuppressWarnings("unchecked")
+ private Child updateChild(String[] values, int hashCode, boolean getIfPresent,
+ ChildEntry[] arr, int pos, ChildEntry newEntry) {
+ // This loop is just for retries after CAS failures of the children field
+ while (true) {
+ final int arrLen = arr.length;
+ final ChildEntry[] newArr;
+ // This gets set to the entry we're replacing, if/when applicable
+ ChildEntry replacing = null;
+ // If pos >= 0, we already know where the new entry should go
+ if (pos == -1) {
+ // Scan the table looking for the a matching entry starting at
+ // the labels' hash position
+ pos = hash(hashCode, arrLen);
+ while (true) {
+ ChildEntry ce = arr[pos];
+ if (ce == null) {
+ // If we reach a null entry then the labels aren't present, and
+ // our current pos is the target location for inserting the new entry
+ if (newEntry == null) {
+ // Nothing to do in removal case
+ return null;
+ }
+ break;
+ }
+ if (ce.hashCode == hashCode && Arrays.equals(ce.labels, values)) {
+ // We found an entry matching the target labels
+ if (getIfPresent) {
+ return ce.child;
+ }
+ replacing = ce;
+ break;
+ }
+ pos = nextIdx(pos, arrLen); // wrap around
+ }
+ }
+ boolean resizeNeeded;
+ // If we reached here we'll be updating the table by either inserting
+ // replacing, or removing an entry
+ if (newEntry == null | replacing != null) {
+ // No need to increase the table capacity if removing or replacing
+ resizeNeeded = false;
+ } else {
+ // We are inserting a new entry so count the existing entries to determine
+ // how full the table is - we resize (double) it if greater than half full.
+ // This avoids having to separately track the count
+ int count = 1;
+ for (ChildEntry ce : arr) {
+ if (ce != null) {
+ count++;
+ }
+ }
+ resizeNeeded = count > arrLen / 2;
+ }
+ if (resizeNeeded) {
+ // Double the size of the table
+ final int newArrLen = arrLen * 2;
+ newArr = new ChildEntry[newArrLen];
+ // Insert our new entry first
+ newArr[hash(hashCode, newArrLen)] = newEntry;
+ // Then re-hash/insert the existing entries
+ for (ChildEntry ce : arr) {
+ // Exclude the entry we're replacing (if applicable - replacing will be non-null)
+ if (ce != null & ce != replacing) {
+ for (int j = hash(ce.hashCode, newArrLen);; j = nextIdx(j, newArrLen)) {
+ if (newArr[j] == null) {
+ newArr[j] = ce;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ // If we're not resizing the table, just make a straight copy
+ // and insert our new entry at the target position
+ newArr = Arrays.copyOf(arr, arrLen, ChildEntry[].class);
+ newArr[pos] = newEntry;
+ }
+ // Attempt to update the shared field atomically
+ if (UPDATER.compareAndSet(this, arr, newArr)) {
+ // Successful table modification
+ // Return the new entry in the get case, old entry in the remove/set cases
+ return getIfPresent ? newEntry.child
+ : (replacing != null ? replacing.child : null);
+ }
+ // Someone else changed the table, read it and start again
+ arr = children;
+ pos = -1;
}
- Child c2 = newChild();
- Child tmp = children.putIfAbsent(key, c2);
- return tmp == null ? c2 : tmp;
}
/**
@@ -84,7 +342,7 @@ public Child labels(String... labelValues) {
* Any references to the Child are invalidated.
*/
public void remove(String... labelValues) {
- children.remove(Arrays.asList(labelValues));
+ updateChild(labelValues, labelsHashCode(labelValues), false, children, -1, null);
initializeNoLabelsChild();
}
@@ -94,7 +352,7 @@ public void remove(String... labelValues) {
* Any references to any children are invalidated.
*/
public void clear() {
- children.clear();
+ UPDATER.set(this, EMPTY);
initializeNoLabelsChild();
}
@@ -103,8 +361,8 @@ public void clear() {
*/
protected void initializeNoLabelsChild() {
// Initialize metric if it has no labels.
- if (labelNames.size() == 0) {
- noLabelsChild = labels();
+ if (labelCount == 0) {
+ noLabelsChild = labels(new String[0]);
}
}
@@ -132,10 +390,10 @@ 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);
+ int hashCode = labelsHashCode(labelValues);
+ updateChild(labelValues, hashCode, false, children, -1,
+ new ChildEntry(labelValues, hashCode, child));
return (T)this;
}
@@ -165,6 +423,7 @@ protected SimpleCollector(Builder b) {
if (b.help.isEmpty()) throw new IllegalStateException("Help hasn't been set.");
help = b.help;
labelNames = Arrays.asList(b.labelNames);
+ labelCount = b.labelNames.length;
for (String n: labelNames) {
checkMetricLabelName(n);
diff --git a/simpleclient/src/main/java/io/prometheus/client/Summary.java b/simpleclient/src/main/java/io/prometheus/client/Summary.java
index a341f2515..1ac562e5d 100644
--- a/simpleclient/src/main/java/io/prometheus/client/Summary.java
+++ b/simpleclient/src/main/java/io/prometheus/client/Summary.java
@@ -275,7 +275,7 @@ private Child(List quantiles, long maxAgeSeconds, int ageBuckets) {
* Observe the given amount.
*/
public void observe(double amt) {
- count.add(1);
+ count.add(1.0);
sum.add(amt);
if (quantileValues != null) {
quantileValues.insert(amt);
@@ -346,8 +346,12 @@ public Child.Value get() {
@Override
public List collect() {
- List samples = new ArrayList();
- for(Map.Entry, Child> c: children.entrySet()) {
+ final Map.Entry, Child>[] children = children();
+ List samples = new ArrayList(children.length);
+ for(Map.Entry,Child> c : children) {
+ if (c == null) {
+ continue;
+ }
Child.Value v = c.getValue().get();
List labelNamesWithQuantile = new ArrayList(labelNames);
labelNamesWithQuantile.add("quantile");