/* * Copyright 2017 ThoughtWorks, 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 * * 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 com.thoughtworks.go.server.service.support; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; import java.lang.management.*; import java.lang.reflect.Method; import java.util.*; @Component public class ThreadInformationProvider implements ServerInfoProvider { private final DaemonThreadStatsCollector daemonThreadStatsCollector; private static final Logger LOGGER = LoggerFactory.getLogger(ThreadInformationProvider.class.getName()); @Autowired public ThreadInformationProvider(DaemonThreadStatsCollector daemonThreadStatsCollector) { this.daemonThreadStatsCollector = daemonThreadStatsCollector; } @Override public double priority() { return 13.0; } private Map<String, Object> getThreadCount(ThreadMXBean threadMXBean) { LinkedHashMap<String, Object> count = new LinkedHashMap<>(); count.put("Current", threadMXBean.getThreadCount()); count.put("Total", threadMXBean.getTotalStartedThreadCount()); count.put("Daemon", threadMXBean.getDaemonThreadCount()); count.put("Peak", threadMXBean.getPeakThreadCount()); return count; } private Map<String, Object> getDeadLockThreadInformation(ThreadMXBean threadMXBean) { LinkedHashMap<String, Object> json = new LinkedHashMap<>(); long[] deadlockedThreads = threadMXBean.findDeadlockedThreads(); if (deadlockedThreads != null && deadlockedThreads.length > 0) { json.put("Count", deadlockedThreads.length); for (long deadlockedThread : deadlockedThreads) { LinkedHashMap<String, Object> threadsInfo = new LinkedHashMap<>(); LinkedHashMap<String, Object> lockedMonitorsInfo = new LinkedHashMap<>(); LinkedHashMap<String, Object> stackTrackInfo = new LinkedHashMap<>(); ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThread); LockInfo lockInfo = threadInfo.getLockInfo(); if (lockInfo != null) { threadsInfo.put(threadInfo.getThreadName(), lockInfo); } else { threadsInfo.put(threadInfo.getThreadName(), "This thread is not waiting for any locks"); } MonitorInfo[] lockedMonitors = threadInfo.getLockedMonitors(); for (MonitorInfo lockedMonitor : lockedMonitors) { lockedMonitorsInfo.put("Monitor for class " + lockedMonitor.getClassName(), "taken at stack frame " + lockedMonitor.getLockedStackFrame()); } stackTrackInfo.put(Long.toString(deadlockedThread), Arrays.toString(threadInfo.getStackTrace())); json.put("Thread Information", threadsInfo); json.put("Monitor Information Stack Frame where locks were taken", lockedMonitorsInfo); json.put("Stack Trace Of DeadLock Threads", stackTrackInfo); } } return json; } private TreeMap<Long, Map<String, Object>> getThreadInformation(ThreadMXBean threadMXBean) { TreeMap<Long, Map<String, Object>> traces = new TreeMap<>(); ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); for (ThreadInfo threadInfo : threadInfos) { LinkedHashMap<String, Object> threadStackTrace = new LinkedHashMap<>(); threadStackTrace.put("Id", threadInfo.getThreadId()); threadStackTrace.put("Name", threadInfo.getThreadName()); threadStackTrace.put("State", threadInfo.getThreadState()); threadStackTrace.put("UserTime(nanoseconds)", threadMXBean.getThreadUserTime(threadInfo.getThreadId())); threadStackTrace.put("CPUTime(nanoseconds)", threadMXBean.getThreadCpuTime(threadInfo.getThreadId())); threadStackTrace.put("DaemonThreadInfo", daemonThreadStatsCollector.statsFor(threadInfo.getThreadId())); threadStackTrace.put("AllocatedMemory(Bytes)", getAllocatedMemory(threadMXBean, threadInfo)); LinkedHashMap<String, Object> lockMonitorInfo = new LinkedHashMap<>(); MonitorInfo[] lockedMonitors = threadInfo.getLockedMonitors(); ArrayList<Map<String, Object>> lockedMonitorsJson = new ArrayList<>(); for (MonitorInfo lockedMonitor : lockedMonitors) { LinkedHashMap<String, Object> lockedMonitorJson = new LinkedHashMap<>(); lockedMonitorJson.put("Class", lockedMonitor.getClassName()); lockedMonitorJson.put("IdentityHashCode", lockedMonitor.getIdentityHashCode()); lockedMonitorJson.put("LockedStackDepth", lockedMonitor.getLockedStackDepth()); lockedMonitorJson.put("StackFrame", lockedMonitor.getLockedStackFrame().toString()); lockedMonitorsJson.add(lockedMonitorJson); } lockMonitorInfo.put("Locked Monitors", lockedMonitorsJson); lockMonitorInfo.put("Locked Synchronizers", asJSON(threadInfo.getLockedSynchronizers())); threadStackTrace.put("Lock Monitor Info", lockMonitorInfo); LinkedHashMap<String, Object> blockedInfo = new LinkedHashMap<>(); blockedInfo.put("Blocked Time(ms)", threadInfo.getBlockedTime()); blockedInfo.put("Blocked Count", threadInfo.getBlockedCount()); threadStackTrace.put("Blocked Info", blockedInfo); LinkedHashMap<String, Object> timeInfo = new LinkedHashMap<>(); timeInfo.put("Waited Time(ms)", threadInfo.getWaitedTime()); timeInfo.put("Waited Count", threadInfo.getWaitedCount()); threadStackTrace.put("Time Info", timeInfo); LinkedHashMap<String, Object> lockInfoMap = new LinkedHashMap<>(); LockInfo lockInfo = threadInfo.getLockInfo(); lockInfoMap.put("Locked On", asJSON(lockInfo)); lockInfoMap.put("Lock Owner Thread Id", threadInfo.getLockOwnerId()); lockInfoMap.put("Lock Owner Thread Name", threadInfo.getLockOwnerName()); threadStackTrace.put("Lock Info", lockInfoMap); LinkedHashMap<String, Object> stateInfo = new LinkedHashMap<>(); stateInfo.put("Suspended", threadInfo.isSuspended()); stateInfo.put("InNative", threadInfo.isInNative()); threadStackTrace.put("State Info", stateInfo); threadStackTrace.put("Stack Trace", asJSON(threadInfo.getStackTrace())); traces.put(threadInfo.getThreadId(), threadStackTrace); } return traces; } private long getAllocatedMemory(ThreadMXBean threadMXBean, ThreadInfo threadInfo) { Method method = ReflectionUtils.findMethod(threadMXBean.getClass(), "getThreadAllocatedBytes", long.class); if (method != null) { try { method.setAccessible(true); return (long) method.invoke(threadMXBean, threadInfo.getThreadId()); } catch (Exception e) { LOGGER.error("Error while capturing allocatedMemory for api/support : {}", e.getMessage()); } } return -1; } private Object asJSON(StackTraceElement[] stackTrace) { ArrayList<String> strings = new ArrayList<>(); for (StackTraceElement o : stackTrace) { strings.add(o.toString()); } return strings; } private ArrayList<LinkedHashMap<String, Object>> asJSON(LockInfo[] lockInfos) { ArrayList<LinkedHashMap<String, Object>> objects = new ArrayList<>(); for (LockInfo lockInfo : lockInfos) { objects.add(asJSON(lockInfo)); } return objects; } private LinkedHashMap<String, Object> asJSON(LockInfo lockInfo) { LinkedHashMap<String, Object> lockedOn = new LinkedHashMap<>(); if (lockInfo != null) { lockedOn.put("Class", lockInfo.getClassName()); lockedOn.put("IdentityHashCode", lockInfo.getIdentityHashCode()); } return lockedOn; } @Override public Map<String, Object> asJson() { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); LinkedHashMap<String, Object> json = new LinkedHashMap<>(); json.put("Thread Count", getThreadCount(threadMXBean)); json.put("DeadLock Threads", getDeadLockThreadInformation(threadMXBean)); json.put("Stack Trace", getThreadInformation(threadMXBean)); return json; } @Override public String name() { return "Thread Information"; } }