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