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