blob: 8de6d12d59a7f87dfdecde35bbfd7d5cc1fb6b45 [file] [log] [blame]
// 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