/** * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.seed.core.internal.diagnostic; import com.google.common.collect.Maps; import org.seedstack.seed.SeedException; import org.seedstack.seed.core.internal.CoreErrorCode; import org.seedstack.seed.diagnostic.DiagnosticManager; import org.seedstack.seed.diagnostic.spi.DiagnosticInfoCollector; import org.seedstack.seed.diagnostic.spi.DiagnosticReporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Implementation of the diagnostic manager. */ public class DiagnosticManagerImpl implements DiagnosticManager { private static final String REPORTER_SYSTEM_PROPERTY = "seedstack.diagnostic"; private static final Logger LOGGER = LoggerFactory.getLogger(DiagnosticManagerImpl.class); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss.SSS"); private final ConcurrentMap<String, DiagnosticInfoCollector> diagnosticCollectors = new ConcurrentHashMap<>(); private final DiagnosticReporter diagnosticReporter; public DiagnosticManagerImpl() { String reporterClassName = System.getProperty(REPORTER_SYSTEM_PROPERTY); DiagnosticReporter diagnosticReporterToUse; if (reporterClassName != null) { try { diagnosticReporterToUse = (DiagnosticReporter) Class.forName(reporterClassName).newInstance(); } catch (Exception e) { LOGGER.warn("Custom diagnostic reporter {} cannot be instantiated, falling back to default reporter", reporterClassName, e); diagnosticReporterToUse = new DefaultDiagnosticReporter(); } } else { diagnosticReporterToUse = new DefaultDiagnosticReporter(); } this.diagnosticReporter = diagnosticReporterToUse; } @Override public Map<String, Object> getDiagnosticInfo(Throwable t) { return collectAllDiagnostics(t); } @Override public void dumpDiagnosticReport(Throwable t) { try { diagnosticReporter.writeDiagnosticReport(collectAllDiagnostics(t)); } catch (Exception e) { LOGGER.error("Unable to write diagnostic information", e); throw SeedException.wrap(t, CoreErrorCode.RETHROW_EXCEPTION_AFTER_DIAGNOSTIC_FAILURE); } } @Override public void registerDiagnosticInfoCollector(String domain, DiagnosticInfoCollector diagnosticInfoCollector) { diagnosticCollectors.put(domain, diagnosticInfoCollector); } private synchronized Map<String, Object> collectAllDiagnostics(Throwable t) { Map<String, Object> allDiagnostics = new HashMap<>(); if (t != null) { Map<String, Object> exceptionInfo = new HashMap<>(); buildExceptionInfo(exceptionInfo, t); allDiagnostics.put("exception", exceptionInfo); } allDiagnostics.put("system", collectSystemInfo()); for (Map.Entry<String, DiagnosticInfoCollector> diagnosticInfoCollectorEntry : diagnosticCollectors.entrySet()) { allDiagnostics.put(diagnosticInfoCollectorEntry.getKey(), diagnosticInfoCollectorEntry.getValue().collect()); } return allDiagnostics; } private void buildExceptionInfo(Map<String, Object> exceptionInfo, Throwable t) { exceptionInfo.put("class", t.getClass().getCanonicalName()); exceptionInfo.put("message", t.getMessage()); List<String> stackTraceList = new ArrayList<>(); for (StackTraceElement stackTraceElement : t.getStackTrace()) { stackTraceList.add(stackTraceElement.toString()); } exceptionInfo.put("stacktrace", stackTraceList); if (t instanceof SeedException) { SeedException seedException = (SeedException) t; exceptionInfo.put("causes", seedException.getCauses()); exceptionInfo.put("fix", seedException.getFix()); } else { Throwable cause = t.getCause(); if (cause != null) { Map<String, Object> causeInfo = new HashMap<>(); buildExceptionInfo(causeInfo, cause); // only recurse when it is not a SEED exception exceptionInfo.put("cause", causeInfo); } } } private Map<String, Object> collectSystemInfo() { Map<String, Object> result = new HashMap<>(); result.put("diagnosticTime", DATE_FORMAT.format(new Date())); result.put("properties", buildSystemPropertiesList()); result.put("threads", buildThreadList()); Runtime runtime = Runtime.getRuntime(); result.put("processors", runtime.availableProcessors()); result.put("memory", buildMemoryInfo(runtime)); RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); result.put("args", runtimeMXBean.getInputArguments()); result.put("startTime", DATE_FORMAT.format(runtimeMXBean.getStartTime())); return result; } private Map<Long, Object> buildThreadList() { Map<Long, Object> results = new HashMap<>(); ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); for (long threadId : threadMXBean.getAllThreadIds()) { ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId); if (threadInfo != null) { // checks if the thread is not alive or it does not exist. Map<String, Object> threadResults = new HashMap<>(); threadResults.put("name", threadInfo.getThreadName()); threadResults.put("cpu", threadMXBean.getThreadCpuTime(threadId)); threadResults.put("user", threadMXBean.getThreadUserTime(threadId)); threadResults.put("blockedCount", threadInfo.getBlockedCount()); threadResults.put("blockedTime", threadInfo.getBlockedTime()); threadResults.put("waitedCount", threadInfo.getWaitedCount()); threadResults.put("waitedTime", threadInfo.getWaitedTime()); threadResults.put("suspended", threadInfo.isSuspended()); threadResults.put("native", threadInfo.isInNative()); threadResults.put("state", threadInfo.getThreadState().toString()); results.put(threadId, threadResults); } } return results; } private Map<String, String> buildSystemPropertiesList() { return Maps.fromProperties(System.getProperties()); } private Map<String, Long> buildMemoryInfo(Runtime runtime) { Map<String, Long> results = new HashMap<>(); results.put("total", runtime.totalMemory()); results.put("used", runtime.totalMemory() - runtime.freeMemory()); results.put("free", runtime.freeMemory()); results.put("max", runtime.maxMemory()); return results; } }