001package io.prometheus.metrics.core.metrics;
002
003import io.prometheus.metrics.config.PrometheusProperties;
004import io.prometheus.metrics.model.snapshots.CounterSnapshot;
005
006import java.util.ArrayList;
007import java.util.Collections;
008import java.util.List;
009import java.util.function.Consumer;
010
011/**
012 * Example:
013 * <pre>{@code
014 * ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
015 *
016 * CounterWithCallback.builder()
017 *         .name("classes_loaded_total")
018 *         .help("The total number of classes that have been loaded since the JVM has started execution")
019 *         .callback(callback -> callback.call(classLoadingMXBean.getLoadedClassCount()))
020 *         .register();
021 * }</pre>
022 */
023public class CounterWithCallback extends CallbackMetric {
024
025    @FunctionalInterface
026    public interface Callback {
027        void call(double value, String... labelValues);
028    }
029
030    private final Consumer<Callback> callback;
031
032    private CounterWithCallback(Builder builder) {
033        super(builder);
034        this.callback = builder.callback;
035        if (callback == null) {
036            throw new IllegalArgumentException("callback cannot be null");
037        }
038    }
039
040    @Override
041    public CounterSnapshot collect() {
042        List<CounterSnapshot.CounterDataPointSnapshot> dataPoints = new ArrayList<>();
043        callback.accept((value, labelValues) -> {
044            dataPoints.add(new CounterSnapshot.CounterDataPointSnapshot(value, makeLabels(labelValues), null, 0L));
045        });
046        return new CounterSnapshot(getMetadata(), dataPoints);
047    }
048
049    public static Builder builder() {
050        return new Builder(PrometheusProperties.get());
051    }
052
053    public static Builder builder(PrometheusProperties properties) {
054        return new Builder(properties);
055    }
056
057    public static class Builder extends CallbackMetric.Builder<CounterWithCallback.Builder, CounterWithCallback> {
058
059        private Consumer<Callback> callback;
060
061        public Builder callback(Consumer<Callback> callback) {
062            this.callback = callback;
063            return self();
064        }
065
066        private Builder(PrometheusProperties properties) {
067            super(Collections.emptyList(), properties);
068        }
069
070        /**
071         * The {@code _total} suffix will automatically be appended if it's missing.
072         * <pre>{@code
073         * CounterWithCallback c1 = CounterWithCallback.builder()
074         *     .name("events_total")
075         *     .build();
076         * CounterWithCallback c2 = CounterWithCallback.builder()
077         *     .name("events")
078         *     .build();
079         * }</pre>
080         * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in Prometheus.
081         * <p>
082         * Throws an {@link IllegalArgumentException} if
083         * {@link io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) MetricMetadata.isValidMetricName(name)}
084         * is {@code false}.
085         */
086        @Override
087        public Builder name(String name) {
088            return super.name(Counter.stripTotalSuffix(name));
089        }
090
091        @Override
092        public CounterWithCallback build() {
093            return new CounterWithCallback(this);
094        }
095
096        @Override
097        protected Builder self() {
098            return this;
099        }
100    }
101}