view agent/src/libmain.cpp @ 19:ac44c043b016

Bug 1717: Reduce memory load operation. reviewed-by: ykubota
author Yasumasa Suenaga <suenaga.yasumasa@lab.ntt.co.jp>
date Fri, 28 Mar 2014 13:40:20 +0900
parents b21d5eef58f0
children
line wrap: on
line source

/*!
 * \file libmain.cpp
 * \brief This file is used to common works.<br>
 *        e.g. initialization, finalization, etc...
 * 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 <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <execinfo.h>
#include <signal.h>

#include "libmain.hpp"
#include "logMain.hpp"
#include "snapShotMain.hpp"

#include "timer.hpp"
#include "util.hpp"
#include "oopUtil.hpp"
#include "jvmInfo.hpp"
#include "trapSender.hpp"
#include "signalManager.hpp"
#include "elapsedTimer.hpp"

/* Variables. */

/*!
 * \brief JVM running performance information.
 */
TJvmInfo *jvmInfo;
/*!
 * \brief Signal manager to reload configuration by signal.
 */
TSignalManager *reloadSigMngr = NULL;
/*!
 * \brief Reload configuration timer thread.
 */
TTimer *intervalSigTimer;
/*!
 * \brief Flag of reload configuration for signal.
 */
volatile sig_atomic_t flagReloadConfig;

/*!
 * \brief Running flag.
 */
int flagRunning = 0;

/*!
 * \brief CPU clock ticks.
 */
long TElapsedTimer::clock_ticks = sysconf(_SC_CLK_TCK);

/*!
 * \brief Datetime of agent initialization.
 */
unsigned long int TTrapSender::initializeTime;

/*!
 * \brief Mutex for TTrapSender.<br>
 * <br>
 * This mutex used in below process.<br>
 *   - TTrapSender::TTrapSender @ trapSender.hpp<br>
 *     To initialize SNMP trap section.<br>
 *   - TTrapSender::~TTrapSender @ trapSender.hpp<br>
 *     To finalize SNMP trap section.<br>
 *   - TTrapSender::sendTrap @ trapSender.hpp<br>
 *     To change SNMP trap section and send SNMP trap.<br>
 */
pthread_mutex_t TTrapSender::senderMutex
    = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

/*!
 * \brief Path of load configuration file at agent initialization.
 */
char *loadConfigPath = NULL;

/* Macros. */

/*!
 * \brief Check memory duplication macro.<br>
 *        If load a library any number of times,<br>
 *        Then the plural agents use single memory area.<br>
 *        E.g. "java -agentlib:A -agentlib:A -jar X.jar"
 */
#define CHECK_DOUBLING_RUN \
    if (flagRunning != 0) {                                      \
        /* Already running another heapstats agent. */           \
        PRINT_WARN_MSG("Already running heapstats agent on JVM." \
            " This agent is disabled.");                         \
        return SUCCESS;                                          \
    }                                                            \
    /* Setting running flag. */                                  \
    flagRunning = 1;

/* Functions. */

/*!
 * \brief Handle signal express user wanna reload config.
 * \param signo   [in] Number of received signal.
 * \param siginfo [in] Information of received signal.
 * \param data    [in] Data of received signal.
 */
void ReloadSigProc(int signo, void *siginfo, void *data) {
    
    /* Enable flag. */
    flagReloadConfig = 1;
    NOTIFY_CATCH_SIGNAL;
}

/*!
 * \brief Setting enable of JVMTI and extension events.
 * \param jvmti  [in] JVMTI environment object.
 * \param enable [in] Event notification is enable.
 * \return Setting process result.
 */
jint SetEventEnable(jvmtiEnv *jvmti, bool enable) {
    jint result = SUCCESS;
    
    /* Set snapshot component enable. */
    result = setEventEnableForSnapShot(jvmti, enable);
    if (result == SUCCESS) {
        
        /* Set logging component enable. */
        result = setEventEnableForLog(jvmti, enable);
    }
    
    return result;
}

/*!
 * \brief Setting enable of agent each threads.
 * \param jvmti  [in] JVMTI environment object.
 * \param env    [in] JNI environment object.
 * \param enable [in] Event notification is enable.
 */
void SetThreadEnable(jvmtiEnv *jvmti, JNIEnv *env, bool enable) {
    
    /* Change thread enable for snapshot. */
    setThreadEnableForSnapShot(jvmti, env, enable);
    
    /* Change thread enable for log. */
    setThreadEnableForLog(jvmti, env, enable);
}

/*!
 * \brief Interval watching for reload config signal.
 * \param jvmti [in] JVMTI environment object.
 * \param env   [in] JNI environment object.
 */
void ReloadConfigProc(jvmtiEnv *jvmti, JNIEnv *env) {
    
    /* If catch reload config signal. */
    if (unlikely(flagReloadConfig != 0)) {
        
        /* Reload configuration. */
        
        /* If agent is attaching now. */
        if (likely(arg.attach)) {
            /* Suspend events. */
            SetEventEnable(jvmti, false);
            
            /* Suspend threads. */
            SetThreadEnable(jvmti, env, false);
        }
        
        /* Copy older configuration setting. */
        TArguments oldArg;
        memcpy(&oldArg, &arg, sizeof(TArguments));
        
        /* If config file is designated at initialization. */
        if (unlikely(loadConfigPath == NULL)) {
            /* Make default configuration path. */
            char confPath[PATH_MAX + 1] = {0};
            snprintf(confPath, PATH_MAX, "%s/heapstats.conf", DEFAULT_CONF_DIR);
            
            /* Load default config file. */
            loadConfiguration(confPath);
        } else {
            
            /* Reload designated config file. */
            loadConfiguration(loadConfigPath);
        }
        
        /* Calculate alert limit. */
        jlong MaxMemory = jvmInfo->getMaxMemory();
        if(MaxMemory == -1){
          arg.AlertThreshold = -1;
          arg.HeapAlertThreshold = -1;
        }
        else{
          arg.AlertThreshold = MaxMemory * arg.AlertPercentage / 100;
          arg.HeapAlertThreshold = MaxMemory * arg.HeapAlertPercentage / 100;
        }
        
        /* Inherited configuration setting. */
        inheritedSetting(&arg, &oldArg);
        freeSetting(&oldArg);
        
        /* If agent is attaching now. */
        if (likely(arg.attach)) {
            /* Ignore perfomance information during agent dettached. */
            jvmInfo->resumeGCinfo();
            
            /* Restart threads. */
            SetThreadEnable(jvmti, env, true);
        }
        
        PRINT_INFO_MSG("Reloaded configuration file.");
        /* Show setting information. */
        if (arg.LogLevel >= INFO) {
            printSetting();
            FLUSH_STDOUT;
        }
        
        /* If agent is attaching now. */
        if (likely(arg.attach)) {
            
            /* Restart events. */
            SetEventEnable(jvmti, true);
        }
        
        /* Reset signal flag. */
        flagReloadConfig = 0;
    }
}

/*!
 * \brief Interval watching for signals.
 * \param jvmti [in] JVMTI environment object.
 * \param env   [in] JNI environment object.
 * \param cause [in] Cause of Invoking.<br>
 *                   This value is always Interval.
 */
void intervalSigProc(jvmtiEnv *jvmti, JNIEnv *env, TInvokeCause cause) {
    
    ReloadConfigProc(jvmti, env);
    
    /* If agent is attaching now and enable log signal. */
    if (likely(arg.attach && arg.triggerOnLogSignal)) {
        intervalSigProcForLog(jvmti, env);
    }
}

/*!
 * \brief JVM initialization event.
 * \param jvmti  [in] JVMTI environment object.
 * \param env    [in] JNI environment object.
 * \param thread [in] Java thread object.
 */
void JNICALL OnVMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) {
  /* Get GC information addres. */
  #ifdef USE_VMSTRUCTS
    jvmInfo->detectInfoAddress();
  #else
    jvmInfo->detectInfoAddress(env);
  #endif
    
    /* If failure signal initialization. */
    if (unlikely(!TSignalManager::globalInitialize(env))) {
        PRINT_WARN_MSG("Failure signal handler initialization.");
        
        /* Disable many signal. */
        arg.triggerOnLogSignal = false;
        free(arg.reloadSignal);
        arg.reloadSignal = strdup("");
    }
    
    /* Initialize signal flag. */
    flagReloadConfig = 0;
    /* Check reload signal is enable. */
    bool enableReloadSig = arg.reloadSignal != NULL
        && strlen(arg.reloadSignal) > 0;
    
    /* Start HeapStats agent threads. */
    try {
        reloadSigMngr = new TSignalManager(env, &ReloadSigProc);
        
        /* Switch reload signal state. */
        if (enableReloadSig) {
            
            /* Setting new reload signal. */
            if (unlikely(!reloadSigMngr->catchSignal(env, arg.reloadSignal))) {
                
                /* Reload configuration signal is disable. */
                PRINT_WARN_MSG("Failure register reload configuration signal.");
                free(arg.reloadSignal);
                arg.reloadSignal = strdup("");
            }
        }
    } catch(const char *errMsg) {
        PRINT_WARN_MSG(errMsg);
    } catch(...) {
        PRINT_WARN_MSG("AgentThread start failed!");
    }
    
    /* Calculate alert limit. */
    jlong MaxMemory = jvmInfo->getMaxMemory();
    if(MaxMemory == -1){
      arg.AlertThreshold = -1;
      arg.HeapAlertThreshold = -1;
    }
    else{
      arg.AlertThreshold = MaxMemory * arg.AlertPercentage / 100;
      arg.HeapAlertThreshold = MaxMemory * arg.HeapAlertPercentage / 100;
    }
    
    /* Invoke JVM initialize event of snapshot function. */
    onVMInitForSnapShot(jvmti, env);
    /* Invoke JVM initialize event of log function. */
    onVMInitForLog(jvmti, env);
    
    /* If agent is attaching now. */
    if(likely(arg.attach)){
      /* Start and enable each agent threads. */
      SetThreadEnable(jvmti, env, true);

      /* Set event enable in each function. */
      SetEventEnable(jvmti, true);
    }

    /* Getting class prepare events. */
    if(isError(jvmti, jvmti->SetEventNotificationMode(
               JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL))){
      PRINT_WARN_MSG("HeapStats will be turned off.\n");

      SetEventEnable(jvmti, false);
      SetThreadEnable(jvmti, env, false);
      FLUSH_STDOUT;
      return;
    }
    
    /* Show setting information. */
    if (arg.LogLevel >= INFO) {
        printSetting();
        FLUSH_STDOUT;
    }
    
    /* Start reload signal watcher. */
    try {
        intervalSigTimer->start(jvmti, env, SIG_WATCHER_INTERVAL);
    } catch(const char *errMsg) {
        PRINT_WARN_MSG(errMsg);
    }
}

/*!
 * \brief JVM finalization event.
 * \param jvmti [in] JVMTI environment object.
 * \param env   [in] JNI environment object.
 */
void JNICALL OnVMDeath(jvmtiEnv *jvmti, JNIEnv *env) {
    
    /* If agent is attaching now. */
    if (likely(arg.attach)) {
        
        /* Stop and disable event notify. */
        SetEventEnable(jvmti, false);
    }
    
    /*
     * Terminate signal watcher thread.
     * This thread is used by collect log and reload configuration.
     * So that, need to this thread stop before other all thread stopped.
     */
    intervalSigTimer->terminate();
    
    /* Check reload signal is enable. */
    bool enableReloadSig = (reloadSigMngr != NULL)
        && (arg.reloadSignal != NULL) && (strlen(arg.reloadSignal) > 0);
    
    /* If reload log signal is enabled. */
    if (likely(reloadSigMngr != NULL)) {
        
        /* Terminate reload signal manager. */
        if (likely(enableReloadSig)) {
            reloadSigMngr->ignoreSignal(env, arg.reloadSignal);
        }
        reloadSigMngr->terminate(env);
        delete reloadSigMngr;
        reloadSigMngr = NULL;
    }
    
    /* Invoke JVM finalize event of snapshot function. */
    onVMDeathForSnapShot(jvmti, env);
    /* Invoke JVM finalize event of log function. */
    onVMDeathForLog(jvmti, env);
    
    /* If agent is attaching now. */
    if (likely(arg.attach)) {
        
        /* Stop and disable each thread. */
        SetThreadEnable(jvmti, env, false);
    }
    
    /* Signal finalization. */
    TSignalManager::globalFinalize(env);
}

/*!
 * \brief Abort JVM by force on illegal status.
 * \param jvmti    [in] JVMTI environment object.
 * \param env      [in] JNI environment object.
 * \param causeMsg [in] Message about cause of aborting JVM.
 * \warning This function is always no return.
 */
void forcedAbortJVM(jvmtiEnv *jvmti, JNIEnv *env, const char *causeMsg) {
    
    /* Terminate all event and thread. */
    OnVMDeath(jvmti, env);
    FLUSH_STDOUT;
    
    /* Output last message. */
    PRINT_CRIT_MSG_HEADER
        << "Abort JVM. cause:\"" << causeMsg << "\""
        << NEWLINE;
    FLUSH_STDERR;
    
    abort();
}

/*!
 * \brief Setting JVMTI and extension events.
 * \param jvmti [in] JVMTI environment object.
 * \return Setting process result.
 */
jint InitEventSetting(jvmtiEnv *jvmti) {
    
    /* Capability setting. */
    jvmtiCapabilities capabilities;
    memset(&capabilities, 0, sizeof(jvmtiCapabilities));
    
    /* Set capablities in snapshot function. */
    setCapabilitiesForSnapShot(&capabilities);
    /* Set capablities in log function. */
    setCapabilitiesForLog(&capabilities);
    
    /* If failure set capabilities. */
    if (isError(jvmti, jvmti->AddCapabilities(&capabilities))) {
        PRINT_CRIT_MSG("Couldn't get event capabilities.");
        return CAPABILITIES_SETTING_FAILED;
    }
    
    /* Callback function setting. */
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
    
    /* Set event callback in snapshot function. */
    
    /* If collect class information when JVM was prepared class. */
    callbacks.ClassPrepare = &OnClassPrepare;
    
    /* If take snaoshot when user data dump request. */
    if (arg.triggerOnDump) {
        callbacks.DataDumpRequest = &OnDataDumpRequest;
    }
    
    /* If take snaoshot when occurred gc. */
    if(arg.triggerOnFullGC){

      if(useCMS){
        callbacks.GarbageCollectionStart  = &OnCMSGCStart;
        callbacks.GarbageCollectionFinish = &OnCMSGCFinish;
      }
      /* FullGC on G1, we process at callbackForG1Full() */
      else if(!useG1){
        callbacks.GarbageCollectionStart  = &OnGarbageCollectionStart;
        callbacks.GarbageCollectionFinish  = &OnGarbageCollectionFinish;
      }

    }
    
    /* Set event callback in log function. */
    
    /* If collect log when JVM's resource exhausted. */
    if (arg.triggerOnLogError) {
        
        /* Setting resource exhusted event. */
        callbacks.ResourceExhausted = &OnResourceExhausted;
    }
    
    /* If collect log when occurred deadlock. */
    if (arg.triggerOnLogLock) {
        
        /* Setting monitor contended event. */
        callbacks.MonitorContendedEnter = &OnMonitorContendedEnter;
    }
    
    /* Set event callback for init/final. */
    
    callbacks.VMInit  = &OnVMInit;
    callbacks.VMDeath = &OnVMDeath;
    
    /* If failure set event callbacks. */
    if (isError(jvmti, jvmti->SetEventCallbacks(&callbacks,
        sizeof(jvmtiEventCallbacks)))) {
        
        PRINT_CRIT_MSG("Couldn't register normal event.");
        return CALLBACKS_SETTING_FAILED;
    }
    
    /* Enable event notification. */
    SWITCH_EVENT_NOTIFICATION(jvmti, JVMTI_EVENT_VM_INIT, JVMTI_ENABLE)
    SWITCH_EVENT_NOTIFICATION(jvmti, JVMTI_EVENT_VM_DEATH, JVMTI_ENABLE)
    
    return SUCCESS;
}

/*!
 * \brief Common initialization function.
 * \param vm    [in]  JavaVM object.
 * \param jvmti [out] JVMTI environment object.
 * \return Initialize process result.
 */
