Index: runtime/vm/profiler.cc |
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc |
index d2545208bf45d2c164446119a59f562de67be46a..328c78f562ecb9fc951ccaefbc6e5af7e5534b64 100644 |
--- a/runtime/vm/profiler.cc |
+++ b/runtime/vm/profiler.cc |
@@ -2,11 +2,11 @@ |
// 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/allocation.h" |
#include "vm/atomic.h" |
+#include "vm/code_patcher.h" |
#include "vm/isolate.h" |
#include "vm/json_stream.h" |
#include "vm/native_symbol.h" |
@@ -218,124 +218,395 @@ void Profiler::RecordSampleInterruptCallback( |
} |
-void Profiler::PrintToJSONStream(Isolate* isolate, JSONStream* stream) { |
- ASSERT(isolate == Isolate::Current()); |
- UNIMPLEMENTED(); |
-} |
+struct AddressEntry { |
+ uintptr_t pc; |
+ uintptr_t ticks; |
+}; |
+ |
+ |
+// A region of code. Each region is a kind of code (Dart, Collected, or Native). |
+class CodeRegion : public ZoneAllocated { |
+ public: |
+ enum Kind { |
+ kDartCode, |
+ kCollectedCode, |
+ kNativeCode |
+ }; |
+ |
+ CodeRegion(Kind kind, uintptr_t start, uintptr_t end) : |
+ kind_(kind), |
+ start_(start), |
+ end_(end), |
+ inclusive_ticks_(0), |
+ exclusive_ticks_(0), |
+ name_(NULL), |
+ address_table_(new ZoneGrowableArray<AddressEntry>()) { |
+ } |
+ |
+ ~CodeRegion() { |
+ } |
+ |
+ uintptr_t start() const { return start_; } |
+ void set_start(uintptr_t start) { |
+ start_ = start; |
+ } |
+ |
+ uintptr_t end() const { return end_; } |
+ void set_end(uintptr_t end) { |
+ end_ = end; |
+ } |
+ void AdjustExtent(uintptr_t start, uintptr_t end) { |
+ if (start < start_) { |
+ start_ = start; |
+ } |
+ if (end > end_) { |
+ end_ = end; |
+ } |
+ } |
-static const char* FindSymbolName(uintptr_t pc, bool* symbol_name_allocated) { |
- // TODO(johnmccutchan): Differentiate between symbols which can't be found |
- // and symbols which were GCed. (Heap::CodeContains). |
- ASSERT(symbol_name_allocated != NULL); |
- const char* symbol_name = "Unknown"; |
- *symbol_name_allocated = false; |
- if (pc == 0) { |
- return const_cast<char*>(Sample::kNoFrame); |
- } |
- const Code& code = Code::Handle(Code::LookupCode(pc)); |
- if (!code.IsNull()) { |
- 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 symbol_name; |
+ bool contains(uintptr_t pc) const { |
+ return (pc >= start_) && (pc < end_); |
+ } |
+ |
+ intptr_t inclusive_ticks() const { return inclusive_ticks_; } |
+ void set_inclusive_ticks(intptr_t inclusive_ticks) { |
+ inclusive_ticks_ = inclusive_ticks; |
+ } |
+ |
+ intptr_t exclusive_ticks() const { return exclusive_ticks_; } |
+ void set_exclusive_ticks(intptr_t exclusive_ticks) { |
+ exclusive_ticks_ = exclusive_ticks; |
+ } |
+ |
+ const char* name() const { return name_; } |
+ void SetName(const char* name) { |
+ if (name == NULL) { |
+ name_ = NULL; |
+ } |
+ intptr_t len = strlen(name); |
+ name_ = Isolate::Current()->current_zone()->Alloc<const char>(len + 1); |
+ strncpy(const_cast<char*>(name_), name, len); |
+ const_cast<char*>(name_)[len] = '\0'; |
+ } |
+ |
+ Kind kind() const { return kind_; } |
+ |
+ static const char* KindToCString(Kind kind) { |
+ switch (kind) { |
+ case kDartCode: |
+ return "Dart"; |
+ case kCollectedCode: |
+ return "Collected"; |
+ case kNativeCode: |
+ return "Native"; |
+ } |
+ UNREACHABLE(); |
+ return NULL; |
+ } |
+ |
+ void AddTick(bool exclusive) { |
+ if (exclusive) { |
+ exclusive_ticks_++; |
+ } else { |
+ inclusive_ticks_++; |
+ } |
+ } |
+ |
+ void AddTickAtAddress(uintptr_t pc) { |
+ const intptr_t length = address_table_->length(); |
+ intptr_t i = 0; |
+ for (; i < length; i++) { |
+ AddressEntry& entry = (*address_table_)[i]; |
+ if (entry.pc == pc) { |
+ entry.ticks++; |
+ return; |
+ } |
+ if (entry.pc > pc) { |
+ break; |
} |
} |
- } else { |
- // Possibly a native symbol. |
- char* native_name = NativeSymbolResolver::LookupSymbolName(pc); |
- if (native_name != NULL) { |
- symbol_name = native_name; |
- *symbol_name_allocated = true; |
- return symbol_name; |
+ AddressEntry entry; |
+ entry.pc = pc; |
+ entry.ticks = 1; |
+ if (i < length) { |
+ // Insert at i. |
+ address_table_->InsertAt(i, entry); |
+ } else { |
+ // Add to end. |
+ address_table_->Add(entry); |
} |
} |
- const intptr_t kBucketSize = 256; |
- const intptr_t kBucketMask = ~(kBucketSize - 1); |
- // Not a Dart symbol or a native symbol. Bin into buckets by PC. |
- pc &= kBucketMask; |
- { |
- const intptr_t kBuffSize = 256; |
+ |
+ |
+ void PrintToJSONArray(JSONArray* events, bool full) { |
+ JSONObject obj(events); |
+ obj.AddProperty("type", "ProfileCode"); |
+ obj.AddProperty("kind", KindToCString(kind())); |
+ obj.AddPropertyF("inclusive_ticks", "%" Pd "", inclusive_ticks()); |
+ obj.AddPropertyF("exclusive_ticks", "%" Pd "", exclusive_ticks()); |
+ if (kind() == kDartCode) { |
+ // Look up code in Dart heap. |
+ Code& code = Code::Handle(Code::LookupCode(start())); |
+ Function& func = Function::Handle(); |
+ ASSERT(!code.IsNull()); |
+ func ^= code.function(); |
+ if (func.IsNull()) { |
+ if (name() == NULL) { |
+ GenerateAndSetSymbolName("Stub"); |
+ } |
+ obj.AddPropertyF("start", "%" Px "", start()); |
+ obj.AddPropertyF("end", "%" Px "", end()); |
+ obj.AddProperty("name", name()); |
+ } else { |
+ obj.AddProperty("code", code, !full); |
+ } |
+ } else if (kind() == kCollectedCode) { |
+ if (name() == NULL) { |
+ GenerateAndSetSymbolName("Collected"); |
+ } |
+ obj.AddPropertyF("start", "%" Px "", start()); |
+ obj.AddPropertyF("end", "%" Px "", end()); |
+ obj.AddProperty("name", name()); |
+ } else { |
+ ASSERT(kind() == kNativeCode); |
+ if (name() == NULL) { |
+ GenerateAndSetSymbolName("Native"); |
+ } |
+ obj.AddPropertyF("start", "%" Px "", start()); |
+ obj.AddPropertyF("end", "%" Px "", end()); |
+ obj.AddProperty("name", name()); |
+ } |
+ { |
+ JSONArray ticks(&obj, "ticks"); |
+ for (intptr_t i = 0; i < address_table_->length(); i++) { |
+ const AddressEntry& entry = (*address_table_)[i]; |
+ ticks.AddValueF("%" Px "", entry.pc); |
+ ticks.AddValueF("%" Pd "", entry.ticks); |
+ } |
+ } |
+ } |
+ |
+ private: |
+ void GenerateAndSetSymbolName(const char* prefix) { |
+ const intptr_t kBuffSize = 512; |
char buff[kBuffSize]; |
- OS::SNPrint(&buff[0], kBuffSize-1, "Unknown [%" Px ", %" Px ")", |
- pc, pc + kBucketSize); |
- symbol_name = strdup(buff); |
- *symbol_name_allocated = true; |
+ OS::SNPrint(&buff[0], kBuffSize-1, "%s [%" Px ", %" Px ")", |
+ prefix, start(), end()); |
+ SetName(buff); |
} |
- return symbol_name; |
-} |
+ Kind kind_; |
+ uintptr_t start_; |
+ uintptr_t end_; |
+ intptr_t inclusive_ticks_; |
+ intptr_t exclusive_ticks_; |
+ const char* name_; |
+ ZoneGrowableArray<AddressEntry>* address_table_; |
-void Profiler::WriteTracingSample(Isolate* isolate, intptr_t pid, |
- Sample* sample, JSONArray& events) { |
- Sample::SampleType type = sample->type; |
- intptr_t tid = Thread::ThreadIdToIntPtr(sample->tid); |
- double timestamp = static_cast<double>(sample->timestamp); |
- const char* isolate_name = isolate->name(); |
- switch (type) { |
- case Sample::kIsolateStart: { |
- JSONObject begin(&events); |
- begin.AddProperty("ph", "B"); |
- begin.AddProperty("tid", tid); |
- begin.AddProperty("pid", pid); |
- begin.AddProperty("name", isolate_name); |
- begin.AddProperty("ts", timestamp); |
+ DISALLOW_COPY_AND_ASSIGN(CodeRegion); |
+}; |
+ |
+ |
+// All code regions. Code region tables are built on demand when a profile |
+// is requested (through the service or on isolate shutdown). |
+class ProfilerCodeRegionTable : public ValueObject { |
+ public: |
+ explicit ProfilerCodeRegionTable(Isolate* isolate) : |
+ heap_(isolate->heap()), |
+ code_region_table_(new ZoneGrowableArray<CodeRegion*>(64)) { |
+ } |
+ |
+ ~ProfilerCodeRegionTable() { |
+ } |
+ |
+ void AddTick(uintptr_t pc, bool exclusive, bool tick_address) { |
+ intptr_t index = FindIndex(pc); |
+ if (index < 0) { |
+ CodeRegion* code_region = CreateCodeRegion(pc); |
+ ASSERT(code_region != NULL); |
+ index = InsertCodeRegion(code_region); |
} |
- break; |
- case Sample::kIsolateStop: { |
- JSONObject begin(&events); |
- begin.AddProperty("ph", "E"); |
- begin.AddProperty("tid", tid); |
- begin.AddProperty("pid", pid); |
- begin.AddProperty("name", isolate_name); |
- begin.AddProperty("ts", timestamp); |
+ ASSERT(index >= 0); |
+ ASSERT(index < code_region_table_->length()); |
+ (*code_region_table_)[index]->AddTick(exclusive); |
+ if (tick_address) { |
+ (*code_region_table_)[index]->AddTickAtAddress(pc); |
} |
- break; |
- case Sample::kIsolateSample: |
- // Write "B" events. |
- for (int i = Sample::kNumStackFrames - 1; i >= 0; i--) { |
- bool symbol_name_allocated = false; |
- const char* symbol_name = FindSymbolName(sample->pcs[i], |
- &symbol_name_allocated); |
- { |
- JSONObject begin(&events); |
- begin.AddProperty("ph", "B"); |
- begin.AddProperty("tid", tid); |
- begin.AddProperty("pid", pid); |
- begin.AddProperty("name", symbol_name); |
- begin.AddProperty("ts", timestamp); |
- } |
- if (symbol_name_allocated) { |
- free(const_cast<char*>(symbol_name)); |
- } |
+ } |
+ |
+ intptr_t Length() const { return code_region_table_->length(); } |
+ |
+ CodeRegion* At(intptr_t idx) { |
+ return (*code_region_table_)[idx]; |
+ } |
+ |
+ private: |
+ intptr_t FindIndex(uintptr_t pc) { |
+ const intptr_t length = code_region_table_->length(); |
+ for (intptr_t i = 0; i < length; i++) { |
+ const CodeRegion* code_region = (*code_region_table_)[i]; |
+ if (code_region->contains(pc)) { |
+ return i; |
} |
- // Write "E" events. |
- for (int i = 0; i < Sample::kNumStackFrames; i++) { |
- bool symbol_name_allocated = false; |
- const char* symbol_name = FindSymbolName(sample->pcs[i], |
- &symbol_name_allocated); |
- { |
- JSONObject begin(&events); |
- begin.AddProperty("ph", "E"); |
- begin.AddProperty("tid", tid); |
- begin.AddProperty("pid", pid); |
- begin.AddProperty("name", symbol_name); |
- begin.AddProperty("ts", timestamp); |
- } |
- if (symbol_name_allocated) { |
- free(const_cast<char*>(symbol_name)); |
+ } |
+ return -1; |
+ } |
+ |
+ CodeRegion* CreateCodeRegion(uintptr_t pc) { |
+ Code& code = Code::Handle(Code::LookupCode(pc)); |
+ if (!code.IsNull()) { |
+ return new CodeRegion(CodeRegion::kDartCode, code.EntryPoint(), |
+ code.EntryPoint() + code.Size()); |
+ } |
+ if (heap_->CodeContains(pc)) { |
+ const intptr_t kDartCodeAlignment = 0x10; |
+ const intptr_t kDartCodeAlignmentMask = ~(kDartCodeAlignment - 1); |
+ return new CodeRegion(CodeRegion::kCollectedCode, |
+ (pc & kDartCodeAlignmentMask), |
+ (pc & kDartCodeAlignmentMask) + kDartCodeAlignment); |
+ } |
+ uintptr_t native_start = 0; |
+ char* native_name = NativeSymbolResolver::LookupSymbolName(pc, |
+ &native_start); |
+ if (native_name == NULL) { |
+ return new CodeRegion(CodeRegion::kNativeCode, pc, pc + 1); |
+ } |
+ ASSERT(pc >= native_start); |
+ CodeRegion* code_region = |
+ new CodeRegion(CodeRegion::kNativeCode, native_start, pc + 1); |
+ code_region->SetName(native_name); |
+ free(native_name); |
+ return code_region; |
+ } |
+ |
+ intptr_t InsertCodeRegion(CodeRegion* code_region) { |
+ const intptr_t length = code_region_table_->length(); |
+ const uintptr_t start = code_region->start(); |
+ const uintptr_t end = code_region->end(); |
+ intptr_t i = 0; |
+ for (; i < length; i++) { |
+ CodeRegion* region = (*code_region_table_)[i]; |
+ if (region->contains(start) || region->contains(end - 1)) { |
+ // We should only see overlapping native code regions. |
+ ASSERT(region->kind() == CodeRegion::kNativeCode); |
+ // When code regions overlap, they should be of the same kind. |
+ ASSERT(region->kind() == code_region->kind()); |
+ // Overlapping code region. |
+ region->AdjustExtent(start, end); |
+ return i; |
+ } else if (start >= region->end()) { |
+ // Insert here. |
+ break; |
+ } |
+ } |
+ if (i != length) { |
+ code_region_table_->InsertAt(i, code_region); |
+ return i; |
+ } |
+ code_region_table_->Add(code_region); |
+ return code_region_table_->length() - 1; |
+ } |
+ |
+ Heap* heap_; |
+ ZoneGrowableArray<CodeRegion*>* code_region_table_; |
+}; |
+ |
+ |
+void Profiler::PrintToJSONStream(Isolate* isolate, JSONStream* stream, |
+ bool full) { |
+ ASSERT(isolate == Isolate::Current()); |
+ // Disable profile interrupts while processing the buffer. |
+ EndExecution(isolate); |
+ MutexLocker profiler_data_lock(isolate->profiler_data_mutex()); |
+ IsolateProfilerData* profiler_data = isolate->profiler_data(); |
+ if (profiler_data == NULL) { |
+ JSONObject error(stream); |
+ error.AddProperty("type", "Error"); |
+ error.AddProperty("text", "Isolate does not have profiling enabled."); |
+ return; |
+ } |
+ SampleBuffer* sample_buffer = profiler_data->sample_buffer(); |
+ ASSERT(sample_buffer != NULL); |
+ { |
+ StackZone zone(isolate); |
+ { |
+ // Build code region table. |
+ ProfilerCodeRegionTable code_region_table(isolate); |
+ intptr_t samples = |
+ ProcessSamples(isolate, &code_region_table, sample_buffer); |
+ { |
+ // Serialize to JSON. |
+ JSONObject obj(stream); |
+ obj.AddProperty("type", "Profile"); |
+ obj.AddProperty("samples", samples); |
+ JSONArray codes(&obj, "codes"); |
+ for (intptr_t i = 0; i < code_region_table.Length(); i++) { |
+ CodeRegion* region = code_region_table.At(i); |
+ ASSERT(region != NULL); |
+ region->PrintToJSONArray(&codes, full); |
} |
} |
- break; |
- default: |
- UNIMPLEMENTED(); |
+ } |
+ } |
+ // Enable profile interrupts. |
+ BeginExecution(isolate); |
+} |
+ |
+ |
+intptr_t Profiler::ProcessSamples(Isolate* isolate, |
+ ProfilerCodeRegionTable* code_region_table, |
+ SampleBuffer* sample_buffer) { |
+ int64_t start = OS::GetCurrentTimeMillis(); |
+ intptr_t samples = 0; |
+ for (intptr_t i = 0; i < sample_buffer->capacity(); i++) { |
+ Sample sample = sample_buffer->GetSample(i); |
+ if (sample.isolate != isolate) { |
+ continue; |
+ } |
+ if (sample.timestamp == 0) { |
+ continue; |
+ } |
+ samples += ProcessSample(isolate, code_region_table, &sample); |
+ } |
+ int64_t end = OS::GetCurrentTimeMillis(); |
+ if (FLAG_trace_profiled_isolates) { |
+ int64_t delta = end - start; |
+ OS::Print("Processed %" Pd " samples from %s in %" Pd64 " milliseconds.\n", |
+ samples, |
+ isolate->name(), |
+ delta); |
} |
+ return samples; |
} |
-void Profiler::WriteTracing(Isolate* isolate) { |
+intptr_t Profiler::ProcessSample(Isolate* isolate, |
+ ProfilerCodeRegionTable* code_region_table, |
+ Sample* sample) { |
+ Sample::SampleType type = sample->type; |
+ if (type != Sample::kIsolateSample) { |
+ return 0; |
+ } |
+ if (sample->pcs[0] == 0) { |
+ // No frames in this sample. |
+ return 0; |
+ } |
+ intptr_t i = 0; |
+ // i points to the leaf (exclusive) PC sample. Do not tick the address. |
+ code_region_table->AddTick(sample->pcs[i], true, false); |
+ // Give all frames an inclusive tick and tick the address. |
+ for (; i < Sample::kNumStackFrames; i++) { |
+ if (sample->pcs[i] == 0) { |
+ break; |
+ } |
+ code_region_table->AddTick(sample->pcs[i], false, true); |
+ } |
+ return 1; |
+} |
+ |
+ |
+void Profiler::WriteProfile(Isolate* isolate) { |
if (isolate == NULL) { |
return; |
} |
@@ -354,41 +625,10 @@ void Profiler::WriteTracing(Isolate* isolate) { |
return; |
} |
// We will be looking up code objects within the isolate. |
- ASSERT(Isolate::Current() != NULL); |
- // We do not want to be interrupted while processing the buffer. |
- EndExecution(isolate); |
- MutexLocker 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); |
+ ASSERT(Isolate::Current() == isolate); |
JSONStream stream(10 * MB); |
intptr_t pid = OS::ProcessId(); |
- { |
- JSONArray events(&stream); |
- { |
- JSONObject process_name(&events); |
- process_name.AddProperty("name", "process_name"); |
- process_name.AddProperty("ph", "M"); |
- process_name.AddProperty("pid", pid); |
- { |
- JSONObject args(&process_name, "args"); |
- args.AddProperty("name", "Dart VM"); |
- } |
- } |
- for (intptr_t i = 0; i < sample_buffer->capacity(); i++) { |
- Sample* sample = sample_buffer->GetSample(i); |
- if (sample->isolate != isolate) { |
- continue; |
- } |
- if (sample->timestamp == 0) { |
- continue; |
- } |
- WriteTracingSample(isolate, pid, sample, events); |
- } |
- } |
+ PrintToJSONStream(isolate, &stream, true); |
const char* format = "%s/dart-profile-%" Pd "-%" Pd ".json"; |
intptr_t len = OS::SNPrint(NULL, 0, format, |
FLAG_profile_dir, pid, isolate->main_port()); |
@@ -404,7 +644,6 @@ void Profiler::WriteTracing(Isolate* isolate) { |
ASSERT(buffer != NULL); |
file_write(buffer->buf(), buffer->length(), f); |
file_close(f); |
- BeginExecution(isolate); |
} |
@@ -424,10 +663,6 @@ IsolateProfilerData::~IsolateProfilerData() { |
} |
-const char* Sample::kLookupSymbol = "Symbol Not Looked Up"; |
-const char* Sample::kNoSymbol = "No Symbol Found"; |
-const char* Sample::kNoFrame = "<no frame>"; |
- |
void Sample::Init(SampleType type, Isolate* isolate, int64_t timestamp, |
ThreadId tid) { |
this->timestamp = timestamp; |