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