jint CommonInitialization(JavaVM *vm, jvmtiEnv **jvmti) {
    
    /* Check instruction set. */
    verifyInstructSet();
    
    /* Show package information. */
    PRINT_INFO_MSG(PACKAGE_STRING);
    PRINT_INFO_MSG("Supported processor features:"
#ifdef SSE2
    " SSE2"
#endif
#ifdef SSE3
    " SSE3"
#endif
#ifdef SSE4_1
    " SSE4.1"
#endif
#ifdef AVX
    " AVX"
#endif
#if (!defined AVX) && (!defined SSE4_1) && (!defined SSE3) && (!defined SSE2)
    " None"
#endif
    );

    FLUSH_STDOUT;
    
    /* Get now date and set time as agent init-time. */
    struct timeval tv;
    gettimeofday(&tv, NULL);
    TTrapSender::initializeTime = (jlong)tv.tv_sec * 100
        + (jlong)tv.tv_usec / 10000;
    
    /* Get JVMTI environment object. */
    if (vm->GetEnv((void **)jvmti, JVMTI_VERSION_1) != JNI_OK) {
        PRINT_CRIT_MSG("Get JVMTI environment information failed!");
        return GET_ENVIRONMENT_FAILED;
    }
    
    /* Create thread instances that controlled snapshot trigger. */
    try {
        jvmInfo = new TJvmInfo();
        
        intervalSigTimer = new TTimer(&intervalSigProc,
            "HeapStats Signal Watcher");
    } catch(const char *errMsg) {
        PRINT_CRIT_MSG(errMsg);
        return AGENT_THREAD_INITIALIZE_FAILED;
    } catch(...) {
        PRINT_CRIT_MSG("AgentThread initialize failed!");
        return AGENT_THREAD_INITIALIZE_FAILED;
    }
    
    /* Invoke agent initialize of each function. */
    jint result;
    result = onAgentInitForSnapShot(jvmti);
    if (result == SUCCESS) {
        result = onAgentInitForLog(jvmti);
    }
    
    return result;
}

/*!
 * \brief Agent attach entry points.
 * \param vm       [in] JavaVM object.
 * \param options  [in] Commandline arguments.
 * \param reserved [in] Reserved.
 * \return Attach initialization result code.
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
    
    /* Check memory duplication run. */
    CHECK_DOUBLING_RUN;
    isOnDemandAttached = false;
    
    /* Parse arguments. */
    if (options == NULL || strlen(options) == 0) {
        /* Make default configuration path. */
        char confPath[PATH_MAX + 1] = {0};
        snprintf(confPath, PATH_MAX, "%s/heapstats.conf", DEFAULT_CONF_DIR);
        
        loadConfiguration(confPath);
    } else {
        loadConfiguration(options);
        loadConfigPath = strdup(options);
    }
    
    /* JVMTI Event Setup. */
    jvmtiEnv *jvmti;
    int result = CommonInitialization(vm, &jvmti);
    if (result != SUCCESS) {
        /* Failure event setup. */
        return result;
    }
    
    /* Call common initialize. */
    return InitEventSetting(jvmti);
}

/*!
 * \brief Common agent unload entry points.
 * \param vm [in] JavaVM object.
 */
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
    
    JNIEnv *env = NULL;
    /* Get JNI environment object. */
    vm->GetEnv((void **)&env, JNI_VERSION_1_6);
    
    /* Invoke agent finalize of snapshot function. */
    onAgentFinalForSnapShot(env);
    /* Invoke agent finalize of log function. */
    onAgentFinalForLog(env);
    
    /* Destroy object is JVM running informations. */
    delete jvmInfo;
    jvmInfo = NULL;
    
    /* Destroy object reload signal watcher timer. */
    delete intervalSigTimer;
    intervalSigTimer = NULL;
    
    /* Free allocated string. */
    freeSetting(&arg);
    
    /* Free allocated configuration file path string. */
    free(loadConfigPath);
    loadConfigPath = NULL;
}

/*!
 * \brief Ondemand attach's entry points.
 * \param vm       [in] JavaVM object.
 * \param options  [in] Commandline arguments.
 * \param reserved [in] Reserved.
 * \return Ondemand-attach initialization result code.
 */
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options,
    void *reserved) {
    
    /* Check memory duplication run. */
    CHECK_DOUBLING_RUN;
    isOnDemandAttached = true;
    
    /* Parse arguments. */
    if (options == NULL || strlen(options) == 0) {
        /* Make default configuration path. */
        char confPath[PATH_MAX + 1] = {0};
        snprintf(confPath, PATH_MAX, "%s/heapstats.conf", DEFAULT_CONF_DIR);
        
        loadConfiguration(confPath);
    } else {
        loadConfiguration(options);
        loadConfigPath = strdup(options);
    }
    
    /* Call common process. */
    jvmtiEnv *jvmti;
    jint result = CommonInitialization(vm, &jvmti);
    if (result != SUCCESS) {
        return result;
    }
    
    /* Get JNI environment object. */
    JNIEnv *env;
    if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
        PRINT_CRIT_MSG("Get JNI environment information failed!");
        return GET_ENVIRONMENT_FAILED;
    }
    
    /* JVMTI Event Setup. */
    InitEventSetting(jvmti);

    /* Call live step initialization. */
    OnVMInit(jvmti, env, NULL);

    jvmInfo->detectDelayInfoAddress();
    
    return SUCCESS;
}