view agent/src/logManager.cpp @ 41:db8eea95ac52

Bug 1849: HeapStats agent should collect systemd-journald information reviewed-by: yasuenag
author KUBOTA Yuji <kubota.yuji@lab.ntt.co.jp>
date Fri, 20 Jun 2014 12:10:55 +0900
parents 48dc7c3ceee5
children c80ffa6f3759
line wrap: on
line source

/*!
 * \file logManager.cpp
 * \brief This file is used collect log information.
 * Copyright (C) 2011-2013 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 "logManager.hpp"
#include "util.hpp"
#include "trapSender.hpp"
#include "cmdArchiver.hpp"
#include "jniZipArchiver.hpp"
#include "jvmSockCmd.hpp"
#include "jvmInfo.hpp"
#include "fsUtil.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;

/* 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;
  
  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();
  } catch(...) { 
    free(tempdirPath);
    
    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 true, if process is succeed.
 */
bool TLogManager::collectLog(jvmtiEnv *jvmti, JNIEnv* env, TInvokeCause cause,
  TMSecTime nowTime) {
    
  bool result = true;
  /* 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.. */
      result &= collectAllLog(jvmti, env, cause, nowTime,
        (char*)arcPath, PATH_MAX);
      
    default:
      /* Collect log about java and machine now status. */
      result &= collectNormalLog(cause, nowTime, arcPath);
  }
  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 true, if process is succeed.
 */
bool TLogManager::collectNormalLog(TInvokeCause cause,
                                      TMSecTime nowTime, char *archivePath) {
  
  bool result = true;
  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 | S_IRGRP | S_IROTH);
  
  /* If failure open file. */
  if (unlikely(fd < 0)) {
    
    PRINT_WARN_MSG("Couldn't open log file.");
    result = false;
  } else {
    
    /* Write line to log file. */
    if (unlikely(write(fd, logData, strlen(logData)) < 0)) {
      PRINT_WARN_MSG("Couldn't append log file.");
      result = false;
    }
    
    /* Cleanup. */
    close(fd);
  }
  
  /* 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 true, if process is succeed.
 */
bool TLogManager::collectAllLog(jvmtiEnv *jvmti, JNIEnv* env,
  TInvokeCause cause, TMSecTime nowTime, char *archivePath, size_t pathLen) {
  
  /* Working directory path. */
  char *basePath = NULL;
  /* Archive file path. */
  char *uniqArcName = NULL;
  
  /* Make directory. */
  createTempDir(&basePath, arg.logDir);
  if (unlikely(basePath == NULL)) {
    PRINT_WARN_MSG("Failure create working directory.");
    return false;
  }
  
  /* Create enviroment report file. */
  if (unlikely(!makeEnvironFile(basePath, cause, nowTime))) {
    PRINT_WARN_MSG("Failure create enviroment report file.");
  }
  
  /* Copy many files. */
  copyInfoFiles(basePath);
  
  /* Dump thread information. */
  
  char *dumpName = NULL;
  
  /* Create dump filename. */
  dumpName = createFilename((char*)basePath, "threaddump.txt");
  if (unlikely(dumpName == NULL)) {
    PRINT_WARN_MSG("Couldn't allocate thread dump file path.");
  } else {
    /* Flag of thread dump is failed. */
    bool flagFailDump = false;
    
    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.
       */
      flagFailDump = true;
      
    } else {
      
      /* Create thread dump file. */
      if (unlikely(jvmCmd->exec("threaddump", dumpName) != 0)) {
        flagFailDump = true;
      }
    }
    
    /* If need original thread dump. */
    if (unlikely(flagFailDump)) {
      flagFailDump = !makeThreadDump(jvmti, env, dumpName, nowTime);
    }
    
    /* If failure thread dump. */
    if (unlikely(flagFailDump)) {
      PRINT_WARN_MSG("Failure thread dumping.");
    }
    
    free(dumpName);
  }
  
  /* Copy gc log file. */
  if (unlikely(!copyOptionFile("-Xloggc:", (char*)basePath))) {
    PRINT_WARN_MSG("Failure copy gc log file.");
  }
  
  /* Variable for archive process result. */
  int 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);
    
    struct stat st = {0};
    /* If process is succeed but archive isn't exists. */
    if (unlikely(result == 0 && stat(uniqArcName, &st) != 0)) {
      
      /* Accept as failure. */
      result = -1;
    }
    
    /* 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)
  
  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. */
    PRINT_WARN_MSG("Failure create archive file.");
    
    /* 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 == 0);
}

/*!
 * \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 Prosess is succeed or failure.
 */
