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