summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/ti-fast/Android.bp56
-rw-r--r--tools/ti-fast/README.md101
-rw-r--r--tools/ti-fast/tifast.cc205
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
+