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}