bool TLogManager::makeEnvironFile(char *basePath,
                                     TInvokeCause cause, TMSecTime nowTime) {
  
  /* Invoke OS version function. */
  struct utsname uInfo;
  memset(&uInfo, 0, sizeof(struct utsname));
  if (unlikely(uname(&uInfo) != 0)) {
    PRINT_WARN_MSG("Failure call uname.");
  }
  
  /* 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 = NULL;
  
  envInfoName = createFilename(basePath, "envInfo.txt");
  /* If failure create file name. */
  if (unlikely(envInfoName == NULL)) {
    PRINT_WARN_MSG("Couldn't allocate filename.");
    return false;
  }
  
  /* Create envInfo.txt. */
  std::ofstream ofs(envInfoName, std::ios::out);
  if (unlikely(!ofs)) {
    PRINT_WARN_MSG("Couldn't open enviroment report.");
    free(envInfoName);
    return false;
  }
  
  /* Separator of between name and value. */
  const char SepChar = '=';
  /* Output enviroment information. */
  ofs << "CollectionDate" << SepChar
    << nowTime << std::endl
    << "LogTrigger"     << SepChar
    << logCauseToInt(cause) << std::endl;
  
  ofs << "VmVersion"      << SepChar;
  if (likely(jvmInfo->getVmVersion() != NULL)) {
    ofs << jvmInfo->getVmVersion();
  }
  ofs << std::endl;
  
  ofs << "OsRelease"      << SepChar
    << (char*)uInfo.release << std::endl;
  
  ofs << "LibCVersion"    << SepChar;
  if (likely(glibcVersion != NULL)) {
    ofs << glibcVersion;
  }
  ofs << std::endl;
  
  ofs << "LibCRelease"    << SepChar;
  if (likely(glibcRelease != NULL)) {
    ofs << glibcRelease;
  }
  ofs << std::endl;
  
  ofs << "VmName"         << SepChar;
  if (likely(jvmInfo->getVmName() != NULL)) {
    ofs << jvmInfo->getVmName();
  }
  ofs << std::endl;
    
  ofs << "ClassPath"      << SepChar;
  if (likely(jvmInfo->getClassPath() != NULL)) {
    ofs << jvmInfo->getClassPath();
  }
  ofs << std::endl;
  
  ofs << "EndorsedPath"   << SepChar;
  if (likely(jvmInfo->getEndorsedPath() != NULL)) {
    ofs << jvmInfo->getEndorsedPath();
  }
  ofs << std::endl;
  
  ofs << "JavaVersion"    << SepChar;
  if (likely(jvmInfo->getJavaVersion() != NULL)) {
    ofs << jvmInfo->getJavaVersion();
  }
  ofs << std::endl;
  
  ofs << "JavaHome"       << SepChar;
  if (likely(jvmInfo->getJavaHome() != NULL)) {
    ofs << jvmInfo->getJavaHome();
  }
  ofs << std::endl;
  
  ofs << "BootClassPath"  << SepChar;
  if (likely(jvmInfo->getBootClassPath() != NULL)) {
    ofs << jvmInfo->getBootClassPath();
  }
  ofs << std::endl;
  
  ofs << "VmArgs"         << SepChar;
  if (likely(jvmInfo->getVmArgs() != NULL)) {
    ofs << jvmInfo->getVmArgs();
  }
  ofs << std::endl;
  
  ofs << "VmFlags"        << SepChar;
  if (likely(jvmInfo->getVmFlags() != NULL)) {
    ofs << jvmInfo->getVmFlags();
  }
  ofs << std::endl;
  
  ofs << "JavaCmd"        << SepChar;
  if (likely(jvmInfo->getJavaCommand() != NULL)) {
    ofs << jvmInfo->getJavaCommand();
  }
  ofs << std::endl;
  
  ofs << "VmTime"         << SepChar
    << jvmInfo->getTickTime() << std::endl;
  
  /* Cleanup. */
  ofs.close();
  free(envInfoName);
  
  return true;
}

/*!
 * \brief Dump thread and stack information to stream.
 * \param jvmti     [in] JVMTI environment object.
 * \param env       [in] JNI environment object.
 * \param ofs       [in] Output stream object.
 * \param stackInfo [in] Stack frame of java thread.
 */
