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