/* * Copyright 2015-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.live; import java.lang.management.LockInfo; import java.lang.management.ManagementFactory; import java.lang.management.MonitorInfo; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.primitives.Longs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.impl.ThreadContextImpl; import org.glowroot.agent.impl.Transaction; import org.glowroot.agent.impl.TransactionCollector; import org.glowroot.agent.impl.TransactionRegistry; import org.glowroot.common.util.NotAvailableAware; import org.glowroot.wire.api.model.DownstreamServiceOuterClass.ThreadDump; import org.glowroot.wire.api.model.Proto.OptionalInt64; class ThreadDumpService { private static final Logger logger = LoggerFactory.getLogger(ThreadDumpService.class); private final TransactionRegistry transactionRegistry; private final TransactionCollector transactionCollector; ThreadDumpService(TransactionRegistry transactionRegistry, TransactionCollector transactionCollector) { this.transactionRegistry = transactionRegistry; this.transactionCollector = transactionCollector; } ThreadDump getThreadDump() { ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); List<ThreadContextImpl> activeThreadContexts = Lists.newArrayList(); for (Transaction transaction : transactionRegistry.getTransactions()) { ThreadContextImpl mainThreadContext = transaction.getMainThreadContext(); if (mainThreadContext.isActive()) { activeThreadContexts.add(mainThreadContext); } activeThreadContexts.addAll(transaction.getActiveAuxThreadContexts()); } @Nullable ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadBean.getAllThreadIds(), threadBean.isObjectMonitorUsageSupported(), false); long currentThreadId = Thread.currentThread().getId(); Map<Long, ThreadInfo> unmatchedThreadInfos = Maps.newHashMap(); ThreadInfo currentThreadInfo = null; for (ThreadInfo threadInfo : threadInfos) { if (threadInfo == null) { continue; } if (threadInfo.getThreadId() == currentThreadId) { currentThreadInfo = threadInfo; } else { unmatchedThreadInfos.put(threadInfo.getThreadId(), threadInfo); } } Map<String, TransactionThreadInfo> transactionThreadInfos = Maps.newHashMap(); // active thread contexts for a given transaction are already sorted by age // so that main thread context will always appear first within a given matched transaction, // and its auxiliary threads will be then sorted by age for (ThreadContextImpl threadContext : activeThreadContexts) { if (!threadContext.isActive()) { continue; } long threadId = threadContext.getThreadId(); ThreadInfo threadInfo = unmatchedThreadInfos.remove(threadId); if (threadInfo == null) { // this should not happen since this thread context was active before and after the // thread dump logger.warn("thread dump not captured for thread: {}", threadId); continue; } Transaction transaction = threadContext.getTransaction(); String traceId = transaction.getTraceId(); TransactionThreadInfo transactionThreadInfo = transactionThreadInfos.get(traceId); if (transactionThreadInfo == null) { transactionThreadInfo = new TransactionThreadInfo(transaction.getHeadline(), transaction.getTransactionType(), transaction.getTransactionName(), transaction.getDurationNanos(), transaction.getTotalCpuNanos(), transactionCollector.shouldStoreSlow(transaction)); transactionThreadInfos.put(traceId, transactionThreadInfo); } transactionThreadInfo.threadInfos.add(threadInfo); } List<ThreadDump.Transaction> transactions = Lists.newArrayList(); for (Entry<String, TransactionThreadInfo> entry : transactionThreadInfos.entrySet()) { TransactionThreadInfo value = entry.getValue(); ThreadDump.Transaction.Builder builder = ThreadDump.Transaction.newBuilder() .setHeadline(value.headline) .setTransactionType(value.transactionType) .setTransactionName(value.transactionName) .setTotalDurationNanos(value.totalDurationNanos); if (!NotAvailableAware.isNA(value.totalCpuNanos)) { builder.setTotalCpuNanos(OptionalInt64.newBuilder().setValue(value.totalCpuNanos)); } if (value.shouldStoreSlow) { builder.setTraceId(entry.getKey()); } for (ThreadInfo auxThreadInfo : value.threadInfos) { builder.addThread(createProtobuf(auxThreadInfo)); } transactions.add(builder.build()); } List<ThreadDump.Thread> unmatchedThreads = Lists.newArrayList(); for (ThreadInfo unmatchedThreadInfo : unmatchedThreadInfos.values()) { unmatchedThreads.add(createProtobuf(unmatchedThreadInfo)); } // sort descending by total time Collections.sort(transactions, new TransactionOrdering()); // sort descending by thread id (same as jstack) Collections.sort(unmatchedThreads, new UnmatchedThreadOrdering()); ThreadDump.Builder builder = ThreadDump.newBuilder() .addAllTransaction(transactions) .addAllUnmatchedThread(unmatchedThreads); if (currentThreadInfo != null) { builder.setThreadDumpingThread(createProtobuf(currentThreadInfo)); } return builder.build(); } private ThreadDump.Thread createProtobuf(ThreadInfo threadInfo) { ThreadDump.Thread.Builder builder = ThreadDump.Thread.newBuilder() .setName(threadInfo.getThreadName()) .setId(threadInfo.getThreadId()) .setState(threadInfo.getThreadState().name()); LockInfo lockInfo = threadInfo.getLockInfo(); if (lockInfo != null) { builder.setLockInfo(ThreadDump.LockInfo.newBuilder() .setIdentityHashCode(lockInfo.getIdentityHashCode()) .setClassName(lockInfo.getClassName())); long lockOwnerId = threadInfo.getLockOwnerId(); if (lockOwnerId != -1) { builder.setLockOwnerId(OptionalInt64.newBuilder().setValue(lockOwnerId)); } } List<ThreadDump.StackTraceElement.Builder> stackTraceElements = Lists.newArrayList(); for (StackTraceElement stackTraceElement : threadInfo.getStackTrace()) { stackTraceElements.add(ThreadDump.StackTraceElement.newBuilder() .setClassName(stackTraceElement.getClassName()) .setMethodName(Strings.nullToEmpty(stackTraceElement.getMethodName())) .setFileName(Strings.nullToEmpty(stackTraceElement.getFileName())) .setLineNumber(stackTraceElement.getLineNumber())); } for (MonitorInfo lockedMonitor : threadInfo.getLockedMonitors()) { int lockedStackDepth = lockedMonitor.getLockedStackDepth(); if (lockedStackDepth >= 0) { stackTraceElements.get(lockedStackDepth) .addMonitorInfo(ThreadDump.LockInfo.newBuilder() .setClassName(lockedMonitor.getClassName()) .setIdentityHashCode(lockedMonitor.getIdentityHashCode())); } } for (ThreadDump.StackTraceElement.Builder stackTraceElement : stackTraceElements) { builder.addStackTraceElement(stackTraceElement); } return builder.build(); } private static class TransactionOrdering extends Ordering<ThreadDump.Transaction> { @Override public int compare(ThreadDump.Transaction left, ThreadDump.Transaction right) { return Longs.compare(right.getTotalDurationNanos(), left.getTotalDurationNanos()); } } private static class UnmatchedThreadOrdering extends Ordering<ThreadDump.Thread> { @Override public int compare(ThreadDump.Thread left, ThreadDump.Thread right) { return Longs.compare(right.getId(), left.getId()); } } private static class TransactionThreadInfo { private final String headline; private final String transactionType; private final String transactionName; private final long totalDurationNanos; private final long totalCpuNanos; private final boolean shouldStoreSlow; private final List<ThreadInfo> threadInfos = Lists.newArrayList(); private TransactionThreadInfo(String headline, String transactionType, String transactionName, long totalDurationNanos, long totalCpuNanos, boolean shouldStoreSlow) { this.headline = headline; this.transactionType = transactionType; this.transactionName = transactionName; this.totalDurationNanos = totalDurationNanos; this.totalCpuNanos = totalCpuNanos; this.shouldStoreSlow = shouldStoreSlow; } } }