| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include <cstdio> |
| |
| #include "platform/utils.h" |
| |
| #include "vm/isolate.h" |
| #include "vm/json_stream.h" |
| #include "vm/native_symbol.h" |
| #include "vm/object.h" |
| #include "vm/os.h" |
| #include "vm/profiler.h" |
| #include "vm/signal_handler.h" |
| |
| namespace dart { |
| |
| // Notes on locking and signal handling: |
| // |
| // The ProfilerManager has a single monitor (monitor_). This monitor guards |
| // access to the schedule list of isolates (isolates_, isolates_size_, etc). |
| // |
| // Each isolate has a mutex (profiler_data_mutex_) which protects access |
| // to the isolate's profiler data. |
| // |
| // Locks can be taken in this order: |
| // 1. ProfilerManager::monitor_ |
| // 2. isolate->profiler_data_mutex_ |
| // In other words, it is not acceptable to take ProfilerManager::monitor_ |
| // after grabbing isolate->profiler_data_mutex_. |
| // |
| // ProfileManager::monitor_ taking entry points: |
| // InitOnce, Shutdown |
| // ProfilerManager::monitor_ |
| // ScheduleIsolate, DescheduleIsolate. |
| // ProfilerManager::monitor_, isolate->profiler_data_mutex_ |
| // ThreadMain |
| // isolate->profiler_data_mutex_ taking entry points: |
| // SetupIsolateForProfiling, FreeIsolateForProfiling. |
| // ProfilerManager::monitor_, isolate->profiler_data_mutex_ |
| // ScheduleIsolate, DescheduleIsolate. |
| // ProfilerManager::monitor_, isolate->profiler_data_mutex_ |
| // ProfileSignalAction |
| // isolate->profiler_data_mutex_ |
| // ProfilerManager::monitor_, isolate->profiler_data_mutex_ |
| // |
| // Signal handling and locking: |
| // On OSes with pthreads (Android, Linux, and Mac) we use signal delivery |
| // to interrupt the isolate running thread for sampling. After a thread |
| // is sent the SIGPROF, it is removed from the scheduled isolate list. |
| // Inside the signal handler, after the sample is taken, the isolate is |
| // added to the scheduled isolate list again. The side effect of this is |
| // that the signal handler must be able to acquire the isolate profiler data |
| // mutex and the profile manager monitor. When an isolate running thread |
| // (potential signal target) calls into an entry point which acquires |
| // ProfileManager::monitor_ signal delivery must be blocked. An example is |
| // ProfileManager::ScheduleIsolate which blocks signal delivery while removing |
| // the scheduling the isolate. |
| // |
| |
| // Notes on stack frame walking: |
| // |
| // The sampling profiler will collect up to Sample::kNumStackFrames stack frames |
| // The stack frame walking code uses the frame pointer to traverse the stack. |
| // If the VM is compiled without frame pointers (which is the default on |
| // recent GCC versions with optimizing enabled) the stack walking code will |
| // fail (sometimes leading to a crash). |
| // |
| |
| DEFINE_FLAG(bool, profile, false, "Enable Sampling Profiler"); |
| DEFINE_FLAG(bool, trace_profiled_isolates, false, "Trace profiled isolates."); |
| |
| bool ProfilerManager::initialized_ = false; |
| bool ProfilerManager::shutdown_ = false; |
| bool ProfilerManager::thread_running_ = false; |
| Monitor* ProfilerManager::monitor_ = NULL; |
| Monitor* ProfilerManager::start_stop_monitor_ = NULL; |
| Isolate** ProfilerManager::isolates_ = NULL; |
| intptr_t ProfilerManager::isolates_capacity_ = 0; |
| intptr_t ProfilerManager::isolates_size_ = 0; |
| |
| |
| void ProfilerManager::InitOnce() { |
| #if defined(USING_SIMULATOR) |
| // Force disable of profiling on simulator. |
| FLAG_profile = false; |
| #endif |
| #if defined(TARGET_OS_WINDOWS) |
| // Force disable of profiling on Windows. |
| FLAG_profile = false; |
| #endif |
| if (!FLAG_profile) { |
| return; |
| } |
| NativeSymbolResolver::InitOnce(); |
| ASSERT(!initialized_); |
| monitor_ = new Monitor(); |
| start_stop_monitor_ = new Monitor(); |
| initialized_ = true; |
| ResizeIsolates(16); |
| if (FLAG_trace_profiled_isolates) { |
| OS::Print("ProfilerManager starting up.\n"); |
| } |
| { |
| ScopedMonitor startup_lock(start_stop_monitor_); |
| Thread::Start(ThreadMain, 0); |
| while (!thread_running_) { |
| // Wait until profiler thread has started up. |
| startup_lock.Wait(); |
| } |
| } |
| if (FLAG_trace_profiled_isolates) { |
| OS::Print("ProfilerManager running.\n"); |
| } |
| } |
| |
| |
| void ProfilerManager::Shutdown() { |
| if (!FLAG_profile) { |
| return; |
| } |
| ASSERT(initialized_); |
| if (FLAG_trace_profiled_isolates) { |
| OS::Print("ProfilerManager shutting down.\n"); |
| } |
| intptr_t size_at_shutdown = 0; |
| { |
| ScopedSignalBlocker ssb; |
| { |
| ScopedMonitor lock(monitor_); |
| shutdown_ = true; |
| size_at_shutdown = isolates_size_; |
| isolates_size_ = 0; |
| free(isolates_); |
| isolates_ = NULL; |
| lock.Notify(); |
| } |
| } |
| NativeSymbolResolver::ShutdownOnce(); |
| { |
| ScopedMonitor shutdown_lock(start_stop_monitor_); |
| while (thread_running_) { |
| // Wait until profiler thread has exited. |
| shutdown_lock.Wait(); |
| } |
| } |
| if (FLAG_trace_profiled_isolates) { |
| OS::Print("ProfilerManager shut down (%" Pd ").\n", size_at_shutdown); |
| } |
| } |
| |
| |
| void ProfilerManager::SetupIsolateForProfiling(Isolate* isolate) { |
| if (!FLAG_profile) { |
| return; |
| } |
| ASSERT(isolate != NULL); |
| { |
| ScopedSignalBlocker ssb; |
| { |
| ScopedMutex profiler_data_lock(isolate->profiler_data_mutex()); |
| SampleBuffer* sample_buffer = new SampleBuffer(); |
| ASSERT(sample_buffer != NULL); |
| IsolateProfilerData* profiler_data = |
| new IsolateProfilerData(isolate, sample_buffer); |
| ASSERT(profiler_data != NULL); |
| profiler_data->set_sample_interval_micros(1000); |
| isolate->set_profiler_data(profiler_data); |
| if (FLAG_trace_profiled_isolates) { |
| OS::Print("ProfilerManager Setup Isolate %p %s %p\n", |
| isolate, |
| isolate->name(), |
| reinterpret_cast<void*>(Thread::GetCurrentThreadId())); |
| } |
| } |
| } |
| } |
| |
| |
| void ProfilerManager::FreeIsolateProfilingData(Isolate* isolate) { |
| ScopedMutex profiler_data_lock(isolate->profiler_data_mutex()); |
| IsolateProfilerData* profiler_data = isolate->profiler_data(); |
| if (profiler_data == NULL) { |
| // Already freed. |
| return; |
| } |
| isolate->set_profiler_data(NULL); |
| SampleBuffer* sample_buffer = profiler_data->sample_buffer(); |
| ASSERT(sample_buffer != NULL); |
| profiler_data->set_sample_buffer(NULL); |
| delete sample_buffer; |
| delete profiler_data; |
| if (FLAG_trace_profiled_isolates) { |
| OS::Print("ProfilerManager Shutdown Isolate %p %s %p\n", |
| isolate, |
| isolate->name(), |
| reinterpret_cast<void*>(Thread::GetCurrentThreadId())); |
| } |
| } |
| |
| |
| void ProfilerManager::ShutdownIsolateForProfiling(Isolate* isolate) { |
| ASSERT(isolate != NULL); |
| if (!FLAG_profile) { |
| return; |
| } |
| { |
| ScopedSignalBlocker ssb; |
| FreeIsolateProfilingData(isolate); |
| } |
| } |
| |
| |
| void ProfilerManager::ScheduleIsolateHelper(Isolate* isolate) { |
| ScopedMonitor lock(monitor_); |
| { |
| if (shutdown_) { |
| // Shutdown. |
| return; |
| } |
| ScopedMutex profiler_data_lock(isolate->profiler_data_mutex()); |
| IsolateProfilerData* profiler_data = isolate->profiler_data(); |
| if (profiler_data == NULL) { |
| return; |
| } |
| profiler_data->Scheduled(OS::GetCurrentTimeMicros(), |
| Thread::GetCurrentThreadId()); |
| } |
| intptr_t i = FindIsolate(isolate); |
| if (i >= 0) { |
| // Already scheduled. |
| return; |
| } |
| AddIsolate(isolate); |
| lock.Notify(); |
| } |
| |
| |
| void ProfilerManager::ScheduleIsolate(Isolate* isolate, bool inside_signal) { |
| if (!FLAG_profile) { |
| return; |
| } |
| ASSERT(initialized_); |
| ASSERT(isolate != NULL); |
| if (!inside_signal) { |
| ScopedSignalBlocker ssb; |
| { |
| ScheduleIsolateHelper(isolate); |
| } |
| } else { |
| // Do not need a signal blocker inside a signal handler. |
| { |
| ScheduleIsolateHelper(isolate); |
| } |
| } |
| } |
| |
| |
| void ProfilerManager::DescheduleIsolate(Isolate* isolate) { |
| if (!FLAG_profile) { |
| return; |
| } |
| ASSERT(initialized_); |
| ASSERT(isolate != NULL); |
| { |
| ScopedSignalBlocker ssb; |
| { |
| ScopedMonitor lock(monitor_); |
| if (shutdown_) { |
| // Shutdown. |
| return; |
| } |
| intptr_t i = FindIsolate(isolate); |
| if (i < 0) { |
| // Not scheduled. |
| return; |
| } |
| { |
| ScopedMutex profiler_data_lock(isolate->profiler_data_mutex()); |
| IsolateProfilerData* profiler_data = isolate->profiler_data(); |
| if (profiler_data != NULL) { |
| profiler_data->Descheduled(); |
| } |
| } |
| RemoveIsolate(i); |
| lock.Notify(); |
| } |
| } |
| } |
| |
| |
| void PrintToJSONStream(Isolate* isolate, JSONStream* stream) { |
| ASSERT(isolate == Isolate::Current()); |
| { |
| // We can't get signals here. |
| } |
| UNIMPLEMENTED(); |
| } |
| |
| |
| void ProfilerManager::ResizeIsolates(intptr_t new_capacity) { |
| ASSERT(new_capacity < kMaxProfiledIsolates); |
| ASSERT(new_capacity > isolates_capacity_); |
| Isolate* isolate = NULL; |
| isolates_ = reinterpret_cast<Isolate**>( |
| realloc(isolates_, sizeof(isolate) * new_capacity)); |
| isolates_capacity_ = new_capacity; |
| } |
| |
| |
| void ProfilerManager::AddIsolate(Isolate* isolate) { |
| // Must be called with monitor_ locked. |
| if (isolates_ == NULL) { |
| // We are shutting down. |
| return; |
| } |
| if (isolates_size_ == isolates_capacity_) { |
| ResizeIsolates(isolates_capacity_ == 0 ? 16 : isolates_capacity_ * 2); |
| } |
| isolates_[isolates_size_] = isolate; |
| isolates_size_++; |
| } |
| |
| |
| intptr_t ProfilerManager::FindIsolate(Isolate* isolate) { |
| // Must be called with monitor_ locked. |
| if (isolates_ == NULL) { |
| // We are shutting down. |
| return -1; |
| } |
| for (intptr_t i = 0; i < isolates_size_; i++) { |
| if (isolates_[i] == isolate) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| |
| void ProfilerManager::RemoveIsolate(intptr_t i) { |
| // Must be called with monitor_ locked. |
| if (isolates_ == NULL) { |
| // We are shutting down. |
| return; |
| } |
| ASSERT(i < isolates_size_); |
| intptr_t last = isolates_size_ - 1; |
| if (i != last) { |
| isolates_[i] = isolates_[last]; |
| } |
| // Mark last as NULL. |
| isolates_[last] = NULL; |
| // Pop. |
| isolates_size_--; |
| } |
| |
| |
| static char* FindSymbolName(uintptr_t pc, bool* native_symbol) { |
| // TODO(johnmccutchan): Differentiate between symbols which can't be found |
| // and symbols which were GCed. (Heap::CodeContains). |
| ASSERT(native_symbol != NULL); |
| const char* symbol_name = "Unknown"; |
| *native_symbol = false; |
| const Code& code = Code::Handle(Code::LookupCode(pc)); |
| if (code.IsNull()) { |
| // Possibly a native symbol. |
| char* native_name = NativeSymbolResolver::LookupSymbolName(pc); |
| if (native_name != NULL) { |
| symbol_name = native_name; |
| *native_symbol = true; |
| } |
| } else { |
| const Function& function = Function::Handle(code.function()); |
| if (!function.IsNull()) { |
| const String& name = String::Handle(function.QualifiedUserVisibleName()); |
| if (!name.IsNull()) { |
| symbol_name = name.ToCString(); |
| } |
| } |
| } |
| return const_cast<char*>(symbol_name); |
| } |
| |
| |
| void ProfilerManager::WriteTracing(Isolate* isolate, const char* name, |
| Dart_Port port) { |
| ASSERT(isolate == Isolate::Current()); |
| { |
| ScopedSignalBlocker ssb; |
| { |
| ScopedMutex profiler_data_lock(isolate->profiler_data_mutex()); |
| IsolateProfilerData* profiler_data = isolate->profiler_data(); |
| if (profiler_data == NULL) { |
| return; |
| } |
| SampleBuffer* sample_buffer = profiler_data->sample_buffer(); |
| ASSERT(sample_buffer != NULL); |
| JSONStream stream(10 * MB); |
| intptr_t tid = reinterpret_cast<intptr_t>(sample_buffer); |
| intptr_t pid = 1; |
| { |
| JSONArray events(&stream); |
| { |
| JSONObject thread_name(&events); |
| thread_name.AddProperty("name", "thread_name"); |
| thread_name.AddProperty("ph", "M"); |
| thread_name.AddProperty("tid", tid); |
| thread_name.AddProperty("pid", pid); |
| { |
| JSONObject args(&thread_name, "args"); |
| args.AddProperty("name", name); |
| } |
| } |
| { |
| JSONObject process_name(&events); |
| process_name.AddProperty("name", "process_name"); |
| process_name.AddProperty("ph", "M"); |
| process_name.AddProperty("tid", tid); |
| process_name.AddProperty("pid", pid); |
| { |
| JSONObject args(&process_name, "args"); |
| args.AddProperty("name", "Dart VM"); |
| } |
| } |
| uint64_t last_time = 0; |
| for (Sample* i = sample_buffer->FirstSample(); |
| i != sample_buffer->LastSample(); |
| i = sample_buffer->NextSample(i)) { |
| if (last_time == 0) { |
| last_time = i->timestamp; |
| } |
| intptr_t delta = i->timestamp - last_time; |
| { |
| double percentage = static_cast<double>(i->cpu_usage) / |
| static_cast<double>(delta) * 100.0; |
| if (percentage != percentage) { |
| percentage = 0.0; |
| } |
| percentage = percentage < 0.0 ? 0.0 : percentage; |
| percentage = percentage > 100.0 ? 100.0 : percentage; |
| { |
| JSONObject cpu_usage(&events); |
| cpu_usage.AddProperty("name", "CPU Usage"); |
| cpu_usage.AddProperty("ph", "C"); |
| cpu_usage.AddProperty("tid", tid); |
| cpu_usage.AddProperty("pid", pid); |
| cpu_usage.AddProperty("ts", static_cast<double>(last_time)); |
| { |
| JSONObject args(&cpu_usage, "args"); |
| args.AddProperty("CPU", percentage); |
| } |
| } |
| { |
| JSONObject cpu_usage(&events); |
| cpu_usage.AddProperty("name", "CPU Usage"); |
| cpu_usage.AddProperty("ph", "C"); |
| cpu_usage.AddProperty("tid", tid); |
| cpu_usage.AddProperty("pid", pid); |
| cpu_usage.AddProperty("ts", static_cast<double>(i->timestamp)); |
| { |
| JSONObject args(&cpu_usage, "args"); |
| args.AddProperty("CPU", percentage); |
| } |
| } |
| } |
| for (int j = 0; j < Sample::kNumStackFrames; j++) { |
| if (i->pcs[j] == 0) { |
| continue; |
| } |
| bool native_symbol = false; |
| char* symbol_name = FindSymbolName(i->pcs[j], &native_symbol); |
| { |
| JSONObject begin(&events); |
| begin.AddProperty("ph", "B"); |
| begin.AddProperty("tid", tid); |
| begin.AddProperty("pid", pid); |
| begin.AddProperty("name", symbol_name); |
| begin.AddProperty("ts", static_cast<double>(last_time)); |
| } |
| if (native_symbol) { |
| NativeSymbolResolver::FreeSymbolName(symbol_name); |
| } |
| } |
| for (int j = Sample::kNumStackFrames-1; j >= 0; j--) { |
| if (i->pcs[j] == 0) { |
| continue; |
| } |
| bool native_symbol = false; |
| char* symbol_name = FindSymbolName(i->pcs[j], &native_symbol); |
| { |
| JSONObject end(&events); |
| end.AddProperty("ph", "E"); |
| end.AddProperty("tid", tid); |
| end.AddProperty("pid", pid); |
| end.AddProperty("name", symbol_name); |
| end.AddProperty("ts", static_cast<double>(i->timestamp)); |
| } |
| if (native_symbol) { |
| NativeSymbolResolver::FreeSymbolName(symbol_name); |
| } |
| } |
| last_time = i->timestamp; |
| } |
| } |
| char fname[1024]; |
| #if defined(TARGET_OS_WINDOWS) |
| snprintf(fname, sizeof(fname)-1, "c:\\tmp\\isolate-%d.prof", |
| static_cast<int>(port)); |
| #else |
| snprintf(fname, sizeof(fname)-1, "/tmp/isolate-%d.prof", |
| static_cast<int>(port)); |
| #endif |
| printf("%s\n", fname); |
| FILE* f = fopen(fname, "wb"); |
| ASSERT(f != NULL); |
| fputs(stream.ToCString(), f); |
| fclose(f); |
| } |
| } |
| } |
| |
| |
| IsolateProfilerData::IsolateProfilerData(Isolate* isolate, |
| SampleBuffer* sample_buffer) { |
| isolate_ = isolate; |
| sample_buffer_ = sample_buffer; |
| timer_expiration_micros_ = kNoExpirationTime; |
| last_sampled_micros_ = 0; |
| thread_id_ = 0; |
| } |
| |
| |
| IsolateProfilerData::~IsolateProfilerData() { |
| } |
| |
| |
| void IsolateProfilerData::SampledAt(int64_t current_time) { |
| last_sampled_micros_ = current_time; |
| } |
| |
| |
| void IsolateProfilerData::Scheduled(int64_t current_time, ThreadId thread_id) { |
| timer_expiration_micros_ = current_time + sample_interval_micros_; |
| thread_id_ = thread_id; |
| Thread::GetThreadCpuUsage(thread_id_, &cpu_usage_); |
| } |
| |
| |
| void IsolateProfilerData::Descheduled() { |
| // TODO(johnmccutchan): Track when we ran for a fraction of our sample |
| // interval and incorporate the time difference when scheduling the |
| // isolate again. |
| cpu_usage_ = kDescheduledCpuUsage; |
| timer_expiration_micros_ = kNoExpirationTime; |
| Sample* sample = sample_buffer_->ReserveSample(); |
| ASSERT(sample != NULL); |
| sample->timestamp = OS::GetCurrentTimeMicros(); |
| sample->cpu_usage = 0; |
| sample->vm_tags = Sample::kIdle; |
| } |
| |
| |
| const char* Sample::kLookupSymbol = "Symbol Not Looked Up"; |
| const char* Sample::kNoSymbol = "No Symbol Found"; |
| |
| Sample::Sample() { |
| timestamp = 0; |
| cpu_usage = 0; |
| for (int i = 0; i < kNumStackFrames; i++) { |
| pcs[i] = 0; |
| } |
| vm_tags = kIdle; |
| runtime_tags = 0; |
| } |
| |
| |
| SampleBuffer::SampleBuffer(intptr_t capacity) { |
| start_ = 0; |
| end_ = 0; |
| capacity_ = capacity; |
| samples_ = reinterpret_cast<Sample*>(calloc(capacity, sizeof(Sample))); |
| } |
| |
| |
| SampleBuffer::~SampleBuffer() { |
| if (samples_ != NULL) { |
| free(samples_); |
| samples_ = NULL; |
| start_ = 0; |
| end_ = 0; |
| capacity_ = 0; |
| } |
| } |
| |
| |
| Sample* SampleBuffer::ReserveSample() { |
| ASSERT(samples_ != NULL); |
| intptr_t index = end_; |
| end_ = WrapIncrement(end_); |
| if (end_ == start_) { |
| start_ = WrapIncrement(start_); |
| } |
| ASSERT(index >= 0); |
| ASSERT(index < capacity_); |
| // Reset. |
| samples_[index] = Sample(); |
| return &samples_[index]; |
| } |
| |
| |
| Sample* SampleBuffer::FirstSample() const { |
| return &samples_[start_]; |
| } |
| |
| |
| Sample* SampleBuffer::NextSample(Sample* sample) const { |
| ASSERT(sample >= &samples_[0]); |
| ASSERT(sample < &samples_[capacity_]); |
| intptr_t index = sample - samples_; |
| index = WrapIncrement(index); |
| return &samples_[index]; |
| } |
| |
| |
| Sample* SampleBuffer::LastSample() const { |
| return &samples_[end_]; |
| } |
| |
| |
| intptr_t SampleBuffer::WrapIncrement(intptr_t i) const { |
| return (i + 1) % capacity_; |
| } |
| |
| |
| ProfilerSampleStackWalker::ProfilerSampleStackWalker(Sample* sample, |
| uintptr_t stack_lower, |
| uintptr_t stack_upper, |
| uintptr_t pc, |
| uintptr_t fp, |
| uintptr_t sp) : |
| sample_(sample), |
| stack_lower_(stack_lower), |
| stack_upper_(stack_upper), |
| original_pc_(pc), |
| original_fp_(fp), |
| original_sp_(sp), |
| lower_bound_(stack_lower) { |
| ASSERT(sample_ != NULL); |
| } |
| |
| |
| int ProfilerSampleStackWalker::walk() { |
| uword* pc = reinterpret_cast<uword*>(original_pc_); |
| #if defined(WALK_STACK) |
| uword* fp = reinterpret_cast<uword*>(original_fp_); |
| uword* previous_fp = fp; |
| if (original_sp_ < lower_bound_) { |
| // The stack pointer gives us a better lower bound than |
| // the isolates stack limit. |
| lower_bound_ = original_sp_; |
| } |
| int i = 0; |
| for (; i < Sample::kNumStackFrames; i++) { |
| sample_->pcs[i] = reinterpret_cast<uintptr_t>(pc); |
| if (!ValidFramePointer(fp)) { |
| break; |
| } |
| pc = CallerPC(fp); |
| previous_fp = fp; |
| fp = CallerFP(fp); |
| if ((fp <= previous_fp) || !ValidFramePointer(fp)) { |
| // Frame pointers should only move to higher addresses. |
| break; |
| } |
| // Move the lower bound up. |
| lower_bound_ = reinterpret_cast<uintptr_t>(fp); |
| } |
| return i; |
| #else |
| sample_->pcs[0] = reinterpret_cast<uintptr_t>(pc); |
| return 0; |
| #endif |
| } |
| |
| |
| uword* ProfilerSampleStackWalker::CallerPC(uword* fp) { |
| ASSERT(fp != NULL); |
| return reinterpret_cast<uword*>(*(fp + 1)); |
| } |
| |
| |
| uword* ProfilerSampleStackWalker::CallerFP(uword* fp) { |
| ASSERT(fp != NULL); |
| return reinterpret_cast<uword*>(*fp); |
| } |
| |
| |
| bool ProfilerSampleStackWalker::ValidFramePointer(uword* fp) { |
| if (fp == NULL) { |
| return false; |
| } |
| uintptr_t cursor = reinterpret_cast<uintptr_t>(fp); |
| cursor += sizeof(fp); |
| bool r = cursor >= lower_bound_ && cursor < stack_upper_; |
| return r; |
| } |
| |
| |
| } // namespace dart |