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 javax.management.InstanceNotFoundException; 009import javax.management.MBeanException; 010import javax.management.MalformedObjectNameException; 011import javax.management.ObjectName; 012import javax.management.ReflectionException; 013import java.lang.management.ManagementFactory; 014import java.util.concurrent.atomic.AtomicBoolean; 015import java.util.function.Consumer; 016import java.util.regex.Matcher; 017import java.util.regex.Pattern; 018 019/** 020 * JVM native memory. JVM native memory tracking is disabled by default. You need to enable it by starting your JVM with this flag: 021 * <pre>-XX:NativeMemoryTracking=summary</pre> 022 * <p> 023 * When native memory tracking is disabled the metrics are not registered either. 024 * <p> 025 * <p> 026 * The {@link JvmNativeMemoryMetrics} are registered as part of the {@link JvmMetrics} like this: 027 * <pre>{@code 028 * JvmMetrics.builder().register(); 029 * }</pre> 030 * However, if you want only the {@link JvmNativeMemoryMetrics} you can also register them directly: 031 * <pre>{@code 032 * JvmNativeMemoryMetrics.builder().register(); 033 * }</pre> 034 * Example metrics being exported: 035 * <pre> 036 * # HELP jvm_native_memory_committed_bytes Committed bytes of a given JVM. Committed memory represents the amount of memory the JVM is using right now. 037 * # TYPE jvm_native_memory_committed_bytes gauge 038 * jvm_native_memory_committed_bytes{pool="Arena Chunk"} 58480.0 039 * jvm_native_memory_committed_bytes{pool="Arguments"} 25119.0 040 * jvm_native_memory_committed_bytes{pool="Class"} 1.00609438E8 041 * jvm_native_memory_committed_bytes{pool="Code"} 2.7980888E7 042 * jvm_native_memory_committed_bytes{pool="Compiler"} 529922.0 043 * jvm_native_memory_committed_bytes{pool="GC"} 515466.0 044 * jvm_native_memory_committed_bytes{pool="Internal"} 673194.0 045 * jvm_native_memory_committed_bytes{pool="Java Heap"} 4.0923136E7 046 * jvm_native_memory_committed_bytes{pool="Logging"} 4596.0 047 * jvm_native_memory_committed_bytes{pool="Module"} 96408.0 048 * jvm_native_memory_committed_bytes{pool="Native Memory Tracking"} 3929432.0 049 * jvm_native_memory_committed_bytes{pool="Other"} 667656.0 050 * jvm_native_memory_committed_bytes{pool="Safepoint"} 8192.0 051 * jvm_native_memory_committed_bytes{pool="Symbol"} 2.4609808E7 052 * jvm_native_memory_committed_bytes{pool="Synchronizer"} 272520.0 053 * jvm_native_memory_committed_bytes{pool="Thread"} 3546896.0 054 * jvm_native_memory_committed_bytes{pool="Total"} 2.0448392E8 055 * jvm_native_memory_committed_bytes{pool="Tracing"} 1.0 056 * jvm_native_memory_committed_bytes{pool="Unknown"} 32768.0 057 * # HELP jvm_native_memory_reserved_bytes Reserved bytes of a given JVM. Reserved memory represents the total amount of memory the JVM can potentially use. 058 * # TYPE jvm_native_memory_reserved_bytes gauge 059 * jvm_native_memory_reserved_bytes{pool="Arena Chunk"} 25736.0 060 * jvm_native_memory_reserved_bytes{pool="Arguments"} 25119.0 061 * jvm_native_memory_reserved_bytes{pool="Class"} 1.162665374E9 062 * jvm_native_memory_reserved_bytes{pool="Code"} 2.55386712E8 063 * jvm_native_memory_reserved_bytes{pool="Compiler"} 529922.0 064 * jvm_native_memory_reserved_bytes{pool="GC"} 1695114.0 065 * jvm_native_memory_reserved_bytes{pool="Internal"} 673191.0 066 * jvm_native_memory_reserved_bytes{pool="Java Heap"} 4.02653184E8 067 * jvm_native_memory_reserved_bytes{pool="Logging"} 4596.0 068 * jvm_native_memory_reserved_bytes{pool="Module"} 96408.0 069 * jvm_native_memory_reserved_bytes{pool="Native Memory Tracking"} 3929400.0 070 * jvm_native_memory_reserved_bytes{pool="Other"} 667656.0 071 * jvm_native_memory_reserved_bytes{pool="Safepoint"} 8192.0 072 * jvm_native_memory_reserved_bytes{pool="Symbol"} 2.4609808E7 073 * jvm_native_memory_reserved_bytes{pool="Synchronizer"} 272520.0 074 * jvm_native_memory_reserved_bytes{pool="Thread"} 3.383272E7 075 * jvm_native_memory_reserved_bytes{pool="Total"} 1.887108421E9 076 * jvm_native_memory_reserved_bytes{pool="Tracing"} 1.0 077 * jvm_native_memory_reserved_bytes{pool="Unknown"} 32768.0 078 * </pre> 079 */ 080public class JvmNativeMemoryMetrics { 081 private static final String JVM_NATIVE_MEMORY_RESERVED_BYTES = "jvm_native_memory_reserved_bytes"; 082 private static final String JVM_NATIVE_MEMORY_COMMITTED_BYTES = "jvm_native_memory_committed_bytes"; 083 084 private static final Pattern pattern = Pattern.compile("\\s*([A-Z][A-Za-z\\s]*[A-Za-z]+).*reserved=(\\d+), committed=(\\d+)"); 085 086 /** 087 * Package private. For testing only. 088 */ 089 static final AtomicBoolean isEnabled = new AtomicBoolean(true); 090 091 private final PrometheusProperties config; 092 private final PlatformMBeanServerAdapter adapter; 093 094 private JvmNativeMemoryMetrics(PrometheusProperties config, PlatformMBeanServerAdapter adapter) { 095 this.config = config; 096 this.adapter = adapter; 097 } 098 099 private void register(PrometheusRegistry registry) { 100 // first call will check if enabled and set the flag 101 vmNativeMemorySummaryInBytesOrEmpty(); 102 if (isEnabled.get()) { 103 GaugeWithCallback.builder(config) 104 .name(JVM_NATIVE_MEMORY_RESERVED_BYTES) 105 .help("Reserved bytes of a given JVM. Reserved memory represents the total amount of memory the JVM can potentially use.") 106 .unit(Unit.BYTES) 107 .labelNames("pool") 108 .callback(makeCallback(true)) 109 .register(registry); 110 111 GaugeWithCallback.builder(config) 112 .name(JVM_NATIVE_MEMORY_COMMITTED_BYTES) 113 .help("Committed bytes of a given JVM. Committed memory represents the amount of memory the JVM is using right now.") 114 .unit(Unit.BYTES) 115 .labelNames("pool") 116 .callback(makeCallback(false)) 117 .register(registry); 118 } 119 } 120 121 private Consumer<GaugeWithCallback.Callback> makeCallback(Boolean reserved) { 122 return callback -> { 123 String summary = vmNativeMemorySummaryInBytesOrEmpty(); 124 if (!summary.isEmpty()) { 125 Matcher matcher = pattern.matcher(summary); 126 while (matcher.find()) { 127 String category = matcher.group(1); 128 long value; 129 if (reserved) { 130 value = Long.parseLong(matcher.group(2)); 131 } else { 132 value = Long.parseLong(matcher.group(3)); 133 } 134 callback.call(value, category); 135 } 136 } 137 }; 138 } 139 140 private String vmNativeMemorySummaryInBytesOrEmpty() { 141 if (!isEnabled.get()) { 142 return ""; 143 } 144 try { 145 // requires -XX:NativeMemoryTracking=summary 146 String summary = adapter.vmNativeMemorySummaryInBytes(); 147 if (summary.isEmpty() || summary.trim().contains("Native memory tracking is not enabled")) { 148 isEnabled.set(false); 149 return ""; 150 } else { 151 return summary; 152 } 153 } catch (Exception ex) { 154 // ignore errors 155 isEnabled.set(false); 156 return ""; 157 } 158 } 159 160 interface PlatformMBeanServerAdapter { 161 String vmNativeMemorySummaryInBytes(); 162 } 163 164 static class DefaultPlatformMBeanServerAdapter implements PlatformMBeanServerAdapter { 165 @Override 166 public String vmNativeMemorySummaryInBytes() { 167 try { 168 return (String) ManagementFactory.getPlatformMBeanServer().invoke( 169 new ObjectName("com.sun.management:type=DiagnosticCommand"), 170 "vmNativeMemory", 171 new Object[]{new String[]{"summary", "scale=B"}}, 172 new String[]{"[Ljava.lang.String;"}); 173 } catch (ReflectionException | MalformedObjectNameException | InstanceNotFoundException | MBeanException e) { 174 throw new IllegalStateException("Native memory tracking is not enabled", e); 175 } 176 } 177 } 178 179 public static Builder builder() { 180 return new Builder(PrometheusProperties.get()); 181 } 182 183 public static Builder builder(PrometheusProperties config) { 184 return new Builder(config); 185 } 186 187 public static class Builder { 188 189 private final PrometheusProperties config; 190 private final PlatformMBeanServerAdapter adapter; 191 192 private Builder(PrometheusProperties config) { 193 this(config, new DefaultPlatformMBeanServerAdapter()); 194 } 195 196 /** 197 * Package private. For testing only. 198 */ 199 Builder(PrometheusProperties config, PlatformMBeanServerAdapter adapter) { 200 this.config = config; 201 this.adapter = adapter; 202 } 203 204 public void register() { 205 register(PrometheusRegistry.defaultRegistry); 206 } 207 208 public void register(PrometheusRegistry registry) { 209 new JvmNativeMemoryMetrics(config, adapter).register(registry); 210 } 211 } 212}