view agent/src/util.cpp @ 35:4502530ca7bd

Bug 2114: Default values are different from binary-builtin and heapstats.conf. reviewed-by: yasuenag
author KUBOTA Yuji <kubota.yuji@gmail.com>
date Sun, 14 Dec 2014 00:39:47 +0900
parents 6c73e9d7876c
children d766054a28ee
line wrap: on
line source

/*!
 * \file util.cpp
 * \brief This file is utilities.
 * 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 <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"
#include "oopUtil.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

/* Macro define. */

/*!
 * \brief Macro of return code length.<br />
 *        E.g. linux is 1(LF), mac is 1(CR), windows is 2 (CRLF).
 */
#define RETURN_CODE_LEN 1

/*!
 * \brief Macro of comment to EOL in configuration file.
 */
#define COMMENT_CHAR "#"

/*!
 * \brief Macro of length of comment to EOL in configuration file.
 */
#define COMMENT_CHAR_LEN 1

/* Functions. */

/*!
 * \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   = true;
    arg.order              = DELTA;
    arg.AlertPercentage    = 50;
    arg.AlertThreshold     = 0; /* Alert disabled. */
    arg.HeapAlertPercentage= 95;
    arg.HeapAlertThreshold = 0; /* Alert disabled. */
    arg.MetaspaceThreshold = 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%");
    arg.killOnError        = false;
}

/*!
 * \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));
    
    /* Inherited settings from old 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 Read order 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 TRankOrder.
  */
static TRankOrder ReadRankOrderValue(const char *key, const char *value,
                                                const TRankOrder default_val){
  TRankOrder ret = default_val;

  if(strcmp(value, "usage") == 0){
    ret = USAGE;
  }
  else if(strcmp(value, "delta") == 0){
    ret = DELTA;
  }
  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) == 0)) {
    /* 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. */
    FILE *conf = fopen(filename, "r");
    if (unlikely(conf == NULL)) {
        int raisedErrNum = errno;
        PRINT_FAIL_OPENFILE_MSG("configuration file", raisedErrNum);
        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.");
        fclose(conf);
        return;
    }
    */
    
    /* Get language table. */
    const unsigned char *tables = NULL; /* pcre_maketables(); */
    
    /*
     * We support below three pattern line format.
     * 1: format ^\s*#.*$
     *    e.g. "# comment form line head to end of line."
     * 2: format ^\s*(.+)\s*=\s*(.+)\s*$
     *    e.g. " config_key = config_value "
     * 3: format ^\s*(.+)\s*=\s*(.+)\s+#.*\\s*$
     *    e.g. " config_key = config_value # And comment to EOL"
     */
    const char strExpr[] = "^\\s*(?(?=" COMMENT_CHAR ").*|"
        "([^\\s" COMMENT_CHAR "]+?)\\s*=\\s*([^" COMMENT_CHAR "]*?)"
        "\\s*(?(?=" COMMENT_CHAR ").*)\\s*)$";
    const char *errMsg = NULL;
    int errPos = -1;
    real_pcre *pcreExpr = NULL;
    
    /* Generated parse trigger. */
    pcreExpr = pcre_compile(strExpr, PCRE_ANCHORED,
        &errMsg, &errPos, tables);
    /* If failure initialize. */
    if (unlikely(pcreExpr == NULL)) {
        PRINT_WARN_MSG_HEADER << "Couldn't initialize PCRE."
            << " cause:\"" << errMsg << "\"" << NEWLINE;
        pcre_free((void*)tables);
        fclose(conf);
        return;
    }
    
    /* Get string line from configure file. */
    long lineCnt = 0;
    char *lineBuff = NULL;
    size_t lineBuffLen = 0;
    
    /* Read line. */
    while (likely(getline(&lineBuff, &lineBuffLen, conf) > 0)) {
        
        lineCnt++;
        /* If this line is empty. */
        if (unlikely(strlen(lineBuff) <= RETURN_CODE_LEN)) {
            
            /* skip this line. */
            continue;
        }
        
        /* Search param in line. */
        int matchCnt = 9;
        int matchArr[9];
        /* Parse line. */
        int result = pcre_exec(pcreExpr, NULL, lineBuff, strlen(lineBuff),
            0, 0, matchArr, matchCnt);
        
        /* Check matched pair. */
        if (likely(result >= 0)) {
            /* Key and value variables. */
            const char *key   = NULL;
            const char *value = NULL;
            
            /* Get matched key string. */
            if (unlikely(pcre_get_substring(lineBuff, matchArr, matchCnt,
                1, &key) <= 0)) {
                
                /* This line maybe comment only. */
                pcre_free_substring(key);
                continue;
            }
            
            /* Get matched value string. */
            if (unlikely(pcre_get_substring(lineBuff, matchArr, matchCnt,
                2, &value) <= 0)) {
                
                /* This config maybe designated default value. e.g. "aaa=" */
                pcre_free_substring(key);
                pcre_free_substring(value);
                continue;
            }
            
            /* 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) {
              arg.order = ReadRankOrderValue(key, value, arg.order);
            } 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, "metaspace_alert_threshold") == 0) {
              arg.MetaspaceThreshold =
                 ReadLongValue(key, value, arg.MetaspaceThreshold, LONG_MAX)
                                                          * 1024 * 1024; // in MB
            } 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)) {
                    
                    /* Copy string exclude "SIG". */
                    if (strcmp(key, "logsignal_normal") == 0) {
                        free(arg.logSignalNormal);
                        arg.logSignalNormal = strdup(value + 3);
                        
                    } else if (strcmp(key, "logsignal_all") == 0) {
                        free(arg.logSignalAll);
                        arg.logSignalAll = strdup(value + 3);
                        
                    } else {
                        free(arg.reloadSignal);
                        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 if (strcmp(key, "kill_on_error") == 0) {
              arg.killOnError = ReadBooleanValue(key, value, arg.killOnError);
            } else {
                PRINT_WARN_MSG_HEADER
                    << "Unknown configuration name. name:" << key
                    << NEWLINE;
            }
            
            /* Cleanup after param setting. */
            pcre_free_substring(key);
            pcre_free_substring(value);
        } else {
            PRINT_WARN_MSG_HEADER
                << "Read configuration failed. line:" << lineCnt << NEWLINE;
        }
    }
    
    /* Cleanup after load file. */
    if (likely(lineBuff != NULL)) {
        free(lineBuff);
    }
    fclose(conf);
    
    pcre_free((void*)tables);
    pcre_free(pcreExpr);
}

/*!
 * \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 = true" << NEWLINE;
    } else {
        PRINT_INFO_MSG_HEADER_NOIF
            << "Log Trigger on Deadlock = 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 metaspace alert. */
    const char *label = enableCR6964458 ? "Metaspace" : "PermGen";
    
    if (arg.MetaspaceThreshold <= 0) {
        PRINT_INFO_MSG_HEADER_NOIF << label << " usage alert is DISABLED." << NEWLINE;
    } else {
        PRINT_INFO_MSG_HEADER_NOIF
            << label << " usage alert threshold = "
            << arg.MetaspaceThreshold / 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;
    
    /* Output about force killing JVM. */
    
    if (arg.killOnError) {
        PRINT_INFO_MSG_HEADER_NOIF << "Kill on Error = true" << NEWLINE;
    } else {
        PRINT_INFO_MSG_HEADER_NOIF << "Kill on Error = false" << 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) : : "memory");
        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);
        }
    }
}