Compare commits

...

6 Commits

Author SHA1 Message Date
zzz
2ffce5d002 Hook PromStat into StatManager 2025-05-06 08:37:38 -04:00
zzz
8c4fd32450 Add group name to all PromStats 2025-05-03 12:15:23 -04:00
zzz
d1b24274c7 label support in subclasses 2025-04-21 14:49:28 -04:00
zzz
4fd82a9fe9 make Counter and Gauge abstract 2025-04-21 10:25:27 -04:00
zzz
d28556e35c add partial label support (WIP) 2025-04-21 08:33:37 -04:00
zzz
b9eda05ac4 WIP: Draft: Prometheus-friendly stat variants
- gauge, counter, and info only, no buckets
- embed type and units in stats
- optimized for low memory/CPU usage, no doubles, no locking
- to be hooked in
2025-04-20 18:22:13 -04:00
22 changed files with 1266 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import net.i2p.stat.prometheus.PromStat;
import net.i2p.util.Log;
/**
@ -25,6 +26,12 @@ public class StatManager {
private final I2PAppContext _context;
private final Log _log;
/**
* stat name to PromStat
* @since 0.9.67
*/
private final ConcurrentHashMap<String, PromStat> _promStats;
/** stat name to FrequencyStat */
private final ConcurrentHashMap<String, FrequencyStat> _frequencyStats;
/** stat name to RateStat */
@ -57,6 +64,7 @@ public class StatManager {
public StatManager(I2PAppContext context) {
_context = context;
_log = context.logManager().getLog(getClass());
_promStats = new ConcurrentHashMap<String, PromStat>(32);
_frequencyStats = new ConcurrentHashMap<String,FrequencyStat>(8);
_rateStats = new ConcurrentHashMap<String,RateStat>(128);
String filter = getStatFilter();
@ -66,6 +74,7 @@ public class StatManager {
/** @since 0.8.8 */
public synchronized void shutdown() {
_promStats.clear();
_frequencyStats.clear();
_rateStats.clear();
}
@ -236,6 +245,70 @@ public class StatManager {
return _frequencyStats.containsKey(statName);
}
///// begin PromStats /////
/**
* Is the given stat a PromStat?
* @since 0.9.67
*/
public boolean isPromStat(String statName) {
return _promStats.containsKey(statName);
}
/**
* Create a PromStat.
* The stat is ONLY created if the stat.full property is true or we are not in the router context.
*
* @param name unique name of the statistic
* @param description simple description of the statistic
* @param group used to group statistics together
* @since 0.9.67
*/
public void createPromStat(PromStat ps) {
if (ignoreStat(ps.getName()))
return;
createRequiredPromStat(ps);
}
/**
* Create a PromStat.
* The stat is always created, independent of the stat.full setting or context.
*
* @param name unique name of the statistic
* @param description simple description of the statistic
* @param group used to group statistics together
* @since 0.9.67
*/
public void createRequiredPromStat(PromStat ps) {
_promStats.putIfAbsent(ps.getName(), ps);
}
/**
* Remove a PromStat.
* @since 0.9.67
*/
public void removePromStat(PromStat ps) {
removePromStat(ps.getName());
}
/**
* Remove a PromStat.
* @since 0.9.67
*/
public void removePromStat(String name) {
_promStats.remove(name);
}
/**
* Get a PromStat.
* @since 0.9.67
*/
public PromStat getPromStat(String name) {
return _promStats.get(name);
}
///// end PromStats /////
/**
* Group name (untranslated String) to a SortedSet of untranslated stat names.
* Map is unsorted.

View File

@ -0,0 +1,69 @@
package net.i2p.stat.prometheus;
/*
* Contains code adapted from Google Guava AtomicDouble:
*
* Written by Doug Lea and Martin Buchholz with assistance from
* members of JCP JSR-166 Expert Group and released to the public
* domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Float.*;
/**
* ref: https://stackoverflow.com/questions/5505460/java-is-there-no-atomicfloat-or-atomicfloat
* with code adapted from Google Guava AtomicDouble
* ref: https://github.com/google/guava/blob/master/guava/src/com/google/common/util/concurrent/AtomicDouble.java
*
* @since 0.9.67
*/
public class AtomicFloat extends Number {
private final AtomicInteger bits;
public AtomicFloat() {
bits = new AtomicInteger();
}
public AtomicFloat(float initialValue) {
bits = new AtomicInteger(floatToIntBits(initialValue));
}
public final void set(float newValue) {
bits.set(floatToIntBits(newValue));
}
public final float get() {
return intBitsToFloat(bits.get());
}
public final float incrementAndGet() {
return addAndGet(1.0f);
}
/**
* Code adapted from Google Guava AtomicDouble
*/
public final float addAndGet(float value) {
while (true) {
int current = bits.get();
float currentVal = intBitsToFloat(current);
float nextVal = currentVal + value;
int next = floatToRawIntBits(nextVal);
if (bits.compareAndSet(current, next))
return nextVal;
}
}
public int intValue() { return (int) get(); }
public long longValue() { return (long) get(); }
public float floatValue() { return get(); }
public double doubleValue() { return get(); }
@Override
public String toString() { return Double.toString(get()); }
}

View File

@ -0,0 +1,49 @@
package net.i2p.stat.prometheus;
/**
* A simple Prometheus-style counter, with overflow protection.
*
* @since 0.9.67
*/
public abstract class Counter extends PromStat {
public Counter(String name, String desc, String group, Unit unit) {
super(name, desc, group, Type.COUNTER, unit);
}
public Counter(String name, String desc, String group, Unit unit, String label, String... values) {
super(name, desc, group, Type.COUNTER, unit, label, values);
}
public abstract void increment();
public abstract void add(int value);
public abstract void add(long value);
public abstract void add(float value);
public abstract void add(double value);
public abstract void increment(int labelIndex);
public abstract void add(int labelIndex, int value);
public abstract void add(int labelIndex, long value);
public abstract void add(int labelIndex, float value);
public abstract void add(int labelIndex, double value);
static void throwIfNegative(int value) {
if (value < 0)
throw new IllegalArgumentException("value cannot be negative");
}
static void throwIfNegative(long value) {
if (value < 0)
throw new IllegalArgumentException("value cannot be negative");
}
static void throwIfNegative(float value) {
if (value < 0)
throw new IllegalArgumentException("value cannot be negative");
}
static void throwIfNegative(double value) {
if (value < 0)
throw new IllegalArgumentException("value cannot be negative");
}
}

View File

@ -0,0 +1,98 @@
package net.i2p.stat.prometheus;
/**
* A simple Prometheus-style counter.
* No overflow protection, shouldn't happen.
*
* @since 0.9.67
*/
public class FCounter extends Counter implements FloatConsumer, FloatSupplier {
private final AtomicFloat[] a;
public FCounter(String name, String desc, String group, Unit unit) {
this(name, desc, group, unit, null);
}
public FCounter(String name, String desc, String group, Unit unit, String label, String... values) {
super(name, desc, group, unit, label, values);
a = new AtomicFloat[label != null ? values.length : 1];
for (int i = 0; i < a.length; i++) {
a[i] = new AtomicFloat();
}
}
public void increment() {
increment(0);
}
public void add(int value) {
add(0, value);
}
public void add(long value) {
add(0, value);
}
public void add(float value) {
add(0, value);
}
public void add(double value) {
add(0, value);
}
public float getValue() {
return getValue(0);
}
public double getDoubleValue() {
return getDoubleValue(0);
}
public void increment(int idx) {
a[idx].incrementAndGet();
}
public void add(int idx, int value) {
throwIfNegative(value);
a[idx].addAndGet(value);
}
public void add(int idx, long value) {
throwIfNegative(value);
a[idx].addAndGet((float) value);
}
public void add(int idx, float value) {
throwIfNegative(value);
a[idx].addAndGet(value);
}
public void add(int idx, double value) {
throwIfNegative(value);
a[idx].addAndGet((float) value);
}
public float getValue(int idx) {
return a[idx].get();
}
public double getDoubleValue(int idx) {
return a[idx].get();
}
/**
* Supplier interface
*/
public float getAsFloat() {
return a[0].get();
}
/**
* Consumer interface
*/
public void accept(float value) {
a[0].addAndGet(value);
}
}

View File

@ -0,0 +1,103 @@
package net.i2p.stat.prometheus;
/**
* A simple Prometheus-style gauge
*
* @since 0.9.67
*/
public class FGauge extends Gauge implements FloatConsumer, FloatSupplier {
private final AtomicFloat[] a;
private final FloatSupplier s;
public FGauge(String name, String desc, String group, Unit unit) {
this(name, desc, group, unit, null);
}
public FGauge(String name, String desc, String group, Unit unit, FloatSupplier supplier) {
this(name, desc, group, unit, supplier, (String) null);
}
public FGauge(String name, String desc, String group, Unit unit, FloatSupplier supplier, String label, String... values) {
super(name, desc, group, unit);
a = new AtomicFloat[label != null ? values.length : 1];
for (int i = 0; i < a.length; i++) {
a[i] = new AtomicFloat();
}
s = supplier;
}
public void setValue(int value) {
setValue(0, value);
}
public void setValue(long value) {
setValue(0, value);
}
public void setValue(float value) {
setValue(0, value);
}
public void setValue(double value) {
setValue(0, value);
}
public float getValue() {
return getValue(0);
}
public double getDoubleValue() {
return getDoubleValue(0);
}
public void setValue(int idx, int value) {
a[idx].set(value);
}
public void setValue(int idx, long value) {
a[idx].set((float) value);
}
public void setValue(int idx, float value) {
a[idx].set(value);
}
public void setValue(int idx, double value) {
a[idx].set((float) value);
}
public float getValue(int idx) {
return a[idx].get();
}
public double getDoubleValue(int idx) {
return a[idx].get();
}
/**
* For StatManager
*/
@Override
public void coalesceStats() {
if (s != null)
a[0].set(s.getAsFloat());
}
/**
* Supplier interface
*/
public float getAsFloat() {
return a[0].get();
}
/**
* Consumer interface
*
* Adds the value
*/
public void accept(float value) {
a[0].set(value);
}
}

View File

@ -0,0 +1,10 @@
package net.i2p.stat.prometheus;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface FloatConsumer {
public void accept(float value);
}

View File

@ -0,0 +1,10 @@
package net.i2p.stat.prometheus;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface FloatSupplier {
public float getAsFloat();
}

View File

@ -0,0 +1,27 @@
package net.i2p.stat.prometheus;
/**
* A simple Prometheus-style gauge
*
* @since 0.9.67
*/
public abstract class Gauge extends PromStat {
public Gauge(String name, String desc, String group, Unit unit) {
super(name, desc, group, Type.GAUGE, unit);
}
public Gauge(String name, String desc, String group, Unit unit, String label, String... values) {
super(name, desc, group, Type.GAUGE, unit, label, values);
}
public abstract void setValue(int value);
public abstract void setValue(long value);
public abstract void setValue(float value);
public abstract void setValue(double value);
public abstract void setValue(int labelIndex, int value);
public abstract void setValue(int labelIndex, long value);
public abstract void setValue(int labelIndex, float value);
public abstract void setValue(int labelIndex, double value);
}

View File

@ -0,0 +1,122 @@
package net.i2p.stat.prometheus;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* A simple Prometheus-style counter, with overflow protection.
*
* @since 0.9.67
*/
public class ICounter extends Counter implements IntConsumer, IntSupplier {
private final AtomicIntegerArray a;
private final AtomicInteger c;
public ICounter(String name, String desc, String group, Unit unit) {
this(name, desc, group, unit, null);
}
public ICounter(String name, String desc, String group, Unit unit, String label, String... values) {
super(name, desc, group, unit, label, values);
if (label != null) {
a = new AtomicIntegerArray(values.length);
c = null;
} else {
a = null;
c = new AtomicInteger();
}
}
/**
* Resets to 0 on overflow.
*/
public void increment() {
int v = c.incrementAndGet();
if (v < 0)
c.set(0);
}
/**
* Resets to 0 on overflow.
*/
public void add(int value) {
throwIfNegative(value);
int v = c.addAndGet(value);
if (v < 0)
c.set(0);
}
public void add(long value) {
add((int) value);
}
public void add(float value) {
add((int) value);
}
public void add(double value) {
add((int) value);
}
public int getValue() {
return c.get();
}
public double getDoubleValue() {
return c.get();
}
/**
* Resets to 0 on overflow.
*/
public void increment(int idx) {
int v = a.incrementAndGet(idx);
if (v < 0)
a.set(idx, 0);
}
/**
* Resets to 0 on overflow.
*/
public void add(int idx, int value) {
throwIfNegative(value);
int v = a.addAndGet(idx, value);
if (v < 0)
a.set(idx, 0);
}
public void add(int idx, long value) {
add(idx, (int) value);
}
public void add(int idx, float value) {
add(idx, (int) value);
}
public void add(int idx, double value) {
add(idx, (int) value);
}
public int getValue(int idx) {
return a.get(idx);
}
public double getDoubleValue(int idx) {
return a.get(idx);
}
/**
* Supplier interface
*/
public int getAsInt() {
return c.get();
}
/**
* Consumer interface
*/
public void accept(int value) {
c.addAndGet(value);
}
}

View File

@ -0,0 +1,110 @@
package net.i2p.stat.prometheus;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* A simple Prometheus-style gauge
*
* @since 0.9.67
*/
public class IGauge extends Gauge implements IntConsumer, IntSupplier {
private final AtomicIntegerArray a;
private final AtomicInteger c;
private final IntSupplier s;
public IGauge(String name, String desc, String group, Unit unit) {
this(name, desc, group, unit, null);
}
public IGauge(String name, String desc, String group, Unit unit, IntSupplier supplier) {
this(name, desc, group, unit, supplier, null);
}
public IGauge(String name, String desc, String group, Unit unit, IntSupplier supplier, String label, String... values) {
super(name, desc, group, unit, label, values);
if (label != null) {
a = new AtomicIntegerArray(values.length);
c = null;
} else {
a = null;
c = new AtomicInteger();
}
s = supplier;
}
public void setValue(int value) {
c.set(value);
}
public void setValue(long value) {
c.set((int) value);
}
public void setValue(float value) {
c.set((int) value);
}
public void setValue(double value) {
c.set((int) value);
}
public int getValue() {
return c.get();
}
public double getDoubleValue() {
return c.get();
}
public void setValue(int idx, int value) {
a.set(idx, value);
}
public void setValue(int idx, long value) {
a.set(idx, (int) value);
}
public void setValue(int idx, float value) {
a.set(idx, (int) value);
}
public void setValue(int idx, double value) {
a.set(idx, (int) value);
}
public long getValue(int idx) {
return a.get(idx);
}
public double getDoubleValue(int idx) {
return a.get(idx);
}
/**
* For StatManager
*/
@Override
public void coalesceStats() {
if (s != null)
c.set(s.getAsInt());
}
/**
* Supplier interface
*/
public int getAsInt() {
return c.get();
}
/**
* Consumer interface
*
* Adds the value
*/
public void accept(int value) {
c.set(value);
}
}

View File

@ -0,0 +1,76 @@
package net.i2p.stat.prometheus;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A simple Prometheus-style info. Value is always 1.
*
* @since 0.9.67
*/
public class Info extends PromStat implements IntSupplier, MapSupplier, MapConsumer {
private final Map<String, String> labels;
/**
* Map returned by getValues() will be a ConcurrentHashMap
*/
public Info(String name, String desc, String group) {
this(name, desc, group, null);
}
/**
* @param labels use caution, use a ConcurrentHashMap if setValue() or accept()
* will be called later to add label names or change the label values
*/
public Info(String name, String desc, String group, Map<String, String> labels) {
super(name, desc, group, Type.GAUGE, Unit.NONE);
this.labels = (labels != null) ? labels : new ConcurrentHashMap<String, String>(4);
}
public int getValue() {
return 1;
}
public double getDoubleValue() {
return 1.0d;
}
/**
* @return not a copy
*/
public Map<String, String> getLabels() {
return labels;
}
public void setLabel(String labelName, String labelValue) {
labels.put(labelName, labelValue);
}
public void removeLabel(String labelName) {
labels.remove(labelName);
}
/**
* Supplier interface
*/
public int getAsInt() {
return 1;
}
/**
* Supplier interface
*/
public Map<String, String> getAsMap() {
return labels;
}
/**
* Consumer interface
*/
public void accept(Map<String, String> labels) {
this.labels.putAll(labels);
}
}

View File

@ -0,0 +1,10 @@
package net.i2p.stat.prometheus;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface IntConsumer {
public void accept(int value);
}

View File

@ -0,0 +1,10 @@
package net.i2p.stat.prometheus;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface IntSupplier {
public int getAsInt();
}

View File

@ -0,0 +1,109 @@
package net.i2p.stat.prometheus;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
/**
* A simple Prometheus-style counter.
* No overflow protection, shouldn't happen.
*
* @since 0.9.67
*/
public class LCounter extends Counter implements LongConsumer, LongSupplier {
private final AtomicLongArray a;
private final AtomicLong c;
public LCounter(String name, String desc, String group, Unit unit) {
this(name, desc, group, unit, null);
}
public LCounter(String name, String desc, String group, Unit unit, String label, String... values) {
super(name, desc, group, unit, label, values);
if (label != null) {
a = new AtomicLongArray(values.length);
c = null;
} else {
a = null;
c = new AtomicLong();
}
}
public void increment() {
c.incrementAndGet();
}
public void add(int value) {
throwIfNegative(value);
c.addAndGet(value);
}
public void add(long value) {
throwIfNegative(value);
c.addAndGet(value);
}
public void add(float value) {
throwIfNegative(value);
c.addAndGet((long) value);
}
public void add(double value) {
throwIfNegative(value);
c.addAndGet((long) value);
}
public long getValue() {
return c.get();
}
public double getDoubleValue() {
return c.get();
}
public void increment(int idx) {
a.incrementAndGet(idx);
}
public void add(int idx, int value) {
throwIfNegative(value);
a.addAndGet(idx, value);
}
public void add(int idx, long value) {
throwIfNegative(value);
a.addAndGet(idx, value);
}
public void add(int idx, float value) {
throwIfNegative(value);
a.addAndGet(idx, (long) value);
}
public void add(int idx, double value) {
throwIfNegative(value);
a.addAndGet(idx, (long) value);
}
public long getValue(int idx) {
return a.get(idx);
}
public double getDoubleValue(int idx) {
return a.get(idx);
}
/**
* Supplier interface
*/
public long getAsLong() {
return c.get();
}
/**
* Consumer interface
*/
public void accept(long value) {
c.addAndGet(value);
}
}

View File

@ -0,0 +1,109 @@
package net.i2p.stat.prometheus;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
/**
* A simple Prometheus-style gauge
*
* @since 0.9.67
*/
public class LGauge extends Gauge implements LongConsumer, LongSupplier {
private final AtomicLongArray a;
private final AtomicLong c;
private final LongSupplier s;
public LGauge(String name, String desc, String group, Unit unit) {
this(name, desc, group, unit, null);
}
public LGauge(String name, String desc, String group, Unit unit, LongSupplier supplier) {
this(name, desc, group, unit, supplier, null);
}
public LGauge(String name, String desc, String group, Unit unit, LongSupplier supplier, String label, String... values) {
super(name, desc, group, unit, label, values);
if (label != null) {
a = new AtomicLongArray(values.length);
c = null;
} else {
a = null;
c = new AtomicLong();
}
s = supplier;
}
public void setValue(int value) {
c.set(value);
}
public void setValue(long value) {
c.set(value);
}
public void setValue(float value) {
c.set((long) value);
}
public void setValue(double value) {
c.set((long) value);
}
public long getValue() {
return c.get();
}
public double getDoubleValue() {
return c.get();
}
public void setValue(int idx, int value) {
a.set(idx, value);
}
public void setValue(int idx, long value) {
a.set(idx, value);
}
public void setValue(int idx, float value) {
a.set(idx, (long) value);
}
public void setValue(int idx, double value) {
a.set(idx, (long) value);
}
public long getValue(int idx) {
return a.get(idx);
}
public double getDoubleValue(int idx) {
return a.get(idx);
}
/**
* For StatManager
*/
@Override
public void coalesceStats() {
if (s != null)
c.set(s.getAsLong());
}
/**
* Supplier interface
*/
public long getAsLong() {
return c.get();
}
/**
* Consumer interface
*
* Adds the value
*/
public void accept(long value) {
c.set(value);
}
}

View File

@ -0,0 +1,10 @@
package net.i2p.stat.prometheus;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface LongConsumer {
public void accept(long value);
}

View File

@ -0,0 +1,10 @@
package net.i2p.stat.prometheus;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface LongSupplier {
public long getAsLong();
}

View File

@ -0,0 +1,12 @@
package net.i2p.stat.prometheus;
import java.util.Map;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface MapConsumer {
public void accept(Map<String, String> values);
}

View File

@ -0,0 +1,12 @@
package net.i2p.stat.prometheus;
import java.util.Map;
/**
* Until we set a min SDK in Android that has java.util.function
*
* @since 0.9.67
*/
public interface MapSupplier {
public Map<String, String> getAsMap();
}

View File

@ -0,0 +1,190 @@
package net.i2p.stat.prometheus;
/**
* A simple Prometheus-style metric.
*
* Units are stored in the units specified,
* and scaled in getScaledValue().
*
* These stats are not persisted by StatManager.
*
* @since 0.9.67
*/
public abstract class PromStat {
private final String desc;
private final String name;
private final String promName;
private final String groupName;
private final String labelName;
private final String[] labelValues;
private final Type type;
private final Unit unit;
/*
* No label
*
* @param name [a-zA-Z0-9_.] only. '.' will be replaced with '_' for getPromName()
* @param group use same group names as RateStat and FrequencyStat
*/
public PromStat(String name, String description, String group, Type type, Unit unit) {
this(name, description, group, type, unit, null);
}
/*
* With one label and one or more values
*
* @param name [a-zA-Z0-9_.] only. '.' will be replaced with '_' for getPromName()
* @param group use same group names as RateStat and FrequencyStat
* @param label [a-zA-Z0-9_.] only. '.' will be replaced with '_' for getLabelName()
* for example "dir"
* @param values for example "in", "out"
*/
public PromStat(String name, String description, String group, Type type, Unit unit, String label, String... values) {
this.name = name;
desc = description;
groupName = group;
this.type = type;
this.unit = unit;
String p = fixup(name);
if (type != Type.INFO)
p += '_' + unit.getName();
if (type == Type.COUNTER)
p += "_total";
promName = p;
if (label != null) {
labelName = fixup(label);
if (values == null || values.length == 0)
throw new IllegalArgumentException("must have at least one label value");
labelValues = values;
} else {
labelName = null;
labelValues = null;
}
}
static String fixup(String name) {
String p = name.replace(".", "_");
if (p.replaceAll("[a-zA-Z0-9_]", "").length() != 0)
throw new IllegalArgumentException("invalid chars in name or label " + name);
return p;
}
public String getName() {
return name;
}
public String getPromName() {
return promName;
}
/*
* @return same group names as RateStat and FrequencyStat
*/
public String getGroupName() {
return groupName;
}
/**
* @return may be null
*/
public String getLabelName() {
return labelName;
}
/**
* @return may be null
*/
public String[] getLabelValues() {
return labelValues;
}
/**
* @return may be null
*/
public String getLabelValue(int labelValueIndex) {
if (labelValues == null || labelValueIndex < 0 || labelValueIndex >= labelValues.length)
return null;
return labelValues[labelValueIndex];
}
/**
* @return number of label values
*/
public int getLabelValueCount() {
return labelValues != null ? labelValues.length : 0;
}
public String getDescription() {
return desc;
}
public Type getType() { return type; }
public Unit getUnit() { return unit; }
/**
* No label.
* In units specified
*/
public abstract double getDoubleValue();
/**
* This implementation requires labelValueIndex to be 0,
* and returns getDoubleValue().
* Classes supporting labels must override.
*
* @return getDoubleValue()
*/
public double getDoubleValue(int labelValueIndex) {
if (labelValueIndex != 0)
throw new IllegalArgumentException("Index must be 0");
return getDoubleValue();
}
/**
* No label.
* Scaled to what Prometheus expects,
* seconds, bytes, or count.
*/
public double getScaledValue() {
double rv = getDoubleValue();
switch (unit) {
case KBYTES:
case KBPS:
rv *= 1000f;
break;
case MS:
rv /= 1000f;
break;
}
return rv;
}
/**
* With label.
* Scaled to what Prometheus expects,
* seconds, bytes, or count.
*/
public double getScaledValue(int labelValueIndex) {
double rv = getDoubleValue(labelValueIndex);
switch (unit) {
case KBYTES:
case KBPS:
rv *= 1000f;
break;
case MS:
rv /= 1000f;
break;
}
return rv;
}
/**
* For StatManager.
* Does nothing (for counters), extend for guages if necessary.
*/
public void coalesceStats() {}
}

View File

@ -0,0 +1,21 @@
package net.i2p.stat.prometheus;
/**
* Prometheus-style types
*
* @since 0.9.67
*/
public enum Type {
COUNTER("counter"),
GAUGE("gauge"),
INFO("info");
private final String name;
Type(String name) {
this.name = name;
}
public String getName() { return name; }
}

View File

@ -0,0 +1,26 @@
package net.i2p.stat.prometheus;
/**
* Prometheus-style units
*
* @since 0.9.67
*/
public enum Unit {
BYTES("bytes"),
KBYTES("bytes"),
BPS("bytes"),
KBPS("bytes"),
SECONDS("seconds"),
MS("seconds"),
EVENTS("count"),
NONE("");
private final String name;
Unit(String name) {
this.name = name;
}
public String getName() { return name; }
}