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