void TLogManager::dumpThreadInformation(jvmtiEnv *jvmti, JNIEnv* env,
  std::ofstream &ofs, 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);
  
  /* Print thread information. */
  if (likely(threadInfo.name != NULL)) {
    ofs << "\"" << threadInfo.name << "\"";
  } else {
    ofs << "UnknownThread";
  }
  
  if (unlikely(threadInfo.isDaemon)) {
    ofs << " daemon";
  }
  
  ofs << " prio=" << threadInfo.priority;
  ofs << std::endl;
  
  ofs << "   java.lang.Thread.State: ";
  if (likely(threadInfo.state != NULL)) {
    ofs << threadInfo.state;
  } else {
    ofs << "Unknown";
  }
  ofs << std::endl;
  
  /* Cleanup. */
  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;
  }
  
  /* 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. */
    ofs << "\tat ";
    if (likely(methodInfo.className != NULL)) {
      ofs << methodInfo.className;
    } else {
      ofs << "UnknwonClass";
    }
    ofs << ".";
    if (likely(methodInfo.methodName != NULL)) {
      ofs << methodInfo.methodName;
    } else {
      ofs << "UnknwonMethod";
    }
    ofs << "(";
    if (unlikely(methodInfo.isNative)) {
      ofs << "Native method";
    } else {
      if (likely(methodInfo.sourceFile != NULL)) {
        ofs << methodInfo.sourceFile;
      } else {
        ofs << "UnknwonFile";
      }
      ofs << ":";
      if (likely(methodInfo.lineNumber > 0)) {
        ofs << methodInfo.lineNumber;
      } else {
        ofs << "UnknwonLine";
      }
    }
    ofs << ")" << std::endl;
    
    /* Cleanup. */
    free(methodInfo.className);
    free(methodInfo.methodName);
    free(methodInfo.sourceFile);
    
    /* 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[1024] = "UNKNOWN";
        char monitorClsName[1024]  = "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);
        }
        
        /* Print contented monitor information. */
        ofs << "\t- waiting to lock "
          << "<owner:" << ownerThreadName << ">"
          << " (a " << monitorClsName << ")" << std::endl;
        
        env->DeleteLocalRef(jMonitor);
      }
    }
    
    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[1024] = "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);
        }
        
        /* Print owned monitor information. */
        ofs << "\t- locked" 
          << " (a " << monitorClsName << ")" << std::endl;
      }
    }
  }
  ofs << std::endl;
  
  /* Cleanup. */
  if (likely(monitorInfo != NULL)) {
    for (jint monitorIdx = 0; monitorIdx < monitorCount; monitorIdx++) {
      env->DeleteLocalRef(monitorInfo[monitorIdx].monitor);
    }
    jvmti->Deallocate((unsigned char *)monitorInfo);
  }
}

/*!
 * \brief Create thread dump file.
 * \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 Prosess is succeed or failure.
 */
bool TLogManager::makeThreadDump(jvmtiEnv *jvmti, JNIEnv* env,
                                    char *filename, TMSecTime nowTime) {
  
  /* Open thread dump file. */
  std::ofstream ofs(filename, std::ios::out);
  if (unlikely(!ofs)) {
    PRINT_WARN_MSG("Couldn't open response file.");
    return false;
  }
  
  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.");
    return false;
  }
  
  /* Output stack trace. */
  for (jint i = 0; i < threadCount; i++) {
    jvmtiStackInfo stackInfo = stackList[i];
    
    /* Output thread information. */
    dumpThreadInformation(jvmti, env, ofs, stackInfo);
  }
  
  /* Cleanup. */
  jvmti->Deallocate((unsigned char *)stackList);
  
  return true;
}

/*!
 * \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)) {
    PRINT_WARN_MSG("Couldn't open process status file.");
    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)) {
    PRINT_WARN_MSG("Couldn't open machine status file.");
    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",
      &times->usrTime, &times->lowUsrTime, &times->sysTime,
      &times->idleTime, &times->iowaitTime, &times->sortIrqTime,
      &times->irqTime, &times->stealTime, &times->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)) {
    PRINT_WARN_MSG("Failure get real path of archive file.");
    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_ALERT_DATE[]   = {SNMP_OID_HEAPALERT,  1};
    oid OID_LOG_PATH[]     = {SNMP_OID_LOGARCHIVE, 1};
    oid OID_TROUBLE_DATE[] = {SNMP_OID_LOGARCHIVE, 2};
    /* Temporary buffer. */
    char buff[256] = {0};
    
    /* Send resource trap. */
    TTrapSender sender(SNMP_VERSION_2c, arg.snmpTarget,
      arg.snmpComName, 162);
    
    /* Setting sysUpTime */
    sender.setSysUpTime();
    
    /* Setting trapOID. */
    sender.setTrapOID(trapOID);
    
    /* Set alert date. */
    snprintf(buff, 255, "%llu", (TMSecTime)getNowTimeSec());
    sender.addValue(OID_ALERT_DATE, OID_LENGTH(OID_ALERT_DATE),
                                          buff, SNMP_VAR_TYPE_COUNTER64);
    
    /* 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();
    }
  }
  
  return result;
}

/*!
 * \brief Collect files about JVM enviroment.
 * \param basePath [in] Temporary working directory.
 */
void TLogManager::copyInfoFiles(char const* basePath){
  /* 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++) {
    if (likely(copyFile(distFileList[i], basePath))) {
      flagCopyedDistFile = true;
      break;
    }
  }

  /* If failure copy distribution file. */
  if (unlikely(!flagCopyedDistFile)) {
    PRINT_WARN_MSG("Failure copy file. path:\"distribution file\"");
  }

  /* 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. */
    if (unlikely(!copyFile(copyFileList[i], basePath))) {
      PRINT_WARN_MSG_HEADER << "Failure copy file."
        << " path:\"" << copyFileList[i] << "\"" << NEWLINE;
    }
  }

  /* 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.
   */
  bool hasSyslog = copyFile("/var/log/messages", basePath);
  if (unlikely(!hasSyslog)) {
    PRINT_WARN_MSG("Failure copy file. path:\"/var/log/messages\"");
  }

  /* Collect systemd-journald instead of syslog when failed to copy syslog, */
  if (!hasSyslog) {
    /*
     * 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(EXIT_FAILURE);
      } if (dup2(fd, 2) < 0) {
        close(fd);
        _exit(EXIT_FAILURE);
      }
      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(EXIT_FAILURE);
    } else if(child < 0) {
      /* vfork failed */
      PRINT_WARN_MSG("Failure collect systemd-journald log by vfork().");
    } else {
      /* Parent process */
      int status;
      /* Wait for the child process to exit. If the child has already exited,
       * this returns immediately. */
      if (waitpid(child, &status, 0) < 0) {
        PRINT_WARN_MSG("Failure collect systemd-journald log by process error.");
      }
      if (WIFEXITED(status)) {
        /* The child exited normally, get the status as result. */
        if (WEXITSTATUS(status) != 0) {
          PRINT_WARN_MSG("Failure collect systemd-journald log.");
        }
      } 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 stdout. */
  if (unlikely(!copyFile("/proc/self/fd/1", basePath, "fd1"))) {
    PRINT_WARN_MSG("Failure copy standard output.");
  }

  /* Copy stderr. */
  if (unlikely(!copyFile("/proc/self/fd/2", basePath, "fd2"))) {
    PRINT_WARN_MSG("Failure copy standard error.");
  }

}

/*!
 * \brief Copy option target file.
 * \param option [in] Path of file designate by JVM options.
 * \param basePath [in] Path of temporary directory.
 * \return Process is succeed or skiped, if value is true.<br>
 *         Process is failure, if value is false.
 */
bool TLogManager::copyOptionFile(char const* option, char const* basePath) {
  char optPath[PATH_MAX + 1] = {0};
  char *pathPos = NULL;
  char *endPos = NULL;
  
  /* Get JVM arguments string. */
  if (unlikely((pathPos = jvmInfo->getVmArgs()) == NULL)) {
    
    /* JVM argument isn't exists in JVM performance data. */
    PRINT_WARN_MSG("Couldn't get JVM arguments.");
    return false;
  }
  
  /* Find option string in JVM arguments. */
  if (unlikely((pathPos = strstr(pathPos, option)) == NULL)) {
    
    /* Not found option. */
    return true;
  }
  
  /* Move head of path. */
  pathPos += strlen(option);
  /* Get option separatar charactor. */
  endPos = strchr(pathPos, ' ');
  
  /* Calculate and adjust copy string length. */
  int optStrLen = (endPos == NULL) ? strlen(pathPos) : endPos - pathPos;
  optStrLen = (optStrLen > PATH_MAX) ? PATH_MAX : optStrLen;
  
  /* Copy string between option string and separator or '\0'. */
  strncpy(optPath, pathPos, optStrLen);
  
  /* Copy file. */
  return copyFile(optPath, basePath);
}

/*!
 * \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);
}