| Index: runtime/vm/profiler.cc
|
| diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..24342d5c0da8980e6e64e63ac318822faeb87172
|
| --- /dev/null
|
| +++ b/runtime/vm/profiler.cc
|
| @@ -0,0 +1,568 @@
|
| +// 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"
|
| +
|
| +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
|
| +// Isolate::SetCurrent which blocks signal delivery while removing the old
|
| +// current isolate from the scheduled list and adding the new current isolate
|
| +// to the scheduled list.
|
| +
|
| +
|
| +DEFINE_FLAG(bool, profile, true, "Enable Sampling Profiler");
|
| +
|
| +bool ProfilerManager::initialized_ = false;
|
| +bool ProfilerManager::shutdown_ = false;
|
| +Monitor* ProfilerManager::monitor_ = NULL;
|
| +Isolate** ProfilerManager::isolates_ = NULL;
|
| +intptr_t ProfilerManager::isolates_capacity_ = 0;
|
| +intptr_t ProfilerManager::isolates_size_ = 0;
|
| +
|
| +
|
| +void ProfilerManager::InitOnce() {
|
| + if (!FLAG_profile) {
|
| + return;
|
| + }
|
| + NativeSymbolResolver::InitOnce();
|
| + ASSERT(!initialized_);
|
| + monitor_ = new Monitor();
|
| + initialized_ = true;
|
| + ResizeIsolates(16);
|
| + Thread::Start(ThreadMain, 0);
|
| +}
|
| +
|
| +
|
| +void ProfilerManager::Shutdown() {
|
| + if (!FLAG_profile) {
|
| + return;
|
| + }
|
| + ScopedMonitor lock(monitor_);
|
| + shutdown_ = true;
|
| + for (intptr_t i = 0; i < isolates_size_; i++) {
|
| + Isolate* isolate = isolates_[i];
|
| + ASSERT(isolate != NULL);
|
| + FreeIsolateProfilingData(isolate);
|
| + }
|
| + isolates_size_ = 0;
|
| + free(isolates_);
|
| + isolates_ = NULL;
|
| + lock.Notify();
|
| + NativeSymbolResolver::ShutdownOnce();
|
| +}
|
| +
|
| +
|
| +void ProfilerManager::SetupIsolateForProfiling(Isolate* isolate) {
|
| + if (!FLAG_profile) {
|
| + return;
|
| + }
|
| + ASSERT(isolate != NULL);
|
| + ScopedMutex profiler_data_lock(isolate->profiler_data_mutex());
|
| + SampleBuffer* sample_buffer = new SampleBuffer();
|
| + IsolateProfilerData* profiler_data =
|
| + new IsolateProfilerData(isolate, sample_buffer);
|
| + profiler_data->set_sample_interval_micros(1000);
|
| + isolate->set_profiler_data(profiler_data);
|
| +}
|
| +
|
| +
|
| +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);
|
| + delete sample_buffer;
|
| + delete profiler_data;
|
| +}
|
| +
|
| +
|
| +void ProfilerManager::ShutdownIsolateForProfiling(Isolate* isolate) {
|
| + ASSERT(isolate != NULL);
|
| + if (!FLAG_profile) {
|
| + return;
|
| + }
|
| + FreeIsolateProfilingData(isolate);
|
| +}
|
| +
|
| +
|
| +void ProfilerManager::ScheduleIsolate(Isolate* isolate) {
|
| + if (!FLAG_profile) {
|
| + return;
|
| + }
|
| + ASSERT(initialized_);
|
| + ASSERT(isolate != NULL);
|
| + ScopedMonitor lock(monitor_);
|
| + 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());
|
| + AddIsolate(isolate);
|
| + lock.Notify();
|
| +}
|
| +
|
| +
|
| +void ProfilerManager::DescheduleIsolate(Isolate* isolate) {
|
| + if (!FLAG_profile) {
|
| + return;
|
| + }
|
| + ASSERT(initialized_);
|
| + ASSERT(isolate != NULL);
|
| + ScopedMonitor lock(monitor_);
|
| + 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();
|
| + ASSERT(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_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.
|
| + 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.
|
| + 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());
|
| + 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::GetThreadCpuUsage(thread_id, &cpu_usage_);
|
| + thread_id_ = thread_id;
|
| +}
|
| +
|
| +
|
| +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;
|
| + thread_id_ = 0;
|
| + 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;
|
| + }
|
| +}
|
| +
|
| +
|
| +Sample* SampleBuffer::ReserveSample() {
|
| + intptr_t index = end_;
|
| + end_ = WrapIncrement(end_);
|
| + if (end_ == start_) {
|
| + start_ = WrapIncrement(start_);
|
| + }
|
| + // 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) {
|
| + ASSERT(sample_ != NULL);
|
| +}
|
| +
|
| +
|
| +int ProfilerSampleStackWalker::walk() {
|
| + uword* pc = reinterpret_cast<uword*>(original_pc_);
|
| + uword* fp = reinterpret_cast<uword*>(original_fp_);
|
| + int i = 0;
|
| + for (; i < Sample::kNumStackFrames; i++) {
|
| + sample_->pcs[i] = reinterpret_cast<uintptr_t>(pc);
|
| + if (!ValidFramePointer(fp)) {
|
| + break;
|
| + }
|
| + pc = CallerPC(fp);
|
| + uword* previous_fp = fp;
|
| + fp = CallerFP(fp);
|
| + if (fp <= previous_fp) {
|
| + // Frame pointers should only move to higher addresses.
|
| + break;
|
| + }
|
| + }
|
| + return i;
|
| +}
|
| +
|
| +
|
| +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 >= stack_lower_ && cursor <= stack_upper_;
|
| + return r;
|
| +}
|
| +
|
| +
|
| +} // namespace dart
|
|
|