MetricUtils.java
package no.nav.data.common.utils;
import com.github.benmanes.caffeine.cache.Cache;
import io.prometheus.client.Counter;
import io.prometheus.client.Gauge;
import io.prometheus.client.SimpleCollector;
import io.prometheus.client.Summary;
import io.prometheus.client.cache.caffeine.CacheMetricsCollector;
import lombok.extern.slf4j.Slf4j;
import no.nav.data.common.exceptions.TechnicalException;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static java.util.Objects.requireNonNull;
/**
* Helper to create metrics - avoid registring metrics multiple times during test - instantiate labels up front to initialize them in prometheus/grafana
*/
@Slf4j
public final class MetricUtils {
public static final String DB_QUERY_TIMED = "db_query_timed";
public static final String QUERY = "query";
private static final Map<String, SimpleCollector<?>> collectors = new ConcurrentHashMap<>();
private static final CacheMetricsCollector cacheCollector = new CacheMetricsCollector().register();
private MetricUtils() {
}
public static CounterBuilder counter() {
return new CounterBuilder();
}
public static SummaryBuilder summary() {
return new SummaryBuilder();
}
public static GaugeBuilder gauge() {
return new GaugeBuilder();
}
public static <C extends Cache<K, V>, K, V> C register(String name, C cache) {
cacheCollector.addCache(name, cache);
return cache;
}
@SuppressWarnings("unchecked")
private static <T extends SimpleCollector<?>> T register(T collector, List<String[]> labels) {
try {
Field nameField = ReflectionUtils.findField(SimpleCollector.class, "fullname");
requireNonNull(nameField).setAccessible(true);
String name = ((String) nameField.get(collector));
SimpleCollector<?> registeredCollector = collectors.computeIfAbsent(name, mapName -> init(collector, labels));
if (registeredCollector.getClass().isAssignableFrom(collector.getClass())) {
return (T) registeredCollector;
} else {
throw new TechnicalException("Collector allready assigned to different type " + collector);
}
} catch (Exception e) {
throw new TechnicalException("failed to init collector", e);
}
}
private static <T extends SimpleCollector<?>> T init(T collector, List<String[]> labels) {
// Initialize labels
for (String[] label : labels) {
collector.labels(label);
}
return collector.register();
}
public static class CounterBuilder extends Counter.Builder {
private final List<String[]> labels = new ArrayList<>();
@Override
public Counter register() {
return MetricUtils.register(super.create(), labels);
}
public CounterBuilder labels(String... labels) {
this.labels.add(labels);
return this;
}
}
public static class SummaryBuilder extends Summary.Builder {
private List<String[]> labels = new ArrayList<>();
@Override
public Summary register() {
return MetricUtils.register(super.create(), labels);
}
public SummaryBuilder labels(String... labels) {
this.labels.add(labels);
return this;
}
public SummaryBuilder labels(List<String[]> labels) {
this.labels = labels;
return this;
}
}
public static class GaugeBuilder extends Gauge.Builder {
private List<String[]> labels = new ArrayList<>();
@Override
public Gauge register() {
return MetricUtils.register(super.create(), labels);
}
public GaugeBuilder labels(String... labels) {
this.labels.add(labels);
return this;
}
public GaugeBuilder labels(List<String[]> labels) {
this.labels = labels;
return this;
}
}
}