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