diff options
| -rw-r--r-- | tools/ti-fast/Android.bp | 56 | ||||
| -rw-r--r-- | tools/ti-fast/README.md | 101 | ||||
| -rw-r--r-- | tools/ti-fast/tifast.cc | 205 |
3 files changed, 362 insertions, 0 deletions
diff --git a/tools/ti-fast/Android.bp b/tools/ti-fast/Android.bp new file mode 100644 index 0000000000..fd867c9bcc --- /dev/null +++ b/tools/ti-fast/Android.bp @@ -0,0 +1,56 @@ +// +// Copyright (C) 2018 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. +// + +// Build variants {target,host} x {debug,ndebug} x {32,64} +cc_defaults { + name: "tifast-defaults", + host_supported: true, + srcs: ["tifast.cc"], + defaults: ["art_defaults"], + + // Note that this tool needs to be built for both 32-bit and 64-bit since it requires + // to be same ISA as what it is attached to. + compile_multilib: "both", + + shared_libs: [ + "libbase", + ], + header_libs: [ + "libopenjdkjvmti_headers", + ], + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + symlink_preferred_arch: true, +} + +art_cc_library { + name: "libtifast", + defaults: ["tifast-defaults"], +} + +art_cc_library { + name: "libtifastd", + defaults: [ + "art_debug_defaults", + "tifast-defaults", + ], +} diff --git a/tools/ti-fast/README.md b/tools/ti-fast/README.md new file mode 100644 index 0000000000..bc468826ec --- /dev/null +++ b/tools/ti-fast/README.md @@ -0,0 +1,101 @@ +# tifast + +tifast is a JVMTI agent designed for profiling the performance impact listening +to various JVMTI events. It is called tifast since none of the event handlers do +anything meaning that it can be considered speed-of-light. + +# Usage +### Build +> `make libtifast` + +The libraries will be built for 32-bit, 64-bit, host and target. Below examples +assume you want to use the 64-bit version. + +### Command Line + +The agent is loaded using -agentpath like normal. It takes arguments in the +following format: +> `[log,][EventName1[,EventName2[,...]]]` + +* If 'log' is the first argument the event handlers will LOG(INFO) when they are + called. This behavior is static. The no-log methods have no branches and just + immediately return. + +* The event-names are the same names as are used in the jvmtiEventCallbacks + struct. + +* All required capabilities are automatically gained. No capabilities other than + those needed to listen for the events are gained. + +* Only events which do not require additional function calls to cause delivery + and are sent more than once are supported. + +#### Supported events + +The following events may be listened for with this agent + +* `SingleStep` + +* `MethodEntry` + +* `MethodExit` + +* `NativeMethodBind` + +* `Exception` + +* `ExceptionCatch` + +* `ThreadStart` + +* `ThreadEnd` + +* `ClassLoad` + +* `ClassPrepare` + +* `ClassFileLoadHook` + +* `CompiledMethodLoad` + +* `CompiledMethodUnload` + +* `DynamicCodeGenerated` + +* `DataDumpRequest` + +* `MonitorContendedEnter` + +* `MonitorContendedEntered` + +* `MonitorWait` + +* `MonitorWaited` + +* `ResourceExhausted` + +* `VMObjectAlloc` + +* `GarbageCollectionStart` + +* `GarbageCollectionFinish` + +All other events cannot be listened for by this agent. Most of these missing +events either require the use of other functions in order to be called +(`FramePop`, `ObjectFree`, etc) or are only called once (`VMInit`, `VMDeath`, +etc). + +#### ART +> `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so '-agentpath:libtifast.so=MethodEntry' -cp tmp/java/helloworld.dex -Xint helloworld` + +* `-Xplugin` and `-agentpath` need to be used, otherwise the agent will fail during init. +* If using `libartd.so`, make sure to use the debug version of jvmti. + +> `adb shell setenforce 0` +> +> `adb push $ANDROID_PRODUCT_OUT/system/lib64/libtifast.so /data/local/tmp/` +> +> `adb shell am start-activity --attach-agent /data/local/tmp/libtifast.so=MonitorWait,ClassPrepare some.debuggable.apps/.the.app.MainActivity` + +#### RI +> `java '-agentpath:libtifast.so=MethodEntry' -cp tmp/helloworld/classes helloworld` diff --git a/tools/ti-fast/tifast.cc b/tools/ti-fast/tifast.cc new file mode 100644 index 0000000000..952fba8720 --- /dev/null +++ b/tools/ti-fast/tifast.cc @@ -0,0 +1,205 @@ +// Copyright (C) 2018 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 <atomic> +#include <iostream> +#include <istream> +#include <iomanip> +#include <jni.h> +#include <jvmti.h> +#include <memory> +#include <string> +#include <sstream> +#include <vector> + +namespace tifast { + +#define EVENT(x) JVMTI_EVENT_ ## x + +namespace { + +static void AddCapsForEvent(jvmtiEvent event, jvmtiCapabilities* caps) { + switch (event) { +#define DO_CASE(name, cap_name) \ + case EVENT(name): \ + caps->cap_name = 1; \ + break + DO_CASE(SINGLE_STEP, can_generate_single_step_events); + DO_CASE(METHOD_ENTRY, can_generate_method_entry_events); + DO_CASE(METHOD_EXIT, can_generate_method_exit_events); + DO_CASE(NATIVE_METHOD_BIND, can_generate_native_method_bind_events); + DO_CASE(EXCEPTION, can_generate_exception_events); + DO_CASE(EXCEPTION_CATCH, can_generate_exception_events); + DO_CASE(COMPILED_METHOD_LOAD, can_generate_compiled_method_load_events); + DO_CASE(COMPILED_METHOD_UNLOAD, can_generate_compiled_method_load_events); + DO_CASE(MONITOR_CONTENDED_ENTER, can_generate_monitor_events); + DO_CASE(MONITOR_CONTENDED_ENTERED, can_generate_monitor_events); + DO_CASE(MONITOR_WAIT, can_generate_monitor_events); + DO_CASE(MONITOR_WAITED, can_generate_monitor_events); + DO_CASE(VM_OBJECT_ALLOC, can_generate_vm_object_alloc_events); + DO_CASE(GARBAGE_COLLECTION_START, can_generate_garbage_collection_events); + DO_CASE(GARBAGE_COLLECTION_FINISH, can_generate_garbage_collection_events); +#undef DO_CASE + default: break; + } +} + +// Setup for all supported events. Give a macro with fun(name, event_num, args) +#define FOR_ALL_SUPPORTED_EVENTS(fun) \ + fun(SingleStep, EVENT(SINGLE_STEP), (jvmtiEnv*, JNIEnv*, jthread, jmethodID, jlocation)) \ + fun(MethodEntry, EVENT(METHOD_ENTRY), (jvmtiEnv*, JNIEnv*, jthread, jmethodID)) \ + fun(MethodExit, EVENT(METHOD_ENTRY), (jvmtiEnv*, JNIEnv*, jthread, jmethodID, jboolean, jvalue)) \ + fun(NativeMethodBind, EVENT(NATIVE_METHOD_BIND), (jvmtiEnv*, JNIEnv*, jthread, jmethodID, void*, void**)) \ + fun(Exception, EVENT(EXCEPTION), (jvmtiEnv*, JNIEnv*, jthread, jmethodID, jlocation, jobject, jmethodID, jlocation)) \ + fun(ExceptionCatch, EVENT(EXCEPTION_CATCH), (jvmtiEnv*, JNIEnv*, jthread, jmethodID, jlocation, jobject)) \ + fun(ThreadStart, EVENT(THREAD_START), (jvmtiEnv*, JNIEnv*, jthread)) \ + fun(ThreadEnd, EVENT(THREAD_END), (jvmtiEnv*, JNIEnv*, jthread)) \ + fun(ClassLoad, EVENT(CLASS_LOAD), (jvmtiEnv*, JNIEnv*, jthread, jclass)) \ + fun(ClassPrepare, EVENT(CLASS_PREPARE), (jvmtiEnv*, JNIEnv*, jthread, jclass)) \ + fun(ClassFileLoadHook, EVENT(CLASS_FILE_LOAD_HOOK), (jvmtiEnv*, JNIEnv*, jclass, jobject, const char*, jobject, jint, const unsigned char*, jint*, unsigned char**)) \ + fun(CompiledMethodLoad, EVENT(COMPILED_METHOD_LOAD), (jvmtiEnv*, jmethodID, jint, const void*, jint, const jvmtiAddrLocationMap*, const void*)) \ + fun(CompiledMethodUnload, EVENT(COMPILED_METHOD_UNLOAD), (jvmtiEnv*, jmethodID, const void*)) \ + fun(DynamicCodeGenerated, EVENT(DYNAMIC_CODE_GENERATED), (jvmtiEnv*, const char*, const void*, jint)) \ + fun(DataDumpRequest, EVENT(DATA_DUMP_REQUEST), (jvmtiEnv*)) \ + fun(MonitorContendedEnter, EVENT(MONITOR_CONTENDED_ENTER), (jvmtiEnv*, JNIEnv*, jthread, jobject)) \ + fun(MonitorContendedEntered, EVENT(MONITOR_CONTENDED_ENTERED), (jvmtiEnv*, JNIEnv*, jthread, jobject)) \ + fun(MonitorWait, EVENT(MONITOR_WAIT), (jvmtiEnv*, JNIEnv*, jthread, jobject, jlong)) \ + fun(MonitorWaited, EVENT(MONITOR_WAITED), (jvmtiEnv*, JNIEnv*, jthread, jobject, jboolean)) \ + fun(ResourceExhausted, EVENT(RESOURCE_EXHAUSTED), (jvmtiEnv*, JNIEnv*, jint, const void*, const char*)) \ + fun(VMObjectAlloc, EVENT(VM_OBJECT_ALLOC), (jvmtiEnv*, JNIEnv*, jthread, jobject, jclass, jlong)) \ + fun(GarbageCollectionStart, EVENT(GARBAGE_COLLECTION_START), (jvmtiEnv*)) \ + fun(GarbageCollectionFinish, EVENT(GARBAGE_COLLECTION_FINISH), (jvmtiEnv*)) + +#define GENERATE_EMPTY_FUNCTION(name, number, args) \ + static void JNICALL empty ## name args { } +FOR_ALL_SUPPORTED_EVENTS(GENERATE_EMPTY_FUNCTION) +#undef GENERATE_EMPTY_FUNCTION + +static jvmtiEventCallbacks kEmptyCallbacks { +#define CREATE_EMPTY_EVENT_CALLBACKS(name, num, args) \ + .name = empty ## name, + FOR_ALL_SUPPORTED_EVENTS(CREATE_EMPTY_EVENT_CALLBACKS) +#undef CREATE_EMPTY_EVENT_CALLBACKS +}; + +#define GENERATE_LOG_FUNCTION(name, number, args) \ + static void JNICALL log ## name args { \ + LOG(INFO) << "Got event " << #name ; \ + } +FOR_ALL_SUPPORTED_EVENTS(GENERATE_LOG_FUNCTION) +#undef GENERATE_LOG_FUNCTION + +static jvmtiEventCallbacks kLogCallbacks { +#define CREATE_LOG_EVENT_CALLBACK(name, num, args) \ + .name = log ## name, + FOR_ALL_SUPPORTED_EVENTS(CREATE_LOG_EVENT_CALLBACK) +#undef CREATE_LOG_EVENT_CALLBACK +}; + +static jvmtiEvent NameToEvent(const std::string& desired_name) { +#define CHECK_NAME(name, event, args) \ + if (desired_name == #name) { \ + return event; \ + } + FOR_ALL_SUPPORTED_EVENTS(CHECK_NAME); + LOG(FATAL) << "Unknown event " << desired_name; + __builtin_unreachable(); +#undef CHECK_NAME +} + +#undef FOR_ALL_SUPPORTED_EVENTS +static std::vector<jvmtiEvent> GetRequestedEventList(const std::string& args) { + std::vector<jvmtiEvent> res; + std::stringstream args_stream(args); + std::string item; + while (std::getline(args_stream, item, ',')) { + if (item == "") { + continue; + } + res.push_back(NameToEvent(item)); + } + return res; +} + +} // namespace + +static jint AgentStart(JavaVM* vm, + char* options, + void* reserved ATTRIBUTE_UNUSED) { + jvmtiEnv* jvmti = nullptr; + jvmtiError error = JVMTI_ERROR_NONE; + { + 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 JNI_ERR; + } + } + std::string args(options); + bool is_log = false; + if (args.compare(0, 3, "log") == 0) { + is_log = true; + args = args.substr(3); + } + + std::vector<jvmtiEvent> events = GetRequestedEventList(args); + + jvmtiCapabilities caps{}; + for (jvmtiEvent e : events) { + AddCapsForEvent(e, &caps); + } + error = jvmti->AddCapabilities(&caps); + if (error != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to set caps"; + return JNI_ERR; + } + + if (is_log) { + error = jvmti->SetEventCallbacks(&kLogCallbacks, static_cast<jint>(sizeof(kLogCallbacks))); + } else { + error = jvmti->SetEventCallbacks(&kEmptyCallbacks, static_cast<jint>(sizeof(kEmptyCallbacks))); + } + if (error != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to set event callbacks."; + return JNI_ERR; + } + for (jvmtiEvent e : events) { + error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, + e, + nullptr /* all threads */); + if (error != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable event " << e; + 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(vm, options, reserved); +} + +// Early attachment +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { + return AgentStart(jvm, options, reserved); +} + +} // namespace tifast + |