Chromium Code Reviews| Index: runtime/vm/profiler.cc |
| diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc |
| index d2545208bf45d2c164446119a59f562de67be46a..51eb1769f7a4a09cb2474058fa3ef458046e28c9 100644 |
| --- a/runtime/vm/profiler.cc |
| +++ b/runtime/vm/profiler.cc |
| @@ -6,7 +6,9 @@ |
| #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 +220,304 @@ void Profiler::RecordSampleInterruptCallback( |
| } |
| -void Profiler::PrintToJSONStream(Isolate* isolate, JSONStream* stream) { |
| - ASSERT(isolate == Isolate::Current()); |
| - UNIMPLEMENTED(); |
| -} |
| +class SymbolLookupCache { |
| + public: |
| + explicit SymbolLookupCache(const intptr_t cache_size = 32) { |
| + cache_index_ = 0; |
| + cache_size_ = cache_size; |
| + pcs_ = reinterpret_cast<uintptr_t*>( |
| + calloc(cache_size, sizeof(uintptr_t))); // NOLINT |
| + names_ = reinterpret_cast<const char**>( |
| + calloc(cache_size, sizeof(const char*))); // NOLINT |
| + } |
| + |
| + virtual ~SymbolLookupCache() { |
| + for (intptr_t i = 0; i < cache_size_; i++) { |
| + ClearCacheIndex(i); |
| + } |
| + free(pcs_); |
| + free(names_); |
| + } |
| + |
| + const char* Lookup(uintptr_t pc) { |
| + const char* symbol = LookupInCache(pc); |
| + if (symbol != NULL) { |
| + return symbol; |
| + } |
| + symbol = QuerySource(pc); |
| + if (symbol != NULL) { |
| + InsertIntoCache(pc, symbol); |
| + } |
| + return symbol; |
| + } |
| + |
| + virtual const char* QuerySource(uintptr_t pc) const = 0; |
| + protected: |
| + const char* GenerateSymbolName(const char* prefix, uintptr_t pc) const { |
| + // Generate a name by binning into buckets by PC. |
| + const intptr_t kBucketSize = 256; |
| + const intptr_t kBucketMask = ~(kBucketSize - 1); |
| + pc &= kBucketMask; |
| + const intptr_t kBuffSize = 256; |
| + char buff[kBuffSize]; |
| + OS::SNPrint(&buff[0], kBuffSize-1, "%s [%" Px ", %" Px ")", |
| + prefix, pc, pc + kBucketSize); |
| + return strdup(buff); |
| + } |
| -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; |
| + private: |
| + const char* LookupInCache(uintptr_t pc) { |
| + for (intptr_t i = 0; i < cache_size_; i++) { |
| + if (pcs_[i] == pc) { |
| + ASSERT(names_[i] != NULL); |
| + return names_[i]; |
| } |
| } |
| - } else { |
| - // Possibly a native symbol. |
| + return NULL; |
| + } |
| + |
| + intptr_t GetCacheIndex(uintptr_t pc) { |
| + cache_index_ = (cache_index_ + 1) % cache_size_; |
| + return cache_index_; |
| + } |
| + |
| + void InsertIntoCache(uintptr_t pc, const char* name) { |
| + intptr_t index = GetCacheIndex(pc); |
| + ClearCacheIndex(index); |
| + pcs_[index] = pc; |
| + names_[index] = name; |
| + } |
| + |
| + void ClearCacheIndex(intptr_t index) { |
| + free(const_cast<char*>(names_[index])); |
| + names_[index] = NULL; |
| + pcs_[index] = 0; |
| + } |
| + |
| + intptr_t cache_index_; |
| + intptr_t cache_size_; |
| + uintptr_t* pcs_; |
| + const char** names_; |
| +}; |
| + |
| + |
| +class NativeSymbolLookup : public SymbolLookupCache { |
| + public: |
| + NativeSymbolLookup() : SymbolLookupCache(32) { |
| + } |
| + |
| + const char* QuerySource(uintptr_t pc) const { |
| char* native_name = NativeSymbolResolver::LookupSymbolName(pc); |
| - if (native_name != NULL) { |
| - symbol_name = native_name; |
| - *symbol_name_allocated = true; |
| - return symbol_name; |
| + if (native_name == NULL) { |
| + return GenerateSymbolName("Unknown", pc); |
| } |
| + return native_name; |
| } |
| - 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; |
| +}; |
| + |
| + |
| +class DartSymbolLookup : public SymbolLookupCache { |
| + public: |
| + explicit DartSymbolLookup(Isolate* isolate) : SymbolLookupCache(32), |
| + isolate_(isolate), |
| + heap_(isolate_->heap()) { |
| + } |
| + |
| + bool InDartCodeSpace(uintptr_t pc) { |
| + ASSERT(heap_ != NULL); |
| + return heap_->CodeContains(pc); |
| + } |
| + |
| + const char* QuerySource(uintptr_t pc) const { |
| + const Code& code = Code::Handle(isolate_, Code::LookupCode(pc)); |
| + if (!code.IsNull()) { |
| + const Function& function = Function::Handle(isolate_, code.function()); |
| + if (!function.IsNull()) { |
| + const String& name = |
| + String::Handle(isolate_, function.QualifiedUserVisibleName()); |
| + if (!name.IsNull()) { |
| + return strdup(name.ToCString()); |
| + } |
| + } else { |
| + return GenerateSymbolName("Detached", pc); |
| + } |
| + } |
| + return GenerateSymbolName("Collected", pc); |
| + } |
| + |
| + private: |
| + Isolate* isolate_; |
| + Heap* heap_; |
| +}; |
| + |
| + |
| +class DartCodeTable { |
| + public: |
| + explicit DartCodeTable(Isolate* isolate) : |
| + heap_(isolate->heap()), |
| + code_table_(GrowableObjectArray::Handle(isolate)) { |
| + code_table_ ^= GrowableObjectArray::New(); |
| + ASSERT(!code_table_.IsNull()); |
| + } |
| + |
| + ~DartCodeTable() { |
| + } |
| + |
| + bool Contains(const Code& code) { |
| + intptr_t length = code_table_.Length(); |
| + Code& table_code = Code::Handle(); |
| + for (intptr_t i = 0; i < length; i++) { |
| + table_code ^= code_table_.At(i); |
| + if (table_code.IsNull()) { |
| + return false; |
| + } |
| + ASSERT(!table_code.IsNull()); |
| + if (table_code.raw() == code.raw()) { |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + void Add(const Code& code) { |
| + ASSERT(!Contains(code)); |
| + code_table_.Add(code); |
| + } |
| + |
| + private: |
| + Heap* heap_; |
| + GrowableObjectArray& code_table_; |
| +}; |
| + |
| + |
| +class ProfilerSymbolHelper { |
| + public: |
| + explicit ProfilerSymbolHelper(Isolate* isolate) : |
| + dart_symbol_(isolate), |
| + code_table_(isolate) { |
| + } |
| + |
| + ~ProfilerSymbolHelper() { |
| + } |
| + |
| + |
| + const char* GetSymbol(uintptr_t pc) { |
| + if (pc == 0) { |
| + return "<no frame>"; |
| + } |
| + if (dart_symbol_.InDartCodeSpace(pc)) { |
|
Ivan Posva
2013/12/30 23:06:09
Restructuring this as discussed will avoid iterati
|
| + return dart_symbol_.Lookup(pc); |
| + } else { |
| + return native_symbol_.Lookup(pc); |
| + } |
| + } |
| + |
| + |
| + RawCode* GetNewCode(uintptr_t pc) { |
| + Code& new_code = Code::Handle(); |
| + new_code ^= Code::LookupCode(pc); |
|
Ivan Posva
2013/12/30 23:06:09
Here you are walking through the heap again. Looki
|
| + if (!new_code.IsNull()) { |
| + Function& function = Function::Handle(new_code.function()); |
| + if (function.IsNull()) { |
| + // If code no longer has a function, ignore it. |
| + new_code ^= Code::null(); |
| + } else if (code_table_.Contains(new_code)) { |
| + // If code has been serialized, ignore it. |
| + new_code ^= Code::null(); |
| + } else { |
| + // Add code to code table. |
| + code_table_.Add(new_code); |
| + } |
| + } |
| + return new_code.raw(); |
| + } |
| + |
| + private: |
| + DartSymbolLookup dart_symbol_; |
| + DartCodeTable code_table_; |
| + NativeSymbolLookup native_symbol_; |
| +}; |
| + |
| + |
| +void Profiler::PrintToJSONStream(Isolate* isolate, JSONStream* stream) { |
| + 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) { |
| + return; |
| + } |
| + SampleBuffer* sample_buffer = profiler_data->sample_buffer(); |
| + ASSERT(sample_buffer != NULL); |
| + ProfilerSymbolHelper symbol_helper(isolate); |
| { |
| - const intptr_t kBuffSize = 256; |
| - char buff[kBuffSize]; |
| - OS::SNPrint(&buff[0], kBuffSize-1, "Unknown [%" Px ", %" Px ")", |
| - pc, pc + kBucketSize); |
| - symbol_name = strdup(buff); |
| - *symbol_name_allocated = true; |
| + JSONArray events(stream); |
| + 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; |
| + } |
| + WriteSample(isolate, &symbol_helper, &sample, events); |
| + } |
| } |
| - return symbol_name; |
| + // Enable profile interrupts. |
| + BeginExecution(isolate); |
| } |
| -void Profiler::WriteTracingSample(Isolate* isolate, intptr_t pid, |
| - Sample* sample, JSONArray& events) { |
| +void Profiler::WriteSample(Isolate* isolate, |
| + ProfilerSymbolHelper* symbol_helper, |
| + Sample* sample, JSONArray& events) { |
| + StackZone zone(isolate); |
| + Code& new_code = Code::Handle(isolate); |
| Sample::SampleType type = sample->type; |
| - intptr_t tid = Thread::ThreadIdToIntPtr(sample->tid); |
| + if (type != Sample::kIsolateSample) { |
| + return; |
| + } |
| 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); |
| + bool any = false; |
| + for (int i = Sample::kNumStackFrames - 1; i >= 0; i--) { |
| + if (sample->pcs[i] == 0) { |
| + continue; |
| } |
| - 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); |
| - } |
| - break; |
| - case Sample::kIsolateSample: |
| - // Write "B" events. |
| + any = true; |
| + } |
| + if (!any) { |
| + // No frames in this sample. |
| + return; |
| + } |
| + { |
| + JSONObject sample_event(&events); |
| + sample_event.AddProperty("ts", timestamp); |
| + { |
| + JSONArray frames(&sample_event, "f"); |
| 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 (sample->pcs[i] == 0) { |
| + continue; |
| } |
| - if (symbol_name_allocated) { |
| - free(const_cast<char*>(symbol_name)); |
| + const char* symbol_name = symbol_helper->GetSymbol(sample->pcs[i]); |
| + new_code ^= symbol_helper->GetNewCode(sample->pcs[i]); |
| + if (!new_code.IsNull()) { |
| + // Dump code. |
| + frames.AddValue(new_code, false); |
| } |
| - } |
| - // 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)); |
| + JSONObject tick(&frames); |
| + tick.AddProperty("s", symbol_name); |
| + tick.AddPropertyF("pc", "%" Px "", sample->pcs[i]); |
| } |
| } |
| - break; |
| - default: |
| - UNIMPLEMENTED(); |
| + } |
| } |
| } |
| -void Profiler::WriteTracing(Isolate* isolate) { |
| +void Profiler::WriteSamples(Isolate* isolate) { |
| if (isolate == NULL) { |
| return; |
| } |
| @@ -354,41 +536,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); |
| 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 +555,6 @@ void Profiler::WriteTracing(Isolate* isolate) { |
| ASSERT(buffer != NULL); |
| file_write(buffer->buf(), buffer->length(), f); |
| file_close(f); |
| - BeginExecution(isolate); |
| } |