Mercurial > hg > release > heapstats-1.1
view agent/src/logManager.cpp @ 51:38c7e47cc6b9
Bug 2879: Unnecessary varbind is sent in log collection trap.
Reviewed-by: ykubota
GitHub: https://github.com/HeapStats/heapstats/pull/22
author | Yasumasa Suenaga <yasuenag@gmail.com> |
---|---|
date | Tue, 15 Mar 2016 12:56:31 +0900 |
parents | 9c027507d9bf |
children |
line wrap: on
line source
/*! * \file logManager.cpp * \brief This file is used collect log information. * Copyright (C) 2011-2014 Nippon Telegraph and Telephone Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include <jni.h> #include <pthread.h> #include <unistd.h> #include <queue> #include <sys/utsname.h> #include <gnu/libc-version.h> #include <fstream> #include <fcntl.h> #include <stdlib.h> #include <limits.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <dirent.h> #include <errno.h> #include "logManager.hpp" #include "util.hpp" #include "oopUtil.hpp" #include "fsUtil.hpp" #include "trapSender.hpp" #include "cmdArchiver.hpp" #include "jniZipArchiver.hpp" #include "jvmSockCmd.hpp" #include "jvmInfo.hpp" /* Static variables. */ /*! * \brief Mutex of collect normal log. */ pthread_mutex_t TLogManager::logMutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; /*! * \brief Mutex of archive file. */ pthread_mutex_t TLogManager::archiveMutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; /* Macro defines. */ /*! * \brief Format string macro for inode number type(ino_t). */ #define INODENUM_FORMAT_STR "%lu" /*! * \brief Separator string mcaro of between name and value for environment file. */ #define ENVIRON_VALUE_SEPARATOR "=" /*! * \brief Symbol string mcaro of GC log filename. */ #define GCLOG_FILENAME_SYMBOL "_ZN9Arguments16_gc_log_filenameE" /* Util method for log manager. */ /*! * \brief Convert log cause to integer for file output. * \param cause [in] Log collect cause. * \return Number of log cause. */ inline int logCauseToInt(TInvokeCause cause) { /* Convert invoke function cause. */ int logCause; switch (cause) { case ResourceExhausted: case ThreadExhausted: logCause = 1; break; case Signal: case AnotherSignal: logCause = 2; break; case Interval: logCause = 3; break; case OccurredDeadlock: logCause = 4; break; default: /* Illegal. */ logCause = 0; } return logCause; } /* Class method. */ /*! * \brief TLogManager constructor. * \param env [in] JNI environment object. * \param info [in] JVM running performance information. */ TLogManager::TLogManager(JNIEnv* env, TJvmInfo *info) { /* Sanity check. */ if (unlikely(info == NULL)) { throw "TJvmInfo is NULL."; } jvmInfo = info; jvmCmd = NULL; arcMaker = NULL; jniArchiver = NULL; char *tempdirPath = NULL; /* Get temporary path of java */ tempdirPath = GetSystemProperty(env, "java.io.tmpdir"); try { /* Create JVM socket commander. */ jvmCmd = new TJVMSockCmd(tempdirPath); /* Archive file maker. */ arcMaker = new TCmdArchiver(); /* Archive file maker to use zip library in java. */ jniArchiver = new TJniZipArchiver(); /* Get GC log filename pointer. */ gcLogFilename = (char**)symFinder->findSymbol(GCLOG_FILENAME_SYMBOL); if (unlikely(gcLogFilename == NULL)) { throw 1; } } catch(...) { free(tempdirPath); delete jvmCmd; delete arcMaker; delete jniArchiver; throw "TLogManager initialize failed!"; } free(tempdirPath); } /*! * \brief TLogManager destructor. */ TLogManager::~TLogManager(void) { /* Destroy instance. */ delete jvmCmd; delete arcMaker; delete jniArchiver; } /*! * \brief Collect log. * \param jvmti [in] JVMTI environment object. * \param env [in] JNI environment object. * \param cause [in] Invoke function cause.<br> * E.g. ResourceExhausted, Signal, Interval. * \param nowTime [in] Log collect time. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::collectLog(jvmtiEnv *jvmti, JNIEnv* env, TInvokeCause cause, TMSecTime nowTime) { int result = 0; /* Variable store archive file path. */ char arcPath[PATH_MAX] = {0}; /* Switch collecting log type by invoking cause. */ switch (cause) { case ResourceExhausted: case ThreadExhausted: case AnotherSignal: case OccurredDeadlock: { /* Collect log about java running environment and etc.. */ int returnCode = collectAllLog(jvmti, env, cause, nowTime, (char*)arcPath, PATH_MAX); if (unlikely(returnCode != 0)) { /* Check and show disk full error. */ result = returnCode; checkDiskFull(returnCode, "collect log"); } } default: { /* Collect log about java and machine now status. */ int returnCode = collectNormalLog(cause, nowTime, arcPath); if (unlikely(returnCode != 0)) { /* Check and show disk full error. */ result = returnCode; checkDiskFull(returnCode, "collect log"); } } } return result; } /*! * \brief Collect normal log. * \param cause [in] Invoke function cause.<br> * E.g. ResourceExhausted, Signal, Interval. * \param nowTime [in] Log collect time. * \param archivePath [in] Archive file path. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::collectNormalLog(TInvokeCause cause, TMSecTime nowTime, char *archivePath) { int result = EINVAL; TLargeUInt systime = 0; TLargeUInt usrtime = 0; TLargeUInt vmsize = 0; TLargeUInt rssize = 0; TMachineTimes cpuTimes = {0}; /* Get java process information. */ if (unlikely(!getProcInfo(&systime, &usrtime, &vmsize, &rssize))) { PRINT_WARN_MSG("Failure getting java process information."); } /* Get machine cpu times. */ if (unlikely(!getSysTimes(&cpuTimes))) { PRINT_WARN_MSG("Failure getting machine cpu times."); } /* Make write log line. */ char logData[4097] = {0}; snprintf(logData, 4096, "%lld,%d" /* Format : Logging information. */ ",%llu,%llu,%llu,%llu" /* Format : Java process information. */ ",%llu,%llu,%llu" /* Format : Machine CPU times. */ ",%llu,%llu,%llu" ",%llu,%llu,%llu" ",%lld,%lld,%lld,%lld" /* Format : JVM running information. */ ",%s\n", /* Format : Archive file name. */ /* Params : Logging information. */ nowTime, logCauseToInt(cause), /* Params : Java process information. */ usrtime, systime, vmsize, rssize, /* Params : Machine CPU times. */ cpuTimes.usrTime, cpuTimes.lowUsrTime, cpuTimes.sysTime, cpuTimes.idleTime, cpuTimes.iowaitTime, cpuTimes.irqTime, cpuTimes.sortIrqTime, cpuTimes.stealTime, cpuTimes.guestTime, /* Params : JVM running information. */ (long long int)jvmInfo->getSyncPark(), (long long int)jvmInfo->getSafepointTime(), (long long int)jvmInfo->getSafepoints(), (long long int)jvmInfo->getThreadLive(), /* Params : Archive file name. */ archivePath); /* Get mutex. */ ENTER_PTHREAD_SECTION(&logMutex) /* Open log file. */ int fd = open(arg.heapLogFile, O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR); /* If failure open file. */ if (unlikely(fd < 0)) { result = errno; PRINT_FAIL_OPENFILE_MSG("common log", result); } else { result = 0; /* Write line to log file. */ if (unlikely(write(fd, logData, strlen(logData)) < 0)) { result = errno; PRINT_FAIL_WRITE_MSG("common log", result); } /* Cleanup. */ if (unlikely(close(fd) < 0 && (result == 0))) { result = errno; PRINT_FAIL_WRITE_MSG("common log", result); } } /* Release mutex. */ EXIT_PTHREAD_SECTION(&logMutex) return result; } /*! * \brief Collect all log. * \param jvmti [in] JVMTI environment object. * \param env [in] JNI environment object. * \param cause [in] Invoke function cause.<br> * E.g. ResourceExhausted, Signal, Interval. * \param nowTime [in] Log collect time. * \param archivePath [out] Archive file path. * \param pathLen [in] Max size of paramter"archivePath". * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::collectAllLog(jvmtiEnv *jvmti, JNIEnv* env, TInvokeCause cause, TMSecTime nowTime, char *archivePath, size_t pathLen) { /* Variable for process result. */ int result = 0; /* Working directory path. */ char *basePath = NULL; /* Archive file path. */ char *uniqArcName = NULL; /* Make directory. */ result = createTempDir(&basePath, arg.logDir); if (unlikely(result != 0)) { PRINT_WARN_MSG("Failure create working directory."); return result; } try { /* Create enviroment report file. */ result = makeEnvironFile(basePath, cause, nowTime); if (unlikely(result != 0)) { PRINT_WARN_MSG("Failure create enviroment file."); /* If raise disk full error. */ if (unlikely(isRaisedDiskFull(result))) { throw 1; } } /* Copy many files. */ result = copyInfoFiles(basePath); /* If raise disk full error. */ if (unlikely(isRaisedDiskFull(result))) { throw 1; } /* Create thread dump file. */ result = makeThreadDumpFile(jvmti, env, basePath, cause, nowTime); if (unlikely(result != 0)) { PRINT_WARN_MSG("Failure thread dumping."); /* If raise disk full error. */ if (unlikely(isRaisedDiskFull(result))) { throw 1; } } /* Copy gc log file. */ result = copyGCLogFile(basePath); if (unlikely(result != 0)) { PRINT_WARN_MSG_AND_ERRNO( "Failure copy file. path:\"gc log\"", result); /* If raise disk full error. */ if (unlikely(isRaisedDiskFull(result))) { throw 1; } } /* Create socket owner file. */ result = makeSocketOwnerFile(basePath); if (unlikely(result != 0)) { PRINT_WARN_MSG("Failure create socket owner file."); /* If raise disk full error. */ if (unlikely(isRaisedDiskFull(result))) { throw 1; } } } catch(...) { ; /* Failed collect files by disk full. */ } if (likely(result == 0)) { /* * Set value mean failed to create archive, * For if failed to get "archiveMutex" mutex. */ result = -1; /* Get mutex. */ ENTER_PTHREAD_SECTION(&archiveMutex) /* Create archive file name. */ uniqArcName = createArchiveName(nowTime); if (unlikely(uniqArcName == NULL)) { /* Failure make archive uniq name. */ PRINT_WARN_MSG("Failure create archive name."); } else { /* Execute archive. */ arcMaker->setTarget(basePath); result = arcMaker->doArchive(env, uniqArcName); /* If failure create archive file. */ if (unlikely(result != 0)) { /* Execute archive to use jniArchiver. */ jniArchiver->setTarget(basePath); result = jniArchiver->doArchive(env, uniqArcName); } } /* Release mutex. */ EXIT_PTHREAD_SECTION(&archiveMutex) /* If failure create archive file yet. */ if (unlikely(result != 0)) { PRINT_WARN_MSG("Failure create archive file."); } } char *sendPath = uniqArcName; bool flagDirectory = false; /* If archive process is succeed. */ if (likely(result == 0)) { /* Search last path separator. */ char *filePos = strrchr(uniqArcName, '/'); if (filePos != NULL) { filePos++; } else { filePos = uniqArcName; } /* Copy archive file name without directory path. */ strncpy(archivePath, filePos, pathLen); } else { /* If failure execute command. */ /* Send working directory path to make up for archive file. */ sendPath = basePath; flagDirectory = true; } /* Send log archive trap. */ if (unlikely(!sendLogArchiveTrap(cause, nowTime, sendPath, flagDirectory))) { PRINT_WARN_MSG("Send SNMP log archive trap failed!"); } /* If execute command is succeed. */ if (likely(result == 0)) { /* Remove needless working directory. */ removeTempDir(basePath); } /* If allocated archive file path. */ if (likely(uniqArcName != NULL)) { free(uniqArcName); } /* Cleanup. */ free(basePath); return result; } /*! * \brief Create file about JVM running environment. * \param basePath [in] Path of directory put report file. * \param cause [in] Invoke function cause.<br> * E.g. Signal, ResourceExhausted, Interval. * \param nowTime [in] Log collect time. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::makeEnvironFile(char *basePath, TInvokeCause cause, TMSecTime nowTime) { int raisedErrNum = 0; /* Invoke OS version function. */ struct utsname uInfo; memset(&uInfo, 0, sizeof(struct utsname)); if (unlikely(uname(&uInfo) != 0)) { int unameErrNum = errno; PRINT_WARN_MSG_AND_ERRNO("Failure call uname.", unameErrNum); } /* Invoke glibc information function. */ const char *glibcVersion = NULL; const char *glibcRelease = NULL; glibcVersion = gnu_get_libc_version(); glibcRelease = gnu_get_libc_release(); if (unlikely(glibcVersion == NULL || glibcRelease == NULL)) { PRINT_WARN_MSG("Failure call glibc function."); } /* Create filename. */ char *envInfoName = createFilename(basePath, "envInfo.txt"); /* If failure create file name. */ if (unlikely(envInfoName == NULL)) { raisedErrNum = errno; PRINT_WARN_MSG("Couldn't allocate filename."); return raisedErrNum; } /* Create envInfo.txt. */ int fd = open(envInfoName, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); if (unlikely(fd < 0)) { raisedErrNum = errno; PRINT_FAIL_CREATEFILE_MSG("enviroment file", raisedErrNum); free(envInfoName); return raisedErrNum; } free(envInfoName); /* Output enviroment information. */ try { /* Output list. */ const struct { const char *colName; const char *valueFmt; TMSecTime valueData; const char *valuePtr; bool isString; } envColumnList [] = { {"CollectionDate", "%lld", nowTime, NULL, false}, {"LogTrigger", "%d", logCauseToInt(cause), NULL, false}, {"VmVersion", "%s", 0, jvmInfo->getVmVersion(), true}, {"OsRelease", "%s", 0, uInfo.release, true}, {"LibCVersion", "%s", 0, glibcVersion, true}, {"LibCRelease", "%s", 0, glibcRelease, true}, {"VmName", "%s", 0, jvmInfo->getVmName(), true}, {"ClassPath", "%s", 0, jvmInfo->getClassPath(), true}, {"EndorsedPath", "%s", 0, jvmInfo->getEndorsedPath(), true}, {"JavaVersion", "%s", 0, jvmInfo->getJavaVersion(), true}, {"JavaHome", "%s", 0, jvmInfo->getJavaHome(), true}, {"BootClassPath", "%s", 0, jvmInfo->getBootClassPath(), true}, {"VmArgs", "%s", 0, jvmInfo->getVmArgs(), true}, {"VmFlags", "%s", 0, jvmInfo->getVmFlags(), true}, {"JavaCmd", "%s", 0, jvmInfo->getJavaCommand(), true}, {"VmTime", JLONG_FORMAT_STR, jvmInfo->getTickTime(), NULL, false}, {NULL, NULL, 0, NULL, '\0'} }; char lineBuf[PATH_MAX + 1] = {0}; char EMPTY_LINE[] = ""; for (int i = 0; envColumnList[i].colName != NULL; i++) { /* Write column and separator. */ snprintf(lineBuf, PATH_MAX, "%s" ENVIRON_VALUE_SEPARATOR , envColumnList[i].colName); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } /* Convert column value to string. */ char const* columnStr = NULL; if (envColumnList[i].isString) { columnStr = envColumnList[i].valuePtr; if (unlikely(columnStr == NULL)) { columnStr = EMPTY_LINE; } } else{ snprintf(lineBuf, PATH_MAX, envColumnList[i].valueFmt, envColumnList[i].valueData); columnStr = lineBuf; } /* Write column value. */ if (unlikely(write(fd, columnStr, strlen(columnStr)) < 0)) { throw 1; } /* Write line separator. */ snprintf(lineBuf, PATH_MAX, "\n"); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } } } catch(...) { /* Stored error number to avoid overwriting by "close". */ raisedErrNum = errno; PRINT_FAIL_WRITE_MSG("enviroment file", raisedErrNum); } /* Cleanup. */ if (unlikely(close(fd) < 0 && raisedErrNum == 0)) { raisedErrNum = errno; PRINT_FAIL_WRITE_MSG("enviroment file", raisedErrNum); } return raisedErrNum; } /*! * \brief Dump thread and stack information to stream. * \param jvmti [in] JVMTI environment object. * \param env [in] JNI environment object. * \param fd [in] Output file descriptor. * \param stackInfo [in] Stack frame of java thread. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::dumpThreadInformation(jvmtiEnv *jvmti, JNIEnv* env, int fd, jvmtiStackInfo stackInfo) { /* * This function use below JVMTI function. * - GetOwnedMonitorStackDepthInfo, GetCurrentContendedMonitor * But the JVMTI function is need capabilities. * The capability is cann't get if agent is attached by on-demand-attach. * So check attach type before call the JVMTI function. */ bool hasCapability = !isOnDemandAttached; /* Get thread information. */ TJavaThreadInfo threadInfo = {0}; getThreadDetailInfo(jvmti, env, stackInfo.thread, &threadInfo); char const EMPTY_STR[] = ""; char lineBuf[PATH_MAX + 1] = {0}; /* Output thread information. */ try { /* Output thread name, thread type and priority. */ snprintf(lineBuf, PATH_MAX, "\"%s\"%s prio=%d\n", ((threadInfo.name != NULL) ? threadInfo.name : EMPTY_STR), ((threadInfo.isDaemon) ? " daemon" : EMPTY_STR), threadInfo.priority); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } /* Output thread state. */ snprintf(lineBuf, PATH_MAX, " java.lang.Thread.State: %s\n", ((threadInfo.state != NULL) ? threadInfo.state : EMPTY_STR)); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } } catch(...) { int raisedErrNum = errno; free(threadInfo.name); free(threadInfo.state); /* Raise write error. */ PRINT_FAIL_WRITE_MSG("jvmti threaddump", raisedErrNum); return raisedErrNum; } free(threadInfo.name); free(threadInfo.state); /* Get thread monitor stack information. */ jint monitorCount = 0; jvmtiMonitorStackDepthInfo *monitorInfo = NULL; if (unlikely(hasCapability && isError(jvmti, jvmti->GetOwnedMonitorStackDepthInfo(stackInfo.thread, &monitorCount, &monitorInfo)))) { /* Agnet is failed getting monitor info. */ monitorInfo = NULL; } int result = 0; try { /* Output thread flame trace. */ for (jint flameIdx = 0, flameSize = stackInfo.frame_count; flameIdx < flameSize; flameIdx++) { jvmtiFrameInfo frameInfo = stackInfo.frame_buffer[flameIdx]; TJavaStackMethodInfo methodInfo = {0}; /* Get method information. */ getMethodFrameInfo(jvmti, env, frameInfo, &methodInfo); /* Output method class, name and source file location. */ char *srcLocation = NULL; if (!methodInfo.isNative) { char locationBuf[PATH_MAX + 1] = {0}; if (likely(methodInfo.lineNumber >= 0)) { snprintf(locationBuf, PATH_MAX, "%d", methodInfo.lineNumber); srcLocation = strdup(locationBuf); } snprintf(locationBuf, PATH_MAX, "%s:%s", (methodInfo.sourceFile != NULL) ? methodInfo.sourceFile : "UnknownFile", (srcLocation != NULL) ? srcLocation : "UnknownLine"); free(srcLocation); srcLocation = strdup(locationBuf); } snprintf(lineBuf, PATH_MAX, "\tat %s.%s(%s)\n", (methodInfo.className != NULL) ? methodInfo.className : "UnknownClass", (methodInfo.methodName != NULL) ? methodInfo.methodName : "UnknownMethod", (srcLocation != NULL) ? srcLocation : "Native method"); /* Cleanup. */ free(methodInfo.className); free(methodInfo.methodName); free(methodInfo.sourceFile); free(srcLocation); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } /* If current stack frame. */ if (unlikely(hasCapability && flameIdx == 0)) { jobject jMonitor = NULL; jvmti->GetCurrentContendedMonitor(stackInfo.thread, &jMonitor); /* If contented monitor is existing now. */ if (likely(jMonitor != NULL)) { jclass monitorClass = NULL; char *tempStr = NULL; char ownerThreadName[1025] = "UNKNOWN"; char monitorClsName[1025] = "UNKNOWN"; /* Get monitor class. */ monitorClass = env->GetObjectClass(jMonitor); if (likely(monitorClass != NULL)) { /* Get class signature. */ jvmti->GetClassSignature(monitorClass, &tempStr, NULL); if (likely(tempStr != NULL)) { snprintf(monitorClsName, 1024, "%s", tempStr); jvmti->Deallocate((unsigned char *)tempStr); } /* Cleanup. */ env->DeleteLocalRef(monitorClass); } /* Get monitor owner. */ jvmtiMonitorUsage monitorInfo = {0}; if (unlikely(!isError(jvmti, jvmti->GetObjectMonitorUsage(jMonitor, &monitorInfo)))) { /* Get owner thread information. */ getThreadDetailInfo(jvmti, env, monitorInfo.owner, &threadInfo); if (likely(threadInfo.name != NULL)) { strncpy(ownerThreadName, threadInfo.name, 1024); } /* Cleanup. */ free(threadInfo.name); free(threadInfo.state); } env->DeleteLocalRef(jMonitor); /* Output contented monitor information. */ snprintf(lineBuf, PATH_MAX, "\t- waiting to lock <owner:%s> (a %s)\n", ownerThreadName, monitorClsName); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } } } if (likely(monitorInfo != NULL)) { /* Search monitor. */ jint monitorIdx = 0; for (; monitorIdx < monitorCount; monitorIdx++) { if (monitorInfo[monitorIdx].stack_depth == flameIdx) { break; } } /* If locked monitor is found. */ if (likely(monitorIdx < monitorCount)) { jobject jMonitor = monitorInfo[monitorIdx].monitor; jclass monitorClass = NULL; char *tempStr = NULL; char monitorClsName[1025] = "UNKNOWN"; /* Get object class. */ monitorClass = env->GetObjectClass(jMonitor); if (likely(monitorClass != NULL)) { /* Get class signature. */ jvmti->GetClassSignature(monitorClass, &tempStr, NULL); if (likely(tempStr != NULL)) { snprintf(monitorClsName, 1024, "%s", tempStr); jvmti->Deallocate((unsigned char *)tempStr); } env->DeleteLocalRef(monitorClass); } /* Output owned monitor information. */ snprintf(lineBuf, PATH_MAX, "\t- locked (a %s)\n", monitorClsName); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } } } } /* Output separator for each thread. */ snprintf(lineBuf, PATH_MAX, "\n"); if (unlikely(write(fd, lineBuf, strlen(lineBuf)) < 0)) { throw 1; } } catch(...) { result = errno; /* Raise write error. */ PRINT_FAIL_WRITE_MSG("jvmti threaddump", result); } /* Cleanup. */ if (likely(monitorInfo != NULL)) { for (jint monitorIdx = 0; monitorIdx < monitorCount; monitorIdx++) { env->DeleteLocalRef(monitorInfo[monitorIdx].monitor); } jvmti->Deallocate((unsigned char *)monitorInfo); } return result; } /*! * \brief Create thread dump with JVMTI. * \param jvmti [in] JVMTI environment object. * \param env [in] JNI environment object. * \param filename [in] Path of thread dump file. * \param nowTime [in] Log collect time. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::makeJvmtiThreadDump(jvmtiEnv *jvmti, JNIEnv* env, char *filename, TMSecTime nowTime) { int result = 0; /* Open thread dump file. */ int fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); /* If failure open file. */ if (unlikely(fd < 0)) { result = errno; PRINT_FAIL_CREATEFILE_MSG("jvmti threaddump", result); return result; } const jint MAX_STACK_COUNT = 100; jvmtiStackInfo *stackList = NULL; jint threadCount = 0; /* If failure get all stack traces. */ if(unlikely(isError(jvmti, jvmti->GetAllStackTraces(MAX_STACK_COUNT, &stackList, &threadCount)))) { PRINT_WARN_MSG("Couldn't get thread stack trace."); close(fd); return -1; } /* Output stack trace. */ for (jint i = 0; i < threadCount; i++) { jvmtiStackInfo stackInfo = stackList[i]; /* Output thread information. */ result = dumpThreadInformation(jvmti, env, fd, stackInfo); if (unlikely(result != 0)) { break; } } /* Cleanup. */ if (unlikely(close(fd) < 0 && result == 0)) { result = errno; PRINT_FAIL_WRITE_MSG("jvmti threaddump", result); } jvmti->Deallocate((unsigned char *)stackList); return result; } /*! * \brief Create thread dump file. * \param jvmti [in] JVMTI environment object. * \param env [in] JNI environment object. * \param basePath [in] Path of directory put report file. * \param cause [in] Invoke function cause.<br> * E.g. Signal, ResourceExhausted, Interval. * \param nowTime [in] Log collect time. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::makeThreadDumpFile(jvmtiEnv *jvmti, JNIEnv* env, char *basePath, TInvokeCause cause, TMSecTime nowTime) { /* Dump thread information. */ int result = -1; /* Create dump filename. */ char *dumpName = createFilename((char*)basePath, "threaddump.txt"); if (unlikely(dumpName == NULL)) { PRINT_WARN_MSG("Couldn't allocate thread dump file path."); } else { if (unlikely(cause == ThreadExhausted && !jvmCmd->isConnectable())) { /* * JVM is aborted when reserve signal SIGQUIT, * if JVM can't make new thread (e.g. RLIMIT_NPROC). * So we need avoid to send SIGQUIT signal. */ ; } else { /* Create thread dump file. */ result = jvmCmd->exec("threaddump", dumpName); } /* If disk isn't full. */ if (unlikely(!isRaisedDiskFull(result))) { /* If need original thread dump. */ if (unlikely(result != 0)) { result = makeJvmtiThreadDump(jvmti, env, dumpName, nowTime); } } free(dumpName); } return result; } /*! * \brief Getting java process information. * \param systime [out] System used cpu time in java process. * \param usrtime [out] User and java used cpu time in java process. * \param vmsize [out] Virtual memory size of java process. * \param rssize [out] Memory size of java process on main memory. * \return Prosess is succeed or failure. */ bool TLogManager::getProcInfo(TLargeUInt *systime, TLargeUInt *usrtime, TLargeUInt *vmsize, TLargeUInt *rssize) { /* Get page size. */ long pageSize = 0; /* If failure get page size from sysconf. */ if (unlikely(systemPageSize <= 0)) { /* Assume page size is 4 KiByte. */ systemPageSize = 1 << 12; PRINT_WARN_MSG("Not found system page size." " Assume that page size is equal 4KiByte."); } /* Use sysconf return value. */ pageSize = systemPageSize; /* Initialize. */ *usrtime = -1; *systime = -1; *vmsize = -1; *rssize = -1; /* Make path of process status file. */ char path[256] = {0}; snprintf(path, 255, "/proc/%d/stat", getpid()); /* Open process status file. */ FILE *proc = fopen(path, "r"); /* If failure open process status file. */ if (unlikely(proc == NULL)) { int raisedErrNum = errno; PRINT_FAIL_OPENFILE_MSG("process status", raisedErrNum); return false; } char *line = NULL; size_t len = 0; /* Get line from process status file. */ if (likely(getline(&line, &len, proc) > 0)) { /* Parse line. */ if (likely(sscanf(line, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u" " %*u %*u %*u %llu %llu %*d %*d %*d %*d %*d %*d %*u %llu %llu", usrtime, systime, vmsize, rssize) == 4)) { /* Convert real page count to real page size. */ *rssize = *rssize * pageSize; } else { /* Kernel may be old or customized. */ PRINT_WARN_MSG("Process data has shortage."); } } else { PRINT_WARN_MSG("Couldn't read process status."); } /* Cleanup. */ if (likely(line != NULL)) { free(line); } fclose(proc); return true; } /*! * \brief Getting machine cpu times. * \param times [out] Machine cpu times information. * \return Prosess is succeed or failure. */ bool TLogManager::getSysTimes(TMachineTimes *times) { /* Initialize. */ memset(times, 0, sizeof(TMachineTimes)); /* Open machine status file. */ FILE *proc = fopen("/proc/stat", "r"); /* If failure open machine status file. */ if (unlikely(proc == NULL)) { int raisedErrNum = errno; PRINT_FAIL_OPENFILE_MSG("machine status", raisedErrNum); return false; } char *line = NULL; size_t len = 0; bool flagFound = false; /* Loop for find "cpu" line. */ while (likely(getline(&line, &len, proc) > 0)) { /* Check line. */ if (unlikely(strncmp(line, "cpu ", 4) != 0)) { /* This line is non-target. */ continue; } /* Parse cpu time information. */ if (unlikely(sscanf(line, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu", ×->usrTime, ×->lowUsrTime, ×->sysTime, ×->idleTime, ×->iowaitTime, ×->sortIrqTime, ×->irqTime, ×->stealTime, ×->guestTime) != 9)) { /* Maybe the kernel is old version. */ PRINT_WARN_MSG("CPU status data has shortage."); } flagFound = true; break; } /* If not found cpu information. */ if (unlikely(!flagFound)) { /* Maybe the kernel is modified. */ PRINT_WARN_MSG("Not found cpu status data."); } /* Cleanup. */ if (likely(line != NULL)) { free(line); } fclose(proc); return flagFound; } /*! * \brief Send log archive trap. * \param cause [in] Invoke function cause.<br> * E.g. Signal, ResourceExhausted, Interval. * \param nowTime [in] Log collect time. * \param path [in] Archive file or directory path. * \param isDirectory [in] Param "path" is directory. * \return Value is true, if process is succeed. */ bool TLogManager::sendLogArchiveTrap(TInvokeCause cause, TMSecTime nowTime, char *path, bool isDirectory) { /* Return value. */ bool result = true; /* Get full path. */ char realSendPath[PATH_MAX] = {0}; if (unlikely(realpath(path, realSendPath) == NULL)) { int raisedErrNum = errno; PRINT_WARN_MSG_AND_ERRNO("Failure get real path of archive file.", raisedErrNum); return false; } /* If path is directory, then append "/" to path. */ if (isDirectory) { size_t pathSize = strlen(realSendPath); pathSize = (pathSize >= PATH_MAX) ? PATH_MAX - 1 : pathSize; realSendPath[pathSize] = '/'; } /* Output archive file path. */ PRINT_INFO_MSG_HEADER << "Completed collect log. file:" << realSendPath << NEWLINE; if (arg.snmpSend) { /* Trap OID. */ char trapOID[50] = OID_LOGARCHIVE; oid OID_LOG_PATH[] = {SNMP_OID_LOGARCHIVE, 1}; oid OID_TROUBLE_DATE[] = {SNMP_OID_LOGARCHIVE, 2}; /* Temporary buffer. */ char buff[256] = {0}; try { /* Send resource trap. */ TTrapSender sender(SNMP_VERSION_2c, arg.snmpTarget, arg.snmpComName, 162); /* Setting sysUpTime */ sender.setSysUpTime(); /* Setting trapOID. */ sender.setTrapOID(trapOID); /* Set log archive path. */ sender.addValue(OID_LOG_PATH, OID_LENGTH(OID_LOG_PATH), realSendPath, SNMP_VAR_TYPE_STRING); /* Set date which JVM resource exhausted. */ switch (cause) { case ResourceExhausted: case ThreadExhausted: case OccurredDeadlock: /* Collect archive by resource or deadlock. */ snprintf(buff, 255, "%llu", nowTime); break; default: /* Collect archive by signal. */ snprintf(buff, 255, "%llu", (TMSecTime)0LL); } sender.addValue(OID_TROUBLE_DATE, OID_LENGTH(OID_TROUBLE_DATE), buff, SNMP_VAR_TYPE_COUNTER64); /* Send trap. */ result = (sender.sendTrap() == SNMP_PROC_SUCCESS); if (unlikely(!result)) { /* Clean up. */ sender.clearValues(); } } catch(...) { result = false; } } return result; } /*! * \brief Collect files about JVM enviroment. * \param basePath [in] Temporary working directory. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::copyInfoFiles(char const* basePath){ int result = 0; /* Copy distribution file list. */ const char distFileList[][255] = { /* Distribution release. */ "/etc/redhat-release", /* For other distribution. */ "/etc/sun-release", "/etc/mandrake-release", "/etc/SuSE-release", "/etc/turbolinux-release", "/etc/gentoo-release", "/etc/debian_version", "/etc/ltib-release", "/etc/angstrom-version", "/etc/fedora-release", "/etc/vine-release", "/etc/issue", /* End flag. */ {0} }; bool flagCopyedDistFile = false; /* Copy distribution file. */ for(int i = 0; strlen(distFileList[i]) > 0; i++){ result = copyFile(distFileList[i], basePath); if((result == 0) || isRaisedDiskFull(result)){ flagCopyedDistFile = (result == 0); break; } } /* If failure copy distribution file. */ if(unlikely(!flagCopyedDistFile)){ PRINT_WARN_MSG_AND_ERRNO( "Failure copy file. path:\"distribution file\"", result); } /* If disk is full. */ if(unlikely(isRaisedDiskFull(result))){ return result; } /* Copy file list. */ const char copyFileList[][255] = { /* Process information. */ "/proc/self/smaps", "/proc/self/limits", "/proc/self/cmdline", "/proc/self/status", /* Netstat infomation. */ "/proc/net/tcp", "/proc/net/tcp6", "/proc/net/udp", "/proc/net/udp6", /* End flag. */ {0} }; /* Copy files in list. */ for(int i = 0; strlen(copyFileList[i]) > 0; i++){ /* Copy file. */ result = copyFile(copyFileList[i], basePath); if(unlikely(result != 0)){ char message[512+PATH_MAX] = {0}; sprintf(message, "Failure copy file. path:\"%s\"", copyFileList[i]); PRINT_WARN_MSG_AND_ERRNO(message,result); /* If disk is full. */ if(unlikely(isRaisedDiskFull(result))){ return result; } } } /* Collect Syslog or Systemd-Journald */ /* * Try to copy syslog at first, because journal daemon will forward all received * log messages to a traditional syslog by default. */ result = copyFile("/var/log/messages", basePath); if (unlikely(result != 0)) { PRINT_WARN_MSG_AND_ERRNO("Failure copy file. path:\"/var/log/messages\"", result); /* If disk is full. */ if(unlikely(isRaisedDiskFull(result))){ return result; } } /* Collect systemd-journald instead of syslog when failed to copy syslog, */ if (result != 0) { /* * Select vfork() to run journalctl in a separate process. Unlikely system(), * this function does not block SIGINT, et al. Unlikely fork(), this function * does not copy but shares all memory with its parent, including the stack. */ pid_t child = vfork(); if ( child == 0 ) { /* Child process */ /* logfile name shows what command is used to output it.*/ char logfile[PATH_MAX]; sprintf(logfile, "%s%s", basePath, "/journalctl_-q_--all_--this-boot_--no-pager_-o_verbose.log"); /* Redirect child process' stdout/stderr to logfile */ int fd = open(logfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (dup2(fd, 1) < 0) { close(fd); _exit(errno); } if (dup2(fd, 2) < 0) { close(fd); _exit(errno); } close(fd); /* Use execve() for journalctl to prevent command injection */ const char * argv[] = {"journalctl", "-q", "--all", "--this-boot", "--no-pager", "-o", "verbose", NULL}; extern char **environ; execve("/bin/journalctl", (char* const*)argv, environ); /* if execve returns, it has failed */ _exit(errno); } else if(child < 0) { /* vfork failed */ result = errno; PRINT_WARN_MSG_AND_ERRNO("Failure collect systemd-journald log by vfork().", result); } else { /* Parent process */ int status; if (waitpid(child, &status, 0) < 0) { /* The child process failed. */ result = errno; PRINT_WARN_MSG_AND_ERRNO("Failure collect systemd-journald log by process error.", result); /* If disk is full. */ if(unlikely(isRaisedDiskFull(result))){ return result; } } if (WIFEXITED(status)) { /* The child exited normally, get the returns as result. */ result = WEXITSTATUS(status); if (result != 0) { PRINT_WARN_MSG_AND_ERRNO("Failure collect systemd-journald log.", result); } } else { /* The child exited with a signal or unknown exit code. */ PRINT_WARN_MSG("Failure collect systemd-journald log by signal or unknown exit code."); } } } /* Copy file descriptors, i.e. stdout and stderr, as avoid double work. */ const int fdNum = 2; const char streamList[fdNum][255] = { /* Standard streams */ "/proc/self/fd/1", "/proc/self/fd/2", }; const char fdFile[fdNum][10] = {"fd1", "fd2"}; char fdPath[fdNum][PATH_MAX]; struct stat fdStat[fdNum]; for (int i=0; i<fdNum; i++) { realpath(streamList[i], fdPath[i]); if(unlikely( stat(fdPath[i], &fdStat[i]) != 0 )) { /* Failure get file information. */ result = errno; PRINT_WARN_MSG_AND_ERRNO("Failure get file information (stat).", result); } else if( i == 1 && result == 0 && fdStat[0].st_ino == fdStat[1].st_ino ) { /* If stdout and stderr are redirected to same file, no need to copy the file again. */ break; } else { result = copyFile(streamList[i], basePath, fdFile[i]); } /* If catch a failure during the copy process, show warn messages. */ if (unlikely(result != 0)) { char message[512+PATH_MAX] = {0}; sprintf(message, "Failure copy file. path:\"%s\"", streamList[i]); PRINT_WARN_MSG_AND_ERRNO(message,result); /* If disk is full. */ if(unlikely(isRaisedDiskFull(result))) { return result; } } } return result; } /*! * \brief Copy GC log file. * \param basePath [in] Path of temporary directory. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::copyGCLogFile(char const* basePath) { char *aGCLogFile = (*gcLogFilename); /* If GC log filename isn't specified. */ if (unlikely(aGCLogFile == NULL)) { return 0; } /* Copy file. */ return copyFile(aGCLogFile, basePath); } /*! * \brief Create file about using socket by JVM. * \param basePath [in] Path of directory put report file. * \return Value is zero, if process is succeed.<br /> * Value is error number a.k.a. "errno", if process is failure. */ int TLogManager::makeSocketOwnerFile(char const* basePath) { int result = 0; /* Create filename. */ char *sockOwnName = createFilename(basePath, "sockowner"); /* If failure create file name. */ if (unlikely(sockOwnName == NULL)) { result = errno; PRINT_WARN_MSG("Couldn't allocate filename."); return result; } /* Create sockowner. */ int fd = open(sockOwnName, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); free(sockOwnName); if (unlikely(fd < 0)) { result = errno; PRINT_FAIL_CREATEFILE_MSG("socket owner", result); return result; } /* Open directory. */ char procSocketPath[256] = {0}; snprintf(procSocketPath, 255, "/proc/%d/fd/", getpid()); DIR *dir = opendir(procSocketPath); /* If failure open directory. */ if (unlikely(dir == NULL)) { result = errno; PRINT_WARN_MSG_AND_ERRNO("Couldn't open directory.", result); close(fd); return result; } /* Read file in directory. */ struct dirent *entry = NULL; while ((entry = readdir(dir)) != NULL) { /* Check file name. */ if (strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, ".") == 0) { continue; } /* Get maybe socket file full path. */ char *socketPath = createFilename(procSocketPath, entry->d_name); if (unlikely(socketPath == NULL)) { result = errno; PRINT_WARN_MSG("Failure allocate working memory."); break; } /* Get file information. */ struct stat st = {0}; if (unlikely(stat(socketPath, &st) != 0)) { free(socketPath); continue; } free(socketPath); /* If this file is socket file. */ if ((st.st_mode & S_IFMT) == S_IFSOCK) { /* Output i-node number of a socket file. */ char buf[256] = {0}; snprintf(buf, 255, INODENUM_FORMAT_STR "\n", st.st_ino); if (unlikely(write(fd, buf, strlen(buf)) < 0)) { result = errno; PRINT_FAIL_WRITE_MSG("socket owner", result); break; } } } /* If failure in read directory. */ if (unlikely(close(fd) != 0 && result == 0)) { result = errno; PRINT_FAIL_WRITE_MSG("socket owner", result); } /* Cleanup. */ closedir(dir); return result; } /*! * \brief Create archive file path. * \param nowTime [in] Log collect time. * \return Return archive file name, if process is succeed.<br> * Don't forget deallocate memory.<br> * Process is failure, if value is null. */ char *TLogManager::createArchiveName(TMSecTime nowTime) { time_t nowTimeSec = 0; struct tm time_struct = {0}; char time_str[20] = {0}; char arcName[PATH_MAX + 1] = {0}; char extPart[PATH_MAX + 1] = {0}; char namePart[PATH_MAX + 1] = {0}; /* Search extension. */ char *extPos = strrchr(arg.archiveFile, '.'); if (likely(extPos != NULL)) { /* Path and extension store each other. */ strncpy(extPart, extPos, PATH_MAX); strncpy(namePart, arg.archiveFile, (extPos - arg.archiveFile)); } else { /* Not found extension in path. */ strncpy(namePart, arg.archiveFile, PATH_MAX); } /* Get now datetime and convert to string. */ nowTimeSec = (time_t)(nowTime / 1000); localtime_r((const time_t*)&nowTimeSec, &time_struct); strftime(time_str, 20, "%y%m%d%H%M%S", &time_struct); /* Create file name. */ snprintf(arcName, PATH_MAX, "%s%s%s", namePart, time_str, extPart); /* Create unique file name. */ return createUniquePath(arcName, false); }