001/* 002 * Copyright 2013 Prometheus Team Licensed under the Apache License, Version 2.0 003 * (the "License"); you may not use this file except in compliance with the 004 * License. You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 010 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 011 * License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package io.prometheus.client.utility.jvmstat; 016 017import io.prometheus.client.Prometheus; 018import io.prometheus.client.metrics.Gauge; 019import sun.jvmstat.monitor.HostIdentifier; 020import sun.jvmstat.monitor.Monitor; 021import sun.jvmstat.monitor.MonitorException; 022import sun.jvmstat.monitor.MonitoredHost; 023import sun.jvmstat.monitor.MonitoredVm; 024import sun.jvmstat.monitor.VmIdentifier; 025 026import javax.annotation.concurrent.GuardedBy; 027import java.lang.management.ManagementFactory; 028import java.net.URISyntaxException; 029import java.util.List; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.concurrent.atomic.AtomicBoolean; 032import java.util.concurrent.atomic.AtomicInteger; 033import java.util.logging.Logger; 034import java.util.regex.PatternSyntaxException; 035 036/** 037 * <p> 038 * {@link JvmstatMonitor} provides HotSpot-specific JVM metrics through the {@link 039 * sun.jvmstat.monitor.MonitoredVm} facilities. 040 * </p> 041 * 042 * <p> 043 * These <em>low-level</em> metrics are defined in the C++ bowels of the HotSpot VM through 044 * internal measurement types. The VM exposes these through the <em>HSPerfData</em> interface 045 * for customers. Typically one accesses this data via a MMAPed direct buffer—namely Java native 046 * I/O (nio). 047 * </p> 048 * 049 * <p> 050 * <h1>Important Notes</h1> 051 * Due to inconsistencies of virtual machine packages and vendoring, the following remarks should 052 * be carefully observed: 053 * <ul> 054 * <li> 055 * Users may need to explicitly add {@code ${JAVA_HOME}/lib/tools.jar} to the <em>CLASSPATH</em> 056 * when using this helper. This is because the {@code sun.jvmstat.monitor} package hierarchy 057 * is not a part of the standard Java library. These packages are, however, typically included 058 * in Sun-, Oracle-, and OpenJDK-provided virtual machines, which is to say they are ubiquitous 059 * in most deployment environments. 060 * </li> 061 * <li> 062 * Users <em>may</em> need to explicitly set {@code -XX:+UsePerfData} in the VM's flags to enable 063 * low-level telemetric export. 064 * </li> 065 * </ul> 066 * </p> 067 * 068 * <p> 069 * The metrics that this class exposes are dependent upon the release of HotSpot, including even 070 * minor releases. Depending on that, it may be possible to use adapt this utility to be more 071 * flexible by incorporating support for common metric aliases, as defined in {@code 072 * sun/jvmstat/perfdata/resources/aliasmap}. 073 * </p> 074 * 075 * @author Matt T. Proud (matt.proud@gmail.com) 076 * @see Prometheus#addPreexpositionHook(io.prometheus.client.Prometheus.ExpositionHook) 077 * @see sun.jvmstat.monitor.MonitoredVm 078 */ 079public class JvmstatMonitor implements Prometheus.ExpositionHook { 080 private static final int REFRESH_INTERVAL = 5; 081 private static final Gauge.Builder gaugePrototype = Gauge.newBuilder() 082 .namespace("jvmstat"); 083 private static final Logger log = Logger.getLogger(JvmstatMonitor.class.getName()); 084 085 private final MonitoredVm bridge; 086 087 private final MonitorVisitor clsInstrumentation = new ClassLoaderInstrumentation(); 088 private final MonitorVisitor nccInstrumentation = new NativeCodeCompilerInstrumentation(); 089 private final MonitorVisitor gcInstrumentation = new GarbageCollectionInstrumentation(); 090 private final MonitorVisitor mmInstrumentation = new ManagedMemoryInstrumentation(); 091 092 private final AtomicInteger refreshes = new AtomicInteger(0); 093 private final ConcurrentHashMap<String, Monitor> monitors = new ConcurrentHashMap<String, Monitor>(400); 094 095 /** 096 * <p>Create a {@link JvmstatMonitor} for the local virtual machine associated with this 097 * specific Java server.</p> 098 * 099 * @throws {@link AttachmentError} if the VM cannot be attached to. 100 */ 101 public JvmstatMonitor() throws AttachmentError { 102 this(getLocalVM()); 103 } 104 105 /** 106 * <p>Create a {@link JvmstatMonitor} monitoring bridge with a supplied {@link 107 * sun.jvmstat.monitor.MonitoredVm}.</p> 108 * 109 * <p><strong>This is only useful for reusing an existing {@link sun.jvmstat.monitor.MonitoredVm} 110 * or for testing purposes.</strong> Users are encouraged to use the {@link #JvmstatMonitor()} 111 * default constructor.</p> 112 * 113 * @param b 114 */ 115 public JvmstatMonitor(final MonitoredVm b) { 116 bridge = b; 117 } 118 119 private void refreshMetrics() { 120 final List<Monitor> monitors; 121 try { 122 monitors = bridge.findByPattern(".*"); 123 } catch (final MonitorException ex) { 124 log.warning(String.format("could not extract telemetry: %s", ex)); 125 return; 126 } catch (final PatternSyntaxException ex) { 127 log.warning(String.format("could not extract telemetry: %s", ex)); 128 return; 129 } 130 if (monitors == null) { 131 return; 132 } 133 for (final Monitor monitor : monitors) { 134 this.monitors.putIfAbsent(monitor.getName(), monitor); 135 } 136 } 137 138 @Override 139 public void run() { 140 if (refreshes.getAndIncrement() % REFRESH_INTERVAL == 0) { 141 /* This model presumes that metrics are only added throughout the course of runtime and never 142 * removed. I think that is a reasonable assumption for now. Handling the removal case 143 * would be easy. 144 */ 145 refreshMetrics(); 146 } 147 /* 148 * There are a few ways we could make this cleaner, through the current implementation is just 149 * fine. Here are a few candidates: 150 * 151 * - Direct buffer access of HSPerfData MMAP. 152 * -- Caveat: We would need to implement our own HSPerfData processor, which becomes a huge 153 * point of fragility with even minor HotSpot releases. 154 * - Extending the Metric and its children's mutation model to use a callback when a value 155 * is requested. 156 * -- Caveat: Unimplemented, but would need to be really careful with the API's design. 157 * - Persistent VmListener registered with HotSpot. 158 * -- Caveat: HotSpot just uses polls as well in the underlying implementation for event 159 * dispatching. 160 */ 161 for (final String monitorName : monitors.keySet()) { 162 final Monitor monitor = monitors.get(monitorName); 163 164 /* 165 * Dynamically visit any metrics if they are found. Unfound metrics are never 166 * registered and exported, because we do not want to clutter the namespace and 167 * confuse users. 168 * 169 * The VM offers no contract about metric order, so it seems imprudent to accumulate 170 * state and infer the existence of extra metrics solely by the presence of an earlier one. 171 */ 172 if (clsInstrumentation.visit(monitorName, monitor)) { 173 continue; 174 } 175 if (nccInstrumentation.visit(monitorName, monitor)) { 176 continue; 177 } 178 if (gcInstrumentation.visit(monitorName, monitor)) { 179 continue; 180 } 181 if (mmInstrumentation.visit(monitorName, monitor)) { 182 continue; 183 } 184 } 185 } 186 187 private static MonitoredVm getLocalVM() throws AttachmentError { 188 final String name = ManagementFactory.getRuntimeMXBean().getName(); 189 final int pidOffset = name.indexOf('@'); 190 final int pid; 191 try { 192 pid = Integer.valueOf(name.substring(0, pidOffset)); 193 } catch (final IndexOutOfBoundsException ex) { 194 throw new AttachmentError("illegal instance name", ex); 195 } catch (final NumberFormatException ex) { 196 throw new AttachmentError("illegal instance name format", ex); 197 } 198 199 try { 200 final HostIdentifier hostId = new HostIdentifier((String) null); 201 final MonitoredHost host = MonitoredHost.getMonitoredHost(hostId); 202 final String localAddr = String.format("//%d?mode=r", pid); 203 final VmIdentifier id = new VmIdentifier(localAddr); 204 205 return host.getMonitoredVm(id); 206 } catch (final URISyntaxException ex) { 207 throw new AttachmentError("invalid target URI", ex); 208 } catch (final MonitorException ex) { 209 throw new AttachmentError("could not resolve host", ex); 210 } 211 } 212 213 /** 214 * <p>A {@link AttachmentError} indicates that it was impossible to attach to the requested 215 * virtual machine.</p> 216 */ 217 public static class AttachmentError extends Exception { 218 AttachmentError(final String message, final Throwable cause) { 219 super(message, cause); 220 } 221 } 222 223 /** 224 * <p>A {@link UnknownMonitorError} indicates that we have an invalid entry in the processing 225 * pipeline.</p> 226 */ 227 static class UnknownMonitorError extends IllegalArgumentException { 228 UnknownMonitorError(final Monitor m) { 229 super(String.format("unhandled jvmstat monitor: %s", m.getName())); 230 } 231 } 232 233 /** 234 * <p>Provide visibility about the internals of the JVM's class loader.</p> 235 * 236 * <h1>Generated Metrics</h1> 237 * <h2>{@code jvmstat_classloader_operations_total}</h2> 238 * This metric tracks the number of classloader operations by event type. 239 * <h3>Metric Dimensions</h3> 240 * <ul> 241 * <li> 242 * {@code event}: The type of event. 243 * <ul> 244 * <li> 245 * {@code loaded}: The virtual machine loaded a class. This occurs when the virtual 246 * machine starts and builds its initial class dependency graph. Alternatively it may occur at 247 * runtime when using the reflection facilities or programmatic class loader: {@code 248 * Class.forName("java.lang.String")}. As noted in {@code unloaded}, this can also 249 * occur if an unneeded class is unloaded due to memory pressure in the <em>permanent 250 * generation</em> and later needs to be reloaded. The virtual machine's default behavior is 251 * to try to keep all classes statically loaded for the lifetime of the process. See {@code 252 * ClassLoadingService::notify_class_loaded} in the HotSpot source. 253 * </li> 254 * <li> 255 * {@code unloaded}: The virtual machine unloaded a class. This event is likely to be rare 256 * and will likely occur when either <em>permanent generation</em> memory space is too small 257 * for all of the needed classes, or memory pressure occurs from excessive class loading churn. 258 * Examples for this latter case of churn and memory pressure may be from using code generation 259 * facilities from mock or dependency injection frameworks, though this case is most likely to 260 * occur only in test suite runs. Most likely, memory pressure is a result from using a 261 * poorly-designed dynamic language on the virtual machine that unintelligently uses code 262 * generation for ad hoc type generation, thereby blasting the <em>permanent generation</em> 263 * with leaky class metadata descriptors. See @{code 264 * ClassLoadingService::notify_class_unloaded} in the HotSpot source. 265 * </li> 266 * <li> 267 * {@code initialized}: The virtual machine initializes a class. The process of initializing 268 * a class is different from loading it, in that initialization takes the process of loading 269 * one important step further: <em>it initializes all static fields and instantiates all 270 * objects required for the fulfillment of that class</em>, including performing further class 271 * loading and initialization of dependent classes. The initialization process includes 272 * running a class' static initializers: {@code static {}} blocks. Consult the <em>Virtual 273 * Machine Specification sections 2.16.4-5</em> and {@code InstanceClass::initialize} in the 274 * HotSpot source for a further discussion. 275 * </li> 276 * </ul> 277 * </li> 278 * </ul> 279 * 280 * <h2>{@code jvmstat_classloader_duration_ms}</h2> 281 * This metric tracks the amount of time the class loader spends in various types of 282 * operations. 283 * <h3>Metric Dimensions</h3> 284 * <ul> 285 * <li> 286 * {@code operation}: The operation type. 287 * </li> 288 * <li> 289 * {@code find} 290 * </li> 291 * <li> 292 * {@code parse} 293 * </li> 294 * </ul> 295 * 296 * <h2>{@code jvmstat_classloader_loaded_bytes}</h2> 297 * This metric tracks the total number of bytes the classloader has loaded and processed. 298 */ 299 public static class ClassLoaderInstrumentation implements MonitorVisitor { 300 // This class has public visibility solely for Javadoc production. 301 private static final Gauge.Builder gaugePrototype = JvmstatMonitor.gaugePrototype 302 .subsystem("classloader"); 303 304 private final AtomicBoolean statesOnce = new AtomicBoolean(false); 305 private final AtomicBoolean sizesOnce = new AtomicBoolean(false); 306 private final AtomicBoolean durationsOnce = new AtomicBoolean(false); 307 308 private @GuardedBy("statesOnce") Gauge states; 309 private @GuardedBy("sizesOnce") Gauge sizes; 310 private @GuardedBy("durationsOnce") Gauge durations; 311 312 public boolean visit(final String name, final Monitor monitor) { 313 if ("java.cls.loadedClasses".equals(name) 314 || "java.cls.unloadedClasses".equals(name) 315 || "sun.cls.initializedClasses".equals(name)) { 316 return visitClassEvents(name, monitor); 317 } else if ("sun.cls.loadedBytes".equals(name)) { 318 return visitSizes(monitor); 319 } else if ("sun.classloader.findClassTime".equals(name) 320 || "sun.cls.parseClassTime".equals(name)) { 321 return visitDurations(name, monitor); 322 } 323 324 return false; 325 } 326 327 private boolean remarkDuration(final String duration, final Monitor monitor) { 328 final Double value = decodeMetric(monitor); 329 if (value == null) { 330 return false; 331 } 332 333 durations.newPartial() 334 .labelPair("operation", duration) 335 .apply() 336 .set(value); 337 338 return true; 339 } 340 341 private boolean visitDurations(final String name, final Monitor monitor) { 342 if (durations == null && durationsOnce.getAndSet(true) == false) { 343 durations = gaugePrototype 344 .name("duration_ms") 345 .documentation("The time it has taken the classloader to perform loadings partitioned by operation.") 346 .labelNames("operation") 347 .build(); 348 } 349 350 if ("sun.classloader.findClassTime".equals(name)) { 351 return remarkDuration("find", monitor); 352 } else if ("sun.cls.parseClassTime".equals(name)) { 353 return remarkDuration("parse", monitor); 354 } 355 356 throw new UnknownMonitorError(monitor); 357 } 358 359 private boolean visitSizes(final Monitor monitor) { 360 if (sizes == null && sizesOnce.getAndSet(true) == false) { 361 sizes = gaugePrototype 362 .name("loaded_bytes") 363 .documentation("The number of bytes the classloader has loaded.") 364 .build(); 365 } 366 367 final Double value = decodeMetric(monitor); 368 if (value == null) { 369 return false; 370 } 371 372 sizes.newPartial() 373 .apply() 374 .set(value); 375 return true; 376 } 377 378 private boolean remarkClassEvent(final String event, final Monitor monitor) { 379 final Double value = decodeMetric(monitor); 380 if (value == null) { 381 return false; 382 } 383 384 states.newPartial() 385 .labelPair("event", event) 386 .apply() 387 .set(value); 388 return true; 389 } 390 391 private boolean visitClassEvents(final String name, final Monitor monitor) { 392 if (states == null && statesOnce.getAndSet(true) == false) { 393 states = gaugePrototype 394 .name("operations_total") 395 .documentation("The number of classes the loader has touched by disposition.") 396 .labelNames("event") 397 .build(); 398 } 399 400 if ("java.cls.loadedClasses".equals(name)) { 401 return remarkClassEvent("loaded", monitor); 402 } else if ("java.cls.unloadedClasses".equals(name)) { 403 return remarkClassEvent("unloaded", monitor); 404 } else if ("sun.cls.initializedClasses".equals(name)) { 405 return remarkClassEvent("initialized", monitor); 406 } 407 408 throw new UnknownMonitorError(monitor); 409 } 410 } 411 412 /** 413 * <p>Provide visibility about the internals of the JVM's just-in-time (JIT) compiler.</p> 414 * 415 * <p>Even if you do not run your Java server with {@code -server} mode, these metrics will be 416 * beneficial to understand how much is being optimized and whether a deeper analysis to find 417 * classes to blacklist from JIT rewriting exist.</p> 418 * 419 * <h1>Generated Metrics</h1> 420 * <h2>{@code jvmstat_jit_compilation_time_ms}</h2> 421 * This metric tracks the amount of time the JIT spends compiling byte code into native code 422 * partitioned by JIT thread. 423 * <h3>Metric Dimensions</h3> 424 * <ul> 425 * <li> 426 * {@code thread}: The thread ID associated with the work. 427 * </li> 428 * <ul> 429 * 430 * <h2>{@code jvmstat_jit_compilation_count}</h2> 431 * This metric tracks the number of classes the JIT has compiled and potentially recompiled after 432 * determining either the initial byte code definition or re-interpreted machine code 433 * representation is suboptimal. 434 * <h3>Metric Dimensions</h3> 435 * <ul> 436 * <li> 437 * {@code thread}: The thread ID associated with the work. 438 * </li> 439 * <ul> 440 */ 441 public static class NativeCodeCompilerInstrumentation implements MonitorVisitor { 442 // This class has public visibility solely for Javadoc production. 443 private static final Gauge.Builder gaugePrototype = JvmstatMonitor.gaugePrototype 444 .subsystem("jit"); 445 446 private final AtomicBoolean compilationOnce = new AtomicBoolean(false); 447 private final AtomicBoolean durationOnce = new AtomicBoolean(false); 448 449 private @GuardedBy("compilationsOnce") Gauge compilations; 450 private @GuardedBy("durationsOnce") Gauge durations; 451 452 public boolean visit(final String name, final Monitor monitor) { 453 if ("sun.ci.compilerThread.0.compiles".equals(name) 454 || "sun.ci.compilerThread.1.compiles".equals(name) 455 || "sun.ci.compilerThread.2.compiles".equals(name)) { 456 /* 457 * Incorporate sun.ci.threads for accurate counting. It is unlikely that there will 458 * be more than two threads at any time. If we see index "2", we will need to revisit 459 * this exposition bridge. 460 */ 461 return visitCompilations(name, monitor); 462 } else if ("sun.ci.compilerThread.0.time".equals(name) 463 || "sun.ci.compilerThread.1.time".equals(name) 464 || "sun.ci.compilerThread.2.time".equals(name)){ 465 return visitDurations(name, monitor); 466 } 467 468 return false; 469 } 470 471 private boolean remarkDuration(final String thread, final Monitor monitor) { 472 final Double value = decodeMetric(monitor); 473 if (value == null) { 474 return false; 475 } 476 477 durations.newPartial() 478 .labelPair("thread", thread) 479 .apply() 480 .set(value); 481 482 return true; 483 } 484 485 private boolean visitDurations(final String name, final Monitor monitor) { 486 if (durations == null && durationOnce.getAndSet(true) == false) { 487 durations = gaugePrototype 488 .name("compilation_time_ms") 489 .documentation("The count of JIT compilation events.") 490 .labelNames("thread") 491 .build(); 492 } 493 494 if ("sun.ci.compilerThread.0.time".equals(name)) { 495 return remarkDuration("0", monitor); 496 } else if ("sun.ci.compilerThread.1.time".equals(name)) { 497 return remarkDuration("1", monitor); 498 } else if ("sun.ci.compilerThread.2.time".equals(name)) { 499 return remarkDuration("2", monitor); 500 } 501 throw new UnknownMonitorError(monitor); 502 } 503 504 private boolean remarkCompilation(final String thread, final Monitor monitor) { 505 final Double value = decodeMetric(monitor); 506 if (value == null) { 507 return false; 508 } 509 510 compilations.newPartial() 511 .labelPair("thread", thread) 512 .apply() 513 .set(value); 514 515 return true; 516 } 517 518 private boolean visitCompilations(final String name, final Monitor monitor) { 519 if (compilations == null && compilationOnce.getAndSet(true) == false) { 520 compilations = gaugePrototype 521 .name("compilation_count") 522 .documentation("The count of JIT compilation events.") 523 .labelNames("thread") 524 .build(); 525 } 526 527 if ("sun.ci.compilerThread.0.compiles".equals(name)) { 528 return remarkCompilation("0", monitor); 529 } else if ("sun.ci.compilerThread.1.compiles".equals(name)) { 530 return remarkCompilation("1", monitor); 531 } else if ("sun.ci.compilerThread.1.compiles".equals(name)) { 532 return remarkCompilation("2", monitor); 533 } 534 535 throw new UnknownMonitorError(monitor); 536 } 537 } 538 539 /** 540 * <p>Provide visibility about the internals of the JVM's garbage collector.</p> 541 * 542 * <h1>Generated Metrics</h1> 543 * <h2>{@code jvmstat_garbage_collection_invocations_total}</h2> 544 * <p>Since JRE 1.1, the Java Virtual Machine has used a generational model for managing memory 545 * due to object lifecycle in well-designed systems typically following an exponential 546 * distribution whereby the majority of object born die young. This means great efficiency gains 547 * can be achieved when long-standing tenured objects are ignored (i.e., collected less 548 * frequently) and the focus of collection becomes those newly-birthed objects</p> 549 * 550 * <pre> 551 * o | 552 * b | X 553 * j | X 554 * e | X X 555 * c | X X 556 * t | X X X 557 * s | X X X 558 * | X X X X X X 559 * # ————————————————————— 560 * 0 1 2 3 4 5 … n 561 * 562 * object cohort age 563 * </pre> 564 * 565 * <p>This is to say, assuming a small amount of persistent in-memory state within a server, the 566 * majority of objects are allocated during units of work (e.g., HTTP requests, RPCs, etc.) and 567 * have no bearing or produce little by way of side effects that need to be retained later 568 * in the life of the server. <strong>This is the ideal case.</strong></p> 569 * 570 * <h3>Metric Dimensions</h3> 571 * <ul> 572 * <li> 573 * {@code generation}: The managed memory region name. 574 * </li> 575 * <ul> 576 * 577 * <h2>{@code jvmstat_garbage_collection_duration_ms_total}</h2> 578 * This metric tracks the amount of time spent spent collecting the respective generation. 579 * <h3>Metric Dimensions</h3> 580 * <ul> 581 * <li> 582 * {@code generation}: The managed memory region name. 583 * </li> 584 * <ul> 585 */ 586 public static class GarbageCollectionInstrumentation implements MonitorVisitor { 587 // This class has public visibility solely for Javadoc production. 588 private static final Gauge.Builder gaugePrototype = JvmstatMonitor.gaugePrototype 589 .subsystem("garbage_collection"); 590 591 final AtomicBoolean invocationsOnce = new AtomicBoolean(false); 592 final AtomicBoolean durationsOnce = new AtomicBoolean(false); 593 594 private @GuardedBy("invocationsOnce") Gauge invocations; 595 private @GuardedBy("durationsOnce") Gauge durations; 596 597 /* 598 * TODO: Add garbage collection failure mode registration instrumentation after coming to 599 * consensus on internal naming inside of the Virtual Machine. 600 * 601 * TODO: Add metrics for "full GC" events from failure modes. 602 */ 603 public boolean visit(final String name, final Monitor monitor) { 604 if ("sun.gc.collector.0.invocations".equals(name) 605 || "sun.gc.collector.1.invocations".equals(name)) { 606 return visitInvocations(name, monitor); 607 } else if ("sun.gc.collector.0.time".equals(name) 608 || "sun.gc.collector.1.time".equals(name)) { 609 return visitDurations(name, monitor); 610 } 611 612 return false; 613 } 614 615 private boolean remarkInvocation(final String generation, final Monitor monitor) { 616 final Double value = decodeMetric(monitor); 617 if (value == null) { 618 return false; 619 } 620 621 invocations.newPartial() 622 .labelPair("generation", generation) 623 .apply() 624 .set(value); 625 626 return true; 627 } 628 629 private boolean visitInvocations(final String name, final Monitor monitor) { 630 if (invocations == null && invocationsOnce.getAndSet(true) == false) { 631 invocations = gaugePrototype 632 .name("invocations_total") 633 .labelNames("generation") 634 .documentation("The total number of times the garbage collector has been invoked.") 635 .build(); 636 } 637 638 if ("sun.gc.collector.0.invocations".equals(name)) { 639 return remarkInvocation("new", monitor); 640 } else if ("sun.gc.collector.1.invocations".equals(name)) { 641 return remarkInvocation("old", monitor); 642 } 643 644 throw new UnknownMonitorError(monitor); 645 } 646 647 private boolean remarkDuration(final String generation, final Monitor monitor) { 648 final Double valueMicros = decodeMetric(monitor); 649 if (valueMicros == null) { 650 return false; 651 } 652 653 final Double valueMillis = valueMicros / 1000; 654 655 durations.newPartial() 656 .labelPair("generation", generation) 657 .apply() 658 .set(valueMillis); 659 660 return true; 661 } 662 663 private boolean visitDurations(final String name, final Monitor monitor) { 664 if (durations == null && durationsOnce.getAndSet(true) == false) { 665 durations = gaugePrototype 666 .name("durations_ms_total") 667 .documentation("The total time spent in garbage collection for a generation.") 668 .build(); 669 } 670 671 if ("sun.gc.collector.0.time".equals(name)) { 672 return remarkDuration("new", monitor); 673 } else if ("sun.gc.collector.1.time".equals(name)) { 674 return remarkDuration("old", monitor); 675 } 676 677 throw new UnknownMonitorError(monitor); 678 } 679 } 680 681 /** 682 * <p>Provide visibility about the internals of the JVM memory regions.</p> 683 * 684 * <h1>Generated Metrics</h1> 685 * <h2>{@code jvmstat_managed_memory_generation_limit_bytes}</h2> 686 * <h3>Metric Dimensions</h3> 687 * <ul> 688 * <li> 689 * {@code generation}: The managed memory region name. 690 * </li> 691 * <ul> 692 * 693 * <h2>{@code jvmstat_managed_memory_generation_usage_bytes}</h2> 694 * <h3>Metric Dimensions</h3> 695 * <ul> 696 * <li> 697 * {@code generation}: The managed memory region name. 698 * </li> 699 * <ul> 700 * 701 * <h2>{@code jvmstat_managed_memory_survivor_space_agetable_size_bytes}</h2> 702 * <h3>Metric Dimensions</h3> 703 * <ul> 704 * <li> 705 * {@code cohort}: The agetable cohort. (TODO: Discuss the agetable design.) 706 * </li> 707 * <ul> 708 * 709 * <h2>{@code jvmstat_managed_memory_survivor_space_agetable_count}</h2> 710 *(TODO: Discuss the agetable design.) 711 * 712 * @see io.prometheus.client.utility.jvmstat.JvmstatMonitor.GarbageCollectionInstrumentation 713 */ 714 public static class ManagedMemoryInstrumentation implements MonitorVisitor { 715 // This class has public visibility solely for Javadoc production. 716 private static final Gauge.Builder gaugePrototype = JvmstatMonitor.gaugePrototype 717 .subsystem("managed_memory"); 718 719 private final AtomicBoolean agetableCohortsOnce = new AtomicBoolean(false); 720 private final AtomicBoolean agetableCountOnce = new AtomicBoolean(false); 721 private final AtomicBoolean generationLimitOnce = new AtomicBoolean(false); 722 private final AtomicBoolean generationUsageOnce = new AtomicBoolean(false); 723 724 // TODO: Discuss markOop and oopDesc types. 725 private @GuardedBy("this") Gauge agetableCohorts; 726 private @GuardedBy("this") Gauge agetableCount; 727 private @GuardedBy("this") Gauge generationLimit; 728 private @GuardedBy("this") Gauge generationUsage; 729 730 public boolean visit(final String name, final Monitor monitor) { 731 if ("sun.gc.generation.0.agetable.bytes.00".equals(name) 732 || "sun.gc.generation.0.agetable.bytes.01".equals(name) 733 || "sun.gc.generation.0.agetable.bytes.02".equals(name) 734 || "sun.gc.generation.0.agetable.bytes.03".equals(name) 735 || "sun.gc.generation.0.agetable.bytes.04".equals(name) 736 || "sun.gc.generation.0.agetable.bytes.05".equals(name) 737 || "sun.gc.generation.0.agetable.bytes.06".equals(name) 738 || "sun.gc.generation.0.agetable.bytes.07".equals(name) 739 || "sun.gc.generation.0.agetable.bytes.08".equals(name) 740 || "sun.gc.generation.0.agetable.bytes.09".equals(name) 741 || "sun.gc.generation.0.agetable.bytes.10".equals(name) 742 || "sun.gc.generation.0.agetable.bytes.11".equals(name) 743 || "sun.gc.generation.0.agetable.bytes.12".equals(name) 744 || "sun.gc.generation.0.agetable.bytes.13".equals(name) 745 || "sun.gc.generation.0.agetable.bytes.14".equals(name) 746 || "sun.gc.generation.0.agetable.bytes.15".equals(name)) { 747 return visitSurvivorSpaceAgetableCohorts(name, monitor); 748 } else if ("sun.gc.generation.0.agetable.size".equals(name)) { 749 return visitSurvivorSpaceAgetableCount(monitor); 750 } else if ("sun.gc.generation.0.space.0.capacity".equals(name) 751 || "sun.gc.generation.0.space.1.capacity".equals(name) 752 || "sun.gc.generation.0.space.2.capacity".equals(name) 753 || "sun.gc.generation.1.space.0.capacity".equals(name) 754 || "sun.gc.generation.2.space.0.capacity".equals(name)) { 755 return visitGenerationLimits(name, monitor); 756 } else if ("sun.gc.generation.0.space.0.used".equals(name) 757 || "sun.gc.generation.0.space.1.used".equals(name) 758 || "sun.gc.generation.0.space.2.used".equals(name) 759 || "sun.gc.generation.1.space.0.used".equals(name) 760 || "sun.gc.generation.2.space.0.used".equals(name)) { 761 return visitGenerationUsage(name, monitor); 762 } 763 764 return false; 765 } 766 767 private boolean remarkGenerationLimit(final String generation, final Monitor monitor) { 768 final Double value = decodeMetric(monitor); 769 if (value == null) { 770 return false; 771 } 772 773 generationLimit.newPartial() 774 .labelPair("generation", generation) 775 .apply() 776 .set(value); 777 return true; 778 } 779 780 private boolean visitGenerationLimits(final String name, final Monitor monitor) { 781 if (generationLimit == null) { 782 synchronized (this) { 783 if (generationLimitOnce.getAndSet(true) == false) { 784 generationLimit = gaugePrototype 785 .name("generation_limit_bytes") 786 .labelNames("generation") 787 .documentation("The total allocation/reservation of each managed memory region or generation.") 788 .build(); 789 790 notifyAll(); 791 } else { 792 try { 793 // In case of data race, wait until this has been initialized. 794 wait(); 795 } catch (final InterruptedException ignored) { 796 } 797 } 798 } 799 } 800 801 if ("sun.gc.generation.0.space.0.capacity".equals(name)) { 802 return remarkGenerationLimit("eden", monitor); 803 } else if ("sun.gc.generation.0.space.1.capacity".equals(name)) { 804 return remarkGenerationLimit("survivor0", monitor); 805 } else if ("sun.gc.generation.0.space.2.capacity".equals(name)) { 806 return remarkGenerationLimit("survivor1", monitor); 807 } else if ("sun.gc.generation.1.space.0.capacity".equals(name)) { 808 return remarkGenerationLimit("old", monitor); 809 } else if ("sun.gc.generation.2.space.0.capacity".equals(name)) { 810 return remarkGenerationLimit("permgen", monitor); 811 } 812 813 throw new UnknownMonitorError(monitor); 814 } 815 816 private boolean remarkGenerationUsage(final String generation, final Monitor monitor) { 817 final Double value = decodeMetric(monitor); 818 if (value == null) { 819 return false; 820 } 821 822 generationUsage.newPartial() 823 .labelPair("generation", generation) 824 .apply() 825 .set(value); 826 827 return true; 828 } 829 830 private boolean visitGenerationUsage(final String name, final Monitor monitor) { 831 if (generationUsage == null) { 832 synchronized (this) { 833 if (generationUsageOnce.getAndSet(true) == false) { 834 generationUsage = gaugePrototype 835 .name("generation_usage_bytes") 836 .labelNames("generation") 837 .documentation("The size used of each managed memory region or generation.") 838 .build(); 839 840 notifyAll(); 841 } else { 842 try { 843 // In case of data race, wait until this has been initialized. 844 wait(); 845 } catch (final InterruptedException ignored) { 846 } 847 } 848 } 849 } 850 851 if ("sun.gc.generation.0.space.0.used".equals(name)) { 852 return remarkGenerationUsage("eden", monitor); 853 } else if ("sun.gc.generation.0.space.1.used".equals(name)) { 854 return remarkGenerationUsage("survivor0", monitor); 855 } else if ("sun.gc.generation.0.space.2.used".equals(name)) { 856 return remarkGenerationUsage("survivor1", monitor); 857 } else if ("sun.gc.generation.1.space.0.used".equals(name)) { 858 return remarkGenerationUsage("old", monitor); 859 } else if ("sun.gc.generation.2.space.0.used".equals(name)) { 860 return remarkGenerationUsage("permgen", monitor); 861 } 862 863 throw new UnknownMonitorError(monitor); 864 } 865 866 private boolean remarkAgetableCohortSize(final String cohort, final Monitor monitor) { 867 final Double value = decodeMetric(monitor); 868 if (value == null) { 869 return false; 870 } 871 872 agetableCohorts.newPartial() 873 .labelPair("cohort", cohort) 874 .apply() 875 .set(value); 876 877 return true; 878 } 879 880 private boolean visitSurvivorSpaceAgetableCohorts(final String name, final Monitor monitor) { 881 if (agetableCohorts == null) { 882 synchronized (this) { 883 if (agetableCohortsOnce.getAndSet(true) == false) { 884 agetableCohorts = gaugePrototype 885 .name("survivor_space_agetable_size_bytes") 886 .labelNames("cohort") 887 .documentation("A measure of the size of each survivor space agetable cohort.") 888 .build(); 889 890 notifyAll(); 891 } else { 892 try { 893 // In case of data race, wait until this has been initialized. 894 wait(); 895 } catch (final InterruptedException ignored) { 896 } 897 } 898 } 899 } 900 901 if ("sun.gc.generation.0.agetable.bytes.00".equals(name)) { 902 return remarkAgetableCohortSize("00", monitor); 903 } else if ("sun.gc.generation.0.agetable.bytes.01".equals(name)) { 904 return remarkAgetableCohortSize("01", monitor); 905 } else if ("sun.gc.generation.0.agetable.bytes.02".equals(name)) { 906 return remarkAgetableCohortSize("02", monitor); 907 } else if ("sun.gc.generation.0.agetable.bytes.03".equals(name)) { 908 return remarkAgetableCohortSize("03", monitor); 909 } else if ("sun.gc.generation.0.agetable.bytes.04".equals(name)) { 910 return remarkAgetableCohortSize("04", monitor); 911 } else if ("sun.gc.generation.0.agetable.bytes.05".equals(name)) { 912 return remarkAgetableCohortSize("05", monitor); 913 } else if ("sun.gc.generation.0.agetable.bytes.06".equals(name)) { 914 return remarkAgetableCohortSize("06", monitor); 915 } else if ("sun.gc.generation.0.agetable.bytes.07".equals(name)) { 916 return remarkAgetableCohortSize("07", monitor); 917 } else if ("sun.gc.generation.0.agetable.bytes.08".equals(name)) { 918 return remarkAgetableCohortSize("08", monitor); 919 } else if ("sun.gc.generation.0.agetable.bytes.09".equals(name)) { 920 return remarkAgetableCohortSize("09", monitor); 921 } else if ("sun.gc.generation.0.agetable.bytes.10".equals(name)) { 922 return remarkAgetableCohortSize("10", monitor); 923 } else if ("sun.gc.generation.0.agetable.bytes.11".equals(name)) { 924 return remarkAgetableCohortSize("11", monitor); 925 } else if ("sun.gc.generation.0.agetable.bytes.12".equals(name)) { 926 return remarkAgetableCohortSize("12", monitor); 927 } else if ("sun.gc.generation.0.agetable.bytes.13".equals(name)) { 928 return remarkAgetableCohortSize("13", monitor); 929 } else if ("sun.gc.generation.0.agetable.bytes.14".equals(name)) { 930 return remarkAgetableCohortSize("14", monitor); 931 } else if ("sun.gc.generation.0.agetable.bytes.15".equals(name)) { 932 return remarkAgetableCohortSize("15", monitor); 933 } 934 935 throw new UnknownMonitorError(monitor); 936 } 937 938 private boolean visitSurvivorSpaceAgetableCount(final Monitor monitor) { 939 if (agetableCount == null) { 940 synchronized (this) { 941 if (agetableCountOnce.getAndSet(true) == false) { 942 agetableCount = gaugePrototype 943 .name("survivor_space_agetable_count") 944 .documentation("The number of survivor space agetable cohorts.") 945 .build(); 946 947 notifyAll(); 948 } else { 949 try { 950 // In case of data race, wait until this has been initialized. 951 wait(); 952 } catch (final InterruptedException ignored) { 953 } 954 } 955 } 956 } 957 958 final Double value = decodeMetric(monitor); 959 if (value == null) { 960 return false; 961 } 962 963 agetableCount.newPartial() 964 .apply() 965 .set(value); 966 967 return true; 968 } 969 } 970 971 static interface MonitorVisitor { 972 boolean visit(final String name, final Monitor monitor); 973 } 974 975 private static Double decodeMetric(final Monitor m) { 976 final Object value = m.getValue(); 977 978 if (value == null) { 979 return null; 980 } else if (value instanceof Long) { 981 return Double.valueOf((Long) value); 982 } else { 983 try { 984 return Double.valueOf(value.toString()); 985 } catch (final NumberFormatException unused) { 986 return null; 987 } 988 } 989 } 990}