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