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}