/* * Copyright 2013-2016 the original author or authors. * * 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 * * http://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 org.glowroot.agent.model; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.glowroot.agent.util.ThreadAllocatedBytes; import static com.google.common.base.Preconditions.checkNotNull; public class ThreadStatsComponent { private static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); private static final boolean IS_THREAD_CPU_TIME_SUPPORTED = threadMXBean.isThreadCpuTimeSupported(); private static final boolean IS_THREAD_CONTENTION_MONITORING_SUPPORTED = threadMXBean.isThreadContentionMonitoringSupported(); private final long threadId; private final long startingCpuNanos; private final long startingBlockedMillis; private final long startingWaitedMillis; private final long startingAllocatedBytes; private final @Nullable ThreadAllocatedBytes threadAllocatedBytes; @GuardedBy("lock") private volatile @MonotonicNonNull ThreadStats completedThreadStats; private final Object lock = new Object(); public ThreadStatsComponent(@Nullable ThreadAllocatedBytes threadAllocatedBytes) { threadId = Thread.currentThread().getId(); ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId, 0); // thread info for current thread cannot be null checkNotNull(threadInfo); if (IS_THREAD_CPU_TIME_SUPPORTED) { startingCpuNanos = threadMXBean.getCurrentThreadCpuTime(); } else { startingCpuNanos = -1; } if (IS_THREAD_CONTENTION_MONITORING_SUPPORTED) { startingBlockedMillis = threadInfo.getBlockedTime(); startingWaitedMillis = threadInfo.getWaitedTime(); } else { startingBlockedMillis = -1; startingWaitedMillis = -1; } if (threadAllocatedBytes != null) { startingAllocatedBytes = threadAllocatedBytes.getThreadAllocatedBytesSafely(threadId); } else { startingAllocatedBytes = -1; } this.threadAllocatedBytes = threadAllocatedBytes; } // must be called from transaction thread public void onComplete() { synchronized (lock) { completedThreadStats = getThreadStats(); } } // safe to be called from another thread public ThreadStats getThreadStats() { synchronized (lock) { if (completedThreadStats == null) { // transaction thread is still alive (and cannot terminate in the middle of this // method because of above lock), so safe to capture ThreadMXBean.getThreadInfo() // and ThreadMXBean.getThreadCpuTime() for the transaction thread return getThreadStatsInternal(); } else { return completedThreadStats; } } } // safe to be called from another thread public long getTotalCpuNanos() { synchronized (lock) { if (completedThreadStats == null) { // transaction thread is still alive (and cannot terminate in the middle of this // method because of above lock), so safe to capture ThreadMXBean.getThreadCpuTime() // for the transaction thread if (IS_THREAD_CPU_TIME_SUPPORTED) { return getTotalCpuNanosInternal(); } else { return -1; } } else { return completedThreadStats.getTotalCpuNanos(); } } } private ThreadStats getThreadStatsInternal() { ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId, 0); if (threadInfo == null) { // thread must have just recently terminated return new ThreadStats(-1, -1, -1, -1); } long totalCpuNanos; if (IS_THREAD_CPU_TIME_SUPPORTED) { totalCpuNanos = getTotalCpuNanosInternal(); } else { totalCpuNanos = -1; } long totalBlockedMillis; long totalWaitedMillis; if (IS_THREAD_CONTENTION_MONITORING_SUPPORTED) { totalBlockedMillis = getTotalBlockedMillis(threadInfo); totalWaitedMillis = getTotalWaitedMillis(threadInfo); } else { totalBlockedMillis = -1; totalWaitedMillis = -1; } long totalAllocatedBytes; if (this.threadAllocatedBytes != null) { totalAllocatedBytes = getThreadAllocatedBytes(); } else { totalAllocatedBytes = -1; } return new ThreadStats(totalCpuNanos, totalBlockedMillis, totalWaitedMillis, totalAllocatedBytes); } private long getTotalCpuNanosInternal() { // getThreadCpuTime() returns -1 if CPU time measurement is disabled (which is different // than whether or not it is supported) long threadCpuNanos = threadMXBean.getThreadCpuTime(threadId); if (startingCpuNanos != -1 && threadCpuNanos != -1) { return threadCpuNanos - startingCpuNanos; } else { return -1; } } private long getTotalBlockedMillis(ThreadInfo threadInfo) { // getBlockedTime() return -1 if thread contention monitoring is disabled (which is // different than whether or not it is supported) long threadBlockedTimeMillis = threadInfo.getBlockedTime(); if (startingBlockedMillis != -1 && threadBlockedTimeMillis != -1) { return threadBlockedTimeMillis - startingBlockedMillis; } else { return -1; } } private long getTotalWaitedMillis(ThreadInfo threadInfo) { // getWaitedTime() returns -1 if thread contention monitoring is disabled (which is // different than whether or not it is supported) long threadWaitedTimeMillis = threadInfo.getWaitedTime(); if (startingWaitedMillis != -1 && threadWaitedTimeMillis != -1) { return threadWaitedTimeMillis - startingWaitedMillis; } else { return -1; } } @RequiresNonNull("threadAllocatedBytes") private long getThreadAllocatedBytes() { long allocatedBytes = threadAllocatedBytes.getThreadAllocatedBytesSafely(threadId); if (startingAllocatedBytes != -1 && allocatedBytes != -1) { return allocatedBytes - startingAllocatedBytes; } else { return -1; } } }