/*
* Copyright 2008-2017 by Emeric Vernat
*
* This file is part of Java Melody.
*
* 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 net.bull.javamelody;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.ThreadMXBean;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.sql.DataSource;
/**
* Informations systèmes sur le serveur, sans code html de présentation.
* L'état d'une instance est initialisé à son instanciation et non mutable;
* il est donc de fait thread-safe.
* Cet état est celui d'une instance de JVM java, de ses threads et du système à un instant t.
* Les instances sont sérialisables pour pouvoir être transmises au serveur de collecte.
* @author Emeric Vernat
*/
class JavaInformations implements Serializable { // NOPMD
static final double HIGH_USAGE_THRESHOLD_IN_PERCENTS = 95d;
private static final long serialVersionUID = 3281861236369720876L;
private static final Date START_DATE = new Date();
private static final boolean SYSTEM_CPU_LOAD_ENABLED = "1.7"
.compareTo(Parameters.JAVA_VERSION) < 0;
private static final boolean SPRING_AVAILABLE = isSpringAvailable();
private static boolean localWebXmlExists = true; // true par défaut
private static boolean localPomXmlExists = true; // true par défaut
private final MemoryInformations memoryInformations;
@SuppressWarnings("all")
private final List<TomcatInformations> tomcatInformationsList;
private final int sessionCount;
private final long sessionAgeSum;
private final int activeThreadCount;
private final int usedConnectionCount;
private final int maxConnectionCount;
private final int activeConnectionCount;
private final long transactionCount;
private final long processCpuTimeMillis;
private final double systemLoadAverage;
private final double systemCpuLoad;
private final long unixOpenFileDescriptorCount;
private final long unixMaxFileDescriptorCount;
private final String host;
private final String os;
private final int availableProcessors;
private final String javaVersion;
private final String jvmVersion;
private final String pid;
private final String serverInfo;
private final String contextPath;
private final String contextDisplayName;
private final String webappVersion;
private final Date startDate;
private final String jvmArguments;
private final long freeDiskSpaceInTemp;
private final int threadCount;
private final int peakThreadCount;
private final long totalStartedThreadCount;
private final String dataBaseVersion;
private final String dataSourceDetails;
@SuppressWarnings("all")
private final List<ThreadInformations> threadInformationsList;
@SuppressWarnings("all")
private final List<CacheInformations> cacheInformationsList;
@SuppressWarnings("all")
private final List<JobInformations> jobInformationsList;
private final boolean webXmlExists = localWebXmlExists;
private final boolean pomXmlExists = localPomXmlExists;
private final boolean springBeanExists;
static final class ThreadInformationsComparator
implements Comparator<ThreadInformations>, Serializable {
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public int compare(ThreadInformations thread1, ThreadInformations thread2) {
return thread1.getName().compareToIgnoreCase(thread2.getName());
}
}
static final class CacheInformationsComparator
implements Comparator<CacheInformations>, Serializable {
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public int compare(CacheInformations cache1, CacheInformations cache2) {
return cache1.getName().compareToIgnoreCase(cache2.getName());
}
}
static final class JobInformationsComparator
implements Comparator<JobInformations>, Serializable {
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public int compare(JobInformations job1, JobInformations job2) {
return job1.getName().compareToIgnoreCase(job2.getName());
}
}
// CHECKSTYLE:OFF
JavaInformations(ServletContext servletContext, boolean includeDetails) {
// CHECKSTYLE:ON
super();
memoryInformations = new MemoryInformations();
tomcatInformationsList = TomcatInformations.buildTomcatInformationsList();
sessionCount = SessionListener.getSessionCount();
sessionAgeSum = SessionListener.getSessionAgeSum();
activeThreadCount = JdbcWrapper.getActiveThreadCount();
usedConnectionCount = JdbcWrapper.getUsedConnectionCount();
activeConnectionCount = JdbcWrapper.getActiveConnectionCount();
maxConnectionCount = JdbcWrapper.getMaxConnectionCount();
transactionCount = JdbcWrapper.getTransactionCount();
systemLoadAverage = buildSystemLoadAverage();
systemCpuLoad = buildSystemCpuLoad();
processCpuTimeMillis = buildProcessCpuTimeMillis();
unixOpenFileDescriptorCount = buildOpenFileDescriptorCount();
unixMaxFileDescriptorCount = buildMaxFileDescriptorCount();
host = Parameters.getHostName() + '@' + Parameters.getHostAddress();
os = buildOS();
availableProcessors = Runtime.getRuntime().availableProcessors();
javaVersion = System.getProperty("java.runtime.name") + ", "
+ System.getProperty("java.runtime.version");
jvmVersion = System.getProperty("java.vm.name") + ", "
+ System.getProperty("java.vm.version") + ", " + System.getProperty("java.vm.info");
if (servletContext == null) {
serverInfo = null;
contextPath = null;
contextDisplayName = null;
webappVersion = null;
} else {
serverInfo = servletContext.getServerInfo();
contextPath = Parameters.getContextPath(servletContext);
contextDisplayName = servletContext.getServletContextName();
webappVersion = MavenArtifact.getWebappVersion();
}
startDate = START_DATE;
jvmArguments = buildJvmArguments();
final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
threadCount = threadBean.getThreadCount();
peakThreadCount = threadBean.getPeakThreadCount();
totalStartedThreadCount = threadBean.getTotalStartedThreadCount();
freeDiskSpaceInTemp = Parameters.TEMPORARY_DIRECTORY.getFreeSpace();
springBeanExists = SPRING_AVAILABLE && SpringContext.getSingleton() != null;
if (includeDetails) {
dataBaseVersion = buildDataBaseVersion();
dataSourceDetails = buildDataSourceDetails();
threadInformationsList = buildThreadInformationsList();
cacheInformationsList = CacheInformations.buildCacheInformationsList();
jobInformationsList = JobInformations.buildJobInformationsList();
pid = PID.getPID();
} else {
dataBaseVersion = null;
dataSourceDetails = null;
threadInformationsList = null;
cacheInformationsList = null;
jobInformationsList = null;
pid = null;
}
}
static void setWebXmlExistsAndPomXmlExists(boolean webXmlExists, boolean pomXmlExists) {
localWebXmlExists = webXmlExists;
localPomXmlExists = pomXmlExists;
}
boolean doesWebXmlExists() {
return webXmlExists;
}
boolean doesPomXmlExists() {
return pomXmlExists;
}
private static String buildOS() {
final String name = System.getProperty("os.name");
final String version = System.getProperty("os.version");
final String patchLevel = System.getProperty("sun.os.patch.level");
final String arch = System.getProperty("os.arch");
final String bits = System.getProperty("sun.arch.data.model");
final StringBuilder sb = new StringBuilder();
sb.append(name).append(", ");
if (!name.toLowerCase(Locale.ENGLISH).contains("windows")) {
// version is "6.1" and useless for os.name "Windows 7",
// and can be "2.6.32-358.23.2.el6.x86_64" for os.name "Linux"
sb.append(version).append(' ');
}
if (!"unknown".equals(patchLevel)) {
// patchLevel is "unknown" and useless on Linux,
// and can be "Service Pack 1" on Windows
sb.append(patchLevel);
}
sb.append(", ").append(arch).append('/').append(bits);
return sb.toString();
}
private static long buildProcessCpuTimeMillis() {
final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
if (isSunOsMBean(operatingSystem)) {
// nano-secondes converties en milli-secondes
return MemoryInformations.getLongFromOperatingSystem(operatingSystem,
"getProcessCpuTime") / 1000000;
}
return -1;
}
private static long buildOpenFileDescriptorCount() {
final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
if (isSunOsMBean(operatingSystem) && isSunUnixMBean(operatingSystem)) {
try {
return MemoryInformations.getLongFromOperatingSystem(operatingSystem,
"getOpenFileDescriptorCount");
} catch (final Error e) {
// pour issue 16 (using jsvc on ubuntu or debian)
return -1;
}
}
return -1;
}
private static long buildMaxFileDescriptorCount() {
final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
if (isSunOsMBean(operatingSystem) && isSunUnixMBean(operatingSystem)) {
try {
return MemoryInformations.getLongFromOperatingSystem(operatingSystem,
"getMaxFileDescriptorCount");
} catch (final Error e) {
// pour issue 16 (using jsvc on ubuntu or debian)
return -1;
}
}
return -1;
}
private static double buildSystemCpuLoad() {
// System cpu load.
// The "recent cpu usage" for the whole system.
// This value is a double in the [0.0,1.0] interval.
// A value of 0.0 means that all CPUs were idle during the recent period of time observed,
// while a value of 1.0 means that all CPUs were actively running 100% of the time during the recent period being observed.
final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
if (SYSTEM_CPU_LOAD_ENABLED && isSunOsMBean(operatingSystem)) {
// systemCpuLoad n'existe qu'à partir du jdk 1.7
return MemoryInformations.getDoubleFromOperatingSystem(operatingSystem,
"getSystemCpuLoad") * 100;
}
return -1;
}
private static double buildSystemLoadAverage() {
// System load average for the last minute.
// The system load average is the sum of
// the number of runnable entities queued to the available processors
// and the number of runnable entities running on the available processors
// averaged over a period of time.
final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
if (operatingSystem.getSystemLoadAverage() >= 0) {
// systemLoadAverage n'existe qu'à partir du jdk 1.6
return operatingSystem.getSystemLoadAverage();
}
return -1;
}
private static String buildJvmArguments() {
final StringBuilder jvmArgs = new StringBuilder();
for (final String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
jvmArgs.append(jvmArg).append('\n');
}
if (jvmArgs.length() > 0) {
jvmArgs.deleteCharAt(jvmArgs.length() - 1);
}
return jvmArgs.toString();
}
static List<ThreadInformations> buildThreadInformationsList() {
final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
final Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
final List<Thread> threads = new ArrayList<Thread>(stackTraces.keySet());
// si "1.6.0_01".compareTo(Parameters.JAVA_VERSION) > 0;
// on récupèrait les threads sans stack trace en contournant bug 6434648 avant 1.6.0_01
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6434648
// hormis pour le thread courant qui obtient sa stack trace différemment sans le bug
// threads = getThreadsFromThreadGroups();
// final Thread currentThread = Thread.currentThread();
// stackTraces = Collections.singletonMap(currentThread, currentThread.getStackTrace());
final boolean cpuTimeEnabled = threadBean.isThreadCpuTimeSupported()
&& threadBean.isThreadCpuTimeEnabled();
final long[] deadlockedThreads = getDeadlockedThreads(threadBean);
final List<ThreadInformations> threadInfosList = new ArrayList<ThreadInformations>(
threads.size());
// hostAddress récupéré ici car il peut y avoir plus de 20000 threads
final String hostAddress = Parameters.getHostAddress();
for (final Thread thread : threads) {
final StackTraceElement[] stackTraceElements = stackTraces.get(thread);
final List<StackTraceElement> stackTraceElementList = stackTraceElements == null ? null
: new ArrayList<StackTraceElement>(Arrays.asList(stackTraceElements));
final long cpuTimeMillis;
final long userTimeMillis;
if (cpuTimeEnabled) {
cpuTimeMillis = threadBean.getThreadCpuTime(thread.getId()) / 1000000;
userTimeMillis = threadBean.getThreadUserTime(thread.getId()) / 1000000;
} else {
cpuTimeMillis = -1;
userTimeMillis = -1;
}
final boolean deadlocked = deadlockedThreads != null
&& Arrays.binarySearch(deadlockedThreads, thread.getId()) >= 0;
// stackTraceElementList est une ArrayList et non unmodifiableList pour lisibilité xml
threadInfosList.add(new ThreadInformations(thread, stackTraceElementList, cpuTimeMillis,
userTimeMillis, deadlocked, hostAddress));
}
// on retourne ArrayList et non unmodifiableList pour lisibilité du xml par xstream
return threadInfosList;
}
static List<Thread> getThreadsFromThreadGroups() {
ThreadGroup group = Thread.currentThread().getThreadGroup(); // NOPMD
while (group.getParent() != null) {
group = group.getParent();
}
final Thread[] threadsArray = new Thread[group.activeCount()];
group.enumerate(threadsArray, true);
return Arrays.asList(threadsArray);
}
private static long[] getDeadlockedThreads(ThreadMXBean threadBean) {
final long[] deadlockedThreads;
if (threadBean.isSynchronizerUsageSupported()) {
deadlockedThreads = threadBean.findDeadlockedThreads();
} else {
deadlockedThreads = threadBean.findMonitorDeadlockedThreads();
}
if (deadlockedThreads != null) {
Arrays.sort(deadlockedThreads);
}
return deadlockedThreads;
}
private static String buildDataBaseVersion() {
if (Parameters.isNoDatabase()) {
return null;
}
final StringBuilder result = new StringBuilder();
try {
// on commence par voir si le driver jdbc a été utilisé
// car s'il n'y a pas de datasource une exception est déclenchée
if (Parameters.getLastConnectUrl() != null) {
final Connection connection = DriverManager.getConnection(
Parameters.getLastConnectUrl(), Parameters.getLastConnectInfo());
connection.setAutoCommit(false);
try {
appendDataBaseVersion(result, connection);
} finally {
// rollback inutile ici car on ne fait que lire les meta-data (+ cf issue 38)
connection.close();
}
}
// on cherche une datasource avec InitialContext pour afficher nom et version bdd + nom et version driver jdbc
// (le nom de la dataSource recherchée dans JNDI est du genre jdbc/Xxx qui est le nom standard d'une DataSource)
final Map<String, DataSource> dataSources = JdbcWrapper.getJndiAndSpringDataSources();
for (final Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
final String name = entry.getKey();
final DataSource dataSource = entry.getValue();
final Connection connection = dataSource.getConnection();
// on ne doit pas changer autoCommit pour la connection d'une DataSource
// (ou alors il faudrait remettre l'autoCommit après, issue 233)
// connection.setAutoCommit(false);
try {
if (result.length() > 0) {
result.append("\n\n");
}
result.append(name).append(":\n");
appendDataBaseVersion(result, connection);
} finally {
// rollback inutile ici car on ne fait que lire les meta-data (+ cf issue 38)
connection.close();
}
}
} catch (final Exception e) {
result.append(e.toString());
}
if (result.length() > 0) {
return result.toString();
}
return null;
}
private static void appendDataBaseVersion(StringBuilder result, Connection connection)
throws SQLException {
final DatabaseMetaData metaData = connection.getMetaData();
// Sécurité: pour l'instant on n'indique pas metaData.getUserName()
result.append(metaData.getURL()).append('\n');
result.append(metaData.getDatabaseProductName()).append(", ")
.append(metaData.getDatabaseProductVersion()).append('\n');
result.append("Driver JDBC:\n").append(metaData.getDriverName()).append(", ")
.append(metaData.getDriverVersion());
}
private static String buildDataSourceDetails() {
final Map<String, Map<String, Object>> dataSourcesProperties = JdbcWrapper
.getBasicDataSourceProperties();
final StringBuilder sb = new StringBuilder();
for (final Map.Entry<String, Map<String, Object>> entry : dataSourcesProperties
.entrySet()) {
final Map<String, Object> dataSourceProperties = entry.getValue();
if (dataSourceProperties.isEmpty()) {
continue;
}
if (sb.length() > 0) {
sb.append('\n');
}
final String name = entry.getKey();
if (name != null) {
sb.append(name).append(":\n");
}
for (final Map.Entry<String, Object> propertyEntry : dataSourceProperties.entrySet()) {
sb.append(propertyEntry.getKey()).append(" = ").append(propertyEntry.getValue())
.append('\n');
}
}
if (sb.length() == 0) {
return null;
}
return sb.toString();
}
private static boolean isSunOsMBean(OperatingSystemMXBean operatingSystem) {
// on ne teste pas operatingSystem instanceof com.sun.management.OperatingSystemMXBean
// car le package com.sun n'existe à priori pas sur une jvm tierce
final String className = operatingSystem.getClass().getName();
return "com.sun.management.OperatingSystem".equals(className)
|| "com.sun.management.UnixOperatingSystem".equals(className)
// sun.management.OperatingSystemImpl pour java 8
|| "sun.management.OperatingSystemImpl".equals(className);
}
private static boolean isSunUnixMBean(OperatingSystemMXBean operatingSystem) {
for (final Class<?> inter : operatingSystem.getClass().getInterfaces()) {
if ("com.sun.management.UnixOperatingSystemMXBean".equals(inter.getName())) {
return true;
}
}
return false;
}
MemoryInformations getMemoryInformations() {
return memoryInformations;
}
List<TomcatInformations> getTomcatInformationsList() {
return tomcatInformationsList;
}
int getSessionCount() {
return sessionCount;
}
long getSessionAgeSum() {
return sessionAgeSum;
}
long getSessionMeanAgeInMinutes() {
if (sessionCount > 0) {
return sessionAgeSum / sessionCount / 60000;
}
return -1;
}
int getActiveThreadCount() {
return activeThreadCount;
}
int getUsedConnectionCount() {
return usedConnectionCount;
}
int getActiveConnectionCount() {
return activeConnectionCount;
}
int getMaxConnectionCount() {
return maxConnectionCount;
}
long getTransactionCount() {
return transactionCount;
}
double getUsedConnectionPercentage() {
if (maxConnectionCount > 0) {
return 100d * usedConnectionCount / maxConnectionCount;
}
return -1d;
}
long getProcessCpuTimeMillis() {
return processCpuTimeMillis;
}
double getSystemLoadAverage() {
return systemLoadAverage;
}
double getSystemCpuLoad() {
return systemCpuLoad;
}
long getUnixOpenFileDescriptorCount() {
return unixOpenFileDescriptorCount;
}
long getUnixMaxFileDescriptorCount() {
return unixMaxFileDescriptorCount;
}
double getUnixOpenFileDescriptorPercentage() {
if (unixOpenFileDescriptorCount >= 0) {
return 100d * unixOpenFileDescriptorCount / unixMaxFileDescriptorCount;
}
return -1d;
}
String getHost() {
return host;
}
String getOS() {
return os;
}
int getAvailableProcessors() {
return availableProcessors;
}
String getJavaVersion() {
return javaVersion;
}
String getJvmVersion() {
return jvmVersion;
}
String getPID() {
return pid;
}
String getServerInfo() {
return serverInfo;
}
String getContextPath() {
return contextPath;
}
String getContextDisplayName() {
return contextDisplayName;
}
String getWebappVersion() {
return webappVersion;
}
Date getStartDate() {
return startDate;
}
String getJvmArguments() {
return jvmArguments;
}
long getFreeDiskSpaceInTemp() {
return freeDiskSpaceInTemp;
}
int getThreadCount() {
return threadCount;
}
int getPeakThreadCount() {
return peakThreadCount;
}
long getTotalStartedThreadCount() {
return totalStartedThreadCount;
}
String getDataBaseVersion() {
return dataBaseVersion;
}
String getDataSourceDetails() {
return dataSourceDetails;
}
List<ThreadInformations> getThreadInformationsList() {
// on trie sur demande (si affichage)
final List<ThreadInformations> result = new ArrayList<ThreadInformations>(
threadInformationsList);
Collections.sort(result, new ThreadInformationsComparator());
return Collections.unmodifiableList(result);
}
List<CacheInformations> getCacheInformationsList() {
// on trie sur demande (si affichage)
final List<CacheInformations> result = new ArrayList<CacheInformations>(
cacheInformationsList);
Collections.sort(result, new CacheInformationsComparator());
return Collections.unmodifiableList(result);
}
List<JobInformations> getJobInformationsList() {
// on trie sur demande (si affichage)
final List<JobInformations> result = new ArrayList<JobInformations>(jobInformationsList);
Collections.sort(result, new JobInformationsComparator());
return Collections.unmodifiableList(result);
}
int getCurrentlyExecutingJobCount() {
int result = 0;
for (final JobInformations jobInformations : jobInformationsList) {
if (jobInformations.isCurrentlyExecuting()) {
result++;
}
}
return result;
}
boolean isStackTraceEnabled() {
for (final ThreadInformations threadInformations : threadInformationsList) {
final List<StackTraceElement> stackTrace = threadInformations.getStackTrace();
if (stackTrace != null && !stackTrace.isEmpty()) {
return true;
}
}
return false;
}
boolean isCacheEnabled() {
return cacheInformationsList != null && !cacheInformationsList.isEmpty();
}
boolean isJobEnabled() {
return jobInformationsList != null && !jobInformationsList.isEmpty();
}
boolean isSpringBeansEnabled() {
return springBeanExists;
}
private static boolean isSpringAvailable() {
try {
Class.forName("org.springframework.context.ApplicationContextAware");
return true;
} catch (final ClassNotFoundException e) {
return false;
}
}
/** {@inheritDoc} */
@Override
public String toString() {
return getClass().getSimpleName() + "[pid=" + getPID() + ", host=" + getHost()
+ ", javaVersion=" + getJavaVersion() + ", serverInfo=" + getServerInfo() + ']';
}
}