Modify traceview to sample instead of instrumenting methods.
Disabled the method instrumentation in traceview. Now when traceview
is triggered, it starts up a thread which periodically samples all
threads instead, generating method entry/exit events that traceview
can understand.
Change-Id: Ifc4012a4509d8eb9f54941eee2a8b42c411e5e9c
diff --git a/runtime/trace.cc b/runtime/trace.cc
index 2bce70f..2ce7c06 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -18,6 +18,7 @@
#include <sys/uio.h>
+#include "base/stl_util.h"
#include "base/unix_file/fd_file.h"
#include "class_linker.h"
#include "common_throws.h"
@@ -32,6 +33,7 @@
#include "object_utils.h"
#include "os.h"
#include "scoped_thread_state_change.h"
+#include "ScopedLocalRef.h"
#include "thread.h"
#include "thread_list.h"
#if !defined(ART_USE_PORTABLE_COMPILER)
@@ -82,6 +84,29 @@
kTraceMethodActionMask = 0x03, // two bits
};
+class BuildStackTraceVisitor : public StackVisitor {
+ public:
+ explicit BuildStackTraceVisitor(Thread* thread) : StackVisitor(thread, NULL),
+ method_trace_(new std::vector<mirror::AbstractMethod*>) {}
+
+ bool VisitFrame() {
+ mirror::AbstractMethod* m = GetMethod();
+ // Ignore runtime frames (in particular callee save).
+ if (!m->IsRuntimeMethod()) {
+ method_trace_->push_back(m);
+ }
+ return true;
+ }
+
+ // Returns a stack trace where the topmost frame corresponds with the first element of the vector.
+ std::vector<mirror::AbstractMethod*>* GetStackTrace() const {
+ return method_trace_;
+ }
+
+ private:
+ std::vector<mirror::AbstractMethod*>* const method_trace_;
+};
+
static const char kTraceTokenChar = '*';
static const uint16_t kTraceHeaderLength = 32;
static const uint32_t kTraceMagicValue = 0x574f4c53;
@@ -96,7 +121,11 @@
ProfilerClockSource Trace::default_clock_source_ = kProfilerClockSourceWall;
#endif
-Trace* Trace::the_trace_ = NULL;
+Trace* volatile Trace::the_trace_ = NULL;
+// TODO: Add way to enable sampling and set interval through gui.
+bool Trace::sampling_enabled_ = true;
+uint32_t Trace::sampling_interval_us_ = 10000;
+pthread_t Trace::sampling_pthread_ = 0U;
static mirror::AbstractMethod* DecodeTraceMethodId(uint32_t tmid) {
return reinterpret_cast<mirror::AbstractMethod*>(tmid & ~kTraceMethodActionMask);
@@ -196,6 +225,82 @@
*buf++ = (uint8_t) (val >> 56);
}
+static void GetSample(Thread* thread, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ BuildStackTraceVisitor build_trace_visitor(thread);
+ build_trace_visitor.WalkStack();
+ std::vector<mirror::AbstractMethod*>* stack_trace = build_trace_visitor.GetStackTrace();
+ Trace* the_trace = reinterpret_cast<Trace*>(arg);
+ the_trace->CompareAndUpdateStackTrace(thread, stack_trace);
+}
+
+void Trace::CompareAndUpdateStackTrace(Thread* thread,
+ std::vector<mirror::AbstractMethod*>* stack_trace) {
+ CHECK_EQ(pthread_self(), sampling_pthread_);
+ SafeMap<Thread*, std::vector<mirror::AbstractMethod*>*>::iterator map_it = thread_stack_trace_map_.find(thread);
+ if (map_it == thread_stack_trace_map_.end()) {
+ // If there's no existing stack trace in the map for this thread, log an entry event for all
+ // methods in the trace.
+ thread_stack_trace_map_.Put(thread, stack_trace);
+ for (std::vector<mirror::AbstractMethod*>::reverse_iterator rit = stack_trace->rbegin();
+ rit != stack_trace->rend(); ++rit) {
+ LogMethodTraceEvent(thread, *rit, instrumentation::Instrumentation::kMethodEntered);
+ }
+ } else {
+ // If there's a previous stack trace for this thread, diff the traces and emit entry and exit
+ // events accordingly.
+ std::vector<mirror::AbstractMethod*>* old_stack_trace = map_it->second;
+ thread_stack_trace_map_.Overwrite(thread, stack_trace);
+ std::vector<mirror::AbstractMethod*>::reverse_iterator old_rit = old_stack_trace->rbegin();
+ std::vector<mirror::AbstractMethod*>::reverse_iterator rit = stack_trace->rbegin();
+
+ // Iterate bottom-up over both traces until there's a difference between them.
+ while (old_rit != old_stack_trace->rend() && rit != stack_trace->rend() && *old_rit == *rit) {
+ old_rit++;
+ rit++;
+ }
+ // Iterate top-down over the old trace until the point where they differ, emitting exit events.
+ for (std::vector<mirror::AbstractMethod*>::iterator old_it = old_stack_trace->begin();
+ old_it != old_rit.base(); ++old_it) {
+ LogMethodTraceEvent(thread, *old_it, instrumentation::Instrumentation::kMethodExited);
+ }
+ // Iterate bottom-up over the new trace from the point where they differ, emitting entry events.
+ for (; rit != stack_trace->rend(); ++rit) {
+ LogMethodTraceEvent(thread, *rit, instrumentation::Instrumentation::kMethodEntered);
+ }
+ delete old_stack_trace;
+ }
+}
+
+void* Trace::RunSamplingThread(void* arg) {
+ Runtime* runtime = Runtime::Current();
+ CHECK(runtime->AttachCurrentThread("Sampling Profiler", true, runtime->GetSystemThreadGroup(),
+ !runtime->IsCompiler()));
+
+ while (true) {
+ usleep(sampling_interval_us_);
+
+ Thread* self = Thread::Current();
+ Trace* the_trace;
+ {
+ MutexLock mu(self, *Locks::trace_lock_);
+ the_trace = the_trace_;
+ if (the_trace == NULL) {
+ break;
+ }
+ }
+
+ runtime->GetThreadList()->SuspendAll();
+ {
+ MutexLock mu(self, *Locks::thread_list_lock_);
+ runtime->GetThreadList()->ForEach(GetSample, the_trace);
+ }
+ runtime->GetThreadList()->ResumeAll();
+ }
+
+ runtime->DetachCurrentThread();
+ return NULL;
+}
+
void Trace::Start(const char* trace_filename, int trace_fd, int buffer_size, int flags,
bool direct_to_ddms) {
Thread* self = Thread::Current();
@@ -240,10 +345,15 @@
runtime->SetStatsEnabled(true);
}
- runtime->GetInstrumentation()->AddListener(the_trace_,
- instrumentation::Instrumentation::kMethodEntered |
- instrumentation::Instrumentation::kMethodExited |
- instrumentation::Instrumentation::kMethodUnwind);
+ if (sampling_enabled_) {
+ CHECK_PTHREAD_CALL(pthread_create, (&sampling_pthread_, NULL, &RunSamplingThread, NULL),
+ "Sampling profiler thread");
+ } else {
+ runtime->GetInstrumentation()->AddListener(the_trace_,
+ instrumentation::Instrumentation::kMethodEntered |
+ instrumentation::Instrumentation::kMethodExited |
+ instrumentation::Instrumentation::kMethodUnwind);
+ }
}
}
runtime->GetThreadList()->ResumeAll();
@@ -253,6 +363,7 @@
Runtime* runtime = Runtime::Current();
runtime->GetThreadList()->SuspendAll();
Trace* the_trace = NULL;
+ pthread_t sampling_pthread = 0U;
{
MutexLock mu(Thread::Current(), *Locks::trace_lock_);
if (the_trace_ == NULL) {
@@ -260,17 +371,26 @@
} else {
the_trace = the_trace_;
the_trace_ = NULL;
+ sampling_pthread = sampling_pthread_;
+ sampling_pthread_ = 0U;
}
}
if (the_trace != NULL) {
the_trace->FinishTracing();
- runtime->GetInstrumentation()->RemoveListener(the_trace,
- instrumentation::Instrumentation::kMethodEntered |
- instrumentation::Instrumentation::kMethodExited |
- instrumentation::Instrumentation::kMethodUnwind);
+
+ if (!sampling_enabled_) {
+ runtime->GetInstrumentation()->RemoveListener(the_trace,
+ instrumentation::Instrumentation::kMethodEntered |
+ instrumentation::Instrumentation::kMethodExited |
+ instrumentation::Instrumentation::kMethodUnwind);
+ }
delete the_trace;
}
runtime->GetThreadList()->ResumeAll();
+
+ if (sampling_enabled_ && sampling_pthread != 0U) {
+ CHECK_PTHREAD_CALL(pthread_join, (sampling_pthread, NULL), "sampling thread shutdown");
+ }
}
void Trace::Shutdown() {
@@ -304,6 +424,11 @@
cur_offset_ = kTraceHeaderLength;
}
+Trace::~Trace() {
+ CHECK_EQ(sampling_pthread_, static_cast<pthread_t>(0U));
+ STLDeleteValues(&thread_stack_trace_map_);
+}
+
static void DumpBuf(uint8_t* buf, size_t buf_size, ProfilerClockSource clock_source)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
uint8_t* ptr = buf + kTraceHeaderLength;
diff --git a/runtime/trace.h b/runtime/trace.h
index bd9c140..d2e6fb1 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -20,6 +20,7 @@
#include <ostream>
#include <set>
#include <string>
+#include <vector>
#include "base/macros.h"
#include "globals.h"
@@ -62,6 +63,9 @@
bool UseWallClock();
bool UseThreadCpuClock();
+ void CompareAndUpdateStackTrace(Thread* thread, std::vector<mirror::AbstractMethod*>* stack_trace)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
virtual void MethodEntered(Thread* thread, mirror::Object* this_object,
const mirror::AbstractMethod* method, uint32_t dex_pc)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
@@ -79,9 +83,13 @@
mirror::Throwable* exception_object)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ ~Trace();
+
private:
explicit Trace(File* trace_file, int buffer_size, int flags);
+ static void* RunSamplingThread(void* arg) LOCKS_EXCLUDED(Locks::trace_lock_);
+
void FinishTracing() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
void LogMethodTraceEvent(Thread* thread, const mirror::AbstractMethod* method,
@@ -94,11 +102,23 @@
void DumpThreadList(std::ostream& os) LOCKS_EXCLUDED(Locks::thread_list_lock_);
// Singleton instance of the Trace or NULL when no method tracing is active.
- static Trace* the_trace_ GUARDED_BY(Locks::trace_lock_);
+ static Trace* volatile the_trace_ GUARDED_BY(Locks::trace_lock_);
// The default profiler clock source.
static ProfilerClockSource default_clock_source_;
+ // True if traceview should sample instead of instrumenting method entry/exit.
+ static bool sampling_enabled_;
+
+ // Sampling interval in microseconds.
+ static uint32_t sampling_interval_us_;
+
+ // Sampling thread, non-zero when sampling.
+ static pthread_t sampling_pthread_;
+
+ // Maps a thread to its most recent stack trace sample.
+ SafeMap<Thread*, std::vector<mirror::AbstractMethod*>*> thread_stack_trace_map_;
+
// Maps a thread to its clock base.
SafeMap<Thread*, uint64_t> thread_clock_base_map_;