001package io.prometheus.metrics.instrumentation.jvm;
002
003import io.prometheus.metrics.annotations.StableApi;
004import io.prometheus.metrics.config.PrometheusProperties;
005import io.prometheus.metrics.core.metrics.GaugeWithCallback;
006import io.prometheus.metrics.model.registry.PrometheusRegistry;
007import io.prometheus.metrics.model.snapshots.Labels;
008import io.prometheus.metrics.model.snapshots.Unit;
009import java.lang.management.ManagementFactory;
010import java.lang.management.MemoryMXBean;
011import java.lang.management.MemoryPoolMXBean;
012import java.lang.management.MemoryUsage;
013import java.util.List;
014import java.util.function.Consumer;
015import java.util.function.Function;
016import javax.annotation.Nullable;
017
018/**
019 * JVM memory metrics. The {@link JvmMemoryMetrics} are registered as part of the {@link JvmMetrics}
020 * like this:
021 *
022 * <pre>{@code
023 * JvmMetrics.builder().register();
024 * }</pre>
025 *
026 * However, if you want only the {@link JvmMemoryMetrics} you can also register them directly:
027 *
028 * <pre>{@code
029 * JvmMemoryMetrics.builder().register();
030 * }</pre>
031 *
032 * Example metrics being exported:
033 *
034 * <pre>
035 * # HELP jvm_memory_committed_bytes Committed (bytes) of a given JVM memory area.
036 * # TYPE jvm_memory_committed_bytes gauge
037 * jvm_memory_committed_bytes{area="heap"} 4.98597888E8
038 * jvm_memory_committed_bytes{area="nonheap"} 1.1993088E7
039 * # HELP jvm_memory_init_bytes Initial bytes of a given JVM memory area.
040 * # TYPE jvm_memory_init_bytes gauge
041 * jvm_memory_init_bytes{area="heap"} 5.20093696E8
042 * jvm_memory_init_bytes{area="nonheap"} 2555904.0
043 * # HELP jvm_memory_max_bytes Max (bytes) of a given JVM memory area.
044 * # TYPE jvm_memory_max_bytes gauge
045 * jvm_memory_max_bytes{area="heap"} 7.38983936E9
046 * jvm_memory_max_bytes{area="nonheap"} -1.0
047 * # HELP jvm_memory_objects_pending_finalization The number of objects waiting in the finalizer queue.
048 * # TYPE jvm_memory_objects_pending_finalization gauge
049 * jvm_memory_objects_pending_finalization 0.0
050 * # HELP jvm_memory_pool_collection_committed_bytes Committed after last collection bytes of a given JVM memory pool.
051 * # TYPE jvm_memory_pool_collection_committed_bytes gauge
052 * jvm_memory_pool_collection_committed_bytes{pool="PS Eden Space"} 1.30023424E8
053 * jvm_memory_pool_collection_committed_bytes{pool="PS Old Gen"} 3.47078656E8
054 * jvm_memory_pool_collection_committed_bytes{pool="PS Survivor Space"} 2.1495808E7
055 * # HELP jvm_memory_pool_collection_init_bytes Initial after last collection bytes of a given JVM memory pool.
056 * # TYPE jvm_memory_pool_collection_init_bytes gauge
057 * jvm_memory_pool_collection_init_bytes{pool="PS Eden Space"} 1.30023424E8
058 * jvm_memory_pool_collection_init_bytes{pool="PS Old Gen"} 3.47078656E8
059 * jvm_memory_pool_collection_init_bytes{pool="PS Survivor Space"} 2.1495808E7
060 * # HELP jvm_memory_pool_collection_max_bytes Max bytes after last collection of a given JVM memory pool.
061 * # TYPE jvm_memory_pool_collection_max_bytes gauge
062 * jvm_memory_pool_collection_max_bytes{pool="PS Eden Space"} 2.727870464E9
063 * jvm_memory_pool_collection_max_bytes{pool="PS Old Gen"} 5.542248448E9
064 * jvm_memory_pool_collection_max_bytes{pool="PS Survivor Space"} 2.1495808E7
065 * # HELP jvm_memory_pool_collection_used_bytes Used bytes after last collection of a given JVM memory pool.
066 * # TYPE jvm_memory_pool_collection_used_bytes gauge
067 * jvm_memory_pool_collection_used_bytes{pool="PS Eden Space"} 0.0
068 * jvm_memory_pool_collection_used_bytes{pool="PS Old Gen"} 1249696.0
069 * jvm_memory_pool_collection_used_bytes{pool="PS Survivor Space"} 0.0
070 * # HELP jvm_memory_pool_committed_bytes Committed bytes of a given JVM memory pool.
071 * # TYPE jvm_memory_pool_committed_bytes gauge
072 * jvm_memory_pool_committed_bytes{pool="Code Cache"} 4128768.0
073 * jvm_memory_pool_committed_bytes{pool="Compressed Class Space"} 917504.0
074 * jvm_memory_pool_committed_bytes{pool="Metaspace"} 6946816.0
075 * jvm_memory_pool_committed_bytes{pool="PS Eden Space"} 1.30023424E8
076 * jvm_memory_pool_committed_bytes{pool="PS Old Gen"} 3.47078656E8
077 * jvm_memory_pool_committed_bytes{pool="PS Survivor Space"} 2.1495808E7
078 * # HELP jvm_memory_pool_init_bytes Initial bytes of a given JVM memory pool.
079 * # TYPE jvm_memory_pool_init_bytes gauge
080 * jvm_memory_pool_init_bytes{pool="Code Cache"} 2555904.0
081 * jvm_memory_pool_init_bytes{pool="Compressed Class Space"} 0.0
082 * jvm_memory_pool_init_bytes{pool="Metaspace"} 0.0
083 * jvm_memory_pool_init_bytes{pool="PS Eden Space"} 1.30023424E8
084 * jvm_memory_pool_init_bytes{pool="PS Old Gen"} 3.47078656E8
085 * jvm_memory_pool_init_bytes{pool="PS Survivor Space"} 2.1495808E7
086 * # HELP jvm_memory_pool_max_bytes Max bytes of a given JVM memory pool.
087 * # TYPE jvm_memory_pool_max_bytes gauge
088 * jvm_memory_pool_max_bytes{pool="Code Cache"} 2.5165824E8
089 * jvm_memory_pool_max_bytes{pool="Compressed Class Space"} 1.073741824E9
090 * jvm_memory_pool_max_bytes{pool="Metaspace"} -1.0
091 * jvm_memory_pool_max_bytes{pool="PS Eden Space"} 2.727870464E9
092 * jvm_memory_pool_max_bytes{pool="PS Old Gen"} 5.542248448E9
093 * jvm_memory_pool_max_bytes{pool="PS Survivor Space"} 2.1495808E7
094 * # HELP jvm_memory_pool_used_bytes Used bytes of a given JVM memory pool.
095 * # TYPE jvm_memory_pool_used_bytes gauge
096 * jvm_memory_pool_used_bytes{pool="Code Cache"} 4065472.0
097 * jvm_memory_pool_used_bytes{pool="Compressed Class Space"} 766680.0
098 * jvm_memory_pool_used_bytes{pool="Metaspace"} 6659432.0
099 * jvm_memory_pool_used_bytes{pool="PS Eden Space"} 7801536.0
100 * jvm_memory_pool_used_bytes{pool="PS Old Gen"} 1249696.0
101 * jvm_memory_pool_used_bytes{pool="PS Survivor Space"} 0.0
102 * # HELP jvm_memory_used_bytes Used bytes of a given JVM memory area.
103 * # TYPE jvm_memory_used_bytes gauge
104 * jvm_memory_used_bytes{area="heap"} 9051232.0
105 * jvm_memory_used_bytes{area="nonheap"} 1.1490688E7
106 * </pre>
107 */
108@StableApi
109public class JvmMemoryMetrics {
110
111  private static final String JVM_MEMORY_OBJECTS_PENDING_FINALIZATION =
112      "jvm_memory_objects_pending_finalization";
113  private static final String JVM_MEMORY_USED_BYTES = "jvm_memory_used_bytes";
114  private static final String JVM_MEMORY_COMMITTED_BYTES = "jvm_memory_committed_bytes";
115  private static final String JVM_MEMORY_MAX_BYTES = "jvm_memory_max_bytes";
116  private static final String JVM_MEMORY_INIT_BYTES = "jvm_memory_init_bytes";
117  private static final String JVM_MEMORY_POOL_USED_BYTES = "jvm_memory_pool_used_bytes";
118  private static final String JVM_MEMORY_POOL_COMMITTED_BYTES = "jvm_memory_pool_committed_bytes";
119  private static final String JVM_MEMORY_POOL_MAX_BYTES = "jvm_memory_pool_max_bytes";
120  private static final String JVM_MEMORY_POOL_INIT_BYTES = "jvm_memory_pool_init_bytes";
121  private static final String JVM_MEMORY_POOL_COLLECTION_USED_BYTES =
122      "jvm_memory_pool_collection_used_bytes";
123  private static final String JVM_MEMORY_POOL_COLLECTION_COMMITTED_BYTES =
124      "jvm_memory_pool_collection_committed_bytes";
125  private static final String JVM_MEMORY_POOL_COLLECTION_MAX_BYTES =
126      "jvm_memory_pool_collection_max_bytes";
127  private static final String JVM_MEMORY_POOL_COLLECTION_INIT_BYTES =
128      "jvm_memory_pool_collection_init_bytes";
129
130  private final PrometheusProperties config;
131  private final MemoryMXBean memoryBean;
132  private final List<MemoryPoolMXBean> poolBeans;
133  private final Labels constLabels;
134
135  private JvmMemoryMetrics(
136      List<MemoryPoolMXBean> poolBeans,
137      MemoryMXBean memoryBean,
138      PrometheusProperties config,
139      Labels constLabels) {
140    this.config = config;
141    this.poolBeans = poolBeans;
142    this.memoryBean = memoryBean;
143    this.constLabels = constLabels;
144  }
145
146  private void register(PrometheusRegistry registry) {
147
148    GaugeWithCallback.builder(config)
149        .name(JVM_MEMORY_OBJECTS_PENDING_FINALIZATION)
150        .help("The number of objects waiting in the finalizer queue.")
151        .callback(callback -> callback.call(memoryBean.getObjectPendingFinalizationCount()))
152        .constLabels(constLabels)
153        .register(registry);
154
155    GaugeWithCallback.builder(config)
156        .name(JVM_MEMORY_USED_BYTES)
157        .help("Used bytes of a given JVM memory area.")
158        .unit(Unit.BYTES)
159        .labelNames("area")
160        .callback(
161            callback -> {
162              callback.call(memoryBean.getHeapMemoryUsage().getUsed(), "heap");
163              callback.call(memoryBean.getNonHeapMemoryUsage().getUsed(), "nonheap");
164            })
165        .constLabels(constLabels)
166        .register(registry);
167
168    GaugeWithCallback.builder(config)
169        .name(JVM_MEMORY_COMMITTED_BYTES)
170        .help("Committed (bytes) of a given JVM memory area.")
171        .unit(Unit.BYTES)
172        .labelNames("area")
173        .callback(
174            callback -> {
175              callback.call(memoryBean.getHeapMemoryUsage().getCommitted(), "heap");
176              callback.call(memoryBean.getNonHeapMemoryUsage().getCommitted(), "nonheap");
177            })
178        .constLabels(constLabels)
179        .register(registry);
180
181    GaugeWithCallback.builder(config)
182        .name(JVM_MEMORY_MAX_BYTES)
183        .help("Max (bytes) of a given JVM memory area.")
184        .unit(Unit.BYTES)
185        .labelNames("area")
186        .callback(
187            callback -> {
188              callback.call(memoryBean.getHeapMemoryUsage().getMax(), "heap");
189              callback.call(memoryBean.getNonHeapMemoryUsage().getMax(), "nonheap");
190            })
191        .constLabels(constLabels)
192        .register(registry);
193
194    GaugeWithCallback.builder(config)
195        .name(JVM_MEMORY_INIT_BYTES)
196        .help("Initial bytes of a given JVM memory area.")
197        .unit(Unit.BYTES)
198        .labelNames("area")
199        .callback(
200            callback -> {
201              callback.call(memoryBean.getHeapMemoryUsage().getInit(), "heap");
202              callback.call(memoryBean.getNonHeapMemoryUsage().getInit(), "nonheap");
203            })
204        .constLabels(constLabels)
205        .register(registry);
206
207    GaugeWithCallback.builder(config)
208        .name(JVM_MEMORY_POOL_USED_BYTES)
209        .help("Used bytes of a given JVM memory pool.")
210        .unit(Unit.BYTES)
211        .labelNames("pool")
212        .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getUsed))
213        .constLabels(constLabels)
214        .register(registry);
215
216    GaugeWithCallback.builder(config)
217        .name(JVM_MEMORY_POOL_COMMITTED_BYTES)
218        .help("Committed bytes of a given JVM memory pool.")
219        .unit(Unit.BYTES)
220        .labelNames("pool")
221        .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getCommitted))
222        .constLabels(constLabels)
223        .register(registry);
224
225    GaugeWithCallback.builder(config)
226        .name(JVM_MEMORY_POOL_MAX_BYTES)
227        .help("Max bytes of a given JVM memory pool.")
228        .unit(Unit.BYTES)
229        .labelNames("pool")
230        .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getMax))
231        .constLabels(constLabels)
232        .register(registry);
233
234    GaugeWithCallback.builder(config)
235        .name(JVM_MEMORY_POOL_INIT_BYTES)
236        .help("Initial bytes of a given JVM memory pool.")
237        .unit(Unit.BYTES)
238        .labelNames("pool")
239        .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getInit))
240        .constLabels(constLabels)
241        .register(registry);
242
243    GaugeWithCallback.builder(config)
244        .name(JVM_MEMORY_POOL_COLLECTION_USED_BYTES)
245        .help("Used bytes after last collection of a given JVM memory pool.")
246        .unit(Unit.BYTES)
247        .labelNames("pool")
248        .callback(
249            makeCallback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getUsed))
250        .constLabels(constLabels)
251        .register(registry);
252
253    GaugeWithCallback.builder(config)
254        .name(JVM_MEMORY_POOL_COLLECTION_COMMITTED_BYTES)
255        .help("Committed after last collection bytes of a given JVM memory pool.")
256        .unit(Unit.BYTES)
257        .labelNames("pool")
258        .callback(
259            makeCallback(
260                poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getCommitted))
261        .constLabels(constLabels)
262        .register(registry);
263
264    GaugeWithCallback.builder(config)
265        .name(JVM_MEMORY_POOL_COLLECTION_MAX_BYTES)
266        .help("Max bytes after last collection of a given JVM memory pool.")
267        .unit(Unit.BYTES)
268        .labelNames("pool")
269        .callback(
270            makeCallback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getMax))
271        .constLabels(constLabels)
272        .register(registry);
273
274    GaugeWithCallback.builder(config)
275        .name(JVM_MEMORY_POOL_COLLECTION_INIT_BYTES)
276        .help("Initial after last collection bytes of a given JVM memory pool.")
277        .unit(Unit.BYTES)
278        .labelNames("pool")
279        .callback(
280            makeCallback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getInit))
281        .constLabels(constLabels)
282        .register(registry);
283  }
284
285  private Consumer<GaugeWithCallback.Callback> makeCallback(
286      List<MemoryPoolMXBean> poolBeans,
287      Function<MemoryPoolMXBean, MemoryUsage> memoryUsageFunc,
288      Function<MemoryUsage, Long> valueFunc) {
289    return callback -> {
290      for (MemoryPoolMXBean pool : poolBeans) {
291        MemoryUsage poolUsage = memoryUsageFunc.apply(pool);
292        if (poolUsage != null) {
293          callback.call(valueFunc.apply(poolUsage), pool.getName());
294        }
295      }
296    };
297  }
298
299  public static Builder builder() {
300    return new Builder(PrometheusProperties.get());
301  }
302
303  public static Builder builder(PrometheusProperties config) {
304    return new Builder(config);
305  }
306
307  public static class Builder {
308
309    private final PrometheusProperties config;
310    @Nullable private MemoryMXBean memoryBean;
311    @Nullable private List<MemoryPoolMXBean> poolBeans;
312    private Labels constLabels = Labels.EMPTY;
313
314    private Builder(PrometheusProperties config) {
315      this.config = config;
316    }
317
318    public Builder constLabels(Labels constLabels) {
319      this.constLabels = constLabels;
320      return this;
321    }
322
323    /** Package private. For testing only. */
324    Builder withMemoryBean(MemoryMXBean memoryBean) {
325      this.memoryBean = memoryBean;
326      return this;
327    }
328
329    /** Package private. For testing only. */
330    Builder withMemoryPoolBeans(List<MemoryPoolMXBean> memoryPoolBeans) {
331      this.poolBeans = memoryPoolBeans;
332      return this;
333    }
334
335    public void register() {
336      register(PrometheusRegistry.defaultRegistry);
337    }
338
339    public void register(PrometheusRegistry registry) {
340      MemoryMXBean bean =
341          this.memoryBean != null ? this.memoryBean : ManagementFactory.getMemoryMXBean();
342      List<MemoryPoolMXBean> poolBeans =
343          this.poolBeans != null ? this.poolBeans : ManagementFactory.getMemoryPoolMXBeans();
344      new JvmMemoryMetrics(poolBeans, bean, config, constLabels).register(registry);
345    }
346  }
347}