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;
        }
    }
}