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