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              new CounterSnapshot.CounterDataPointSnapshot(
051                  value, makeLabels(labelValues), null, 0L));
052        });
053    return new CounterSnapshot(metadata, dataPoints);
054  }
055
056  /**
057   * @deprecated Use {@link #getMetricFamilyDescriptor()} instead.
058   */
059  @Override
060  @Deprecated
061  @SuppressWarnings("InlineMeSuggester")
062  public MetricType getMetricType() {
063    return MetricType.COUNTER;
064  }
065
066  public static Builder builder() {
067    return new Builder(PrometheusProperties.get());
068  }
069
070  public static Builder builder(PrometheusProperties properties) {
071    return new Builder(properties);
072  }
073
074  public static class Builder
075      extends CallbackMetric.Builder<CounterWithCallback.Builder, CounterWithCallback> {
076
077    @Nullable private Consumer<Callback> callback;
078
079    public Builder callback(Consumer<Callback> callback) {
080      this.callback = callback;
081      return self();
082    }
083
084    private Builder(PrometheusProperties properties) {
085      super(Collections.emptyList(), properties);
086    }
087
088    /**
089     * The {@code _total} suffix will automatically be appended if it's missing.
090     *
091     * <pre>{@code
092     * CounterWithCallback c1 = CounterWithCallback.builder()
093     *     .name("events_total")
094     *     .build();
095     * CounterWithCallback c2 = CounterWithCallback.builder()
096     *     .name("events")
097     *     .build();
098     * }</pre>
099     *
100     * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in
101     * Prometheus.
102     *
103     * <p>Throws an {@link IllegalArgumentException} if {@link
104     * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String)
105     * MetricMetadata.isValidMetricName(name)} is {@code false}.
106     */
107    @Override
108    public Builder name(String name) {
109      return super.name(Counter.stripTotalSuffix(name));
110    }
111
112    @Override
113    public CounterWithCallback build() {
114      return new CounterWithCallback(this);
115    }
116
117    @Override
118    protected Builder self() {
119      return this;
120    }
121  }
122}