view agent/src/libmain.cpp @ 22:6fec95209844

Bug 1522: Add Java heap alert. reviewed-by: shintak
author Yasumasa Suenaga <suenaga.yasumasa@lab.ntt.co.jp>
date Mon, 19 Aug 2013 10:40:21 +0900
parents 6d475889590a
children a4b4008e4ec0
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-2013 Nippon Telegraph and Telephone Corporation
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <jni.h>
#include <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);
  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);
  } 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) {

  /* Detach agent. */
  SetEventEnable(jvmti, false);
  SetThreadEnable(jvmti, env, false);
  
  /* Terminate each thread. */
  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);
  
  /* Signal finalization. */
  TSignalManager::globalFinalize(env);
}

/*!
 * \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,
                              SIG_WATCHER_INTERVAL, "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) {
  
  /* Invoke agent finalize of snapshot function. */
  onAgentFinalForSnapShot();
  /* Invoke agent finalize of log function. */
  onAgentFinalForLog();
  
  /* 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);
}

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