// Copyright (c) 2015, 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 "vm/profiler_service.h" #include "vm/growable_array.h" #include "vm/hash_map.h" #include "vm/log.h" #include "vm/malloc_hooks.h" #include "vm/native_symbol.h" #include "vm/object.h" #include "vm/os.h" #include "vm/profiler.h" #include "vm/reusable_handles.h" #include "vm/scope_timer.h" namespace dart { DECLARE_FLAG(int, max_profile_depth); DECLARE_FLAG(int, profile_period); DECLARE_FLAG(bool, show_invisible_frames); DECLARE_FLAG(bool, profile_vm); #ifndef PRODUCT class DeoptimizedCodeSet : public ZoneAllocated { public: explicit DeoptimizedCodeSet(Isolate* isolate) : previous_( GrowableObjectArray::ZoneHandle(isolate->deoptimized_code_array())), current_(GrowableObjectArray::ZoneHandle( previous_.IsNull() ? GrowableObjectArray::null() : GrowableObjectArray::New())) {} void Add(const Code& code) { if (current_.IsNull()) { return; } if (!Contained(code, previous_) || Contained(code, current_)) { return; } current_.Add(code); } void UpdateIsolate(Isolate* isolate) { intptr_t size_before = SizeOf(previous_); intptr_t size_after = SizeOf(current_); if ((size_before > 0) && FLAG_trace_profiler) { intptr_t length_before = previous_.Length(); intptr_t length_after = current_.Length(); OS::Print( "Updating isolate deoptimized code array: " "%" Pd " -> %" Pd " [%" Pd " -> %" Pd "]\n", size_before, size_after, length_before, length_after); } isolate->set_deoptimized_code_array(current_); } private: bool Contained(const Code& code, const GrowableObjectArray& array) { if (array.IsNull() || code.IsNull()) { return false; } NoSafepointScope no_safepoint_scope; for (intptr_t i = 0; i < array.Length(); i++) { if (code.raw() == array.At(i)) { return true; } } return false; } intptr_t SizeOf(const GrowableObjectArray& array) { if (array.IsNull()) { return 0; } Code& code = Code::ZoneHandle(); intptr_t size = 0; for (intptr_t i = 0; i < array.Length(); i++) { code ^= array.At(i); ASSERT(!code.IsNull()); size += code.Size(); } return size; } // Array holding code that is being kept around only for the profiler. const GrowableObjectArray& previous_; // Array holding code that should continue to be kept around for the profiler. const GrowableObjectArray& current_; }; ProfileFunctionSourcePosition::ProfileFunctionSourcePosition( TokenPosition token_pos) : token_pos_(token_pos), exclusive_ticks_(0), inclusive_ticks_(0) {} void ProfileFunctionSourcePosition::Tick(bool exclusive) { if (exclusive) { exclusive_ticks_++; } else { inclusive_ticks_++; } } ProfileFunction::ProfileFunction(Kind kind, const char* name, const Function& function, const intptr_t table_index) : kind_(kind), name_(name), function_(Function::ZoneHandle(function.raw())), table_index_(table_index), profile_codes_(0), source_position_ticks_(0), exclusive_ticks_(0), inclusive_ticks_(0), inclusive_serial_(-1) { ASSERT((kind_ != kDartFunction) || !function_.IsNull()); ASSERT((kind_ != kDartFunction) || (table_index_ >= 0)); ASSERT(profile_codes_.length() == 0); } const char* ProfileFunction::Name() const { if (name_ != NULL) { return name_; } ASSERT(!function_.IsNull()); const String& func_name = String::Handle(function_.QualifiedUserVisibleName()); return func_name.ToCString(); } bool ProfileFunction::is_visible() const { if (function_.IsNull()) { // Some synthetic function. return true; } return FLAG_show_invisible_frames || function_.is_visible(); } void ProfileFunction::Tick(bool exclusive, intptr_t inclusive_serial, TokenPosition token_position) { if (exclusive) { exclusive_ticks_++; TickSourcePosition(token_position, exclusive); } // Fall through and tick inclusive count too. if (inclusive_serial_ == inclusive_serial) { // Already ticked. return; } inclusive_serial_ = inclusive_serial; inclusive_ticks_++; TickSourcePosition(token_position, false); } void ProfileFunction::TickSourcePosition(TokenPosition token_position, bool exclusive) { intptr_t i = 0; for (; i < source_position_ticks_.length(); i++) { ProfileFunctionSourcePosition& position = source_position_ticks_[i]; if (position.token_pos().value() == token_position.value()) { if (FLAG_trace_profiler_verbose) { OS::Print("Ticking source position %s %s\n", exclusive ? "exclusive" : "inclusive", token_position.ToCString()); } // Found existing position, tick it. position.Tick(exclusive); return; } if (position.token_pos().value() > token_position.value()) { break; } } // Add new one, sorted by token position value. ProfileFunctionSourcePosition pfsp(token_position); if (FLAG_trace_profiler_verbose) { OS::Print("Ticking source position %s %s\n", exclusive ? "exclusive" : "inclusive", token_position.ToCString()); } pfsp.Tick(exclusive); if (i < source_position_ticks_.length()) { source_position_ticks_.InsertAt(i, pfsp); } else { source_position_ticks_.Add(pfsp); } } const char* ProfileFunction::KindToCString(Kind kind) { switch (kind) { case kDartFunction: return "Dart"; case kNativeFunction: return "Native"; case kTagFunction: return "Tag"; case kStubFunction: return "Stub"; case kUnknownFunction: return "Collected"; default: UNIMPLEMENTED(); return ""; } } void ProfileFunction::PrintToJSONObject(JSONObject* func) { func->AddProperty("type", "@Function"); func->AddProperty("name", name()); func->AddProperty("_kind", KindToCString(kind())); } void ProfileFunction::PrintToJSONArray(JSONArray* functions) { JSONObject obj(functions); obj.AddProperty("kind", KindToCString(kind())); obj.AddProperty("inclusiveTicks", inclusive_ticks()); obj.AddProperty("exclusiveTicks", exclusive_ticks()); if (kind() == kDartFunction) { ASSERT(!function_.IsNull()); obj.AddProperty("function", function_); } else { JSONObject func(&obj, "function"); PrintToJSONObject(&func); } { JSONArray codes(&obj, "codes"); for (intptr_t i = 0; i < profile_codes_.length(); i++) { intptr_t code_index = profile_codes_[i]; codes.AddValue(code_index); } } } void ProfileFunction::AddProfileCode(intptr_t code_table_index) { for (intptr_t i = 0; i < profile_codes_.length(); i++) { if (profile_codes_[i] == code_table_index) { return; } } profile_codes_.Add(code_table_index); } bool ProfileFunction::GetSinglePosition(ProfileFunctionSourcePosition* pfsp) { if (pfsp == NULL) { return false; } if (source_position_ticks_.length() != 1) { return false; } *pfsp = source_position_ticks_[0]; return true; } ProfileCodeAddress::ProfileCodeAddress(uword pc) : pc_(pc), exclusive_ticks_(0), inclusive_ticks_(0) {} void ProfileCodeAddress::Tick(bool exclusive) { if (exclusive) { exclusive_ticks_++; } else { inclusive_ticks_++; } } ProfileCode::ProfileCode(Kind kind, uword start, uword end, int64_t timestamp, const Code& code) : kind_(kind), start_(start), end_(end), exclusive_ticks_(0), inclusive_ticks_(0), inclusive_serial_(-1), code_(code), name_(NULL), compile_timestamp_(0), function_(NULL), code_table_index_(-1), address_ticks_(0) { ASSERT(start_ < end_); } void ProfileCode::TruncateLower(uword start) { if (start > start_) { start_ = start; } ASSERT(start_ < end_); } void ProfileCode::TruncateUpper(uword end) { if (end < end_) { end_ = end; } ASSERT(start_ < end_); } void ProfileCode::ExpandLower(uword start) { if (start < start_) { start_ = start; } ASSERT(start_ < end_); } void ProfileCode::ExpandUpper(uword end) { if (end > end_) { end_ = end; } ASSERT(start_ < end_); } bool ProfileCode::Overlaps(const ProfileCode* other) const { ASSERT(other != NULL); return other->Contains(start_) || other->Contains(end_ - 1) || Contains(other->start()) || Contains(other->end() - 1); } bool ProfileCode::IsOptimizedDart() const { return !code_.IsNull() && code_.is_optimized(); } void ProfileCode::SetName(const char* name) { if (name == NULL) { name_ = NULL; } intptr_t len = strlen(name); name_ = Thread::Current()->zone()->Alloc(len + 1); strncpy(name_, name, len); name_[len] = '\0'; } void ProfileCode::GenerateAndSetSymbolName(const char* prefix) { const intptr_t kBuffSize = 512; char buff[kBuffSize]; Utils::SNPrint(&buff[0], kBuffSize - 1, "%s [%" Px ", %" Px ")", prefix, start(), end()); SetName(buff); } void ProfileCode::Tick(uword pc, bool exclusive, intptr_t serial) { // If exclusive is set, tick it. if (exclusive) { exclusive_ticks_++; TickAddress(pc, true); } // Fall through and tick inclusive count too. if (inclusive_serial_ == serial) { // Already gave inclusive tick for this sample. return; } inclusive_serial_ = serial; inclusive_ticks_++; TickAddress(pc, false); } void ProfileCode::TickAddress(uword pc, bool exclusive) { const intptr_t length = address_ticks_.length(); intptr_t i = 0; for (; i < length; i++) { ProfileCodeAddress& entry = address_ticks_[i]; if (entry.pc() == pc) { // Tick the address entry. entry.Tick(exclusive); return; } if (entry.pc() > pc) { break; } } // New address, add entry. ProfileCodeAddress entry(pc); entry.Tick(exclusive); if (i < length) { // Insert at i. address_ticks_.InsertAt(i, entry); } else { // Add to end. address_ticks_.Add(entry); } } void ProfileCode::PrintNativeCode(JSONObject* profile_code_obj) { ASSERT(kind() == kNativeCode); JSONObject obj(profile_code_obj, "code"); obj.AddProperty("type", "@Code"); obj.AddProperty("kind", "Native"); obj.AddProperty("name", name()); obj.AddProperty("_optimized", false); obj.AddPropertyF("start", "%" Px "", start()); obj.AddPropertyF("end", "%" Px "", end()); { // Generate a fake function entry. JSONObject func(&obj, "function"); ASSERT(function_ != NULL); function_->PrintToJSONObject(&func); } } void ProfileCode::PrintCollectedCode(JSONObject* profile_code_obj) { ASSERT(kind() == kCollectedCode); JSONObject obj(profile_code_obj, "code"); obj.AddProperty("type", "@Code"); obj.AddProperty("kind", "Collected"); obj.AddProperty("name", name()); obj.AddProperty("_optimized", false); obj.AddPropertyF("start", "%" Px "", start()); obj.AddPropertyF("end", "%" Px "", end()); { // Generate a fake function entry. JSONObject func(&obj, "function"); ASSERT(function_ != NULL); function_->PrintToJSONObject(&func); } } void ProfileCode::PrintOverwrittenCode(JSONObject* profile_code_obj) { ASSERT(kind() == kReusedCode); JSONObject obj(profile_code_obj, "code"); obj.AddProperty("type", "@Code"); obj.AddProperty("kind", "Collected"); obj.AddProperty("name", name()); obj.AddProperty("_optimized", false); obj.AddPropertyF("start", "%" Px "", start()); obj.AddPropertyF("end", "%" Px "", end()); { // Generate a fake function entry. JSONObject func(&obj, "function"); ASSERT(function_ != NULL); function_->PrintToJSONObject(&func); } } void ProfileCode::PrintTagCode(JSONObject* profile_code_obj) { ASSERT(kind() == kTagCode); JSONObject obj(profile_code_obj, "code"); obj.AddProperty("type", "@Code"); obj.AddProperty("kind", "Tag"); obj.AddProperty("name", name()); obj.AddPropertyF("start", "%" Px "", start()); obj.AddPropertyF("end", "%" Px "", end()); obj.AddProperty("_optimized", false); { // Generate a fake function entry. JSONObject func(&obj, "function"); ASSERT(function_ != NULL); function_->PrintToJSONObject(&func); } } const char* ProfileCode::KindToCString(Kind kind) { switch (kind) { case kDartCode: return "Dart"; case kCollectedCode: return "Collected"; case kNativeCode: return "Native"; case kReusedCode: return "Overwritten"; case kTagCode: return "Tag"; } UNREACHABLE(); return NULL; } void ProfileCode::PrintToJSONArray(JSONArray* codes) { JSONObject obj(codes); obj.AddProperty("kind", ProfileCode::KindToCString(kind())); obj.AddProperty("inclusiveTicks", inclusive_ticks()); obj.AddProperty("exclusiveTicks", exclusive_ticks()); if (kind() == kDartCode) { ASSERT(!code_.IsNull()); obj.AddProperty("code", code_); } else if (kind() == kCollectedCode) { PrintCollectedCode(&obj); } else if (kind() == kReusedCode) { PrintOverwrittenCode(&obj); } else if (kind() == kTagCode) { PrintTagCode(&obj); } else { ASSERT(kind() == kNativeCode); PrintNativeCode(&obj); } { JSONArray ticks(&obj, "ticks"); for (intptr_t i = 0; i < address_ticks_.length(); i++) { const ProfileCodeAddress& entry = address_ticks_[i]; ticks.AddValueF("%" Px "", entry.pc()); ticks.AddValue(entry.exclusive_ticks()); ticks.AddValue(entry.inclusive_ticks()); } } } class ProfileFunctionTable : public ZoneAllocated { public: ProfileFunctionTable() : null_function_(Function::ZoneHandle()), unknown_function_(NULL), table_(8) { unknown_function_ = Add(ProfileFunction::kUnknownFunction, ""); } ProfileFunction* LookupOrAdd(const Function& function) { ASSERT(!function.IsNull()); ProfileFunction* profile_function = Lookup(function); if (profile_function != NULL) { return profile_function; } return Add(function); } ProfileFunction* Lookup(const Function& function) { ASSERT(!function.IsNull()); return function_hash_.LookupValue(&function); } ProfileFunction* GetUnknown() { ASSERT(unknown_function_ != NULL); return unknown_function_; } // No protection against being called more than once for the same tag_id. ProfileFunction* AddTag(uword tag_id, const char* name) { // TODO(johnmccutchan): Canonicalize ProfileFunctions for tags. return Add(ProfileFunction::kTagFunction, name); } // No protection against being called more than once for the same native // address. ProfileFunction* AddNative(uword start_address, const char* name) { // TODO(johnmccutchan): Canonicalize ProfileFunctions for natives. return Add(ProfileFunction::kNativeFunction, name); } // No protection against being called more tha once for the same stub. ProfileFunction* AddStub(uword start_address, const char* name) { return Add(ProfileFunction::kStubFunction, name); } intptr_t length() const { return table_.length(); } ProfileFunction* At(intptr_t i) const { ASSERT(i >= 0); ASSERT(i < length()); return table_[i]; } private: ProfileFunction* Add(ProfileFunction::Kind kind, const char* name) { ASSERT(kind != ProfileFunction::kDartFunction); ASSERT(name != NULL); ProfileFunction* profile_function = new ProfileFunction(kind, name, null_function_, table_.length()); table_.Add(profile_function); return profile_function; } ProfileFunction* Add(const Function& function) { ASSERT(Lookup(function) == NULL); ProfileFunction* profile_function = new ProfileFunction( ProfileFunction::kDartFunction, NULL, function, table_.length()); table_.Add(profile_function); function_hash_.Insert(profile_function); return profile_function; } // Needed for DirectChainedHashMap. struct ProfileFunctionTableTrait { typedef ProfileFunction* Value; typedef const Function* Key; typedef ProfileFunction* Pair; static Key KeyOf(Pair kv) { return kv->function(); } static Value ValueOf(Pair kv) { return kv; } static inline intptr_t Hashcode(Key key) { return key->Hash(); } static inline bool IsKeyEqual(Pair kv, Key key) { return kv->function()->raw() == key->raw(); } }; const Function& null_function_; ProfileFunction* unknown_function_; ZoneGrowableArray table_; DirectChainedHashMap function_hash_; }; ProfileFunction* ProfileCode::SetFunctionAndName(ProfileFunctionTable* table) { ASSERT(function_ == NULL); ProfileFunction* function = NULL; if ((kind() == kReusedCode) || (kind() == kCollectedCode)) { if (name() == NULL) { // Lazily set generated name. GenerateAndSetSymbolName("[Collected]"); } // Map these to a canonical unknown function. function = table->GetUnknown(); } else if (kind() == kDartCode) { ASSERT(!code_.IsNull()); const char* name = code_.QualifiedName(); const Object& obj = Object::Handle(code_.owner()); if (obj.IsFunction()) { function = table->LookupOrAdd(Function::Cast(obj)); } else { // A stub. function = table->AddStub(start(), name); } SetName(name); } else if (kind() == kNativeCode) { if (name() == NULL) { // Lazily set generated name. GenerateAndSetSymbolName("[Native]"); } function = table->AddNative(start(), name()); } else if (kind() == kTagCode) { if (name() == NULL) { if (UserTags::IsUserTag(start())) { const char* tag_name = UserTags::TagName(start()); ASSERT(tag_name != NULL); SetName(tag_name); } else if (VMTag::IsVMTag(start()) || VMTag::IsRuntimeEntryTag(start()) || VMTag::IsNativeEntryTag(start())) { const char* tag_name = VMTag::TagName(start()); ASSERT(tag_name != NULL); SetName(tag_name); } else { switch (start()) { case VMTag::kRootTagId: SetName("Root"); break; case VMTag::kTruncatedTagId: SetName("[Truncated]"); break; case VMTag::kNoneCodeTagId: SetName("[No Code]"); break; case VMTag::kOptimizedCodeTagId: SetName("[Optimized Code]"); break; case VMTag::kUnoptimizedCodeTagId: SetName("[Unoptimized Code]"); break; case VMTag::kNativeCodeTagId: SetName("[Native Code]"); break; case VMTag::kInlineStartCodeTagId: SetName("[Inline Start]"); break; case VMTag::kInlineEndCodeTagId: SetName("[Inline End]"); break; default: UNIMPLEMENTED(); break; } } } function = table->AddTag(start(), name()); } else { UNREACHABLE(); } ASSERT(function != NULL); function->AddProfileCode(code_table_index()); function_ = function; return function_; } intptr_t ProfileCodeTable::FindCodeIndexForPC(uword pc) const { intptr_t length = table_.length(); if (length == 0) { return -1; // Not found. } intptr_t lo = 0; intptr_t hi = length - 1; while (lo <= hi) { intptr_t mid = (hi - lo + 1) / 2 + lo; ASSERT(mid >= lo); ASSERT(mid <= hi); ProfileCode* code = At(mid); if (code->Contains(pc)) { return mid; } else if (pc < code->start()) { hi = mid - 1; } else { lo = mid + 1; } } return -1; } intptr_t ProfileCodeTable::InsertCode(ProfileCode* new_code) { const intptr_t length = table_.length(); if (length == 0) { table_.Add(new_code); return length; } // Determine the correct place to insert or merge |new_code| into table. intptr_t lo = -1; intptr_t hi = -1; ProfileCode* lo_code = NULL; ProfileCode* hi_code = NULL; const uword pc = new_code->end() - 1; FindNeighbors(pc, &lo, &hi, &lo_code, &hi_code); ASSERT((lo_code != NULL) || (hi_code != NULL)); if (lo != -1) { // Has left neighbor. new_code->TruncateLower(lo_code->end()); ASSERT(!new_code->Overlaps(lo_code)); } if (hi != -1) { // Has right neighbor. new_code->TruncateUpper(hi_code->start()); ASSERT(!new_code->Overlaps(hi_code)); } if ((lo != -1) && (lo_code->kind() == ProfileCode::kNativeCode) && (new_code->kind() == ProfileCode::kNativeCode) && (lo_code->end() == new_code->start())) { // Adjacent left neighbor of the same kind: merge. // (dladdr doesn't give us symbol size so processing more samples may see // more PCs we didn't previously know belonged to it.) lo_code->ExpandUpper(new_code->end()); return lo; } if ((hi != -1) && (hi_code->kind() == ProfileCode::kNativeCode) && (new_code->kind() == ProfileCode::kNativeCode) && (new_code->end() == hi_code->start())) { // Adjacent right neighbor of the same kind: merge. // (dladdr doesn't give us symbol size so processing more samples may see // more PCs we didn't previously know belonged to it.) hi_code->ExpandLower(new_code->start()); return hi; } intptr_t insert; if (lo == -1) { insert = 0; } else if (hi == -1) { insert = length; } else { insert = lo + 1; } table_.InsertAt(insert, new_code); return insert; } void ProfileCodeTable::FindNeighbors(uword pc, intptr_t* lo, intptr_t* hi, ProfileCode** lo_code, ProfileCode** hi_code) const { ASSERT(table_.length() >= 1); intptr_t length = table_.length(); if (pc < At(0)->start()) { // Lower than any existing code. *lo = -1; *lo_code = NULL; *hi = 0; *hi_code = At(*hi); return; } if (pc >= At(length - 1)->end()) { // Higher than any existing code. *lo = length - 1; *lo_code = At(*lo); *hi = -1; *hi_code = NULL; return; } *lo = 0; *lo_code = At(*lo); *hi = length - 1; *hi_code = At(*hi); while ((*hi - *lo) > 1) { intptr_t mid = (*hi - *lo + 1) / 2 + *lo; ASSERT(*lo <= mid); ASSERT(*hi >= mid); ProfileCode* code = At(mid); if (code->end() <= pc) { *lo = mid; *lo_code = code; } if (pc < code->end()) { *hi = mid; *hi_code = code; } } } void ProfileCodeTable::VerifyOrder() { const intptr_t length = table_.length(); if (length == 0) { return; } uword last = table_[0]->end(); for (intptr_t i = 1; i < length; i++) { ProfileCode* a = table_[i]; ASSERT(last <= a->start()); last = a->end(); } } void ProfileCodeTable::VerifyOverlap() { const intptr_t length = table_.length(); for (intptr_t i = 0; i < length; i++) { ProfileCode* a = table_[i]; for (intptr_t j = i + 1; j < length; j++) { ProfileCode* b = table_[j]; ASSERT(!a->Contains(b->start()) && !a->Contains(b->end() - 1) && !b->Contains(a->start()) && !b->Contains(a->end() - 1)); } } } ProfileTrieNode::ProfileTrieNode(intptr_t table_index) : table_index_(table_index), count_(0), exclusive_allocations_(0), inclusive_allocations_(0), children_(0), frame_id_(-1) { ASSERT(table_index_ >= 0); } ProfileTrieNode::~ProfileTrieNode() {} void ProfileTrieNode::Tick(ProcessedSample* sample, bool exclusive) { count_++; IncrementAllocation(sample->native_allocation_size_bytes(), exclusive); } void ProfileTrieNode::SortChildren() { children_.Sort(ProfileTrieNodeCompare); // Recurse. for (intptr_t i = 0; i < children_.length(); i++) { children_[i]->SortChildren(); } } intptr_t ProfileTrieNode::IndexOf(ProfileTrieNode* node) { for (intptr_t i = 0; i < children_.length(); i++) { if (children_[i] == node) { return i; } } return -1; } class ProfileCodeTrieNode : public ProfileTrieNode { public: explicit ProfileCodeTrieNode(intptr_t table_index) : ProfileTrieNode(table_index) {} void PrintToJSONArray(JSONArray* array) const { ASSERT(array != NULL); // Write CodeRegion index. array->AddValue(table_index()); // Write count. array->AddValue(count()); // Write number of children. intptr_t child_count = NumChildren(); array->AddValue(child_count); // Write inclusive allocations. array->AddValue64(inclusive_allocations_); // Write exclusive allocations. array->AddValue64(exclusive_allocations_); // Recurse. for (intptr_t i = 0; i < child_count; i++) { children_[i]->PrintToJSONArray(array); } } ProfileCodeTrieNode* GetChild(intptr_t child_table_index) { const intptr_t length = NumChildren(); intptr_t i = 0; while (i < length) { ProfileCodeTrieNode* child = reinterpret_cast(children_[i]); if (child->table_index() == child_table_index) { return child; } if (child->table_index() > child_table_index) { break; } i++; } ProfileCodeTrieNode* child = new ProfileCodeTrieNode(child_table_index); if (i < length) { // Insert at i. children_.InsertAt(i, reinterpret_cast(child)); } else { // Add to end. children_.Add(reinterpret_cast(child)); } return child; } }; class ProfileFunctionTrieNodeCode { public: explicit ProfileFunctionTrieNodeCode(intptr_t index) : code_index_(index), ticks_(0) {} intptr_t index() const { return code_index_; } void Tick() { ticks_++; } intptr_t ticks() const { return ticks_; } private: intptr_t code_index_; intptr_t ticks_; }; class ProfileFunctionTrieNode : public ProfileTrieNode { public: explicit ProfileFunctionTrieNode(intptr_t table_index) : ProfileTrieNode(table_index), code_objects_(1) {} void PrintToJSONArray(JSONArray* array) const { ASSERT(array != NULL); // Write CodeRegion index. array->AddValue(table_index()); // Write count. array->AddValue(count()); // Write inclusive allocations. array->AddValue64(inclusive_allocations_); // Write exclusive allocations. array->AddValue64(exclusive_allocations_); // Write number of code objects. intptr_t code_count = code_objects_.length(); array->AddValue(code_count); // Write each code object index and ticks. for (intptr_t i = 0; i < code_count; i++) { array->AddValue(code_objects_[i].index()); array->AddValue(code_objects_[i].ticks()); } // Write number of children. intptr_t child_count = children_.length(); array->AddValue(child_count); // Recurse. for (intptr_t i = 0; i < child_count; i++) { children_[i]->PrintToJSONArray(array); } } ProfileFunctionTrieNode* GetChild(intptr_t child_table_index) { const intptr_t length = NumChildren(); intptr_t i = 0; while (i < length) { ProfileFunctionTrieNode* child = reinterpret_cast(children_[i]); if (child->table_index() == child_table_index) { return child; } if (child->table_index() > child_table_index) { break; } i++; } ProfileFunctionTrieNode* child = new ProfileFunctionTrieNode(child_table_index); if (i < length) { // Insert at i. children_.InsertAt(i, reinterpret_cast(child)); } else { // Add to end. children_.Add(reinterpret_cast(child)); } return child; } void AddCodeObjectIndex(intptr_t index) { for (intptr_t i = 0; i < code_objects_.length(); i++) { ProfileFunctionTrieNodeCode& code_object = code_objects_[i]; if (code_object.index() == index) { code_object.Tick(); return; } } ProfileFunctionTrieNodeCode code_object(index); code_object.Tick(); code_objects_.Add(code_object); } private: ZoneGrowableArray code_objects_; }; class ProfileCodeInlinedFunctionsCache : public ValueObject { public: ProfileCodeInlinedFunctionsCache() : cache_cursor_(0), last_hit_(0) { for (intptr_t i = 0; i < kCacheSize; i++) { cache_[i].Reset(); } cache_hit_ = 0; cache_miss_ = 0; } ~ProfileCodeInlinedFunctionsCache() { if (FLAG_trace_profiler) { intptr_t total = cache_hit_ + cache_miss_; OS::Print("LOOKUPS: %" Pd " HITS: %" Pd " MISSES: %" Pd "\n", total, cache_hit_, cache_miss_); } } void Get(uword pc, const Code& code, ProcessedSample* sample, intptr_t frame_index, // Outputs: GrowableArray** inlined_functions, GrowableArray** inlined_token_positions, TokenPosition* token_position) { const intptr_t offset = OffsetForPC(pc, code, sample, frame_index); if (FindInCache(pc, offset, inlined_functions, inlined_token_positions, token_position)) { // Found in cache. return; } Add(pc, code, sample, frame_index, inlined_functions, inlined_token_positions, token_position); } private: bool FindInCache(uword pc, intptr_t offset, GrowableArray** inlined_functions, GrowableArray** inlined_token_positions, TokenPosition* token_position) { // Simple linear scan. for (intptr_t i = 0; i < kCacheSize; i++) { intptr_t index = (last_hit_ + i) % kCacheSize; if ((cache_[index].pc == pc) && (cache_[index].offset == offset)) { // Hit. if (cache_[index].inlined_functions.length() == 0) { *inlined_functions = NULL; *inlined_token_positions = NULL; } else { *inlined_functions = &cache_[index].inlined_functions; *inlined_token_positions = &cache_[index].inlined_token_positions; } *token_position = cache_[index].token_position; cache_hit_++; last_hit_ = index; return true; } } cache_miss_++; return false; } // Add to cache and fill in outputs. void Add(uword pc, const Code& code, ProcessedSample* sample, intptr_t frame_index, // Outputs: GrowableArray** inlined_functions, GrowableArray** inlined_token_positions, TokenPosition* token_position) { const intptr_t offset = OffsetForPC(pc, code, sample, frame_index); CacheEntry* cache_entry = &cache_[NextFreeIndex()]; cache_entry->Reset(); cache_entry->pc = pc; cache_entry->offset = offset; code.GetInlinedFunctionsAtInstruction( offset, &(cache_entry->inlined_functions), &(cache_entry->inlined_token_positions)); if (cache_entry->inlined_functions.length() == 0) { *inlined_functions = NULL; *inlined_token_positions = NULL; *token_position = cache_entry->token_position = TokenPosition(); return; } // Write outputs. *inlined_functions = &(cache_entry->inlined_functions); *inlined_token_positions = &(cache_entry->inlined_token_positions); *token_position = cache_entry->token_position = cache_entry->inlined_token_positions[0]; } intptr_t NextFreeIndex() { cache_cursor_ = (cache_cursor_ + 1) % kCacheSize; return cache_cursor_; } intptr_t OffsetForPC(uword pc, const Code& code, ProcessedSample* sample, intptr_t frame_index) { intptr_t offset = pc - code.PayloadStart(); if (frame_index != 0) { // The PC of frames below the top frame is a call's return address, // which can belong to a different inlining interval than the call. offset--; } else if (sample->IsAllocationSample()) { // Allocation samples skip the top frame, so the top frame's pc is // also a call's return address. offset--; } else if (!sample->first_frame_executing()) { // If the first frame wasn't executing code (i.e. we started to collect // the stack trace at an exit frame), the top frame's pc is also a // call's return address. offset--; } return offset; } struct CacheEntry { void Reset() { pc = 0; offset = 0; inlined_functions.Clear(); inlined_token_positions.Clear(); } uword pc; intptr_t offset; GrowableArray inlined_functions; GrowableArray inlined_token_positions; TokenPosition token_position; }; static const intptr_t kCacheSize = 128; intptr_t cache_cursor_; intptr_t last_hit_; CacheEntry cache_[kCacheSize]; intptr_t cache_miss_; intptr_t cache_hit_; }; class ProfileBuilder : public ValueObject { public: enum ProfileInfoKind { kNone, kOptimized, kUnoptimized, kNative, kInlineStart, kInlineFinish, kNumProfileInfoKind, }; ProfileBuilder(Thread* thread, SampleFilter* filter, SampleBuffer* sample_buffer, Profile::TagOrder tag_order, intptr_t extra_tags, Profile* profile) : thread_(thread), vm_isolate_(Dart::vm_isolate()), filter_(filter), sample_buffer_(sample_buffer), tag_order_(tag_order), extra_tags_(extra_tags), profile_(profile), deoptimized_code_(new DeoptimizedCodeSet(thread->isolate())), null_code_(Code::ZoneHandle()), null_function_(Function::ZoneHandle()), tick_functions_(false), inclusive_tree_(false), samples_(NULL), info_kind_(kNone) { ASSERT((sample_buffer_ == Profiler::sample_buffer()) || (sample_buffer_ == Profiler::allocation_sample_buffer())); ASSERT(profile_ != NULL); } void Build() { ScopeTimer sw("ProfileBuilder::Build", FLAG_trace_profiler); if (!FilterSamples()) { return; } Setup(); BuildCodeTable(); FinalizeCodeIndexes(); BuildFunctionTable(); BuildCodeTrie(Profile::kExclusiveCode); BuildCodeTrie(Profile::kInclusiveCode); BuildFunctionTrie(Profile::kExclusiveFunction); BuildFunctionTrie(Profile::kInclusiveFunction); } private: // Returns true if |frame_index| in |sample| is using CPU. static bool IsExecutingFrame(ProcessedSample* sample, intptr_t frame_index) { return (frame_index == 0) && (sample->first_frame_executing() || sample->IsAllocationSample()); } static bool IsInclusiveTrie(Profile::TrieKind kind) { return (kind == Profile::kInclusiveFunction) || (kind == Profile::kInclusiveCode); } void Setup() { profile_->live_code_ = new ProfileCodeTable(); profile_->dead_code_ = new ProfileCodeTable(); profile_->tag_code_ = new ProfileCodeTable(); profile_->functions_ = new ProfileFunctionTable(); // Register some synthetic tags. RegisterProfileCodeTag(VMTag::kRootTagId); RegisterProfileCodeTag(VMTag::kTruncatedTagId); RegisterProfileCodeTag(VMTag::kNoneCodeTagId); RegisterProfileCodeTag(VMTag::kOptimizedCodeTagId); RegisterProfileCodeTag(VMTag::kUnoptimizedCodeTagId); RegisterProfileCodeTag(VMTag::kNativeCodeTagId); RegisterProfileCodeTag(VMTag::kInlineStartCodeTagId); RegisterProfileCodeTag(VMTag::kInlineEndCodeTagId); } bool FilterSamples() { ScopeTimer sw("ProfileBuilder::FilterSamples", FLAG_trace_profiler); ASSERT(sample_buffer_ != NULL); samples_ = sample_buffer_->BuildProcessedSampleBuffer(filter_); profile_->samples_ = samples_; profile_->sample_count_ = samples_->length(); return true; } void UpdateMinMaxTimes(int64_t timestamp) { profile_->min_time_ = timestamp < profile_->min_time_ ? timestamp : profile_->min_time_; profile_->max_time_ = timestamp > profile_->max_time_ ? timestamp : profile_->max_time_; } void SanitizeMinMaxTimes() { if ((profile_->min_time_ == kMaxInt64) && (profile_->max_time_ == 0)) { profile_->min_time_ = 0; profile_->max_time_ = 0; } } void BuildCodeTable() { ScopeTimer sw("ProfileBuilder::BuildCodeTable", FLAG_trace_profiler); Isolate* isolate = thread_->isolate(); ASSERT(isolate != NULL); // Build the live code table eagerly by populating it with code objects // from the processed sample buffer. const CodeLookupTable& code_lookup_table = samples_->code_lookup_table(); for (intptr_t i = 0; i < code_lookup_table.length(); i++) { const CodeDescriptor* descriptor = code_lookup_table.At(i); ASSERT(descriptor != NULL); const Code& code = Code::Handle(descriptor->code()); ASSERT(!code.IsNull()); RegisterLiveProfileCode(new ProfileCode( ProfileCode::kDartCode, code.PayloadStart(), code.PayloadStart() + code.Size(), code.compile_timestamp(), code)); } // Iterate over samples. for (intptr_t sample_index = 0; sample_index < samples_->length(); sample_index++) { ProcessedSample* sample = samples_->At(sample_index); const int64_t timestamp = sample->timestamp(); // This is our first pass over the sample buffer, use this as an // opportunity to determine the min and max time ranges of this profile. UpdateMinMaxTimes(timestamp); // Make sure VM tag exists. if (VMTag::IsNativeEntryTag(sample->vm_tag())) { RegisterProfileCodeTag(VMTag::kNativeTagId); } else if (VMTag::IsRuntimeEntryTag(sample->vm_tag())) { RegisterProfileCodeTag(VMTag::kRuntimeTagId); } RegisterProfileCodeTag(sample->vm_tag()); // Make sure user tag exists. RegisterProfileCodeTag(sample->user_tag()); // Make sure that a ProfileCode objects exist for all pcs in the sample // and tick each one. for (intptr_t frame_index = 0; frame_index < sample->length(); frame_index++) { const uword pc = sample->At(frame_index); ASSERT(pc != 0); ProfileCode* code = FindOrRegisterProfileCode(pc, timestamp); ASSERT(code != NULL); code->Tick(pc, IsExecutingFrame(sample, frame_index), sample_index); } TickExitFrame(sample->vm_tag(), sample_index, sample); } SanitizeMinMaxTimes(); } void FinalizeCodeIndexes() { ScopeTimer sw("ProfileBuilder::FinalizeCodeIndexes", FLAG_trace_profiler); ProfileCodeTable* live_table = profile_->live_code_; ProfileCodeTable* dead_table = profile_->dead_code_; ProfileCodeTable* tag_table = profile_->tag_code_; const intptr_t dead_code_index_offset = live_table->length(); const intptr_t tag_code_index_offset = dead_table->length() + dead_code_index_offset; profile_->dead_code_index_offset_ = dead_code_index_offset; profile_->tag_code_index_offset_ = tag_code_index_offset; for (intptr_t i = 0; i < live_table->length(); i++) { const intptr_t index = i; ProfileCode* code = live_table->At(i); ASSERT(code != NULL); code->set_code_table_index(index); } for (intptr_t i = 0; i < dead_table->length(); i++) { const intptr_t index = dead_code_index_offset + i; ProfileCode* code = dead_table->At(i); ASSERT(code != NULL); code->set_code_table_index(index); } for (intptr_t i = 0; i < tag_table->length(); i++) { const intptr_t index = tag_code_index_offset + i; ProfileCode* code = tag_table->At(i); ASSERT(code != NULL); code->set_code_table_index(index); } } void BuildFunctionTable() { ScopeTimer sw("ProfileBuilder::BuildFunctionTable", FLAG_trace_profiler); ProfileCodeTable* live_table = profile_->live_code_; ProfileCodeTable* dead_table = profile_->dead_code_; ProfileCodeTable* tag_table = profile_->tag_code_; ProfileFunctionTable* function_table = profile_->functions_; for (intptr_t i = 0; i < live_table->length(); i++) { ProfileCode* code = live_table->At(i); ASSERT(code != NULL); code->SetFunctionAndName(function_table); } for (intptr_t i = 0; i < dead_table->length(); i++) { ProfileCode* code = dead_table->At(i); ASSERT(code != NULL); code->SetFunctionAndName(function_table); } for (intptr_t i = 0; i < tag_table->length(); i++) { ProfileCode* code = tag_table->At(i); ASSERT(code != NULL); code->SetFunctionAndName(function_table); } } void BuildCodeTrie(Profile::TrieKind kind) { ProfileCodeTrieNode* root = new ProfileCodeTrieNode(GetProfileCodeTagIndex(VMTag::kRootTagId)); inclusive_tree_ = IsInclusiveTrie(kind); if (inclusive_tree_) { BuildInclusiveCodeTrie(root); } else { BuildExclusiveCodeTrie(root); } root->SortChildren(); profile_->roots_[static_cast(kind)] = root; } void BuildInclusiveCodeTrie(ProfileCodeTrieNode* root) { ScopeTimer sw("ProfileBuilder::BuildInclusiveCodeTrie", FLAG_trace_profiler); for (intptr_t sample_index = 0; sample_index < samples_->length(); sample_index++) { ProcessedSample* sample = samples_->At(sample_index); // Tick the root. ProfileCodeTrieNode* current = root; current->Tick(sample); // VM & User tags. current = AppendTags(sample->vm_tag(), sample->user_tag(), current, sample); ResetKind(); // Truncated tag. if (sample->truncated()) { current = AppendTruncatedTag(current, sample); } // Walk the sampled PCs. Code& code = Code::Handle(); for (intptr_t frame_index = sample->length() - 1; frame_index >= 0; frame_index--) { ASSERT(sample->At(frame_index) != 0); intptr_t index = GetProfileCodeIndex(sample->At(frame_index), sample->timestamp()); ASSERT(index >= 0); ProfileCode* profile_code = GetProfileCode(sample->At(frame_index), sample->timestamp()); ASSERT(profile_code->code_table_index() == index); code ^= profile_code->code(); current = AppendKind(code, current, sample); current = current->GetChild(index); current->Tick(sample, (frame_index == 0)); } if (!sample->first_frame_executing()) { current = AppendExitFrame(sample->vm_tag(), current, sample); } } } void BuildExclusiveCodeTrie(ProfileCodeTrieNode* root) { ScopeTimer sw("ProfileBuilder::BuildExclusiveCodeTrie", FLAG_trace_profiler); for (intptr_t sample_index = 0; sample_index < samples_->length(); sample_index++) { ProcessedSample* sample = samples_->At(sample_index); // Tick the root. ProfileCodeTrieNode* current = root; current->Tick(sample); // VM & User tags. current = AppendTags(sample->vm_tag(), sample->user_tag(), current, sample); ResetKind(); if (!sample->first_frame_executing()) { current = AppendExitFrame(sample->vm_tag(), current, sample); } // Walk the sampled PCs. Code& code = Code::Handle(); for (intptr_t frame_index = 0; frame_index < sample->length(); frame_index++) { ASSERT(sample->At(frame_index) != 0); intptr_t index = GetProfileCodeIndex(sample->At(frame_index), sample->timestamp()); ASSERT(index >= 0); ProfileCode* profile_code = GetProfileCode(sample->At(frame_index), sample->timestamp()); ASSERT(profile_code->code_table_index() == index); code ^= profile_code->code(); current = current->GetChild(index); if (ShouldTickNode(sample, frame_index)) { current->Tick(sample, (frame_index == 0)); } current = AppendKind(code, current, sample); } // Truncated tag. if (sample->truncated()) { current = AppendTruncatedTag(current, sample); } } } void BuildFunctionTrie(Profile::TrieKind kind) { ProfileFunctionTrieNode* root = new ProfileFunctionTrieNode( GetProfileFunctionTagIndex(VMTag::kRootTagId)); // We tick the functions while building the trie, but, we don't want to do // it for both tries, just the exclusive trie. inclusive_tree_ = IsInclusiveTrie(kind); tick_functions_ = !inclusive_tree_; if (inclusive_tree_) { BuildInclusiveFunctionTrie(root); } else { BuildExclusiveFunctionTrie(root); } root->SortChildren(); profile_->roots_[static_cast(kind)] = root; } void BuildInclusiveFunctionTrie(ProfileFunctionTrieNode* root) { ScopeTimer sw("ProfileBuilder::BuildInclusiveFunctionTrie", FLAG_trace_profiler); ASSERT(!tick_functions_); for (intptr_t sample_index = 0; sample_index < samples_->length(); sample_index++) { ProcessedSample* sample = samples_->At(sample_index); // Tick the root. ProfileFunctionTrieNode* current = root; current->Tick(sample); // VM & User tags. current = AppendTags(sample->vm_tag(), sample->user_tag(), current, sample); // Truncated tag. if (sample->truncated()) { current = AppendTruncatedTag(current, sample); } // Walk the sampled PCs. for (intptr_t frame_index = sample->length() - 1; frame_index >= 0; frame_index--) { ASSERT(sample->At(frame_index) != 0); current = ProcessFrame(current, sample_index, sample, frame_index); } if (!sample->first_frame_executing()) { current = AppendExitFrame(sample->vm_tag(), current, sample); } sample->set_timeline_trie(current); } } void BuildExclusiveFunctionTrie(ProfileFunctionTrieNode* root) { ScopeTimer sw("ProfileBuilder::BuildExclusiveFunctionTrie", FLAG_trace_profiler); ASSERT(tick_functions_); for (intptr_t sample_index = 0; sample_index < samples_->length(); sample_index++) { ProcessedSample* sample = samples_->At(sample_index); // Tick the root. ProfileFunctionTrieNode* current = root; current->Tick(sample); // VM & User tags. current = AppendTags(sample->vm_tag(), sample->user_tag(), current, sample); ResetKind(); if (!sample->first_frame_executing()) { current = AppendExitFrame(sample->vm_tag(), current, sample); } // Walk the sampled PCs. for (intptr_t frame_index = 0; frame_index < sample->length(); frame_index++) { ASSERT(sample->At(frame_index) != 0); current = ProcessFrame(current, sample_index, sample, frame_index); } TickExitFrameFunction(sample->vm_tag(), sample_index); // Truncated tag. if (sample->truncated()) { current = AppendTruncatedTag(current, sample); InclusiveTickTruncatedTag(sample); } } } ProfileFunctionTrieNode* ProcessFrame(ProfileFunctionTrieNode* current, intptr_t sample_index, ProcessedSample* sample, intptr_t frame_index) { const uword pc = sample->At(frame_index); ProfileCode* profile_code = GetProfileCode(pc, sample->timestamp()); ProfileFunction* function = profile_code->function(); ASSERT(function != NULL); const intptr_t code_index = profile_code->code_table_index(); ASSERT(profile_code != NULL); const Code& code = Code::ZoneHandle(profile_code->code()); GrowableArray* inlined_functions = NULL; GrowableArray* inlined_token_positions = NULL; TokenPosition token_position = TokenPosition::kNoSource; if (!code.IsNull()) { inlined_functions_cache_.Get(pc, code, sample, frame_index, &inlined_functions, &inlined_token_positions, &token_position); if (FLAG_trace_profiler_verbose) { for (intptr_t i = 0; i < inlined_functions->length(); i++) { const String& name = String::Handle((*inlined_functions)[i]->QualifiedScrubbedName()); THR_Print("InlinedFunction[%" Pd "] = {%s, %s}\n", i, name.ToCString(), (*inlined_token_positions)[i].ToCString()); } } } if (code.IsNull() || (inlined_functions == NULL) || (inlined_functions->length() <= 1)) { // No inlined functions. if (inclusive_tree_) { current = AppendKind(code, current, sample); } current = ProcessFunction(current, sample_index, sample, frame_index, function, token_position, code_index); if (!inclusive_tree_) { current = AppendKind(code, current, sample); } return current; } if (!code.is_optimized()) { OS::PrintErr("Code that should be optimized is not. Please file a bug\n"); OS::PrintErr("Code object: %s\n", code.ToCString()); OS::PrintErr("Inlined functions length: %" Pd "\n", inlined_functions->length()); for (intptr_t i = 0; i < inlined_functions->length(); i++) { OS::PrintErr("IF[%" Pd "] = %s\n", i, (*inlined_functions)[i]->ToFullyQualifiedCString()); } } ASSERT(code.is_optimized()); if (inclusive_tree_) { for (intptr_t i = 0; i < inlined_functions->length(); i++) { const Function* inlined_function = (*inlined_functions)[i]; ASSERT(inlined_function != NULL); ASSERT(!inlined_function->IsNull()); TokenPosition inlined_token_position = (*inlined_token_positions)[i]; const bool inliner = i == 0; if (inliner) { current = AppendKind(code, current, sample); } current = ProcessInlinedFunction(current, sample_index, sample, frame_index, inlined_function, inlined_token_position, code_index); if (inliner) { current = AppendKind(kInlineStart, current, sample); } } current = AppendKind(kInlineFinish, current, sample); } else { // Append the inlined children. current = AppendKind(kInlineFinish, current, sample); for (intptr_t i = inlined_functions->length() - 1; i >= 0; i--) { const Function* inlined_function = (*inlined_functions)[i]; ASSERT(inlined_function != NULL); ASSERT(!inlined_function->IsNull()); TokenPosition inlined_token_position = (*inlined_token_positions)[i]; const bool inliner = i == 0; if (inliner) { current = AppendKind(kInlineStart, current, sample); } current = ProcessInlinedFunction(current, sample_index, sample, frame_index + i, inlined_function, inlined_token_position, code_index); if (inliner) { current = AppendKind(code, current, sample); } } } return current; } ProfileFunctionTrieNode* ProcessInlinedFunction( ProfileFunctionTrieNode* current, intptr_t sample_index, ProcessedSample* sample, intptr_t frame_index, const Function* inlined_function, TokenPosition inlined_token_position, intptr_t code_index) { ProfileFunctionTable* function_table = profile_->functions_; ProfileFunction* function = function_table->LookupOrAdd(*inlined_function); ASSERT(function != NULL); return ProcessFunction(current, sample_index, sample, frame_index, function, inlined_token_position, code_index); } bool ShouldTickNode(ProcessedSample* sample, intptr_t frame_index) { if (frame_index != 0) { return true; } // Only tick the first frame's node, if we are executing OR // vm tags have been emitted. return IsExecutingFrame(sample, frame_index) || !FLAG_profile_vm || vm_tags_emitted(); } ProfileFunctionTrieNode* ProcessFunction(ProfileFunctionTrieNode* current, intptr_t sample_index, ProcessedSample* sample, intptr_t frame_index, ProfileFunction* function, TokenPosition token_position, intptr_t code_index) { if (!function->is_visible()) { return current; } if (tick_functions_) { if (FLAG_trace_profiler_verbose) { THR_Print("S[%" Pd "]F[%" Pd "] %s %s 0x%" Px "\n", sample_index, frame_index, function->Name(), token_position.ToCString(), sample->At(frame_index)); } function->Tick(IsExecutingFrame(sample, frame_index), sample_index, token_position); } function->AddProfileCode(code_index); current = current->GetChild(function->table_index()); if (ShouldTickNode(sample, frame_index)) { current->Tick(sample, (frame_index == 0)); } current->AddCodeObjectIndex(code_index); return current; } // Tick the truncated tag's inclusive tick count. void InclusiveTickTruncatedTag(ProcessedSample* sample) { ProfileCodeTable* tag_table = profile_->tag_code_; intptr_t index = tag_table->FindCodeIndexForPC(VMTag::kTruncatedTagId); ASSERT(index >= 0); ProfileCode* code = tag_table->At(index); code->IncInclusiveTicks(); ASSERT(code != NULL); ProfileFunction* function = code->function(); function->IncInclusiveTicks(); } // Tag append functions are overloaded for |ProfileCodeTrieNode| and // |ProfileFunctionTrieNode| types. // ProfileCodeTrieNode ProfileCodeTrieNode* AppendUserTag(uword user_tag, ProfileCodeTrieNode* current, ProcessedSample* sample) { intptr_t user_tag_index = GetProfileCodeTagIndex(user_tag); if (user_tag_index >= 0) { current = current->GetChild(user_tag_index); current->Tick(sample); } return current; } ProfileCodeTrieNode* AppendTruncatedTag(ProfileCodeTrieNode* current, ProcessedSample* sample) { intptr_t truncated_tag_index = GetProfileCodeTagIndex(VMTag::kTruncatedTagId); ASSERT(truncated_tag_index >= 0); current = current->GetChild(truncated_tag_index); current->Tick(sample); return current; } ProfileCodeTrieNode* AppendVMTag(uword vm_tag, ProfileCodeTrieNode* current, ProcessedSample* sample) { if (VMTag::IsNativeEntryTag(vm_tag)) { // Insert a dummy kNativeTagId node. intptr_t tag_index = GetProfileCodeTagIndex(VMTag::kNativeTagId); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } else if (VMTag::IsRuntimeEntryTag(vm_tag)) { // Insert a dummy kRuntimeTagId node. intptr_t tag_index = GetProfileCodeTagIndex(VMTag::kRuntimeTagId); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } else { intptr_t tag_index = GetProfileCodeTagIndex(vm_tag); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } return current; } ProfileCodeTrieNode* AppendSpecificNativeRuntimeEntryVMTag( uword vm_tag, ProfileCodeTrieNode* current, ProcessedSample* sample) { // Only Native and Runtime entries have a second VM tag. if (!VMTag::IsNativeEntryTag(vm_tag) && !VMTag::IsRuntimeEntryTag(vm_tag)) { return current; } intptr_t tag_index = GetProfileCodeTagIndex(vm_tag); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); return current; } uword ProfileInfoKindToVMTag(ProfileInfoKind kind) { switch (kind) { case kNone: return VMTag::kNoneCodeTagId; case kOptimized: return VMTag::kOptimizedCodeTagId; case kUnoptimized: return VMTag::kUnoptimizedCodeTagId; case kNative: return VMTag::kNativeCodeTagId; case kInlineStart: return VMTag::kInlineStartCodeTagId; case kInlineFinish: return VMTag::kInlineEndCodeTagId; default: UNIMPLEMENTED(); return VMTag::kInvalidTagId; } } ProfileCodeTrieNode* AppendKind(ProfileInfoKind kind, ProfileCodeTrieNode* current, ProcessedSample* sample) { if (!TagsEnabled(ProfilerService::kCodeTransitionTagsBit)) { // Only emit if debug tags are requested. return current; } if (kind != info_kind_) { info_kind_ = kind; intptr_t tag_index = GetProfileCodeTagIndex(ProfileInfoKindToVMTag(kind)); ASSERT(tag_index >= 0); current = current->GetChild(tag_index); current->Tick(sample); } return current; } ProfileCodeTrieNode* AppendKind(const Code& code, ProfileCodeTrieNode* current, ProcessedSample* sample) { if (code.IsNull()) { return AppendKind(kNone, current, sample); } else if (code.is_optimized()) { return AppendKind(kOptimized, current, sample); } else { return AppendKind(kUnoptimized, current, sample); } } ProfileCodeTrieNode* AppendVMTags(uword vm_tag, ProfileCodeTrieNode* current, ProcessedSample* sample) { current = AppendVMTag(vm_tag, current, sample); current = AppendSpecificNativeRuntimeEntryVMTag(vm_tag, current, sample); return current; } void TickExitFrame(uword vm_tag, intptr_t serial, ProcessedSample* sample) { if (FLAG_profile_vm) { return; } if (!VMTag::IsExitFrameTag(vm_tag)) { return; } ProfileCodeTable* tag_table = profile_->tag_code_; ProfileCode* code = tag_table->FindCodeForPC(vm_tag); ASSERT(code != NULL); code->Tick(vm_tag, true, serial); } void TickExitFrameFunction(uword vm_tag, intptr_t serial) { if (FLAG_profile_vm) { return; } if (!VMTag::IsExitFrameTag(vm_tag)) { return; } ProfileCodeTable* tag_table = profile_->tag_code_; ProfileCode* code = tag_table->FindCodeForPC(vm_tag); ASSERT(code != NULL); ProfileFunction* function = code->function(); ASSERT(function != NULL); function->Tick(true, serial, TokenPosition::kNoSource); } ProfileCodeTrieNode* AppendExitFrame(uword vm_tag, ProfileCodeTrieNode* current, ProcessedSample* sample) { if (FLAG_profile_vm) { return current; } if (!VMTag::IsExitFrameTag(vm_tag)) { return current; } if (VMTag::IsNativeEntryTag(vm_tag) || VMTag::IsRuntimeEntryTag(vm_tag)) { current = AppendSpecificNativeRuntimeEntryVMTag(vm_tag, current, sample); } else { intptr_t tag_index = GetProfileCodeTagIndex(vm_tag); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } return current; } ProfileCodeTrieNode* AppendTags(uword vm_tag, uword user_tag, ProfileCodeTrieNode* current, ProcessedSample* sample) { if (FLAG_profile_vm) { // None. if (tag_order() == Profile::kNoTags) { return current; } // User first. if ((tag_order() == Profile::kUserVM) || (tag_order() == Profile::kUser)) { current = AppendUserTag(user_tag, current, sample); // Only user. if (tag_order() == Profile::kUser) { return current; } return AppendVMTags(vm_tag, current, sample); } // VM first. ASSERT((tag_order() == Profile::kVMUser) || (tag_order() == Profile::kVM)); current = AppendVMTags(vm_tag, current, sample); // Only VM. if (tag_order() == Profile::kVM) { return current; } return AppendUserTag(user_tag, current, sample); } if (tag_order() == Profile::kNoTags) { return current; } return AppendUserTag(user_tag, current, sample); } // ProfileFunctionTrieNode void ResetKind() { info_kind_ = kNone; } ProfileFunctionTrieNode* AppendKind(ProfileInfoKind kind, ProfileFunctionTrieNode* current, ProcessedSample* sample) { if (!TagsEnabled(ProfilerService::kCodeTransitionTagsBit)) { // Only emit if debug tags are requested. return current; } if (kind != info_kind_) { info_kind_ = kind; intptr_t tag_index = GetProfileFunctionTagIndex(ProfileInfoKindToVMTag(kind)); ASSERT(tag_index >= 0); current = current->GetChild(tag_index); current->Tick(sample); } return current; } ProfileFunctionTrieNode* AppendKind(const Code& code, ProfileFunctionTrieNode* current, ProcessedSample* sample) { if (code.IsNull()) { return AppendKind(kNone, current, sample); } else if (code.is_optimized()) { return AppendKind(kOptimized, current, sample); } else { return AppendKind(kUnoptimized, current, sample); } } ProfileFunctionTrieNode* AppendUserTag(uword user_tag, ProfileFunctionTrieNode* current, ProcessedSample* sample) { intptr_t user_tag_index = GetProfileFunctionTagIndex(user_tag); if (user_tag_index >= 0) { current = current->GetChild(user_tag_index); current->Tick(sample); } return current; } ProfileFunctionTrieNode* AppendTruncatedTag(ProfileFunctionTrieNode* current, ProcessedSample* sample) { intptr_t truncated_tag_index = GetProfileFunctionTagIndex(VMTag::kTruncatedTagId); ASSERT(truncated_tag_index >= 0); current = current->GetChild(truncated_tag_index); current->Tick(sample); return current; } ProfileFunctionTrieNode* AppendVMTag(uword vm_tag, ProfileFunctionTrieNode* current, ProcessedSample* sample) { if (VMTag::IsNativeEntryTag(vm_tag)) { // Insert a dummy kNativeTagId node. intptr_t tag_index = GetProfileFunctionTagIndex(VMTag::kNativeTagId); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } else if (VMTag::IsRuntimeEntryTag(vm_tag)) { // Insert a dummy kRuntimeTagId node. intptr_t tag_index = GetProfileFunctionTagIndex(VMTag::kRuntimeTagId); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } else { intptr_t tag_index = GetProfileFunctionTagIndex(vm_tag); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } return current; } ProfileFunctionTrieNode* AppendSpecificNativeRuntimeEntryVMTag( uword vm_tag, ProfileFunctionTrieNode* current, ProcessedSample* sample) { // Only Native and Runtime entries have a second VM tag. if (!VMTag::IsNativeEntryTag(vm_tag) && !VMTag::IsRuntimeEntryTag(vm_tag)) { return current; } intptr_t tag_index = GetProfileFunctionTagIndex(vm_tag); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); return current; } ProfileFunctionTrieNode* AppendVMTags(uword vm_tag, ProfileFunctionTrieNode* current, ProcessedSample* sample) { current = AppendVMTag(vm_tag, current, sample); current = AppendSpecificNativeRuntimeEntryVMTag(vm_tag, current, sample); return current; } ProfileFunctionTrieNode* AppendExitFrame(uword vm_tag, ProfileFunctionTrieNode* current, ProcessedSample* sample) { if (FLAG_profile_vm) { return current; } if (!VMTag::IsExitFrameTag(vm_tag)) { return current; } if (VMTag::IsNativeEntryTag(vm_tag) || VMTag::IsRuntimeEntryTag(vm_tag)) { current = AppendSpecificNativeRuntimeEntryVMTag(vm_tag, current, sample); } else { intptr_t tag_index = GetProfileFunctionTagIndex(vm_tag); current = current->GetChild(tag_index); // Give the tag a tick. current->Tick(sample); } return current; } ProfileFunctionTrieNode* AppendTags(uword vm_tag, uword user_tag, ProfileFunctionTrieNode* current, ProcessedSample* sample) { if (FLAG_profile_vm) { // None. if (tag_order() == Profile::kNoTags) { return current; } // User first. if ((tag_order() == Profile::kUserVM) || (tag_order() == Profile::kUser)) { current = AppendUserTag(user_tag, current, sample); // Only user. if (tag_order() == Profile::kUser) { return current; } return AppendVMTags(vm_tag, current, sample); } // VM first. ASSERT((tag_order() == Profile::kVMUser) || (tag_order() == Profile::kVM)); current = AppendVMTags(vm_tag, current, sample); // Only VM. if (tag_order() == Profile::kVM) { return current; } return AppendUserTag(user_tag, current, sample); } if (tag_order() == Profile::kNoTags) { return current; } return AppendUserTag(user_tag, current, sample); } intptr_t GetProfileCodeTagIndex(uword tag) { ProfileCodeTable* tag_table = profile_->tag_code_; intptr_t index = tag_table->FindCodeIndexForPC(tag); ASSERT(index >= 0); ProfileCode* code = tag_table->At(index); ASSERT(code != NULL); return code->code_table_index(); } intptr_t GetProfileFunctionTagIndex(uword tag) { ProfileCodeTable* tag_table = profile_->tag_code_; intptr_t index = tag_table->FindCodeIndexForPC(tag); ASSERT(index >= 0); ProfileCode* code = tag_table->At(index); ASSERT(code != NULL); ProfileFunction* function = code->function(); ASSERT(function != NULL); return function->table_index(); } intptr_t GetProfileCodeIndex(uword pc, int64_t timestamp) { return GetProfileCode(pc, timestamp)->code_table_index(); } ProfileCode* GetProfileCode(uword pc, int64_t timestamp) { ProfileCodeTable* live_table = profile_->live_code_; ProfileCodeTable* dead_table = profile_->dead_code_; intptr_t index = live_table->FindCodeIndexForPC(pc); ProfileCode* code = NULL; if (index < 0) { index = dead_table->FindCodeIndexForPC(pc); ASSERT(index >= 0); code = dead_table->At(index); } else { code = live_table->At(index); ASSERT(code != NULL); if (code->compile_timestamp() > timestamp) { // Code is newer than sample. Fall back to dead code table. index = dead_table->FindCodeIndexForPC(pc); ASSERT(index >= 0); code = dead_table->At(index); } } ASSERT(code != NULL); ASSERT(code->Contains(pc)); ASSERT(code->compile_timestamp() <= timestamp); return code; } void RegisterProfileCodeTag(uword tag) { if (tag == 0) { // No tag. return; } ProfileCodeTable* tag_table = profile_->tag_code_; intptr_t index = tag_table->FindCodeIndexForPC(tag); if (index >= 0) { // Already created. return; } ProfileCode* code = new ProfileCode(ProfileCode::kTagCode, tag, tag + 1, 0, null_code_); index = tag_table->InsertCode(code); ASSERT(index >= 0); } ProfileCode* CreateProfileCodeReused(uword pc) { ProfileCode* code = new ProfileCode(ProfileCode::kReusedCode, pc, pc + 1, 0, null_code_); return code; } bool IsPCInDartHeap(uword pc) { return vm_isolate_->heap()->CodeContains(pc) || thread_->isolate()->heap()->CodeContains(pc); } ProfileCode* FindOrRegisterNativeProfileCode(uword pc) { // Check if |pc| is already known in the live code table. ProfileCodeTable* live_table = profile_->live_code_; ProfileCode* profile_code = live_table->FindCodeForPC(pc); if (profile_code != NULL) { return profile_code; } // We haven't seen this pc yet. Code& code = Code::Handle(thread_->zone()); // Check NativeSymbolResolver for pc. uintptr_t native_start = 0; char* native_name = NativeSymbolResolver::LookupSymbolName(pc, &native_start); if (native_name == NULL) { // Failed to find a native symbol for pc. native_start = pc; } #if defined(HOST_ARCH_ARM) // The symbol for a Thumb function will be xxx1, but we may have samples // at function entry which will have pc xxx0. native_start &= ~1; #endif if (native_start > pc) { // Bogus lookup result. if (native_name != NULL) { NativeSymbolResolver::FreeSymbolName(native_name); native_name = NULL; } native_start = pc; } if ((pc - native_start) > (32 * KB)) { // Suspect lookup result. More likely dladdr going off the rails than a // jumbo function. if (native_name != NULL) { NativeSymbolResolver::FreeSymbolName(native_name); native_name = NULL; } native_start = pc; } ASSERT(pc >= native_start); profile_code = new ProfileCode(ProfileCode::kNativeCode, native_start, pc + 1, 0, code); if (native_name != NULL) { profile_code->SetName(native_name); NativeSymbolResolver::FreeSymbolName(native_name); } RegisterLiveProfileCode(profile_code); return profile_code; } void RegisterLiveProfileCode(ProfileCode* code) { ProfileCodeTable* live_table = profile_->live_code_; intptr_t index = live_table->InsertCode(code); ASSERT(index >= 0); } ProfileCode* FindOrRegisterDeadProfileCode(uword pc) { ProfileCodeTable* dead_table = profile_->dead_code_; ProfileCode* code = dead_table->FindCodeForPC(pc); if (code != NULL) { return code; } // Create a new dead code entry. intptr_t index = dead_table->InsertCode(CreateProfileCodeReused(pc)); ASSERT(index >= 0); return dead_table->At(index); } ProfileCode* FindOrRegisterProfileCode(uword pc, int64_t timestamp) { ProfileCodeTable* live_table = profile_->live_code_; ProfileCode* code = live_table->FindCodeForPC(pc); if ((code != NULL) && (code->compile_timestamp() <= timestamp)) { // Code was compiled before sample was taken. return code; } if ((code == NULL) && !IsPCInDartHeap(pc)) { // Not a PC from Dart code. Check with native code. return FindOrRegisterNativeProfileCode(pc); } // We either didn't find the code or it was compiled after the sample. return FindOrRegisterDeadProfileCode(pc); } Profile::TagOrder tag_order() const { return tag_order_; } bool vm_tags_emitted() const { return (tag_order_ == Profile::kUserVM) || (tag_order_ == Profile::kVMUser) || (tag_order_ == Profile::kVM); } bool TagsEnabled(intptr_t extra_tags_bits) const { return (extra_tags_ & extra_tags_bits) != 0; } Thread* thread_; Isolate* vm_isolate_; SampleFilter* filter_; SampleBuffer* sample_buffer_; Profile::TagOrder tag_order_; intptr_t extra_tags_; Profile* profile_; DeoptimizedCodeSet* deoptimized_code_; const Code& null_code_; const Function& null_function_; bool tick_functions_; bool inclusive_tree_; ProfileCodeInlinedFunctionsCache inlined_functions_cache_; ProcessedSampleBuffer* samples_; ProfileInfoKind info_kind_; }; // ProfileBuilder. Profile::Profile(Isolate* isolate) : isolate_(isolate), zone_(Thread::Current()->zone()), samples_(NULL), live_code_(NULL), dead_code_(NULL), tag_code_(NULL), functions_(NULL), dead_code_index_offset_(-1), tag_code_index_offset_(-1), min_time_(kMaxInt64), max_time_(0) { ASSERT(isolate_ != NULL); for (intptr_t i = 0; i < kNumTrieKinds; i++) { roots_[i] = NULL; } } void Profile::Build(Thread* thread, SampleFilter* filter, SampleBuffer* sample_buffer, TagOrder tag_order, intptr_t extra_tags) { ProfileBuilder builder(thread, filter, sample_buffer, tag_order, extra_tags, this); builder.Build(); } intptr_t Profile::NumFunctions() const { return functions_->length(); } ProfileFunction* Profile::GetFunction(intptr_t index) { ASSERT(functions_ != NULL); return functions_->At(index); } ProfileCode* Profile::GetCode(intptr_t index) { ASSERT(live_code_ != NULL); ASSERT(dead_code_ != NULL); ASSERT(tag_code_ != NULL); ASSERT(dead_code_index_offset_ >= 0); ASSERT(tag_code_index_offset_ >= 0); // Code indexes span three arrays. // 0 ... |live_code| // |live_code| ... |dead_code| // |dead_code| ... |tag_code| if (index < dead_code_index_offset_) { return live_code_->At(index); } if (index < tag_code_index_offset_) { index -= dead_code_index_offset_; return dead_code_->At(index); } index -= tag_code_index_offset_; return tag_code_->At(index); } ProfileTrieNode* Profile::GetTrieRoot(TrieKind trie_kind) { return roots_[static_cast(trie_kind)]; } void Profile::PrintHeaderJSON(JSONObject* obj) { obj->AddProperty("samplePeriod", static_cast(FLAG_profile_period)); obj->AddProperty("stackDepth", static_cast(FLAG_max_profile_depth)); obj->AddProperty("sampleCount", sample_count()); obj->AddProperty("timeSpan", MicrosecondsToSeconds(GetTimeSpan())); obj->AddPropertyTimeMicros("timeOriginMicros", min_time()); obj->AddPropertyTimeMicros("timeExtentMicros", GetTimeSpan()); ProfilerCounters counters = Profiler::counters(); { JSONObject counts(obj, "counters"); counts.AddProperty64("bail_out_unknown_task", counters.bail_out_unknown_task); counts.AddProperty64("bail_out_jump_to_exception_handler", counters.bail_out_jump_to_exception_handler); counts.AddProperty64("bail_out_check_isolate", counters.bail_out_check_isolate); counts.AddProperty64("single_frame_sample_deoptimizing", counters.single_frame_sample_deoptimizing); counts.AddProperty64("single_frame_sample_register_check", counters.single_frame_sample_register_check); counts.AddProperty64( "single_frame_sample_get_and_validate_stack_bounds", counters.single_frame_sample_get_and_validate_stack_bounds); counts.AddProperty64("stack_walker_native", counters.stack_walker_native); counts.AddProperty64("stack_walker_dart_exit", counters.stack_walker_dart_exit); counts.AddProperty64("stack_walker_dart", counters.stack_walker_dart); counts.AddProperty64("stack_walker_none", counters.stack_walker_none); } } static const intptr_t kRootFrameId = 0; void Profile::PrintTimelineFrameJSON(JSONObject* frames, ProfileTrieNode* current, ProfileTrieNode* parent, intptr_t* next_id) { ASSERT(current->frame_id() == -1); const intptr_t id = *next_id; *next_id = id + 1; current->set_frame_id(id); ASSERT(current->frame_id() != -1); if (id != kRootFrameId) { // The samples from many isolates may be merged into a single timeline, // so prefix frames id with the isolate. intptr_t isolate_id = reinterpret_cast(isolate_); const char* key = zone_->PrintToString("%" Pd "-%" Pd, isolate_id, current->frame_id()); JSONObject frame(frames, key); frame.AddProperty("category", "Dart"); ProfileFunction* func = GetFunction(current->table_index()); frame.AddProperty("name", func->Name()); if ((parent != NULL) && (parent->frame_id() != kRootFrameId)) { ASSERT(parent->frame_id() != -1); frame.AddPropertyF("parent", "%" Pd "-%" Pd, isolate_id, parent->frame_id()); } } for (intptr_t i = 0; i < current->NumChildren(); i++) { ProfileTrieNode* child = current->At(i); PrintTimelineFrameJSON(frames, child, current, next_id); } } void Profile::PrintTimelineJSON(JSONStream* stream) { ScopeTimer sw("Profile::PrintTimelineJSON", FLAG_trace_profiler); JSONObject obj(stream); obj.AddProperty("type", "_CpuProfileTimeline"); PrintHeaderJSON(&obj); { JSONObject frames(&obj, "stackFrames"); ProfileTrieNode* root = GetTrieRoot(kInclusiveFunction); intptr_t next_id = kRootFrameId; PrintTimelineFrameJSON(&frames, root, NULL, &next_id); } { JSONArray events(&obj, "traceEvents"); intptr_t pid = OS::ProcessId(); intptr_t isolate_id = reinterpret_cast(isolate_); for (intptr_t sample_index = 0; sample_index < samples_->length(); sample_index++) { ProcessedSample* sample = samples_->At(sample_index); JSONObject event(&events); event.AddProperty("ph", "P"); // kind = sample event // Add a blank name to keep about:tracing happy. event.AddProperty("name", ""); event.AddProperty64("pid", pid); event.AddProperty64("tid", OSThread::ThreadIdToIntPtr(sample->tid())); event.AddPropertyTimeMicros("ts", sample->timestamp()); event.AddProperty("cat", "Dart"); if (!Isolate::IsVMInternalIsolate(isolate_)) { JSONObject args(&event, "args"); args.AddProperty("mode", "basic"); } ProfileTrieNode* trie = sample->timeline_trie(); ASSERT(trie->frame_id() != -1); event.AddPropertyF("sf", "%" Pd "-%" Pd, isolate_id, trie->frame_id()); } } } ProfileFunction* Profile::FindFunction(const Function& function) { return (functions_ != NULL) ? functions_->Lookup(function) : NULL; } void Profile::PrintProfileJSON(JSONStream* stream) { ScopeTimer sw("Profile::PrintProfileJSON", FLAG_trace_profiler); JSONObject obj(stream); obj.AddProperty("type", "_CpuProfile"); PrintHeaderJSON(&obj); { JSONArray codes(&obj, "codes"); for (intptr_t i = 0; i < live_code_->length(); i++) { ProfileCode* code = live_code_->At(i); ASSERT(code != NULL); code->PrintToJSONArray(&codes); } for (intptr_t i = 0; i < dead_code_->length(); i++) { ProfileCode* code = dead_code_->At(i); ASSERT(code != NULL); code->PrintToJSONArray(&codes); } for (intptr_t i = 0; i < tag_code_->length(); i++) { ProfileCode* code = tag_code_->At(i); ASSERT(code != NULL); code->PrintToJSONArray(&codes); } } { JSONArray functions(&obj, "functions"); for (intptr_t i = 0; i < functions_->length(); i++) { ProfileFunction* function = functions_->At(i); ASSERT(function != NULL); function->PrintToJSONArray(&functions); } } { JSONArray code_trie(&obj, "exclusiveCodeTrie"); ProfileTrieNode* root = roots_[static_cast(kExclusiveCode)]; ASSERT(root != NULL); root->PrintToJSONArray(&code_trie); } { JSONArray code_trie(&obj, "inclusiveCodeTrie"); ProfileTrieNode* root = roots_[static_cast(kInclusiveCode)]; ASSERT(root != NULL); root->PrintToJSONArray(&code_trie); } { JSONArray function_trie(&obj, "exclusiveFunctionTrie"); ProfileTrieNode* root = roots_[static_cast(kExclusiveFunction)]; ASSERT(root != NULL); root->PrintToJSONArray(&function_trie); } { JSONArray function_trie(&obj, "inclusiveFunctionTrie"); ProfileTrieNode* root = roots_[static_cast(kInclusiveFunction)]; ASSERT(root != NULL); root->PrintToJSONArray(&function_trie); } } void ProfileTrieWalker::Reset(Profile::TrieKind trie_kind) { code_trie_ = Profile::IsCodeTrie(trie_kind); parent_ = NULL; current_ = profile_->GetTrieRoot(trie_kind); ASSERT(current_ != NULL); } const char* ProfileTrieWalker::CurrentName() { if (current_ == NULL) { return NULL; } if (code_trie_) { ProfileCode* code = profile_->GetCode(current_->table_index()); return code->name(); } else { ProfileFunction* func = profile_->GetFunction(current_->table_index()); return func->Name(); } UNREACHABLE(); return NULL; } intptr_t ProfileTrieWalker::CurrentNodeTickCount() { if (current_ == NULL) { return -1; } return current_->count(); } intptr_t ProfileTrieWalker::CurrentInclusiveTicks() { if (current_ == NULL) { return -1; } if (code_trie_) { ProfileCode* code = profile_->GetCode(current_->table_index()); return code->inclusive_ticks(); } else { ProfileFunction* func = profile_->GetFunction(current_->table_index()); return func->inclusive_ticks(); } UNREACHABLE(); return -1; } intptr_t ProfileTrieWalker::CurrentExclusiveTicks() { if (current_ == NULL) { return -1; } if (code_trie_) { ProfileCode* code = profile_->GetCode(current_->table_index()); return code->exclusive_ticks(); } else { ProfileFunction* func = profile_->GetFunction(current_->table_index()); return func->exclusive_ticks(); } UNREACHABLE(); return -1; } intptr_t ProfileTrieWalker::CurrentInclusiveAllocations() { if (current_ == NULL) { return -1; } return current_->inclusive_allocations(); } intptr_t ProfileTrieWalker::CurrentExclusiveAllocations() { if (current_ == NULL) { return -1; } return current_->exclusive_allocations(); } const char* ProfileTrieWalker::CurrentToken() { if (current_ == NULL) { return NULL; } if (code_trie_) { return NULL; } ProfileFunction* func = profile_->GetFunction(current_->table_index()); const Function& function = *(func->function()); if (function.IsNull()) { // No function. return NULL; } Zone* zone = Thread::Current()->zone(); const Script& script = Script::Handle(zone, function.script()); if (script.IsNull()) { // No script. return NULL; } ProfileFunctionSourcePosition pfsp(TokenPosition::kNoSource); if (!func->GetSinglePosition(&pfsp)) { // Not exactly one source position. return NULL; } TokenPosition token_pos = pfsp.token_pos(); if (!token_pos.IsSourcePosition()) { // Not a location in a script. return NULL; } if (token_pos.IsSynthetic()) { token_pos = token_pos.FromSynthetic(); } String& str = String::Handle(zone); if (script.kind() == RawScript::kKernelTag) { intptr_t line = 0, column = 0, token_len = 0; script.GetTokenLocation(token_pos, &line, &column, &token_len); str = script.GetSnippet(line, column, line, column + token_len); } else { const TokenStream& token_stream = TokenStream::Handle(zone, script.tokens()); if (token_stream.IsNull()) { // No token position. return NULL; } TokenStream::Iterator iterator(zone, token_stream, token_pos); str = iterator.CurrentLiteral(); } return str.IsNull() ? NULL : str.ToCString(); } bool ProfileTrieWalker::Down() { if ((current_ == NULL) || (current_->NumChildren() == 0)) { return false; } parent_ = current_; current_ = current_->At(0); return true; } bool ProfileTrieWalker::NextSibling() { if (parent_ == NULL) { return false; } intptr_t current_index = parent_->IndexOf(current_); if (current_index < 0) { return false; } current_index++; if (current_index >= parent_->NumChildren()) { return false; } current_ = parent_->At(current_index); return true; } intptr_t ProfileTrieWalker::SiblingCount() { ASSERT(parent_ != NULL); return parent_->NumChildren(); } void ProfilerService::PrintJSONImpl(Thread* thread, JSONStream* stream, Profile::TagOrder tag_order, intptr_t extra_tags, SampleFilter* filter, SampleBuffer* sample_buffer, bool as_timeline) { Isolate* isolate = thread->isolate(); // Disable thread interrupts while processing the buffer. DisableThreadInterruptsScope dtis(thread); if (sample_buffer == NULL) { stream->PrintError(kFeatureDisabled, NULL); return; } { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); profile.Build(thread, filter, sample_buffer, tag_order, extra_tags); if (as_timeline) { profile.PrintTimelineJSON(stream); } else { profile.PrintProfileJSON(stream); } } } class NoAllocationSampleFilter : public SampleFilter { public: NoAllocationSampleFilter(Dart_Port port, intptr_t thread_task_mask, int64_t time_origin_micros, int64_t time_extent_micros) : SampleFilter(port, thread_task_mask, time_origin_micros, time_extent_micros) {} bool FilterSample(Sample* sample) { return !sample->is_allocation_sample(); } }; void ProfilerService::PrintJSON(JSONStream* stream, Profile::TagOrder tag_order, intptr_t extra_tags, int64_t time_origin_micros, int64_t time_extent_micros) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask, time_origin_micros, time_extent_micros); const bool as_timeline = false; PrintJSONImpl(thread, stream, tag_order, extra_tags, &filter, Profiler::sample_buffer(), as_timeline); } class ClassAllocationSampleFilter : public SampleFilter { public: ClassAllocationSampleFilter(Dart_Port port, const Class& cls, intptr_t thread_task_mask, int64_t time_origin_micros, int64_t time_extent_micros) : SampleFilter(port, thread_task_mask, time_origin_micros, time_extent_micros), cls_(Class::Handle(cls.raw())) { ASSERT(!cls_.IsNull()); } bool FilterSample(Sample* sample) { return sample->is_allocation_sample() && (sample->allocation_cid() == cls_.id()); } private: const Class& cls_; }; void ProfilerService::PrintAllocationJSON(JSONStream* stream, Profile::TagOrder tag_order, const Class& cls, int64_t time_origin_micros, int64_t time_extent_micros) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); ClassAllocationSampleFilter filter(isolate->main_port(), cls, Thread::kMutatorTask, time_origin_micros, time_extent_micros); const bool as_timeline = false; PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter, Profiler::sample_buffer(), as_timeline); } void ProfilerService::PrintNativeAllocationJSON(JSONStream* stream, Profile::TagOrder tag_order, int64_t time_origin_micros, int64_t time_extent_micros) { Thread* thread = Thread::Current(); NativeAllocationSampleFilter filter(time_origin_micros, time_extent_micros); const bool as_timeline = false; PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter, Profiler::allocation_sample_buffer(), as_timeline); } void ProfilerService::PrintTimelineJSON(JSONStream* stream, Profile::TagOrder tag_order, int64_t time_origin_micros, int64_t time_extent_micros) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); const intptr_t thread_task_mask = Thread::kMutatorTask | Thread::kCompilerTask | Thread::kSweeperTask | Thread::kMarkerTask; NoAllocationSampleFilter filter(isolate->main_port(), thread_task_mask, time_origin_micros, time_extent_micros); const bool as_timeline = true; PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter, Profiler::sample_buffer(), as_timeline); } void ProfilerService::ClearSamples() { SampleBuffer* sample_buffer = Profiler::sample_buffer(); if (sample_buffer == NULL) { return; } Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); // Disable thread interrupts while processing the buffer. DisableThreadInterruptsScope dtis(thread); ClearProfileVisitor clear_profile(isolate); sample_buffer->VisitSamples(&clear_profile); } #endif // !PRODUCT } // namespace dart