628 lines
22 KiB
C++
628 lines
22 KiB
C++
/*
|
|
* JavaWorkload.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <foundationdb/ClientWorkload.h>
|
|
#define FDB_API_VERSION 720
|
|
#include <foundationdb/fdb_c.h>
|
|
|
|
#include "com_apple_foundationdb_testing_AbstractWorkload.h"
|
|
#include "com_apple_foundationdb_testing_Promise.h"
|
|
#include "com_apple_foundationdb_testing_WorkloadContext.h"
|
|
|
|
#include <jni.h>
|
|
#include <set>
|
|
#include <iostream>
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
namespace {
|
|
// to make logging more convenient
|
|
// this should be fine as it is guarded by
|
|
// a anon namespace
|
|
auto debug = FDBSeverity::Debug;
|
|
auto info = FDBSeverity::Info;
|
|
auto error = FDBSeverity::Error;
|
|
|
|
void printTrace(JNIEnv* env, jclass, jlong logger, jint severity, jstring message, jobject details) {
|
|
auto log = reinterpret_cast<FDBLogger*>(logger);
|
|
jboolean isCopy;
|
|
const char* msg = env->GetStringUTFChars(message, &isCopy);
|
|
std::vector<std::pair<std::string, std::string>> detailsMap;
|
|
if (details != nullptr) {
|
|
jclass mapClass = env->FindClass("java/util/Map");
|
|
jclass setClass = env->FindClass("java/util/Set");
|
|
jclass iteratorClass = env->FindClass("java/util/Iterator");
|
|
jmethodID keySetID = env->GetMethodID(mapClass, "keySet", "()Ljava/util/Set;");
|
|
jobject keySet = env->CallObjectMethod(details, keySetID);
|
|
jmethodID iteratorMethodID = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
jobject iterator = env->CallObjectMethod(keySet, iteratorMethodID);
|
|
jmethodID hasNextID = env->GetMethodID(iteratorClass, "hasNext", "()Z");
|
|
jmethodID nextID = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
|
|
jmethodID getID = env->GetMethodID(mapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
|
|
while (env->CallBooleanMethod(iterator, hasNextID)) {
|
|
jobject next = env->CallObjectMethod(iterator, nextID);
|
|
jstring key = jstring(next);
|
|
jstring value = jstring(env->CallObjectMethod(details, getID, next));
|
|
auto keyStr = env->GetStringUTFChars(key, nullptr);
|
|
auto keyLen = env->GetStringUTFLength(key);
|
|
auto valueStr = env->GetStringUTFChars(value, nullptr);
|
|
auto valueLen = env->GetStringUTFLength(value);
|
|
detailsMap.emplace_back(std::string(keyStr, keyLen), std::string(valueStr, valueLen));
|
|
env->ReleaseStringUTFChars(key, keyStr);
|
|
env->ReleaseStringUTFChars(value, valueStr);
|
|
env->DeleteLocalRef(key);
|
|
env->DeleteLocalRef(value);
|
|
}
|
|
}
|
|
FDBSeverity sev;
|
|
if (severity < 10) {
|
|
sev = debug;
|
|
} else if (severity < 20) {
|
|
sev = FDBSeverity::Info;
|
|
} else if (severity < 30) {
|
|
sev = FDBSeverity::Warn;
|
|
} else if (severity < 40) {
|
|
sev = FDBSeverity::WarnAlways;
|
|
} else {
|
|
assert(false);
|
|
std::abort();
|
|
}
|
|
log->trace(sev, msg, detailsMap);
|
|
if (isCopy) {
|
|
env->ReleaseStringUTFChars(message, msg);
|
|
}
|
|
}
|
|
|
|
jlong getProcessID(JNIEnv* env, jclass, jlong self) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
return jlong(context->getProcessID());
|
|
}
|
|
|
|
void setProcessID(JNIEnv* env, jclass, jlong self, jlong processID) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
context->setProcessID(processID);
|
|
}
|
|
|
|
jboolean getOptionBool(JNIEnv* env, jclass, jlong self, jstring name, jboolean defaultValue) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
jboolean isCopy = true;
|
|
const char* utf = env->GetStringUTFChars(name, &isCopy);
|
|
auto res = jboolean(context->getOption(utf, bool(defaultValue)));
|
|
if (isCopy) {
|
|
env->ReleaseStringUTFChars(name, utf);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
jlong getOptionLong(JNIEnv* env, jclass, jlong self, jstring name, jlong defaultValue) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
jboolean isCopy = true;
|
|
const char* utf = env->GetStringUTFChars(name, &isCopy);
|
|
auto res = jlong(context->getOption(utf, long(defaultValue)));
|
|
if (isCopy) {
|
|
env->ReleaseStringUTFChars(name, utf);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
jdouble getOptionDouble(JNIEnv* env, jclass, jlong self, jstring name, jdouble defaultValue) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
jboolean isCopy = true;
|
|
const char* utf = env->GetStringUTFChars(name, &isCopy);
|
|
auto res = jdouble(context->getOption(utf, double(defaultValue)));
|
|
if (isCopy) {
|
|
env->ReleaseStringUTFChars(name, utf);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
jstring getOptionString(JNIEnv* env, jclass, jlong self, jstring name, jstring defaultValue) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
jboolean isCopy;
|
|
jboolean defIsCopy;
|
|
const char* nameStr = env->GetStringUTFChars(name, &isCopy);
|
|
const char* defStr = env->GetStringUTFChars(defaultValue, &defIsCopy);
|
|
auto res = context->getOption(nameStr, std::string(defStr));
|
|
if (isCopy) {
|
|
env->ReleaseStringUTFChars(name, nameStr);
|
|
}
|
|
if (defIsCopy) {
|
|
env->ReleaseStringUTFChars(defaultValue, defStr);
|
|
}
|
|
return env->NewStringUTF(res.c_str());
|
|
}
|
|
|
|
jint getClientID(JNIEnv* env, jclass, jlong self) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
return jint(context->clientId());
|
|
}
|
|
|
|
jint getClientCount(JNIEnv* env, jclass, jlong self) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
return jint(context->clientCount());
|
|
}
|
|
|
|
jlong getSharedRandomNumber(JNIEnv* env, jclass, jlong self) {
|
|
FDBWorkloadContext* context = reinterpret_cast<FDBWorkloadContext*>(self);
|
|
return jlong(context->sharedRandomNumber());
|
|
}
|
|
|
|
struct JavaPromise {
|
|
GenericPromise<bool> impl;
|
|
JavaPromise(GenericPromise<bool>&& promise) : impl(std::move(promise)) {}
|
|
|
|
void send(bool val) {
|
|
impl.send(val);
|
|
delete this;
|
|
}
|
|
};
|
|
|
|
void promiseSend(JNIEnv, jclass, jlong self, jboolean value) {
|
|
auto p = reinterpret_cast<JavaPromise*>(self);
|
|
p->send(bool(value));
|
|
}
|
|
|
|
struct JNIError {
|
|
JNIEnv* env;
|
|
jthrowable throwable{ nullptr };
|
|
const char* file{ nullptr };
|
|
int line{ 0 };
|
|
|
|
std::string location() const {
|
|
if (file == nullptr) {
|
|
return "UNKNOWN";
|
|
} else {
|
|
return file + std::string(":") + std::to_string(line);
|
|
}
|
|
}
|
|
|
|
std::string toString() {
|
|
if (!throwable) {
|
|
return "JNIError";
|
|
} else {
|
|
jboolean isCopy = false;
|
|
jmethodID toStringM =
|
|
env->GetMethodID(env->FindClass("java/lang/Object"), "toString", "()Ljava/lang/String;");
|
|
jstring s = (jstring)env->CallObjectMethod(throwable, toStringM);
|
|
const char* utf = env->GetStringUTFChars(s, &isCopy);
|
|
std::string res(utf);
|
|
env->ReleaseStringUTFChars(s, utf);
|
|
return res;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct JVM {
|
|
FDBLogger* log;
|
|
JavaVM* jvm;
|
|
JNIEnv* env;
|
|
std::set<std::string> classPath;
|
|
bool healthy = false;
|
|
jclass throwableClass;
|
|
jclass abstractWorkloadClass = nullptr;
|
|
// this is a bit ugly - but JNINativeMethod requires
|
|
// char* not const char *
|
|
std::vector<char*> charArrays;
|
|
|
|
void checkExceptionImpl(const char* file, int line) {
|
|
if (env->ExceptionCheck()) {
|
|
throw JNIError{ env, env->ExceptionOccurred(), file, line };
|
|
}
|
|
}
|
|
|
|
#define checkException() checkExceptionImpl(__FILE__, __LINE__)
|
|
|
|
void success(int res) {
|
|
bool didThrow = env->ExceptionCheck();
|
|
if (res == JNI_ERR || didThrow) {
|
|
throw JNIError{ env, didThrow ? env->ExceptionOccurred() : nullptr };
|
|
}
|
|
}
|
|
|
|
JVM(FDBLogger* log) : log(log) {
|
|
try {
|
|
log->trace(FDBSeverity::Debug, "InitializeJVM", {});
|
|
JavaVMInitArgs args;
|
|
args.version = JNI_VERSION_1_6;
|
|
args.ignoreUnrecognized = JNI_TRUE;
|
|
args.nOptions = 0;
|
|
success(JNI_CreateJavaVM(&jvm, reinterpret_cast<void**>(&env), &args));
|
|
log->trace(debug, "JVMCreated", {});
|
|
throwableClass = env->FindClass("java/lang/Throwable");
|
|
} catch (JNIError& e) {
|
|
healthy = false;
|
|
env->ExceptionClear();
|
|
}
|
|
}
|
|
|
|
~JVM() {
|
|
log->trace(debug, "JVMDestruct", {});
|
|
if (jvm) {
|
|
jvm->DestroyJavaVM();
|
|
}
|
|
for (auto& a : charArrays) {
|
|
delete[] a;
|
|
}
|
|
log->trace(debug, "JVMDestructDone", {});
|
|
}
|
|
|
|
void setNativeMethods(jclass clazz,
|
|
const std::initializer_list<std::tuple<std::string_view, std::string_view, void*>>& methods) {
|
|
charArrays.reserve(charArrays.size() + 2 * methods.size());
|
|
std::unique_ptr<JNINativeMethod[]> nativeMethods;
|
|
int numNativeMethods = methods.size();
|
|
nativeMethods.reset(new JNINativeMethod[numNativeMethods]);
|
|
int i = 0;
|
|
for (const auto& t : methods) {
|
|
auto& w = nativeMethods[i];
|
|
auto nameStr = std::get<0>(t);
|
|
auto sigStr = std::get<1>(t);
|
|
charArrays.push_back(new char[nameStr.size() + 1]);
|
|
char* name = charArrays.back();
|
|
charArrays.push_back(new char[sigStr.size() + 1]);
|
|
char* sig = charArrays.back();
|
|
memcpy(name, nameStr.data(), nameStr.size());
|
|
memcpy(sig, sigStr.data(), sigStr.size());
|
|
name[nameStr.size()] = '\0';
|
|
sig[sigStr.size()] = '\0';
|
|
w.name = name;
|
|
w.signature = sig;
|
|
w.fnPtr = std::get<2>(t);
|
|
log->trace(info,
|
|
"PreparedNativeMethod",
|
|
{ { "Name", w.name },
|
|
{ "Signature", w.signature },
|
|
{ "Ptr", std::to_string(reinterpret_cast<uintptr_t>(w.fnPtr)) } });
|
|
++i;
|
|
}
|
|
env->RegisterNatives(clazz, nativeMethods.get(), numNativeMethods);
|
|
checkException();
|
|
}
|
|
|
|
jclass getClassImpl(const char* file, int line, const char* name) {
|
|
auto res = env->FindClass(name);
|
|
checkExceptionImpl(file, line);
|
|
return res;
|
|
}
|
|
|
|
#define getClass(name) getClassImpl(__FILE__, __LINE__, name)
|
|
|
|
jmethodID getMethodImpl(const char* file, int line, jclass clazz, const char* name, const char* signature) {
|
|
auto res = env->GetMethodID(clazz, name, signature);
|
|
checkExceptionImpl(file, line);
|
|
return res;
|
|
}
|
|
|
|
#define getMethod(clazz, name, signature) getMethodImpl(__FILE__, __LINE__, clazz, name, signature)
|
|
|
|
jfieldID getFieldImpl(const char* file, int line, jclass clazz, const char* name, const char* signature) {
|
|
auto res = env->GetFieldID(clazz, name, signature);
|
|
checkException();
|
|
return res;
|
|
}
|
|
|
|
#define getField(clazz, name, signature) getFieldImpl(__FILE__, __LINE__, clazz, name, signature)
|
|
|
|
void addToClassPath(const std::string& path) {
|
|
log->trace(info, "TryAddToClassPath", { { "Path", path } });
|
|
if (!env) {
|
|
throw JNIError{};
|
|
}
|
|
if (classPath.count(path) > 0) {
|
|
// already added
|
|
return;
|
|
}
|
|
auto p = env->NewStringUTF(path.c_str());
|
|
checkException();
|
|
auto fileClass = getClass("java/io/File");
|
|
auto file = env->NewObject(fileClass, getMethod(fileClass, "<init>", "(Ljava/lang/String;)V"), p);
|
|
checkException();
|
|
auto uri = env->CallObjectMethod(file, env->GetMethodID(fileClass, "toURI", "()Ljava/net/URI;"));
|
|
checkException();
|
|
auto uriClass = getClass("java/net/URI");
|
|
auto url = env->CallObjectMethod(uri, getMethod(uriClass, "toURL", "()Ljava/net/URL;"));
|
|
checkException();
|
|
auto classLoaderClass = getClass("java/lang/ClassLoader");
|
|
auto sysLoaderMethod =
|
|
env->GetStaticMethodID(classLoaderClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
|
|
checkException();
|
|
auto classLoader = env->CallStaticObjectMethod(classLoaderClass, sysLoaderMethod);
|
|
checkException();
|
|
auto urlLoaderClass = getClass("java/net/URLClassLoader");
|
|
env->CallVoidMethod(classLoader, getMethod(urlLoaderClass, "addURL", "(Ljava/net/URL;)V"), url);
|
|
env->DeleteLocalRef(classLoader);
|
|
checkException();
|
|
}
|
|
|
|
void init() {
|
|
if (abstractWorkloadClass != nullptr) {
|
|
return;
|
|
}
|
|
abstractWorkloadClass = getClass("com/apple/foundationdb/testing/AbstractWorkload");
|
|
setNativeMethods(abstractWorkloadClass,
|
|
{ { "log", "(JILjava/lang/String;Ljava/util/Map;)V", reinterpret_cast<void*>(&printTrace) } });
|
|
auto loggerField = env->GetStaticFieldID(abstractWorkloadClass, "logger", "J");
|
|
checkException();
|
|
env->SetStaticLongField(abstractWorkloadClass, loggerField, reinterpret_cast<jlong>(log));
|
|
log->trace(info, "SetLogger", { { "Logger", std::to_string(reinterpret_cast<jlong>(log)) } });
|
|
setNativeMethods(getClass("com/apple/foundationdb/testing/WorkloadContext"),
|
|
{ { "getProcessID", "(J)J", reinterpret_cast<void*>(&getProcessID) },
|
|
{ "setProcessID", "(JJ)V", reinterpret_cast<void*>(&setProcessID) },
|
|
{ "getOption", "(JLjava/lang/String;Z)Z", reinterpret_cast<void*>(&getOptionBool) },
|
|
{ "getOption", "(JLjava/lang/String;J)J", reinterpret_cast<void*>(&getOptionLong) },
|
|
{ "getOption", "(JLjava/lang/String;D)D", reinterpret_cast<void*>(&getOptionDouble) },
|
|
{ "getOption",
|
|
"(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
|
|
reinterpret_cast<void*>(&getOptionString) },
|
|
{ "getClientID", "(J)I", reinterpret_cast<void*>(&getClientID) },
|
|
{ "getClientCount", "(J)I", reinterpret_cast<void*>(&getClientCount) },
|
|
{ "getSharedRandomNumber", "(J)J", reinterpret_cast<void*>(&getSharedRandomNumber) } });
|
|
setNativeMethods(getClass("com/apple/foundationdb/testing/Promise"),
|
|
{ { "send", "(JZ)V", reinterpret_cast<void*>(&promiseSend) } });
|
|
auto fdbClass = getClass("com/apple/foundationdb/FDB");
|
|
jmethodID selectMethod =
|
|
env->GetStaticMethodID(fdbClass, "selectAPIVersion", "(I)Lcom/apple/foundationdb/FDB;");
|
|
checkException();
|
|
auto fdbInstance = env->CallStaticObjectMethod(fdbClass, selectMethod, jint(720));
|
|
checkException();
|
|
env->CallObjectMethod(fdbInstance, getMethod(fdbClass, "disableShutdownHook", "()V"));
|
|
checkException();
|
|
}
|
|
|
|
jobject createWorkloadContext(FDBWorkloadContext* context) {
|
|
auto clazz = getClass("com/apple/foundationdb/testing/WorkloadContext");
|
|
auto constructor = getMethod(clazz, "<init>", "(J)V");
|
|
auto jContext = reinterpret_cast<jlong>(context);
|
|
jobject res = env->NewObject(clazz, constructor, jContext);
|
|
std::cout.flush();
|
|
checkException();
|
|
auto field = env->GetFieldID(clazz, "impl", "J");
|
|
checkException();
|
|
auto impl = env->GetLongField(res, field);
|
|
checkException();
|
|
if (impl != jContext) {
|
|
log->trace(error,
|
|
"ContextNotCorrect",
|
|
{ { "Expected", std::to_string(jContext) }, { "Impl", std::to_string(impl) } });
|
|
std::terminate();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
jobject createWorkload(jobject context, const std::string& workloadName) {
|
|
auto clazz = getClass(workloadName.c_str());
|
|
if (!env->IsAssignableFrom(clazz, abstractWorkloadClass)) {
|
|
log->trace(error, "ClassNotAWorkload", { { "Class", workloadName } });
|
|
return nullptr;
|
|
}
|
|
auto constructor = getMethod(clazz, "<init>", "(Lcom/apple/foundationdb/testing/WorkloadContext;)V");
|
|
auto res = env->NewObject(clazz, constructor, context);
|
|
checkException();
|
|
env->NewGlobalRef(res);
|
|
return res;
|
|
}
|
|
|
|
jobject createPromise(GenericPromise<bool>&& promise) {
|
|
auto p = std::make_unique<JavaPromise>(std::move(promise));
|
|
auto clazz = getClass("com/apple/foundationdb/testing/Promise");
|
|
auto res = env->NewObject(clazz, getMethod(clazz, "<init>", "(J)V"), reinterpret_cast<jlong>(p.get()));
|
|
checkException();
|
|
p.release();
|
|
return res;
|
|
}
|
|
|
|
void shutdownWorkload(jobject workload, const std::string& workloadName) {
|
|
auto clazz = getClass(workloadName.c_str());
|
|
env->CallVoidMethod(workload, getMethod(clazz, "shutdown", "()V"));
|
|
checkException();
|
|
}
|
|
|
|
std::string jtoStr(jstring str) {
|
|
jboolean isCopy;
|
|
auto arr = env->GetStringUTFChars(str, &isCopy);
|
|
std::string res(arr);
|
|
if (isCopy) {
|
|
env->ReleaseStringUTFChars(str, arr);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void getMetrics(jobject workload, const std::string& workloadName, std::vector<FDBPerfMetric>& result) {
|
|
auto clazz = getClass(workloadName.c_str());
|
|
auto perfMetricClass = getClass("Lcom/apple/foundationdb/testing/PerfMetric;");
|
|
auto nameId = getField(perfMetricClass, "name", "Ljava/lang/String;");
|
|
auto valueId = getField(perfMetricClass, "value", "D");
|
|
auto averagedId = getField(perfMetricClass, "averaged", "Z");
|
|
auto formatCodeId = getField(perfMetricClass, "formatCode", "Ljava/lang/String;");
|
|
auto v = env->CallObjectMethod(workload, getMethod(clazz, "getMetrics", "()Ljava/util/List;"));
|
|
checkException();
|
|
auto listClass = getClass("java/util/List");
|
|
auto iter = env->CallObjectMethod(v, getMethod(listClass, "iterator", "()Ljava/util/Iterator;"));
|
|
checkException();
|
|
auto iterClass = getClass("java/util/Iterator");
|
|
auto hasNextM = getMethod(iterClass, "hasNext", "()Z");
|
|
auto nextM = getMethod(iterClass, "next", "()Ljava/lang/Object;");
|
|
jboolean hasNext = env->CallBooleanMethod(iter, hasNextM);
|
|
checkException();
|
|
while (hasNext) {
|
|
auto perfMetric = env->CallObjectMethod(iter, nextM);
|
|
checkException();
|
|
auto name = jtoStr(jstring(env->GetObjectField(perfMetric, nameId)));
|
|
checkException();
|
|
auto value = env->GetDoubleField(perfMetric, valueId);
|
|
checkException();
|
|
auto averaged = env->GetBooleanField(perfMetric, averagedId);
|
|
checkException();
|
|
auto formatCode = jtoStr(jstring(env->GetObjectField(perfMetric, formatCodeId)));
|
|
result.emplace_back(FDBPerfMetric{ name, value, bool(averaged), formatCode });
|
|
hasNext = env->CallBooleanMethod(iter, hasNextM);
|
|
checkException();
|
|
}
|
|
return;
|
|
}
|
|
|
|
jobject createDatabase(jobject workload, FDBDatabase* db) {
|
|
auto executor = env->CallObjectMethod(workload,
|
|
getMethod(getClass("com/apple/foundationdb/testing/AbstractWorkload"),
|
|
"getExecutor",
|
|
"()Ljava/util/concurrent/Executor;"));
|
|
auto databaseClass = getClass("com/apple/foundationdb/FDBDatabase");
|
|
jlong databasePtr = reinterpret_cast<jlong>(db);
|
|
jobject javaDatabase = env->NewObject(databaseClass,
|
|
getMethod(databaseClass, "<init>", "(JLjava/util/concurrent/Executor;)V"),
|
|
databasePtr,
|
|
executor);
|
|
env->DeleteLocalRef(executor);
|
|
return javaDatabase;
|
|
}
|
|
|
|
void callWorkload(jobject workload, FDBDatabase* db, const char* method, GenericPromise<bool>&& promise) {
|
|
jobject jPromise = nullptr;
|
|
try {
|
|
auto clazz = getClass("com/apple/foundationdb/testing/AbstractWorkload");
|
|
auto jdb = createDatabase(workload, db);
|
|
jPromise = createPromise(std::move(promise));
|
|
env->CallVoidMethod(
|
|
workload,
|
|
getMethod(
|
|
clazz, method, "(Lcom/apple/foundationdb/Database;Lcom/apple/foundationdb/testing/Promise;)V"),
|
|
jdb,
|
|
jPromise);
|
|
env->DeleteLocalRef(jdb);
|
|
env->DeleteLocalRef(jPromise);
|
|
jPromise = nullptr;
|
|
checkException();
|
|
} catch (...) {
|
|
if (jPromise) {
|
|
env->DeleteLocalRef(jPromise);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct JavaWorkload final : FDBWorkload {
|
|
std::shared_ptr<JVM> jvm;
|
|
FDBLogger& log;
|
|
FDBWorkloadContext* context = nullptr;
|
|
std::string name;
|
|
bool failed = false;
|
|
jobject workload = nullptr;
|
|
JavaWorkload(const std::shared_ptr<JVM>& jvm, FDBLogger& log, const std::string& name)
|
|
: jvm(jvm), log(log), name(name) {
|
|
boost::replace_all(this->name, ".", "/");
|
|
}
|
|
~JavaWorkload() {
|
|
if (workload) {
|
|
try {
|
|
jvm->shutdownWorkload(workload, name);
|
|
jvm->env->DeleteGlobalRef(workload);
|
|
} catch (JNIError& e) {
|
|
log.trace(error, "JNIShutDownUnsucessful", { { "Error", e.toString() }, { "Location", e.location() } });
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string description() const override { return name; }
|
|
bool init(FDBWorkloadContext* context) override {
|
|
this->context = context;
|
|
try {
|
|
std::string classPath = context->getOption("classPath", std::string(""));
|
|
std::vector<std::string> paths;
|
|
boost::split(paths, classPath, boost::is_any_of(";,"), boost::token_compress_on);
|
|
for (const auto& path : paths) {
|
|
jvm->addToClassPath(path);
|
|
}
|
|
jvm->init();
|
|
jobject jContext = jvm->createWorkloadContext(context);
|
|
if (jContext == nullptr) {
|
|
failed = true;
|
|
return failed;
|
|
}
|
|
workload = jvm->createWorkload(jContext, name);
|
|
} catch (JNIError& e) {
|
|
failed = true;
|
|
log.trace(error, "JNIError", { { "Location", e.location() }, { "Error", e.toString() } });
|
|
}
|
|
return failed;
|
|
};
|
|
void setup(FDBDatabase* db, GenericPromise<bool> done) override {
|
|
if (failed) {
|
|
done.send(false);
|
|
return;
|
|
}
|
|
try {
|
|
jvm->callWorkload(workload, db, "setup", std::move(done));
|
|
} catch (JNIError& e) {
|
|
failed = true;
|
|
log.trace(error, "SetupFailedWithJNIError", { { "Error", e.toString() }, { "Location", e.location() } });
|
|
}
|
|
}
|
|
void start(FDBDatabase* db, GenericPromise<bool> done) override {
|
|
if (failed) {
|
|
done.send(false);
|
|
return;
|
|
}
|
|
try {
|
|
jvm->callWorkload(workload, db, "start", std::move(done));
|
|
} catch (JNIError& e) {
|
|
failed = true;
|
|
log.trace(error, "StartFailedWithJNIError", { { "Error", e.toString() }, { "Location", e.location() } });
|
|
}
|
|
}
|
|
void check(FDBDatabase* db, GenericPromise<bool> done) override {
|
|
if (failed) {
|
|
done.send(false);
|
|
return;
|
|
}
|
|
try {
|
|
jvm->callWorkload(workload, db, "check", std::move(done));
|
|
} catch (JNIError& e) {
|
|
failed = true;
|
|
log.trace(error, "CheckFailedWithJNIError", { { "Error", e.toString() }, { "Location", e.location() } });
|
|
}
|
|
}
|
|
void getMetrics(std::vector<FDBPerfMetric>& out) const override { jvm->getMetrics(workload, name, out); }
|
|
};
|
|
|
|
struct JavaWorkloadFactory : FDBWorkloadFactory {
|
|
FDBLogger* log;
|
|
std::weak_ptr<JVM> jvm;
|
|
JavaWorkloadFactory(FDBLogger* log) : log(log) {}
|
|
JavaWorkloadFactory(const JavaWorkloadFactory&) = delete;
|
|
JavaWorkloadFactory& operator=(const JavaWorkloadFactory&) = delete;
|
|
std::shared_ptr<FDBWorkload> create(const std::string& name) override {
|
|
auto jvmPtr = jvm.lock();
|
|
if (!jvmPtr) {
|
|
jvmPtr = std::make_shared<JVM>(log);
|
|
jvm = jvmPtr;
|
|
}
|
|
return std::make_shared<JavaWorkload>(jvmPtr, *log, name);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
extern "C" DLLEXPORT FDBWorkloadFactory* workloadFactory(FDBLogger* logger);
|
|
|
|
FDBWorkloadFactory* workloadFactory(FDBLogger* logger) {
|
|
static JavaWorkloadFactory factory(logger);
|
|
return &factory;
|
|
}
|