blob: 9ea99d5c51236a2ceab10e2f52fe28a06d99cda0 [file] [log] [blame]
// Copyright (C) 2020 The Android Open Source Project
//
// 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 <android-base/logging.h>
#include <fcntl.h>
#include <jni.h>
#include <jvmti.h>
#include <atomic>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "android-base/unique_fd.h"
#include "nativehelper/scoped_local_ref.h"
namespace simple_profile {
static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
#define CHECK_JVMTI(a) CHECK_EQ(JVMTI_ERROR_NONE, a)
struct DataDefinition {
std::string_view class_name;
std::string_view method_name;
std::string_view method_descriptor;
uint64_t count;
};
std::ostream& operator<<(std::ostream& os, const DataDefinition& dd) {
return os << "{\"class_name\":\"" << dd.class_name << "\",\"method_name\":\"" << dd.method_name
<< "\",\"method_descriptor\":\"" << dd.method_descriptor << "\",\"count\":" << dd.count
<< "}";
}
class SimpleProfileData {
public:
SimpleProfileData(
jvmtiEnv* env, std::string out_fd_name, int fd, bool dump_on_shutdown, bool dump_on_main_stop)
: dump_id_(0),
out_fd_name_(std::move(out_fd_name)),
out_fd_(fd),
shutdown_(false),
dump_on_shutdown_(dump_on_shutdown || dump_on_main_stop),
dump_on_main_stop_(dump_on_main_stop) {
CHECK_JVMTI(env->CreateRawMonitor("simple_profile_mon", &mon_));
method_counts_.reserve(10000);
}
void Dump(jvmtiEnv* jvmti);
void Enter(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth);
void RunDumpLoop(jvmtiEnv* jvmti, JNIEnv* env);
static SimpleProfileData* GetProfileData(jvmtiEnv* env) {
void* data;
CHECK_JVMTI(env->GetEnvironmentLocalStorage(&data));
return static_cast<SimpleProfileData*>(data);
}
void FinishInitialization(jvmtiEnv* jvmti, JNIEnv* jni, jthread cur);
void Shutdown(jvmtiEnv* jvmti, JNIEnv* jni);
private:
void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, const std::unordered_map<jmethodID, uint64_t>& copy);
jlong dump_id_;
jrawMonitorID mon_;
std::string out_fd_name_;
int out_fd_;
std::unordered_map<jmethodID, uint64_t> method_counts_;
bool shutdown_;
bool dump_on_shutdown_;
bool dump_on_main_stop_;
};
struct ScopedJvmtiMonitor {
public:
ScopedJvmtiMonitor(jvmtiEnv* env, jrawMonitorID mon) : jvmti_(env), mon_(mon) {
CHECK_JVMTI(jvmti_->RawMonitorEnter(mon_));
}
~ScopedJvmtiMonitor() {
CHECK_JVMTI(jvmti_->RawMonitorExit(mon_));
}
void Notify() {
CHECK_JVMTI(jvmti_->RawMonitorNotifyAll(mon_));
}
void Wait() {
CHECK_JVMTI(jvmti_->RawMonitorWait(mon_, 0));
}
private:
jvmtiEnv* jvmti_;
jrawMonitorID mon_;
};
void SimpleProfileData::Enter(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth) {
ScopedJvmtiMonitor sjm(jvmti, mon_);
// Keep all classes from being unloaded to allow us to know we can get the method info later.
jclass tmp;
CHECK_JVMTI(jvmti->GetMethodDeclaringClass(meth, &tmp));
ScopedLocalRef<jclass> klass(env, tmp);
jlong tag;
CHECK_JVMTI(jvmti->GetTag(klass.get(), &tag));
if (tag == 0) {
CHECK_JVMTI(jvmti->SetTag(klass.get(), 1u));
env->NewGlobalRef(klass.get());
}
method_counts_.insert({ meth, 0u }).first->second++;
}
void SimpleProfileData::Dump(jvmtiEnv* jvmti) {
ScopedJvmtiMonitor sjm(jvmti, mon_);
dump_id_++;
sjm.Notify();
}
void SimpleProfileData::RunDumpLoop(jvmtiEnv* jvmti, JNIEnv* env) {
jlong current_id = 0;
do {
std::unordered_map<jmethodID, uint64_t> copy;
{
ScopedJvmtiMonitor sjm(jvmti, mon_);
while (!shutdown_ && current_id == dump_id_) {
sjm.Wait();
}
if (shutdown_) {
break;
}
current_id = dump_id_;
copy = method_counts_;
}
DoDump(jvmti, env, std::move(copy));
} while (true);
}
void SimpleProfileData::Shutdown(jvmtiEnv* jvmti, JNIEnv* jni) {
std::unordered_map<jmethodID, uint64_t> copy;
{
ScopedJvmtiMonitor sjm(jvmti, mon_);
if (shutdown_) {
return;
}
shutdown_ = true;
copy = method_counts_;
sjm.Notify();
}
if (dump_on_shutdown_) {
DoDump(jvmti, jni, std::move(copy));
}
}
void SimpleProfileData::FinishInitialization(jvmtiEnv* jvmti, JNIEnv* env, jthread cur) {
// Finish up startup.
// Create a Thread object.
std::string name = std::string("profile dump Thread: ") + this->out_fd_name_;
ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name.c_str()));
CHECK_NE(thread_name.get(), nullptr);
ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread"));
CHECK_NE(thread_klass.get(), nullptr);
ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get()));
CHECK_NE(thread.get(), nullptr);
jmethodID initID = env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V");
jmethodID setDaemonId = env->GetMethodID(thread_klass.get(), "setDaemon", "(Z)V");
CHECK_NE(initID, nullptr);
CHECK_NE(setDaemonId, nullptr);
env->CallNonvirtualVoidMethod(thread.get(), thread_klass.get(), initID, thread_name.get());
env->CallVoidMethod(thread.get(), setDaemonId, JNI_TRUE);
CHECK(!env->ExceptionCheck());
CHECK_JVMTI(jvmti->RunAgentThread(
thread.get(),
[](jvmtiEnv* jvmti, JNIEnv* jni, [[maybe_unused]] void* unused_data) {
SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti);
data->RunDumpLoop(jvmti, jni);
},
nullptr,
JVMTI_THREAD_NORM_PRIORITY));
CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr));
CHECK_JVMTI(
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr));
if (dump_on_main_stop_) {
CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, cur));
}
CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr));
}
class ScopedClassInfo {
public:
ScopedClassInfo(jvmtiEnv* jvmti_env, jclass c)
: jvmti_env_(jvmti_env), class_(c), name_(nullptr), generic_(nullptr) {}
~ScopedClassInfo() {
if (class_ != nullptr) {
jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(name_));
jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
}
}
bool Init() {
if (class_ == nullptr) {
name_ = const_cast<char*>("<NONE>");
generic_ = const_cast<char*>("<NONE>");
return true;
} else {
return jvmti_env_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE;
}
}
jclass GetClass() const {
return class_;
}
const char* GetName() const {
return name_;
}
// Generic type parameters, whatever is in the <> for a class
const char* GetGeneric() const {
return generic_;
}
private:
jvmtiEnv* jvmti_env_;
jclass class_;
char* name_;
char* generic_;
};
class ScopedMethodInfo {
public:
ScopedMethodInfo(jvmtiEnv* jvmti_env, JNIEnv* env, jmethodID method)
: jvmti_env_(jvmti_env),
env_(env),
method_(method),
declaring_class_(nullptr),
class_info_(nullptr),
name_(nullptr),
signature_(nullptr),
generic_(nullptr) {}
~ScopedMethodInfo() {
env_->DeleteLocalRef(declaring_class_);
jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(name_));
jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(signature_));
jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
}
bool Init() {
if (jvmti_env_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) {
LOG(INFO) << "No decl";
return false;
}
class_info_.reset(new ScopedClassInfo(jvmti_env_, declaring_class_));
return class_info_->Init() &&
(jvmti_env_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE);
}
const ScopedClassInfo& GetDeclaringClassInfo() const {
return *class_info_;
}
jclass GetDeclaringClass() const {
return declaring_class_;
}
const char* GetName() const {
return name_;
}
const char* GetSignature() const {
return signature_;
}
const char* GetGeneric() const {
return generic_;
}
private:
jvmtiEnv* jvmti_env_;
JNIEnv* env_;
jmethodID method_;
jclass declaring_class_;
std::unique_ptr<ScopedClassInfo> class_info_;
char* name_;
char* signature_;
char* generic_;
friend std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method);
};
std::ostream& operator<<(std::ostream& os, const ScopedMethodInfo* method) {
return os << *method;
}
std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method) {
return os << method.GetDeclaringClassInfo().GetName() << "->" << method.GetName()
<< method.GetSignature();
}
void SimpleProfileData::DoDump(jvmtiEnv* jvmti,
JNIEnv* jni,
const std::unordered_map<jmethodID, uint64_t>& copy) {
std::ostringstream oss;
oss << "[";
bool is_first = true;
for (auto [meth, cnt] : copy) {
ScopedMethodInfo smi(jvmti, jni, meth);
if (!smi.Init()) {
continue;
}
if (!is_first) {
oss << "," << std::endl;
}
is_first = false;
oss << DataDefinition {
.class_name = smi.GetDeclaringClassInfo().GetName(),
.method_name = smi.GetName(),
.method_descriptor = smi.GetSignature(),
.count = cnt,
};
}
oss << "]";
CHECK_GE(TEMP_FAILURE_RETRY(write(out_fd_, oss.str().c_str(), oss.str().size())), 0)
<< strerror(errno) << out_fd_ << " " << out_fd_name_;
fsync(out_fd_);
}
static void DataDumpCb(jvmtiEnv* jvmti_env) {
SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti_env);
data->Dump(jvmti_env);
}
static void MethodEntryCB(jvmtiEnv* jvmti_env,
JNIEnv* env,
[[maybe_unused]] jthread thread,
jmethodID method) {
SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti_env);
data->Enter(jvmti_env, env, method);
}
static void VMInitCB(jvmtiEnv* jvmti, JNIEnv* env, jthread thr) {
SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti);
data->FinishInitialization(jvmti, env, thr);
}
static void VMDeathCB(jvmtiEnv* jvmti, JNIEnv* env) {
SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti);
data->Shutdown(jvmti, env);
}
// Fills targets with the breakpoints to add.
// Lname/of/Klass;->methodName(Lsig/of/Method)Lreturn/Type;@location,<...>
static bool ParseArgs(const std::string& start_options,
/*out*/ std::string* fd_name,
/*out*/ int* fd,
/*out*/ bool* dump_on_shutdown,
/*out*/ bool* dump_on_main_stop) {
std::istringstream iss(start_options);
std::string item;
*dump_on_main_stop = false;
*dump_on_shutdown = false;
bool has_fd = false;
while (std::getline(iss, item, ',')) {
if (item == "dump_on_shutdown") {
*dump_on_shutdown = true;
} else if (item == "dump_on_main_stop") {
*dump_on_main_stop = true;
} else if (has_fd) {
LOG(ERROR) << "Too many args!";
return false;
} else {
has_fd = true;
*fd_name = item;
*fd = TEMP_FAILURE_RETRY(open(fd_name->c_str(), O_WRONLY | O_CLOEXEC | O_CREAT, 00666));
CHECK_GE(*fd, 0) << strerror(errno);
}
}
return has_fd;
}
enum class StartType {
OnAttach,
OnLoad,
};
static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) {
jint res = 0;
res = vm->GetEnv(reinterpret_cast<void**>(jvmti), JVMTI_VERSION_1_1);
if (res != JNI_OK || *jvmti == nullptr) {
LOG(ERROR) << "Unable to access JVMTI, error code " << res;
return vm->GetEnv(reinterpret_cast<void**>(jvmti), kArtTiVersion);
}
return res;
}
static jint AgentStart(StartType start,
JavaVM* vm,
const char* options,
[[maybe_unused]] void* reserved) {
if (options == nullptr) {
options = "";
}
jvmtiEnv* jvmti = nullptr;
jvmtiError error = JVMTI_ERROR_NONE;
{
jint res = 0;
res = SetupJvmtiEnv(vm, &jvmti);
if (res != JNI_OK || jvmti == nullptr) {
LOG(ERROR) << "Unable to access JVMTI, error code " << res;
return JNI_ERR;
}
}
int fd;
std::string fd_name;
bool dump_on_shutdown;
bool dump_on_main_stop;
if (!ParseArgs(options,
/*out*/ &fd_name,
/*out*/ &fd,
/*out*/ &dump_on_shutdown,
/*out*/ &dump_on_main_stop)) {
LOG(ERROR) << "failed to get output file from " << options << "!";
return JNI_ERR;
}
void* data_mem = nullptr;
error = jvmti->Allocate(sizeof(SimpleProfileData), reinterpret_cast<unsigned char**>(&data_mem));
if (error != JVMTI_ERROR_NONE) {
LOG(ERROR) << "Unable to alloc memory for breakpoint target data";
return JNI_ERR;
}
SimpleProfileData* data =
new (data_mem) SimpleProfileData(jvmti, fd_name, fd, dump_on_shutdown, dump_on_main_stop);
error = jvmti->SetEnvironmentLocalStorage(data);
if (error != JVMTI_ERROR_NONE) {
LOG(ERROR) << "Unable to set local storage";
return JNI_ERR;
}
jvmtiCapabilities caps {};
caps.can_generate_method_entry_events = JNI_TRUE;
caps.can_tag_objects = JNI_TRUE;
error = jvmti->AddCapabilities(&caps);
if (error != JVMTI_ERROR_NONE) {
LOG(ERROR) << "Unable to set caps";
return JNI_ERR;
}
jvmtiEventCallbacks callbacks {};
callbacks.MethodEntry = &MethodEntryCB;
callbacks.VMInit = &VMInitCB;
callbacks.DataDumpRequest = &DataDumpCb;
callbacks.VMDeath = &VMDeathCB;
callbacks.ThreadEnd = [](jvmtiEnv* env, JNIEnv* jni, [[maybe_unused]] jthread thr) {
VMDeathCB(env, jni);
};
error = jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks)));
if (error != JVMTI_ERROR_NONE) {
LOG(ERROR) << "Unable to set event callbacks.";
return JNI_ERR;
}
if (start == StartType::OnAttach) {
JNIEnv* env = nullptr;
jint res = 0;
res = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
if (res != JNI_OK || env == nullptr) {
LOG(ERROR) << "Unable to get jnienv";
return JNI_ERR;
}
jthread temp;
ScopedLocalRef<jthread> cur(env, nullptr);
CHECK_JVMTI(jvmti->GetCurrentThread(&temp));
cur.reset(temp);
VMInitCB(jvmti, env, cur.get());
} else {
error = jvmti->SetEventNotificationMode(
JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr /* all threads */);
if (error != JVMTI_ERROR_NONE) {
LOG(ERROR) << "Unable to set event vminit";
return JNI_ERR;
}
}
return JNI_OK;
}
// Late attachment (e.g. 'am attach-agent').
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
return AgentStart(StartType::OnAttach, vm, options, reserved);
}
// Early attachment
extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
return AgentStart(StartType::OnLoad, jvm, options, reserved);
}
} // namespace simple_profile