001package io.prometheus.metrics.core.metrics;
002
003import io.prometheus.metrics.config.PrometheusProperties;
004import io.prometheus.metrics.model.snapshots.CounterSnapshot;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.List;
008import java.util.function.Consumer;
009
010/**
011 * Example:
012 *
013 * <pre>{@code
014 * ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
015 *
016 * CounterWithCallback.builder()
017 *         .name("classes_loaded_total")
018 *         .help("The total number of classes 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(
044        (value, labelValues) -> {
045          dataPoints.add(
046              new CounterSnapshot.CounterDataPointSnapshot(
047                  value, makeLabels(labelValues), null, 0L));
048        });
049    return new CounterSnapshot(getMetadata(), dataPoints);
050  }
051
052  public static Builder builder() {
053    return new Builder(PrometheusProperties.get());
054  }
055
056  public static Builder builder(PrometheusProperties properties) {
057    return new Builder(properties);
058  }
059
060  public static class Builder
061      extends CallbackMetric.Builder<CounterWithCallback.Builder, CounterWithCallback> {
062
063    private Consumer<Callback> callback;
064
065    public Builder callback(Consumer<Callback> callback) {
066      this.callback = callback;
067      return self();
068    }
069
070    private Builder(PrometheusProperties properties) {
071      super(Collections.emptyList(), properties);
072    }
073
074    /**
075     * The {@code _total} suffix will automatically be appended if it's missing.
076     *
077     * <pre>{@code
078     * CounterWithCallback c1 = CounterWithCallback.builder()
079     *     .name("events_total")
080     *     .build();
081     * CounterWithCallback c2 = CounterWithCallback.builder()
082     *     .name("events")
083     *     .build();
084     * }</pre>
085     *
086     * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in
087     * Prometheus.
088     *
089     * <p>Throws an {@link IllegalArgumentException} if {@link
090     * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String)
091     * MetricMetadata.isValidMetricName(name)} is {@code false}.
092     */
093    @Override
094    public Builder name(String name) {
095      return super.name(Counter.stripTotalSuffix(name));
096    }
097
098    @Override
099    public CounterWithCallback build() {
100      return new CounterWithCallback(this);
101    }
102
103    @Override
104    protected Builder self() {
105      return this;
106    }
107  }
108}