All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micrometer.core.instrument.binder.jvm.JvmGcMetrics Maven / Gradle / Ivy

There is a newer version: 1.13.0
Show newest version
/**
 * Copyright 2017 VMware, Inc.
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.micrometer.core.instrument.binder.jvm; import com.sun.management.GarbageCollectionNotificationInfo; import com.sun.management.GcInfo; import io.micrometer.core.instrument.*; import io.micrometer.core.instrument.binder.BaseUnits; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.lang.NonNullApi; import io.micrometer.core.lang.NonNullFields; import io.micrometer.core.lang.Nullable; import io.micrometer.core.util.internal.logging.InternalLogger; import io.micrometer.core.util.internal.logging.InternalLoggerFactory; import javax.management.ListenerNotFoundException; import javax.management.NotificationEmitter; import javax.management.NotificationListener; import javax.management.openmbean.CompositeData; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static io.micrometer.core.instrument.binder.jvm.JvmMemory.*; import static java.util.Collections.emptyList; /** * Record metrics that report a number of statistics related to garbage * collection emanating from the MXBean and also adds information about GC causes. *

* This provides metrics for OpenJDK garbage collectors: serial, parallel, G1, Shenandoah, ZGC. * * @author Jon Schneider * @author Tommy Ludwig * @see GarbageCollectorMXBean */ @NonNullApi @NonNullFields public class JvmGcMetrics implements MeterBinder, AutoCloseable { private static final InternalLogger log = InternalLoggerFactory.getInstance(JvmGcMetrics.class); private final boolean managementExtensionsPresent = isManagementExtensionsPresent(); private final Iterable tags; @Nullable private String allocationPoolName; @Nullable private String oldGenPoolName; @Nullable private String nonGenerationalMemoryPool; private final List notificationListenerCleanUpRunnables = new CopyOnWriteArrayList<>(); public JvmGcMetrics() { this(emptyList()); } public JvmGcMetrics(Iterable tags) { for (MemoryPoolMXBean mbean : ManagementFactory.getMemoryPoolMXBeans()) { String name = mbean.getName(); if (isAllocationPool(name)) { allocationPoolName = name; } else if (isOldGenPool(name)) { oldGenPoolName = name; } else if (isNonGenerationalHeapPool(name)) { nonGenerationalMemoryPool = name; } } this.tags = tags; } @Override public void bindTo(MeterRegistry registry) { if (!this.managementExtensionsPresent) { return; } double maxLongLivedPoolBytes = getLongLivedHeapPool().map(mem -> getUsageValue(mem, MemoryUsage::getMax)).orElse(0.0); AtomicLong maxDataSize = new AtomicLong((long) maxLongLivedPoolBytes); Gauge.builder("jvm.gc.max.data.size", maxDataSize, AtomicLong::get) .tags(tags) .description("Max size of long-lived heap memory pool") .baseUnit(BaseUnits.BYTES) .register(registry); AtomicLong liveDataSize = new AtomicLong(); Gauge.builder("jvm.gc.live.data.size", liveDataSize, AtomicLong::get) .tags(tags) .description("Size of long-lived heap memory pool after reclamation") .baseUnit(BaseUnits.BYTES) .register(registry); Counter allocatedBytes = Counter.builder("jvm.gc.memory.allocated").tags(tags) .baseUnit(BaseUnits.BYTES) .description("Incremented for an increase in the size of the (young) heap memory pool after one GC to before the next") .register(registry); Counter promotedBytes = (oldGenPoolName == null) ? null : Counter.builder("jvm.gc.memory.promoted").tags(tags) .baseUnit(BaseUnits.BYTES) .description("Count of positive increases in the size of the old generation memory pool before GC to after GC") .register(registry); final AtomicLong allocationPoolSizeAfter = new AtomicLong(0L); for (GarbageCollectorMXBean mbean : ManagementFactory.getGarbageCollectorMXBeans()) { if (!(mbean instanceof NotificationEmitter)) { continue; } NotificationListener notificationListener = (notification, ref) -> { CompositeData cd = (CompositeData) notification.getUserData(); GarbageCollectionNotificationInfo notificationInfo = GarbageCollectionNotificationInfo.from(cd); String gcCause = notificationInfo.getGcCause(); String gcAction = notificationInfo.getGcAction(); GcInfo gcInfo = notificationInfo.getGcInfo(); long duration = gcInfo.getDuration(); if (isConcurrentPhase(gcCause, notificationInfo.getGcName())) { Timer.builder("jvm.gc.concurrent.phase.time") .tags(tags) .tags("action", gcAction, "cause", gcCause) .description("Time spent in concurrent phase") .register(registry) .record(duration, TimeUnit.MILLISECONDS); } else { Timer.builder("jvm.gc.pause") .tags(tags) .tags("action", gcAction, "cause", gcCause) .description("Time spent in GC pause") .register(registry) .record(duration, TimeUnit.MILLISECONDS); } final Map before = gcInfo.getMemoryUsageBeforeGc(); final Map after = gcInfo.getMemoryUsageAfterGc(); if (nonGenerationalMemoryPool != null) { countPoolSizeDelta(gcInfo.getMemoryUsageBeforeGc(), gcInfo.getMemoryUsageAfterGc(), allocatedBytes, allocationPoolSizeAfter, nonGenerationalMemoryPool); if (after.get(nonGenerationalMemoryPool).getUsed() < before.get(nonGenerationalMemoryPool).getUsed()) { liveDataSize.set(after.get(nonGenerationalMemoryPool).getUsed()); final long longLivedMaxAfter = after.get(nonGenerationalMemoryPool).getMax(); maxDataSize.set(longLivedMaxAfter); } return; } if (oldGenPoolName != null) { final long oldBefore = before.get(oldGenPoolName).getUsed(); final long oldAfter = after.get(oldGenPoolName).getUsed(); final long delta = oldAfter - oldBefore; if (delta > 0L) { promotedBytes.increment(delta); } // Some GC implementations such as G1 can reduce the old gen size as part of a minor GC. To track the // live data size we record the value if we see a reduction in the old gen heap size or // after a major GC. if (oldAfter < oldBefore || isMajorGc(notificationInfo.getGcName())) { liveDataSize.set(oldAfter); final long oldMaxAfter = after.get(oldGenPoolName).getMax(); maxDataSize.set(oldMaxAfter); } } if (allocationPoolName != null) { countPoolSizeDelta(gcInfo.getMemoryUsageBeforeGc(), gcInfo.getMemoryUsageAfterGc(), allocatedBytes, allocationPoolSizeAfter, allocationPoolName); } }; NotificationEmitter notificationEmitter = (NotificationEmitter) mbean; notificationEmitter.addNotificationListener(notificationListener, notification -> notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION), null); notificationListenerCleanUpRunnables.add(() -> { try { notificationEmitter.removeNotificationListener(notificationListener); } catch (ListenerNotFoundException ignore) { } }); } } private void countPoolSizeDelta(Map before, Map after, Counter counter, AtomicLong previousPoolSize, String poolName) { final long beforeBytes = before.get(poolName).getUsed(); final long afterBytes = after.get(poolName).getUsed(); final long delta = beforeBytes - previousPoolSize.get(); previousPoolSize.set(afterBytes); if (delta > 0L) { counter.increment(delta); } } private boolean isMajorGc(String gcName) { return GcGenerationAge.fromGcName(gcName) == GcGenerationAge.OLD; } private static boolean isManagementExtensionsPresent() { if ( ManagementFactory.getMemoryPoolMXBeans().isEmpty() ) { // Substrate VM, for example, doesn't provide or support these beans (yet) log.warn("GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM"); return false; } try { Class.forName("com.sun.management.GarbageCollectionNotificationInfo", false, MemoryPoolMXBean.class.getClassLoader()); return true; } catch (Throwable e) { // We are operating in a JVM without access to this level of detail log.warn("GC notifications will not be available because " + "com.sun.management.GarbageCollectionNotificationInfo is not present"); return false; } } @Override public void close() { notificationListenerCleanUpRunnables.forEach(Runnable::run); } /** * Generalization of which parts of the heap are considered "young" or "old" for multiple GC implementations */ @NonNullApi enum GcGenerationAge { OLD, YOUNG, UNKNOWN; private static final Map knownCollectors = new HashMap() {{ put("ConcurrentMarkSweep", OLD); put("Copy", YOUNG); put("G1 Old Generation", OLD); put("G1 Young Generation", YOUNG); put("MarkSweepCompact", OLD); put("PS MarkSweep", OLD); put("PS Scavenge", YOUNG); put("ParNew", YOUNG); }}; static GcGenerationAge fromGcName(String gcName) { return knownCollectors.getOrDefault(gcName, UNKNOWN); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy