view agent/src/util.cpp @ 71:5c6af4ec5c40

Bug 3338: Make reloadable option as same as the latest version Reviewed-by: yasuenag
author KUBOTA Yuji <kubota.yuji@lab.ntt.co.jp>
date Fri, 10 Mar 2017 09:31:39 +0900
parents 9db2ed159a6f
children 24fe0024d3e2
line wrap: on
line source

/*!
 * \file util.cpp
 * \brief This file is utilities.
 * Copyright (C) 2011-2017 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 <jvmti.h>

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pcre.h>
#include <iostream>
#include <fstream>
#include <string.h>
#include <locale>
#include <climits>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>

#include "util.hpp"
#include "fsUtil.hpp"
#include "jvmInfo.hpp"

/* Extern variables. */

/*!
 * \brief Agent attach mode flag.
 *        Value is true, if agent is attached by on-demand-attach.
 *        Value is false, if agent is attached by command line.
 */
bool isOnDemandAttached = false;

/*!
 * \brief HeapStats configurations.
 */
TArguments arg;

/*!
 * \brief SSE2 instruction usable flag.
 */
bool usableSSE2   = false;

/*!
 * \brief SSE3 instruction usable flag.
 */
bool usableSSE3   = false;

/*!
 * \brief SSSE3 instruction usable flag.
 */
bool usableSSSE3  = false;

/*!
 * \brief SSE4.1 instruction usable flag.
 */
bool usableSSE4_1 = false;

/*!
 * \brief SSE4.2 instruction usable flag.
 */
bool usableSSE4_2 = false;

/*!
 * \brief Intel AVX instruction usable flag.
 */
bool usableAVX    = false;

/*!
 * \brief Page size got from sysconf.
 */
long systemPageSize = 
#ifdef _SC_PAGESIZE
  sysconf(_SC_PAGESIZE);
#else
  #ifdef _SC_PAGE_SIZE
    sysconf(_SC_PAGE_SIZE);
  #else
    /* Suppose system page size to be 4KiByte. */
    4 * 1024;
  #endif
#endif

/* Inner variables. */

/*!
 * \brief Header of comment in configuration file.
 */
const char* COMMENT_HEADER = "//";
/*!
 * \brief Length of comment header.
 */
const int COMMENT_HEADER_LEN = 2;

/*!
 * \brief JVMTI error detector.
 * \param jvmti [in] JVMTI envrionment object.
 * \param error [in] JVMTI error code.
 * \return Param "error" is error code?(true/false)
 */
bool isError(jvmtiEnv *jvmti, jvmtiError error){
  
  /* If param "error" is error code. */
  if (unlikely(error != JVMTI_ERROR_NONE)) {
    
    /* Get and output error message. */
    char *errStr = NULL;
    jvmti->GetErrorName(error, &errStr);
    PRINT_WARN_MSG(errStr);
    jvmti->Deallocate((unsigned char*)errStr);
    
    return true;
  }
  
  return false;
}

/*!
 * \brief Set config to default.
 */
void initSetting(void){
  /* Default Setting. */
  arg.attach             = true;
  arg.FileName           = strdup("heapstats_snapshot.dat");
  arg.heapLogFile        = strdup("heapstats_log.csv");
  arg.archiveFile        = strdup("heapstats_analyze.zip");
  arg.RankLevel          = 5;
  arg.LogLevel           = INFO;
  arg.reduceSnapShot     = true;
  arg.triggerOnFullGC    = true;
  arg.triggerOnDump      = true;
  arg.triggerOnLogError  = true;
  arg.triggerOnLogSignal = true;
  arg.triggerOnLogLock   = false;
  arg.order              = DELTA;
  arg.AlertPercentage    = 50;
  arg.AlertThreshold     = 0; /* Alert disabled. */
  arg.HeapAlertPercentage= 95;
  arg.HeapAlertThreshold = 0; /* Alert disabled. */
  arg.TimerInterval      = 0; /* Timer disabled. */
  arg.LogInterval        = 300000; /* 5min. */
  arg.firstCollect       = true;
  arg.isFirstCollected   = false; /* Don't collected yet, */
  arg.logSignalNormal    = strdup(""); /* Signal disabled. */
  arg.logSignalAll       = strdup("USR2");
  arg.reloadSignal       = strdup("HUP");
  arg.snmpSend           = true;
  arg.snmpTarget         = strdup("localhost");
  arg.snmpComName        = strdup("public");
  arg.logDir             = strdup("./tmp");
  arg.archiveCommand     = strdup("/usr/bin/zip %archivefile% -jr %logdir%");
}

/*!
 * \brief Free config memory.
 * \param targetArg [in,out] Deallocate target settings.
 */
void freeSetting(TArguments *targetArg){
  free(targetArg->FileName);
  targetArg->FileName = NULL;

  free(targetArg->heapLogFile);
  targetArg->heapLogFile = NULL;

  free(targetArg->archiveFile);
  targetArg->archiveFile = NULL;

  free(targetArg->logSignalNormal);
  targetArg->logSignalNormal = NULL;

  free(targetArg->logSignalAll);
  targetArg->logSignalAll = NULL;

  free(targetArg->reloadSignal);
  targetArg->reloadSignal = NULL;

  free(targetArg->snmpTarget);
  targetArg->snmpTarget = NULL;

  free(targetArg->snmpComName);
  targetArg->snmpComName = NULL;

  free(targetArg->logDir);
  targetArg->logDir = NULL;

  free(targetArg->archiveCommand);
  targetArg->archiveCommand = NULL;
}

/*!
 * \brief Inherited config from old config.
 * \param newArg [in,out] New argument settings.
 * \param oldArg [in,out] Old argument settings.
 */
void inheritedSetting(TArguments *newArg, TArguments *oldArg){
  TArguments swapArg;
  memcpy(&swapArg, newArg, sizeof(TArguments));

  /* Copy settings. */
  newArg->triggerOnFullGC   &= oldArg->triggerOnFullGC;
  newArg->triggerOnDump     &= oldArg->triggerOnDump;
  newArg->triggerOnLogError &= oldArg->triggerOnLogError;
  newArg->triggerOnLogSignal = oldArg->triggerOnLogSignal;
  newArg->triggerOnLogLock   = oldArg->triggerOnLogLock;
  newArg->logSignalNormal    = oldArg->logSignalNormal;
  newArg->logSignalAll       = oldArg->logSignalAll;
  newArg->reloadSignal       = oldArg->reloadSignal;
  newArg->snmpSend          &= oldArg->snmpSend;
  newArg->snmpTarget         = oldArg->snmpTarget;
  newArg->snmpComName        = oldArg->snmpComName;

  /* Swap setting. */
  oldArg->logSignalNormal = swapArg.logSignalNormal;
  oldArg->logSignalAll    = swapArg.logSignalAll;
  oldArg->reloadSignal    = swapArg.reloadSignal;
  oldArg->snmpSend       &= swapArg.snmpSend;
  oldArg->snmpTarget      = swapArg.snmpTarget;
  oldArg->snmpComName     = swapArg.snmpComName;

  if(newArg->attach && (!oldArg->attach)){
    /* Reset collected flag. */
    newArg->isFirstCollected = false;
  }
  else{
    newArg->isFirstCollected = oldArg->isFirstCollected;
  }

}

/*!
 * \brief Read boolean value from configuration.
 * \param key [in] Key of this configuration.
 * \param value [in] Value of this configuration.
 * \param default_val [in] Defalt value of this configuration.
 * \return value which is represented by boolean.
 */
static bool ReadBooleanValue(const char *key,
                                const char *value, const bool default_val){

  if(strcmp(value, "true") == 0){
    return true;
  }
  else if(strcmp(value, "false") == 0){
    return false;
  }
  else{
    PRINT_WARN_MSG_HEADER << "Ignore illegal configuration value."
      << " name:" << key << " value:" << value << NEWLINE;

    return default_val;
  }

}

/*!
 * \brief Read string value for filename from configuration.
 * \param key [in] Key of this configuration.
 * \param value [in] Value of this configuration.
 * \param dest [in] [out] Destination of this configuration.
 */
static void ReadFileNameValue(const char *key, const char *value, char **dest){
  /* If file path is legal. */
  std::ofstream testStream(value, std::ios::app);

  if(testStream){

    if(*dest != NULL){
      free(*dest);
    }

    *dest = strdup(value);
  }
  else{
    PRINT_WARN_MSG_HEADER << "Ignore illegal configuration value."
                        << " name:" << key << " value:" << value << NEWLINE;
  }

}

/*!
 * \brief Read long/int value from configuration.
 * \param key [in] Key of this configuration.
 * \param value [in] Value of this configuration.
 * \param default_val [in] Defalt value of this configuration.
 * \param max_val [in] Max value of this parameter.
 * \return value which is represented by long.
 */
static long ReadLongValue(const char *key, const char *value,
                                  const long default_val, const long max_val){
  long ret = default_val;

  /* Convert to number from string. */
  char *done = NULL;
  long temp = strtol(value, &done, 10);

  /* If all string is able to convert to number. */
  if(*done == '\0' && errno != ERANGE && temp <= INT_MAX && temp >= 0){
    ret = temp;
  }
  else{
    PRINT_WARN_MSG_HEADER << "Ignore illegal configuration value."
                       << " name:" << key << " value:" << value   << NEWLINE;
  }

  return ret;
}

/*!
 * \brief Check that path is accessible.
 * \param key [in] Key of this configuration.
 * \param value [in] Value of this configuration.
 * \return Path is accessible.
 */
static bool IsValidPath(const char *key, const char *value){
  bool ret = false;

  /* Check archive file path. */
  char *dir = getParentDirectoryPath(value);
  if(dir != NULL && isAccessibleDirectory(dir, true, true)) {
    /* File is accessible. */
    ret = true;
  }
  else{
    PRINT_WARN_MSG_HEADER << "Ignore illegal configuration value."
                        << " name:" << key << " value:" << value << NEWLINE;
  }

  if(likely(dir != NULL)) {
    free(dir);
  }

  return ret;
}

/*!
 * \brief Load configuration from file.
 * \param filename [in] Read configuration file path.
 */
void loadConfiguration(const char *filename) {
  
  /* Initialize config. */
  initSetting();
  
  /* Check filename. */
  if (filename == NULL || strlen(filename) == 0) {
    return;
  }
  
  /* Open file. */
  std::ifstream ifs(filename);
  if (!ifs) {
    PRINT_WARN_MSG("Couldn't open configuration file.");
    return;
  }
  
  /* Set locale. */
  
  /* Please rewrite as you like if you routinely don't speak English. */
  /*
  if (setlocale(LC_CTYPE, "") == NULL) {
    PRINT_WARN_MSG("Couldn't set locale information.");
    return;
  }
  */
  
  /* Get language table. */
  const unsigned char *tables = NULL; /* pcre_maketables(); */
  
  /* Initialize PCRE. */
  char pExpression[255] = "^\\s*(.+?)\\s*=\\s*(.+?)?\\s*$";
  const char *errMsg;
  int errOffset;
  real_pcre *pPcre;
  /* Generated parse trigger. */
  pPcre = pcre_compile(pExpression, PCRE_ANCHORED, &errMsg, &errOffset,
    tables);
  
  /* If failure initialize. */
  if (pPcre == NULL) {
    PRINT_WARN_MSG("Couldn't initialize PCRE.");
    pcre_free((void*)tables);
    return;
  }
  
  /* Get string line from configure file. */
  long lineCnt = 0;
  char pBuff[512] = {0};
  
  while (!ifs.eof()) {
    /* Read line. */
    ifs.getline(pBuff, 512);
    
    lineCnt++;
    
    /* If this line is comment. */
    if (memcmp(pBuff, COMMENT_HEADER, COMMENT_HEADER_LEN) == 0
      || strlen(pBuff) == 0) {
      
      /* skip this line. */
      continue;
    }
    
    /* Search param in line. */
    int matchCnt = 10;
    int matchArr[10];
    
    try {
      /* Parse line. */
      matchCnt = pcre_exec(pPcre, NULL, pBuff, strlen(pBuff), 0,
                                                0, matchArr, matchCnt);
      
      /* Check matched pair. */
      if (matchCnt > 0) {
        /* Key and value variables. */
        const char *key   = NULL;
        const char *value = NULL;
        
        /* Get matched key string. */
        if (pcre_get_substring(pBuff, matchArr, matchCnt, 1, &key) <= 0) {
          continue;
        }
        /* Get matched value string. */
        if (pcre_get_substring(pBuff, matchArr, matchCnt, 2, &value) <= 0) {
          /* Get "" to set default value or disable option */
          value = (char *)calloc(1, sizeof(char));
        }
        
        /* Check key name. */
        if(strcmp(key, "attach") == 0){
          arg.attach = ReadBooleanValue(key, value, arg.attach);
        }
        else if(strcmp(key, "file") == 0){
          ReadFileNameValue(key, value, &arg.FileName);
        }
        else if (strcmp(key, "heaplogfile") == 0){
          ReadFileNameValue(key, value, &arg.heapLogFile);
        }
        else if(strcmp(key, "archivefile") == 0){

          if(IsValidPath(key, value)){
            free(arg.archiveFile);
            arg.archiveFile = strdup(value);
          }

        }
        else if (strcmp(key, "rank_level") == 0){
          arg.RankLevel = ReadLongValue(key, value, arg.RankLevel, INT_MAX);
        }
        else if (strcmp(key, "loglevel") == 0){
          /* Check log level. */
          if (strcmp(value, "CRIT") == 0) {
            arg.LogLevel = CRIT;
          } else if (strcmp(value, "WARN") == 0) {
            arg.LogLevel = WARN;
          } else if (strcmp(value, "INFO") == 0) {
            arg.LogLevel = INFO;
          } else if (strcmp(value, "DEBUG") == 0) {
            arg.LogLevel = DEBUG;
          } else {
            PRINT_WARN_MSG_HEADER
              << "Ignore illegal configuration value."
              << " name:" << key << " value:" << value
              << NEWLINE;
          }
        }
        else if (strcmp(key, "reduce_snapshot") == 0){
          arg.reduceSnapShot = ReadBooleanValue(key, value, arg.reduceSnapShot);
        }
        else if(strcmp(key, "trigger_on_fullgc") == 0){
          arg.triggerOnFullGC = ReadBooleanValue(key, value, arg.triggerOnFullGC);
        }
        else if(strcmp(key, "trigger_on_dump") == 0){
          arg.triggerOnDump = ReadBooleanValue(key, value, arg.triggerOnDump);
        }
        else if(strcmp(key, "trigger_on_logerror") == 0){
          arg.triggerOnLogError = ReadBooleanValue(key, value,
                                                   arg.triggerOnLogError);
        }
        else if(strcmp(key, "trigger_on_logsignal") == 0){
          arg.triggerOnLogSignal = ReadBooleanValue(key, value,
                                                   arg.triggerOnLogSignal);
        }
        else if(strcmp(key, "trigger_on_loglock") == 0){
          arg.triggerOnLogLock = ReadBooleanValue(key, value,
                                                     arg.triggerOnLogLock);
        }
        else if(strcmp(key, "rank_order") == 0){
          /* Check ranking order value. */
          if (strcmp(value, "usage") == 0) {
            arg.order = USAGE;
          } else if (strcmp(value, "delta") == 0) {
            arg.order = DELTA;
          } else {
            PRINT_WARN_MSG_HEADER
              << "Ignore illegal configuration value."
              << " name:" << key << " value:" << value
              << NEWLINE;
          }
        }
        else if(strcmp(key, "alert_percentage") == 0){
          arg.AlertPercentage = ReadLongValue(key, value,
                                             arg.AlertPercentage, INT_MAX);
        }
        else if (strcmp(key, "javaheap_alert_percentage") == 0) {
          arg.HeapAlertPercentage = ReadLongValue(key, value,
                                             arg.HeapAlertPercentage, INT_MAX);
        }
        else if(strcmp(key, "snapshot_interval") == 0){
          arg.TimerInterval = ReadLongValue(key, value,
                                  arg.TimerInterval, LONG_MAX / 1000) * 1000;
        }
        else if(strcmp(key, "log_interval") == 0){
          arg.LogInterval = ReadLongValue(key, value,
                                  arg.LogInterval, LONG_MAX / 1000) * 1000;
        }
        else if(strcmp(key, "first_collect") == 0){
          arg.firstCollect = ReadBooleanValue(key, value, arg.firstCollect);
        } else if (strcmp(key, "logsignal_normal") == 0
          || strcmp(key, "logsignal_all") == 0
          || strcmp(key, "signal_reload") == 0) {
          
          /* If signal is supported by JVM. */
          if (isSupportSignal(value) || value == NULL || value[0] == '\0') {
            
            /* Copy string exclude "SIG". */
            if (strcmp(key, "logsignal_normal") == 0) {
              free(arg.logSignalNormal);
              if (value == NULL || value[0] == '\0') {
                arg.logSignalNormal = strdup("");
              } else {
                arg.logSignalNormal = strdup(value + 3);
              }

            } else if (strcmp(key, "logsignal_all") == 0) {
              free(arg.logSignalAll);
              if (value == NULL || value[0] == '\0') {
                arg.logSignalAll = strdup("");
              } else {
                arg.logSignalAll = strdup(value + 3);
              }

            } else {
              free(arg.reloadSignal);
              if (value == NULL || value[0] == '\0') {
                arg.reloadSignal = strdup("");
              } else {
                arg.reloadSignal = strdup(value + 3);
              }
            }
          } else {
            PRINT_WARN_MSG_HEADER
              << "Ignore illegal configuration value."
              << " name:" << key << " value:" << value
              << NEWLINE;
          }
        }
        else if(strcmp(key, "snmp_send") == 0){
          arg.snmpSend = ReadBooleanValue(
                         key, value, arg.snmpSend);
        }
        else if(strcmp(key, "snmp_target") == 0){
          /* Setting snmp target. */
          free(arg.snmpTarget);
          arg.snmpTarget = strdup(value);
        }
        else if(strcmp(key, "snmp_comname") == 0){
          /* Setting snmp community name. */
          free(arg.snmpComName);
          /* If set empty value to community name. */
          if (strcmp(value, "(NULL)") == 0) {
            arg.snmpComName = strdup("");
          } else {
            arg.snmpComName = strdup(value);
          }
        }
        else if(strcmp(key, "logdir") == 0){

          if(IsValidPath(key, value)){
            free(arg.logDir);
            arg.logDir = strdup(value);
          }

        }
        else if(strcmp(key, "archive_command") == 0){
          /* Setting log archive command. */
          free(arg.archiveCommand);
          arg.archiveCommand = strdup(value);
        }
        else{
          PRINT_WARN_MSG_HEADER
            << "Unknown configuration name. name:" << key
            << NEWLINE;
        }
        
        /* Cleanup after param setting. */
        pcre_free_substring(key);
        pcre_free_substring(value);
      }
    } catch(...){
      PRINT_WARN_MSG_HEADER
        << "Read configuration failed. line:" << lineCnt << NEWLINE;
    }
  }
  
  /* Cleanup after load file. */
  pcre_free((void*)tables);
  pcre_free(pPcre);
}

/*!
 * \brief Print setting information.
 */
void printSetting(void) {
  const char *EMPTY_STR = "";
  
  /* Agent attach state. */
  if (arg.attach) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Agent Attach Enable = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Agent Attach Enable = false" << NEWLINE;
  }
  
  /* Output filenames. */
  PRINT_INFO_MSG_HEADER_NOIF << "SnapShot FileName = "
    << ((arg.FileName != NULL) ? arg.FileName : EMPTY_STR) << NEWLINE;
  
  PRINT_INFO_MSG_HEADER_NOIF << "Heap Log FileName = "
    << ((arg.heapLogFile != NULL) ? arg.heapLogFile : EMPTY_STR) << NEWLINE;
  
  PRINT_INFO_MSG_HEADER_NOIF << "Archive FileName = "
    << ((arg.archiveFile != NULL) ? arg.archiveFile : EMPTY_STR) << NEWLINE;
  
  /* Output log-level. */
  switch(arg.LogLevel) {
    case CRIT:
      PRINT_INFO_MSG_HEADER_NOIF << "LogLevel = Critical" << NEWLINE;
      break;
      
    case WARN:
      PRINT_INFO_MSG_HEADER_NOIF << "LogLevel = Warning" << NEWLINE;
      break;
      
    case INFO:
      PRINT_INFO_MSG_HEADER_NOIF << "LogLevel = Info" << NEWLINE;
      break;

    case DEBUG:
      PRINT_INFO_MSG_HEADER_NOIF << "LogLevel = Debug" << NEWLINE;
      break;

    default: /* Illegal log level. */
      PRINT_INFO_MSG_HEADER_NOIF << "LogLevel = UNKNOWN" << NEWLINE;
      break;
  }
  
  /* Output about reduce SnapShot. */
  
  if (arg.reduceSnapShot) {
    PRINT_INFO_MSG_HEADER_NOIF << "ReduceSnapShot = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "ReduceSnapShot = false" << NEWLINE;
  }
  
  /* Output status of snapshot triggers. */
  
  if (arg.triggerOnFullGC) {
    PRINT_INFO_MSG_HEADER_NOIF << "Trigger on FullGC = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "Trigger on FullGC = false" << NEWLINE;
  }
  
  if (arg.triggerOnDump) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Trigger on DumpRequest = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Trigger on DumpRequest = false" << NEWLINE;
  }
  
  /* Output status of logging triggers. */
  
  if (arg.triggerOnLogError) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Log Trigger on Error = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Log Trigger on Error = false" << NEWLINE;
  }
  
  if (arg.triggerOnLogSignal) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Log Trigger on Signal = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Log Trigger on Signal = false" << NEWLINE;
  }
  
  if (arg.triggerOnLogLock) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Log Trigger on Deadlock (experimental feature) = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Log Trigger on Deadlock (experimental feature) = false" << NEWLINE;
  }
  
  /* Output about ranking. */
  
  if (arg.order == DELTA) {
    PRINT_INFO_MSG_HEADER_NOIF << "RankingOrder = DELTA" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "RankingOrder = USAGE" << NEWLINE;
  }
  
  PRINT_INFO_MSG_HEADER_NOIF << "RankLevel = " << arg.RankLevel << NEWLINE;
  
  /* Output about heap alert. */
  
  if (arg.AlertThreshold <= 0) {
    PRINT_INFO_MSG_HEADER_NOIF << "HeapAlert is DISABLED." << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "AlertPercentage = " << arg.AlertPercentage
      << " ( " << arg.AlertThreshold << " bytes )" << NEWLINE;
  }

  /* Output about heap alert. */

  if (arg.HeapAlertThreshold <= 0) {
    PRINT_INFO_MSG_HEADER_NOIF << "Java heap usage alert is DISABLED." << NEWLINE;
  }
  else{
    PRINT_INFO_MSG_HEADER_NOIF
            << "Java heap usage alert percentage = " << arg.HeapAlertPercentage
            << " ( " << arg.HeapAlertThreshold / 1024 / 1024 << " MB )"
            << NEWLINE;
  }
  
  /* Output about interval snapshot. */
  
  if (arg.TimerInterval == 0) {
    PRINT_INFO_MSG_HEADER_NOIF << "Interval SnapShot is DISABLED."
      << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "SnapShot interval = "
      << arg.TimerInterval / 1000 << " sec" << NEWLINE;
  }
  
  /* Output about interval logging. */
  
  if (arg.LogInterval == 0) {
    PRINT_INFO_MSG_HEADER_NOIF << "Interval Logging is DISABLED."
      << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "Log interval = "
      << arg.LogInterval / 1000 << " sec" << NEWLINE;
  }
  
  if (arg.firstCollect) {
    PRINT_INFO_MSG_HEADER_NOIF << "First Collect log = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "First Collect log = false" << NEWLINE;
  }
  
  /* Output logging signal name. */
  if (arg.logSignalNormal == NULL || strlen(arg.logSignalNormal) == 0) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Normal logging signal is DISABLED." << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Normal logging signal = " << arg.logSignalNormal << NEWLINE;
  }
  
  if (arg.logSignalAll == NULL || strlen(arg.logSignalAll) == 0) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "All logging signal is DISABLED." << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "All logging signal = " << arg.logSignalAll << NEWLINE;
  }
  
  if (arg.reloadSignal == NULL || strlen(arg.reloadSignal) == 0) {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Reload signal is DISABLED." << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF
      << "Reload signal = " << arg.reloadSignal << NEWLINE;
  }
  
  /* Output about SNMP trap. */
  
  if (arg.snmpSend) {
    PRINT_INFO_MSG_HEADER_NOIF << "SNMP send = true" << NEWLINE;
  } else {
    PRINT_INFO_MSG_HEADER_NOIF << "SNMP send = false" << NEWLINE;
  }
  
  PRINT_INFO_MSG_HEADER_NOIF << "SNMP target = "
    << ((arg.snmpTarget != NULL) ? arg.snmpTarget : EMPTY_STR)
    << NEWLINE;
  PRINT_INFO_MSG_HEADER_NOIF << "SNMP community = "
    << ((arg.snmpComName != NULL) ? arg.snmpComName : EMPTY_STR)
    << NEWLINE;
  
  /* Output temporary log directory path. */
  
  PRINT_INFO_MSG_HEADER_NOIF << "Log dirictory = "
    << ((arg.logDir != NULL) ? arg.logDir : EMPTY_STR)
    << NEWLINE;
  
  /* Output archive command. */
  
  PRINT_INFO_MSG_HEADER_NOIF << "Archive command = \""
    << ((arg.archiveCommand != NULL) ? arg.archiveCommand : EMPTY_STR)
    << "\"" << NEWLINE;

}

/*!
 * \brief Get system information.
 * \param env [in] JNI envrionment.
 * \param key [in] System property key.
 * \return String of system property.
 */
char *GetSystemProperty(JNIEnv *env, const char *key) {
  /* Convert string. */
  jstring key_str = env->NewStringUTF(key);
  
  /* Search class. */
  jclass sysClass = env->FindClass("Ljava/lang/System;");
  
  /* if raised exception. */
  if (env->ExceptionOccurred()) {
    env->ExceptionDescribe();
    env->ExceptionClear();
    
    PRINT_WARN_MSG("Get system class failed !");
    return NULL;
  }
  
  /* Search method. */
  jmethodID System_getProperty = env->GetStaticMethodID(sysClass,
    "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
  
  /* If raised exception. */
  if (env->ExceptionOccurred()) {
    env->ExceptionDescribe();
    env->ExceptionClear();
    
    PRINT_WARN_MSG("Get system method failed !");
    return NULL;
  }
  
  /* Call method in find class. */
  
  jstring ret_str = (jstring)env->CallStaticObjectMethod(sysClass,
    System_getProperty, key_str);
  
  /* If got nothing or raised exception. */
  if ((ret_str == NULL) || env->ExceptionOccurred()) {
    env->ExceptionDescribe();
    env->ExceptionClear();
    
    PRINT_WARN_MSG("Get system properties failed !");
    return NULL;
  }
  
  /* Get result and clean up. */
  
  const char *ret_utf8 = env->GetStringUTFChars(ret_str, NULL);
  char *ret = NULL;
  
  /* If raised exception. */
  if (env->ExceptionOccurred()) {
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  else if(ret_utf8 != NULL) {
    /* Copy and free if got samething. */
    ret = strdup(ret_utf8);
    env->ReleaseStringUTFChars(ret_str, ret_utf8);
  }
  
  /* Cleanup. */
  env->DeleteLocalRef(key_str);
  return ret;
}

/*!
 * \brief Get ClassUnload event index.
 * \param jvmti [in] JVMTI envrionment object.
 * \return ClassUnload event index.
 * \sa     hotspot/src/share/vm/prims/jvmtiExport.cpp<br>
 *         hotspot/src/share/vm/prims/jvmtiEventController.cpp<br>
 *         hotspot/src/share/vm/prims/jvmti.xml<br>
 *         in JDK.
 */
jint GetClassUnloadingExtEventIndex(jvmtiEnv *jvmti) {
  jint count, ret = -1;
  jvmtiExtensionEventInfo *events;
  
  /* Get extension events. */
  if (isError(jvmti, jvmti->GetExtensionEvents(&count, &events))) {
    
    /* Failure get extension events. */
    PRINT_WARN_MSG("Get JVMTI Extension Event failed!");
    return -1;
    
  } else if(count <= 0) {
    
    /* If extension event is none. */
    PRINT_WARN_MSG("VM has no JVMTI Extension Event!");
    if (events != NULL) {
      jvmti->Deallocate((unsigned char *)events);
    }
    
    return -1;
  }
  
  /* Check extension events. */
  for (int Cnt = 0; Cnt < count; Cnt++) {
    if (strcmp(events[Cnt].id, "com.sun.hotspot.events.ClassUnload") == 0) {
      ret = events[Cnt].extension_event_index;
      break;
    }
  }
  
  /* Clean up. */
  
  jvmti->Deallocate((unsigned char *)events);
  return ret;
}

/*!
 * \brief Verify CPU instruction set.
 */
void verifyInstructSet(void){
  int cFlag = 0;
  int dFlag = 0;
  
  /* Call CPUID. */
  asm volatile(
    
    /* ebx is address register by PIC in 32bit. */
  #ifdef __i386__
    "pushl %%ebx;\n\t"
  #endif
    
    "cpuid;\n\t"
    
  #ifdef __i386__
    "popl %%ebx;\n\t"
  #endif
    
    : "=c"(cFlag), "=d"(dFlag)
    : "a"(1)
    : "cc"
    #ifndef __i386__
    , "%ebx"
    #endif
  );
  
  /* Set instruction usable flag. */
  usableAVX    = ((cFlag >> 28) & 1) != 0;
  usableSSE4_2 = ((cFlag >> 20) & 1) != 0;
  usableSSE4_1 = (((cFlag >> 19) & 1) != 0) || usableSSE4_2;
  usableSSSE3  = ((cFlag >>  9) & 1) != 0;
  usableSSE3   = (( cFlag        & 1) != 0) || usableSSSE3;
  usableSSE2   = ((dFlag >> 26) & 1) != 0;
  
  /* If doesn't meet required performance conditions. */
  if (!usableSSE2) {
    PRINT_WARN_MSG("Performance isn't enough."
      " please confirm this machine spec.");
  }
}

/*!
 * \brief Replace old string in new string on string.
 * \param str    [in] Process target string.
 * \param oldStr [in] Targer of replacing.
 * \param newStr [in] A string will replace existing string.
 * \return String invoked replace.<br>Don't forget deallocate.
 */
char *strReplase(char const* str, char const* oldStr, char const* newStr) {
  char *strPos = NULL;
  char *chrPos = NULL;
  int oldStrCnt = 0;
  int oldStrLen = 0;
  char *newString = NULL;
  int newStrLen = 0;
  
  /* Sanity check. */
  if (unlikely(str == NULL || oldStr == NULL || newStr == NULL
                             || strlen(str) == 0 || strlen(oldStr) == 0)) {
    
    PRINT_WARN_MSG("Illegal string replacing paramters.");
    return NULL;
  }
  
  oldStrLen = strlen(oldStr);
  strPos = (char*)str;
  /* Counting oldStr */
  while (true) {
    /* Find old string. */
    chrPos = strstr(strPos, oldStr);
    if (chrPos == NULL) {
      /* All old string is already found. */
      break;
    }
    
    /* Counting. */
    oldStrCnt++;
    /* Move next. */
    strPos = chrPos + oldStrLen;
  }
  
  newStrLen = strlen(newStr);
  /* Allocate result string memory. */
  newString = (char*)calloc(1, strlen(str) + (newStrLen * oldStrCnt)
                                               - (oldStrLen * oldStrCnt) + 1);
  /* If failure allocate result string. */
  if (unlikely(newString == NULL)) {
    PRINT_WARN_MSG("Failure allocate replaced string.");
    return NULL;
  }
  
  strPos = (char*)str;
  while (true) {
    /* Find old string. */
    chrPos = strstr(strPos, oldStr);
    if (unlikely(chrPos == NULL)) {
      
      /* all old string is replaced. */
      strcat(newString, strPos);
      break;
    }
    /* Copy from string exclude old string. */
    strncat(newString, strPos, (chrPos - strPos));
    /* Copy from new string. */
    strcat(newString, newStr);
    
    /* Move next. */
    strPos = chrPos + oldStrLen;
  }
  
  /* Succeed. */
  return newString;
}

/*!
 * \brief Get now date and time.
 * \return Mili-second elapsed time from 1970/1/1 0:00:00.
 */
jlong getNowTimeSec(void) {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return (jlong)tv.tv_sec * 1000 + (jlong)tv.tv_usec / 1000;
}

/*!
 * \brief Check signal is supported by JVM.
 * \param sigName [in] Name of signal.
 * \return Signal is supported, if value is true.
 *         Otherwise, value is false.
 * \sa Please see below JDK source about JVM supported signal.<br>
 *     hotspot/src/os/linux/vm/jvm_linux.cpp
 */
bool isSupportSignal(char const* sigName) {
  /* JVM supported signals. */
  const char *supportSignals[] = {
    /* POSIX signal. */
    "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", "SIGFPE", 
    "SIGKILL", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1",
    "SIGUSR2", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN",
    "SIGTTOU", "SIGBUS", "SIGPOLL", "SIGTRAP", "SIGURG", "SIGVTALRM",
    "SIGXCPU", "SIGXFSZ", "SIGPROF",
  #ifdef SIGSYS
    "SIGSYS",
  #endif
    
    /* Non-POSIX signal. */
    "SIGIOT", "SIGIO", "SIGCLD", "SIGPWR", "SIGWINCH",
  #ifdef SIGSTKFLT
    "SIGSTKFLT",
  #endif
    
    /* End flag. */
    NULL
    /* JVM was not supported below signal. */
    /* "SIGINFO", "SIGLOST", "SIGEMT", "SIGUNUSED" and etc.. */
  };
  
  /* Search signal name. */
  for (int sigIdx = 0; supportSignals[sigIdx] != NULL; sigIdx++) {
    if (strcmp(sigName, supportSignals[sigIdx]) == 0) {
      
      /* Found. */
      return true;
    }
  }
  
  return false;
}

/*!
 * \brief A little sleep.
 * \param sec  [in] Second of sleep range.
 * \param nsec [in] Nano second of sleep range.
 */
void littleSleep(const long int sec, const long int nsec) {
  
  /* Timer setting variables. */
  int result = 0;
  struct timespec buf1;
  struct timespec buf2 = {sec, nsec};
  
  struct timespec *req = &buf1;
  struct timespec *rem = &buf2;
  
  /* Wait loop. */
  do {
    /* Reset and exchange time. */
    memset(req, 0, sizeof(timespec));
    asm volatile(
      #ifdef __x86_64__
        "xchgq %0, %1;"
      #else // __i386__
        "xchgl %0, %1;"
      #endif
      : "=r" (req), "=r" (rem) : "0" (req), "1" (rem));
    errno = 0;
    
    /* Sleep. */
    result = nanosleep(req, rem);
  } while(result != 0 && errno == EINTR);
}

/*!
 * \brief Get thread information.
 * \param jvmti  [in]  JVMTI environment object.
 * \param env    [in]  JNI environment object.
 * \param thread [in]  Thread object in java.
 * \param info   [out] Record stored thread information.
 */
void getThreadDetailInfo(jvmtiEnv *jvmti, JNIEnv* env, jthread thread,
  TJavaThreadInfo *info) {
  
  /* Get thread basic information. */
  jvmtiThreadInfo threadInfo = {0};
  if (unlikely(isError(jvmti,
    jvmti->GetThreadInfo(thread, &threadInfo)))) {
    
    /* Setting dummy information. */
    info->name = strdup("Unknown-Thread");
    info->isDaemon = false;
    info->priority = 0;
  } else {
    
    /* Setting information. */
    info->name = strdup(threadInfo.name);
    info->isDaemon = (threadInfo.is_daemon == JNI_TRUE);
    info->priority = threadInfo.priority;
    
    /* Cleanup. */
    jvmti->Deallocate((unsigned char *)threadInfo.name);
    env->DeleteLocalRef(threadInfo.thread_group);
    env->DeleteLocalRef(threadInfo.context_class_loader);
  }
  
  /* Get thread state. */
  jint state = 0;
  jvmti->GetThreadState(thread, &state);
  
  /* Set thread state. */
  switch (state & JVMTI_JAVA_LANG_THREAD_STATE_MASK) {
    case JVMTI_JAVA_LANG_THREAD_STATE_NEW:
      info->state = strdup("NEW");
      break;
    case JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED:
      info->state = strdup("TERMINATED");
      break;
    case JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE:
      info->state = strdup("RUNNABLE");
      break;
    case JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED:
      info->state = strdup("BLOCKED");
      break;
    case JVMTI_JAVA_LANG_THREAD_STATE_WAITING:
      info->state = strdup("WAITING");
      break;
    case JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING:
      info->state = strdup("TIMED_WAITING");
      break;
    default:
      info->state = NULL;
  }
}

/*!
 * \brief Get method information in designed stack frame.
 * \param jvmti [in]  JVMTI environment object.
 * \param env   [in]  JNI environment object.
 * \param frame [in]  method stack frame.
 * \param info  [out] Record stored method information in stack frame.
 */
void getMethodFrameInfo(jvmtiEnv *jvmti, JNIEnv* env, jvmtiFrameInfo frame,
  TJavaStackMethodInfo *info) {
  
  char *tempStr = NULL;
  jclass declareClass = NULL;
  
  /* Get method class. */
  if (unlikely(isError(jvmti,
    jvmti->GetMethodDeclaringClass(frame.method, &declareClass)))) {
    
    info->className  = NULL;
    info->sourceFile = NULL;
  } else {
    /* Get class signature. */
    if (unlikely(isError(jvmti,
      jvmti->GetClassSignature(declareClass, &tempStr, NULL)))) {
      
      info->className = NULL;
    } else {
      info->className = strdup(tempStr);
      jvmti->Deallocate((unsigned char *)tempStr);
    }
    
    /* Get source filename. */
    if (unlikely(isError(jvmti,
      jvmti->GetSourceFileName(declareClass, &tempStr)))) {
      
      info->sourceFile = NULL;
    } else {
      info->sourceFile = strdup(tempStr);
      jvmti->Deallocate((unsigned char *)tempStr);
    }
    
    env->DeleteLocalRef(declareClass);
  }
  
  /* Get method name. */
  if (unlikely(isError(jvmti,
    jvmti->GetMethodName(frame.method, &tempStr, NULL, NULL)))) {
    
    info->methodName = NULL;
  } else {
    info->methodName = strdup(tempStr);
    jvmti->Deallocate((unsigned char *)tempStr);
  }
  
  /* Check method is native. */
  jboolean isNativeMethod = JNI_TRUE;
  jvmti->IsMethodNative(frame.method, &isNativeMethod);
  
  if (unlikely(isNativeMethod == JNI_TRUE)) {
    
    /* method is native. */
    info->isNative = true;
    info->lineNumber = -1;
  } else {
    
    /* method is java method. */
    info->isNative = false;
    
    /* Get source code line number. */
    jint entriyCount = 0;
    jvmtiLineNumberEntry *entries = NULL;
    if (unlikely(isError(jvmti,
      jvmti->GetLineNumberTable(frame.method, &entriyCount, &entries)))) {
      
      /* Method location is unknown. */
      info->lineNumber = -1;
    } else {
      
      /* Search running line. */
      jint lineIdx = 0;
      entriyCount--;
      for (; lineIdx < entriyCount; lineIdx++) {
        if (frame.location <= entries[lineIdx].start_location) {
          break;
        }
      }
      
      info->lineNumber = entries[lineIdx].line_number;
      jvmti->Deallocate((unsigned char *)entries);
    }
  }
}