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}