view agent/src/heapstats-engines/heapstatsMBean.cpp @ 259:07a69089c840

Bug 3512: Drop SSE3 instruction set supporting Reviewed-by: yasuenag https://github.com/HeapStats/heapstats/pull/129
author KUBOTA Yuji <kubota.yuji@lab.ntt.co.jp>
date Tue, 23 Jan 2018 23:22:34 +0900
parents 1ac3803a35d1
children
line wrap: on
line source

/*!
 * \file heapstatsMBean.cpp
 * \brief JNI implementation for HeapStatsMBean.
 * Copyright (C) 2014-2017 Yasumasa Suenaga
 *
 * 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 <limits.h>
#include <sched.h>

#include <list>

#ifdef HAVE_ATOMIC
#include <atomic>
#else
#include <cstdatomic>
#endif

#include "globals.hpp"
#include "configuration.hpp"
#include "heapstatsMBean.hpp"
#include "util.hpp"

/* Variables */
static jclass mapCls = NULL;
static jmethodID map_ctor = NULL;
static jmethodID map_put = NULL;

static jclass boolCls = NULL;
static jmethodID boolValue = NULL;
static jobject boolFalse = NULL;
static jobject boolTrue = NULL;

static jobjectArray logLevelArray;
static jobjectArray rankOrderArray;

static jclass integerCls = NULL;
static jmethodID intValue = NULL;
static jmethodID intValueOf = NULL;

static jclass longCls = NULL;
static jmethodID longValue = NULL;
static jmethodID longValueOf = NULL;

static jclass linkedCls = NULL;
static volatile bool isLoaded = false;
static std::atomic_int processing(0);


/*!
 * \brief Raise Java Exception.
 *
 * \param env     Pointer of JNI environment.
 * \param cls_sig Class signature of Throwable.
 * \param message Error message.
 */
static void raiseException(JNIEnv *env, const char *cls_sig,
                           const char *message) {
  jthrowable ex = env->ExceptionOccurred();
  if (ex != NULL) {
    env->Throw(ex);
    return;
  }

  jclass ex_cls = env->FindClass(cls_sig);
  env->ThrowNew(ex_cls, message);
}

/*!
 * \brief Load jclass object.
 *        This function returns jclass as JNI global object.
 *
 * \param env       Pointer of JNI environment.
 * \param className Class name to load.
 * \return jclass object.
 */
static jclass loadClassGlobal(JNIEnv *env, const char *className) {
  jclass cls = env->FindClass(className);

  if (cls != NULL) {
    cls = (jclass)env->NewGlobalRef(cls);

    if (cls == NULL) {
      raiseException(env, "java/lang/RuntimeException",
                     "Could not get JNI Global value.");
    }

  } else {
    raiseException(env, "java/lang/NoClassDefFoundError", className);
  }

  return cls;
}

/*!
 * \brief Preparation for Map object.
 *        This function initialize JNI variables for LinkedHashMap to use in
 *        getConfigurationList0()
 *
 * \param env Pointer of JNI environment.
 * \return true if succeeded.
 */
static bool prepareForMapObject(JNIEnv *env) {
  mapCls = loadClassGlobal(env, "java/util/LinkedHashMap");
  if (mapCls == NULL) {
    return false;
  }

  map_ctor = env->GetMethodID(mapCls, "<init>", "()V");
  map_put = env->GetMethodID(
      mapCls, "put",
      "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
  if ((map_ctor == NULL) || (map_put == NULL)) {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not get methods in LinkedHashMap.");
    return false;
  }

  return true;
}

/*!
 * \brief Preparation for Boolean object.
 *        This function initialize JNI variables for Boolean.TRUE and FALSE.
 *
 * \param env       Pointer of JNI environment.
 * \param fieldName Field name in Boolean. It must be "TRUE" or "FALSE".
 * \param target    jobject to store value of field.
 * \return true if succeeded.
 */
static bool prepareForBoolean(JNIEnv *env, const char *fieldName,
                              jobject *target) {
  if (boolCls == NULL) {
    boolCls = loadClassGlobal(env, "java/lang/Boolean");

    if (boolCls == NULL) {
      return false;
    }

    boolValue = env->GetMethodID(boolCls, "booleanValue", "()Z");

    if (boolValue == NULL) {
      raiseException(env, "java/lang/RuntimeException",
                     "Could not find Boolean method.");
      return false;
    }
  }

  jfieldID field =
      env->GetStaticFieldID(boolCls, fieldName, "Ljava/lang/Boolean;");
  if (field == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not find Boolean field.");
    return false;
  }

  *target = env->GetStaticObjectField(boolCls, field);
  if (*target != NULL) {
    *target = env->NewGlobalRef(*target);

    if (*target == NULL) {
      raiseException(env, "java/lang/RuntimeException",
                     "Could not get JNI Global value.");
      return false;
    }

  } else {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not get Boolean value.");
    return false;
  }

  return true;
}

/*!
 * \brief Preparation for Enum object in HeapStatsMBean.
 *
 * \param env       Pointer of JNI environment.
 * \param className Class name to initialize.
 *                  It must be "LogLevel" or "RankOrder".
 * \param target    jobjectArray to store values in enum.
 * \return true if succeeded.
 */
static bool prepareForEnumObject(JNIEnv *env, const char *className,
                                 jobjectArray *target) {
  char jniCls[100] = "jp/co/ntt/oss/heapstats/mbean/HeapStatsMBean$";
  strcat(jniCls, className);
  char sig[110];
  sprintf(sig, "()[L%s;", jniCls);

  jclass cls = loadClassGlobal(env, jniCls);
  if (cls == NULL) {
    return false;
  }

  jmethodID values = env->GetStaticMethodID(cls, "values", sig);
  if (values == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not get Enum values method.");
    return false;
  }

  *target = (jobjectArray)env->CallStaticObjectMethod(cls, values);
  if (*target != NULL) {
    *target = (jobjectArray)env->NewGlobalRef(*target);

    if (*target == NULL) {
      raiseException(env, "java/lang/RuntimeException",
                     "Could not get JNI Global value.");
      return false;
    }

  } else {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not get Enum values.");
    return false;
  }

  return true;
}

/*!
 * \brief Preparation for number object.
 *        This function can initialize Integer and Long.
 *
 * \param env       Pointer of JNI environment.
 * \param className Class name to initialize.
 *                  It must be "Integer" or "Long".
 * \param cls       jclass object to store its class.
 * \param valueOfMethod jmethodID to store "valueOf" method in its class.
 * \param valueMethod   jmethodID to store "intValue" or "longValue" method
 *                      in its class.
 * \return true if succeeded.
 */
static bool prepareForNumObject(JNIEnv *env, const char *className, jclass *cls,
                                jmethodID *valueOfMethod,
                                jmethodID *valueMethod) {
  char jniCls[30] = "java/lang/";
  strcat(jniCls, className);
  char sig[40];

  *cls = loadClassGlobal(env, jniCls);
  if (*cls == NULL) {
    return false;
  }

  if (strcmp(className, "Long") == 0) {
    sprintf(sig, "(J)L%s;", jniCls);
    *valueOfMethod = env->GetStaticMethodID(*cls, "valueOf", sig);
    *valueMethod = env->GetMethodID(*cls, "longValue", "()J");
  } else {
    sprintf(sig, "(I)L%s;", jniCls);
    *valueOfMethod = env->GetStaticMethodID(*cls, "valueOf", sig);
    *valueMethod = env->GetMethodID(*cls, "intValue", "()I");
  }

  if ((*valueOfMethod == NULL) || (*valueMethod == NULL)) {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not get valueOf method.");
    return false;
  }

  return true;
}

/*!
 * \brief Register JNI functions in libheapstats.
 *
 * \param env Pointer of JNI environment.
 * \param cls Class of HeapStatsMBean implementation.
 */
JNIEXPORT void JNICALL RegisterHeapStatsNative(JNIEnv *env, jclass cls) {
  /* Regist JNI functions */
  JNINativeMethod methods[] = {
      {(char *)"getHeapStatsVersion0",
       (char *)"()Ljava/lang/String;",
       (void *)GetHeapStatsVersion},
      {(char *)"getConfiguration0",
       (char *)"(Ljava/lang/String;)Ljava/lang/Object;",
       (void *)GetConfiguration},
      {(char *)"getConfigurationList0",
       (char *)"()Ljava/util/Map;",
       (void *)GetConfigurationList},
      {(char *)"changeConfiguration0",
       (char *)"(Ljava/lang/String;Ljava/lang/Object;)Z",
       (void *)ChangeConfiguration},
      {(char *)"invokeLogCollection0",
       (char *)"()Z",
       (void *)InvokeLogCollection},
      {(char *)"invokeAllLogCollection0",
       (char *)"()Z",
       (void *)InvokeAllLogCollection}};

  if (env->RegisterNatives(cls, methods, 6) != 0) {
    raiseException(env, "java/lang/UnsatisfiedLinkError",
                   "Native function for HeapStatsMBean failed.");
    return;
  }

  TProcessMark mark(processing);

  /* Initialize variables. */
  linkedCls = (jclass)env->NewGlobalRef(cls);
  isLoaded = true;

  /* For Map object */
  if (!prepareForMapObject(env)) {
    return;
  }

  /* For Boolean object */
  if (!prepareForBoolean(env, "TRUE", &boolTrue) ||
      !prepareForBoolean(env, "FALSE", &boolFalse)) {
    return;
  }

  /* For Enum values */
  if (!prepareForEnumObject(env, "LogLevel", &logLevelArray) ||
      !prepareForEnumObject(env, "RankOrder", &rankOrderArray)) {
    return;
  }

  /* For number Object */
  if (!prepareForNumObject(env, "Integer", &integerCls, &intValueOf,
                           &intValue) ||
      !prepareForNumObject(env, "Long", &longCls, &longValueOf, &longValue)) {
    return;
  }
}

static void *JNIDummy(void) {
  return NULL;
}

/*!
 * \brief Unregister JNI functions in libheapstats.
 * \param env Pointer of JNI environment.
 */
void UnregisterHeapStatsNatives(JNIEnv *env) {
  if (!isLoaded) {
    return;
  }

  /* Regist JNI functions */
  JNINativeMethod methods[] = {
      {(char *)"getHeapStatsVersion0",
       (char *)"()Ljava/lang/String;",
       (void *)JNIDummy},
      {(char *)"getConfiguration0",
       (char *)"(Ljava/lang/String;)Ljava/lang/Object;",
       (void *)JNIDummy},
      {(char *)"getConfigurationList0",
       (char *)"()Ljava/util/Map;",
       (void *)JNIDummy},
      {(char *)"changeConfiguration0",
       (char *)"(Ljava/lang/String;Ljava/lang/Object;)Z",
       (void *)JNIDummy},
      {(char *)"invokeLogCollection0",
       (char *)"()Z",
       (void *)JNIDummy},
      {(char *)"invokeAllLogCollection0",
       (char *)"()Z",
       (void *)JNIDummy}};

  if (env->RegisterNatives(linkedCls, methods, 6) != 0) {
    raiseException(env, "java/lang/UnsatisfiedLinkError",
                   "Could not unregister HeapStats native functions.");
  }

  env->DeleteGlobalRef(linkedCls);

  while (processing > 0) {
    sched_yield();
  }
}

/*!
 * \brief Get HeapStats version string from libheapstats.
 *
 * \param env Pointer of JNI environment.
 * \param obj Instance of HeapStatsMBean implementation.
 * \return Version string which is attached.
 */
JNIEXPORT jstring JNICALL GetHeapStatsVersion(JNIEnv *env, jobject obj) {
  TProcessMark mark(processing);
  jstring versionStr = env->NewStringUTF(PACKAGE_STRING
                                         " ("
#ifdef SSE2
                                         "SSE2)"
#endif
#ifdef SSE4
                                         "SSE4)"
#endif
#ifdef AVX
                                         "AVX)"
#endif
#if (!defined AVX) && (!defined SSE4) && (!defined SSE2)
                                         "None)"
#endif
                                         );

  if (versionStr == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Could not create HeapStats version string.");
  }

  return versionStr;
}

/*!
 * \brief Create java.lang.String instance.
 *
 * \param env Pointer of JNI environment.
 * \param val Value of string.
 * \return Instance of String.
 */
static jstring createString(JNIEnv *env, const char *val) {
  if (val == NULL) {
    return NULL;
  }

  jstring ret = env->NewStringUTF(val);
  if (ret == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Cannot get string in JNI");
  }

  return ret;
}

/*!
 * \brief Get configuration value as jobject.
 *
 * \param env    Pointer of JNI environment.
 * \param config Configuration element.
 * \return jobject value of configuration.
 */
static jobject getConfigAsJObject(JNIEnv *env, TConfigElementSuper *config) {
  jobject ret = NULL;

  switch (config->getConfigDataType()) {
    case BOOLEAN:
      ret = ((TBooleanConfig *)config)->get() ? boolTrue : boolFalse;
      break;
    case INTEGER:
      ret = env->CallStaticObjectMethod(integerCls, intValueOf,
                                        ((TIntConfig *)config)->get());
      break;
    case LONG:
      ret = env->CallStaticObjectMethod(longCls, longValueOf,
                                        ((TLongConfig *)config)->get());
      break;
    case STRING:
      ret = createString(env, ((TStringConfig *)config)->get());
      break;
    case LOGLEVEL:
      ret = env->GetObjectArrayElement(logLevelArray,
                                       ((TLogLevelConfig *)config)->get());
      break;
    case RANKORDER:
      ret = env->GetObjectArrayElement(rankOrderArray,
                                       ((TRankOrderConfig *)config)->get());
      break;
  }

  return ret;
}

/*!
 * \brief Get HeapStats agent configuration from libheapstats.
 *
 * \param env Pointer of JNI environment.
 * \param obj Instance of HeapStatsMBean implementation.
 * \param key Name of configuration.
 * \return Current value of configuration key.
 */
JNIEXPORT jobject JNICALL
    GetConfiguration(JNIEnv *env, jobject obj, jstring key) {
  TProcessMark mark(processing);
  const char *opt = env->GetStringUTFChars(key, NULL);
  if (opt == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Cannot get string in JNI");
    return NULL;
  }

  jobject result = NULL;
  std::list<TConfigElementSuper *> configs = conf->getConfigs();

  for (std::list<TConfigElementSuper *>::iterator itr = configs.begin();
       itr != configs.end(); itr++) {
    if (strcmp(opt, (*itr)->getConfigName()) == 0) {
      result = getConfigAsJObject(env, *itr);
      jthrowable ex = env->ExceptionOccurred();

      if (ex != NULL) {
        env->Throw(ex);
        return NULL;
      }
    }
  }

  env->ReleaseStringUTFChars(key, opt);
  return result;
}

/*!
 * \brief Get all of HeapStats agent configurations from libheapstats.
 *
 * \param env Pointer of JNI environment.
 * \param obj Instance of HeapStatsMBean implementation.
 * \return Current configuration list.
 */
JNIEXPORT jobject JNICALL GetConfigurationList(JNIEnv *env, jobject obj) {
  TProcessMark mark(processing);
  jobject result = env->NewObject(mapCls, map_ctor);
  if (result == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Cannot create Map instance.");
    return NULL;
  }

  std::list<TConfigElementSuper *> configs = conf->getConfigs();
  jstring key;
  jobject value = NULL;

  for (std::list<TConfigElementSuper *>::iterator itr = configs.begin();
       itr != configs.end(); itr++) {
    key = createString(env, (*itr)->getConfigName());
    if (key == NULL) {
      return NULL;
    }

    value = getConfigAsJObject(env, *itr);

    env->CallObjectMethod(result, map_put, key, value);
    if (env->ExceptionCheck()) {
      raiseException(env, "java/lang/RuntimeException",
                     "Cannot put config to Map instance.");
      return NULL;
    }
  }

  return result;
}

/*!
 * \brief Change HeapStats agent configuration in libheapstats.
 *
 * \param env   Pointer of JNI environment.
 * \param obj   Instance of HeapStatsMBean implementation.
 * \param key   Name of configuration.
 * \param value New configuration value.
 * \return Result of this change.
 */
JNIEXPORT jboolean JNICALL
    ChangeConfiguration(JNIEnv *env, jobject obj, jstring key, jobject value) {
  TProcessMark mark(processing);
  jclass valueCls = env->GetObjectClass(value);
  char *opt = (char *)env->GetStringUTFChars(key, NULL);
  if (opt == NULL) {
    raiseException(env, "java/lang/RuntimeException",
                   "Cannot get string in JNI");
    return JNI_FALSE;
  }

  TConfiguration new_conf = *conf;
  std::list<TConfigElementSuper *> configs = new_conf.getConfigs();

  for (std::list<TConfigElementSuper *>::iterator itr = configs.begin();
       itr != configs.end(); itr++) {
    try{
      if (strcmp(opt, (*itr)->getConfigName()) == 0) {
        switch ((*itr)->getConfigDataType()) {
          case BOOLEAN:
            if (env->IsAssignableFrom(valueCls, boolCls)) {
              jboolean newval = env->CallBooleanMethod(value, boolValue);
              ((TBooleanConfig *)*itr)->set((bool)newval);
            } else {
              raiseException(env, "java/lang/ClassCastException",
                             "Cannot convert new configuration to Boolean.");
            }
            break;
          case INTEGER:
            if (env->IsAssignableFrom(valueCls, integerCls)) {
              jint newval = env->CallIntMethod(value, intValue);
              ((TIntConfig *)*itr)->set(newval);
            } else {
              raiseException(env, "java/lang/ClassCastException",
                             "Cannot convert new configuration to Integer.");
            }
            break;
          case LONG:
            if (env->IsAssignableFrom(valueCls, longCls)) {
              jlong newval = env->CallLongMethod(value, longValue);
              ((TLongConfig *)*itr)->set(newval);
            } else {
              raiseException(env, "java/lang/ClassCastException",
                             "Cannot convert new configuration to Long.");
            }
            break;
          case LOGLEVEL:
            for (int Cnt = 0; Cnt < env->GetArrayLength(logLevelArray); Cnt++) {
              if (env->IsSameObject(
                      env->GetObjectArrayElement(logLevelArray, Cnt), value)) {
                ((TLogLevelConfig *)*itr)->set((TLogLevel)Cnt);
                break;
              }
            }
            break;
          case RANKORDER:
            for (int Cnt = 0;
                 Cnt < env->GetArrayLength(rankOrderArray); Cnt++) {
              if (env->IsSameObject(
                      env->GetObjectArrayElement(rankOrderArray, Cnt), value)) {
                ((TRankOrderConfig *)*itr)->set((TRankOrder)Cnt);
                break;
              }
            }
            break;
          default:  // String

            if (value == NULL) {
              ((TStringConfig *)*itr)->set(NULL);
            } else if (env->IsAssignableFrom(
                           valueCls, env->FindClass("java/lang/String"))) {
              char *val = (char *)env->GetStringUTFChars((jstring)value, NULL);

              if (val == NULL) {
                raiseException(env, "java/lang/RuntimeException",
                               "Cannot get string in JNI");
              } else {
                ((TStringConfig *)*itr)->set(val);
                env->ReleaseStringUTFChars((jstring)value, val);
              }

            } else {
              raiseException(env, "java/lang/RuntimeException",
                             "Cannot support this configuration type.");
            }
        }
      }
    } catch (const char *msg) {
      const char *const_msg = " cannot be set new value: ";
      size_t msg_len = strlen(opt) + strlen(const_msg) + strlen(msg);
      char *exception_msg = (char *)malloc(msg_len);
      sprintf(exception_msg, "%s%s%s", opt, const_msg, msg);

      raiseException(env, "java/lang/IllegalArgumentException", exception_msg);

      free(exception_msg);
    }
  }

  jboolean result = JNI_FALSE;

  if (!env->ExceptionOccurred()) {
    if (new_conf.validate()) {
      conf->merge(&new_conf);
      logger->printInfoMsg("Configuration has been changed through JMX.");
      conf->printSetting();
      result = JNI_TRUE;
    } else {
      raiseException(env, "java/lang/IllegalArgumentException",
                     "Illegal parameter was set.");
    }
  }

  env->ReleaseStringUTFChars(key, opt);
  return result;
}

/*!
 * \brief Invoke Resource Log collection at libheapstats.
 *
 * \param env   Pointer of JNI environment.
 * \param obj   Instance of HeapStatsMBean implementation.
 * \return Result of this call.
 */
JNIEXPORT jboolean JNICALL InvokeLogCollection(JNIEnv *env, jobject obj) {
  TProcessMark mark(processing);
  int ret = logManager->collectLog(NULL, env, Signal,
                                   (TMSecTime)getNowTimeSec(), "JMX event");
  return ret == 0 ? JNI_TRUE : JNI_FALSE;
}

/*!
 * \brief Invoke All Log collection at libheapstats.
 *
 * \param env   Pointer of JNI environment.
 * \param obj   Instance of HeapStatsMBean implementation.
 * \return Result of this call.
 */
JNIEXPORT jboolean JNICALL InvokeAllLogCollection(JNIEnv *env, jobject obj) {
  TProcessMark mark(processing);
  int ret = logManager->collectLog(NULL, env, AnotherSignal,
                                   (TMSecTime)getNowTimeSec(), "JMX event");
  return ret == 0 ? JNI_TRUE : JNI_FALSE;
}