| // 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 <memory> | |
| #include "platform/text_buffer.h" | |
| #include "vm/growable_array.h" | |
| #include "vm/hash_map.h" | |
| #include "vm/heap/safepoint.h" | |
| #if !defined(PRODUCT) | |
| #include "vm/json_stream.h" | |
| #endif | |
| #include "vm/log.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" | |
| #include "vm/service.h" | |
| #include "vm/service_event.h" | |
| #include "vm/timeline.h" | |
| #if defined(SUPPORT_PERFETTO) | |
| #include "perfetto/ext/tracing/core/trace_packet.h" | |
| #include "perfetto/protozero/scattered_heap_buffer.h" | |
| #include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" | |
| #include "vm/perfetto_utils.h" | |
| #endif // defined(SUPPORT_PERFETTO) | |
| namespace dart { | |
| DECLARE_FLAG(int, max_profile_depth); | |
| DECLARE_FLAG(int, profile_period); | |
| DECLARE_FLAG(bool, profile_vm); | |
| #if defined(DART_INCLUDE_PROFILER) | |
| 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.ptr())), | |
| 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_ != nullptr) { | |
| return name_; | |
| } | |
| ASSERT(!function_.IsNull()); | |
| const String& func_name = | |
| String::Handle(function_.QualifiedUserVisibleName()); | |
| return func_name.ToCString(); | |
| } | |
| const char* ProfileFunction::ResolvedScriptUrl() const { | |
| if (function_.IsNull()) { | |
| return nullptr; | |
| } | |
| const Script& script = Script::Handle(function_.script()); | |
| if (script.IsNull()) { | |
| return nullptr; | |
| } | |
| const String& uri = String::Handle(script.resolved_url()); | |
| if (uri.IsNull()) { | |
| return nullptr; | |
| } | |
| return uri.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]; | |
| const intptr_t cmp = | |
| TokenPosition::CompareForSorting(position.token_pos(), token_position); | |
| if (cmp > 0) { | |
| // Found insertion point. | |
| break; | |
| } else if (cmp == 0) { | |
| if (FLAG_trace_profiler_verbose) { | |
| OS::PrintErr("Ticking source position %s %s\n", | |
| exclusive ? "exclusive" : "inclusive", | |
| token_position.ToCString()); | |
| } | |
| // Found existing position, tick it. | |
| position.Tick(exclusive); | |
| return; | |
| } | |
| } | |
| // Add new one, sorted by token position value. | |
| ProfileFunctionSourcePosition pfsp(token_position); | |
| if (FLAG_trace_profiler_verbose) { | |
| OS::PrintErr("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 ""; | |
| } | |
| } | |
| #if !defined(PRODUCT) | |
| void ProfileFunction::PrintToJSONObject(JSONObject* func) { | |
| func->AddProperty("type", "NativeFunction"); | |
| func->AddProperty("name", name()); | |
| func->AddProperty("_kind", KindToCString(kind())); | |
| } | |
| void ProfileFunction::PrintToJSONArray(JSONArray* functions, | |
| bool print_only_ids) { | |
| if (print_only_ids) { | |
| JSONObject obj(functions); | |
| if (kind() == kDartFunction) { | |
| ASSERT(!function_.IsNull()); | |
| obj.AddProperty("type", "@Object"); | |
| function_.AddFunctionServiceId(obj); | |
| } else { | |
| PrintToJSONObject(&obj); | |
| } | |
| return; | |
| } | |
| JSONObject obj(functions); | |
| obj.AddProperty("type", "ProfileFunction"); | |
| obj.AddProperty("kind", KindToCString(kind())); | |
| obj.AddProperty("inclusiveTicks", inclusive_ticks()); | |
| obj.AddProperty("exclusiveTicks", exclusive_ticks()); | |
| obj.AddProperty("resolvedUrl", ResolvedScriptUrl()); | |
| 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); | |
| } | |
| } | |
| } | |
| #endif // !defined(PRODUCT) | |
| 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 == nullptr) { | |
| 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 AbstractCode code) | |
| : kind_(kind), | |
| start_(start), | |
| end_(end), | |
| exclusive_ticks_(0), | |
| inclusive_ticks_(0), | |
| inclusive_serial_(-1), | |
| code_(code), | |
| name_(nullptr), | |
| compile_timestamp_(0), | |
| function_(nullptr), | |
| 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 != nullptr); | |
| 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 == nullptr) { | |
| name_ = nullptr; | |
| } | |
| intptr_t len = strlen(name) + 1; | |
| name_ = Thread::Current()->zone()->Alloc<char>(len); | |
| strncpy(name_, name, len); | |
| } | |
| 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); | |
| } | |
| } | |
| #if !defined(PRODUCT) | |
| 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_ != nullptr); | |
| 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_ != nullptr); | |
| 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_ != nullptr); | |
| 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_ != nullptr); | |
| 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 nullptr; | |
| } | |
| 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_.handle()); | |
| } 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()); | |
| } | |
| } | |
| } | |
| #endif // !defined(PRODUCT) | |
| class ProfileFunctionTable : public ZoneObject { | |
| public: | |
| ProfileFunctionTable() | |
| : null_function_(Function::ZoneHandle()), | |
| unknown_function_(nullptr), | |
| table_(8) { | |
| unknown_function_ = | |
| Add(ProfileFunction::kUnknownFunction, "<unknown Dart function>"); | |
| } | |
| ProfileFunction* LookupOrAdd(const Function& function) { | |
| ASSERT(!function.IsNull()); | |
| ProfileFunction* profile_function = Lookup(function); | |
| if (profile_function != nullptr) { | |
| return profile_function; | |
| } | |
| return Add(function); | |
| } | |
| ProfileFunction* Lookup(const Function& function) { | |
| ASSERT(!function.IsNull()); | |
| return function_hash_.LookupValue(&function); | |
| } | |
| ProfileFunction* GetUnknown() { | |
| ASSERT(unknown_function_ != nullptr); | |
| 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 != nullptr); | |
| 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) == nullptr); | |
| ProfileFunction* profile_function = new ProfileFunction( | |
| ProfileFunction::kDartFunction, nullptr, 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 uword Hash(Key key) { return key->Hash(); } | |
| static inline bool IsKeyEqual(Pair kv, Key key) { | |
| return kv->function()->ptr() == key->ptr(); | |
| } | |
| }; | |
| const Function& null_function_; | |
| ProfileFunction* unknown_function_; | |
| ZoneGrowableArray<ProfileFunction*> table_; | |
| DirectChainedHashMap<ProfileFunctionTableTrait> function_hash_; | |
| }; | |
| 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 = nullptr; | |
| ProfileCode* hi_code = nullptr; | |
| const uword pc = new_code->end() - 1; | |
| FindNeighbors(pc, &lo, &hi, &lo_code, &hi_code); | |
| ASSERT((lo_code != nullptr) || (hi_code != nullptr)); | |
| 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 = nullptr; | |
| *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 = nullptr; | |
| 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)); | |
| } | |
| } | |
| } | |
| void ProfileCodeInlinedFunctionsCache::Get( | |
| uword pc, | |
| const Code& code, | |
| ProcessedSample* sample, | |
| intptr_t frame_index, | |
| // Outputs: | |
| GrowableArray<const Function*>** inlined_functions, | |
| GrowableArray<TokenPosition>** 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); | |
| } | |
| bool ProfileCodeInlinedFunctionsCache::FindInCache( | |
| uword pc, | |
| intptr_t offset, | |
| GrowableArray<const Function*>** inlined_functions, | |
| GrowableArray<TokenPosition>** 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 = nullptr; | |
| *inlined_token_positions = nullptr; | |
| } 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 ProfileCodeInlinedFunctionsCache::Add( | |
| uword pc, | |
| const Code& code, | |
| ProcessedSample* sample, | |
| intptr_t frame_index, | |
| // Outputs: | |
| GrowableArray<const Function*>** inlined_functions, | |
| GrowableArray<TokenPosition>** 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 = nullptr; | |
| *inlined_token_positions = nullptr; | |
| *token_position = cache_entry->token_position = TokenPosition::kNoSource; | |
| 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 ProfileCodeInlinedFunctionsCache::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; | |
| } | |
| class ProfileBuilder : public ValueObject { | |
| public: | |
| enum ProfileInfoKind { | |
| kNone, | |
| kOptimized, | |
| kUnoptimized, | |
| kNative, | |
| kInlineStart, | |
| kInlineFinish, | |
| kNumProfileInfoKind, | |
| }; | |
| ProfileBuilder(Thread* thread, | |
| Isolate* isolate, | |
| SampleFilter* filter, | |
| SampleBlockBuffer* sample_buffer, | |
| Profile* profile) | |
| : thread_(thread), | |
| isolate_(isolate), | |
| filter_(filter), | |
| sample_buffer_(sample_buffer), | |
| profile_(profile), | |
| null_code_(Code::null()), | |
| null_function_(Function::ZoneHandle()), | |
| inclusive_tree_(false), | |
| inlined_functions_cache_(new ProfileCodeInlinedFunctionsCache()), | |
| samples_(nullptr), | |
| info_kind_(kNone) { | |
| ASSERT(profile_ != nullptr); | |
| } | |
| void Build() { | |
| ScopeTimer sw("ProfileBuilder::Build", FLAG_trace_profiler); | |
| if (!FilterSamples()) { | |
| return; | |
| } | |
| Setup(); | |
| BuildCodeTable(); | |
| FinalizeCodeIndexes(); | |
| BuildFunctionTable(); | |
| PopulateFunctionTicks(); | |
| SanitizeMinMaxTimes(); | |
| } | |
| Thread* thread() const { return thread_; } | |
| Isolate* isolate() const { return isolate_; } | |
| ProfileFunctionTable* function_table() const { return profile_->functions_; } | |
| 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()); | |
| } | |
| 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); | |
| if (sample_buffer_ == nullptr) { | |
| return false; | |
| } | |
| samples_ = sample_buffer_->BuildProcessedSampleBuffer(isolate_, 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); | |
| // 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 != nullptr); | |
| const AbstractCode code = descriptor->code(); | |
| RegisterLiveProfileCode(new ProfileCode( | |
| ProfileCode::kDartCode, code.PayloadStart(), | |
| code.PayloadStart() + code.Size(), code.compile_timestamp(), code)); | |
| thread_->CheckForSafepoint(); | |
| } | |
| // 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 != nullptr); | |
| code->Tick(pc, IsExecutingFrame(sample, frame_index), sample_index); | |
| } | |
| TickExitFrame(sample->vm_tag(), sample_index, sample); | |
| thread_->CheckForSafepoint(); | |
| } | |
| } | |
| 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 != nullptr); | |
| 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 != nullptr); | |
| 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 != nullptr); | |
| 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_; | |
| for (intptr_t i = 0; i < live_table->length(); i++) { | |
| ProfileCode* code = live_table->At(i); | |
| ASSERT(code != nullptr); | |
| code->SetFunctionAndName(this); | |
| thread_->CheckForSafepoint(); | |
| } | |
| for (intptr_t i = 0; i < dead_table->length(); i++) { | |
| ProfileCode* code = dead_table->At(i); | |
| ASSERT(code != nullptr); | |
| code->SetFunctionAndName(this); | |
| thread_->CheckForSafepoint(); | |
| } | |
| for (intptr_t i = 0; i < tag_table->length(); i++) { | |
| ProfileCode* code = tag_table->At(i); | |
| ASSERT(code != nullptr); | |
| code->SetFunctionAndName(this); | |
| thread_->CheckForSafepoint(); | |
| } | |
| } | |
| void PopulateFunctionTicks() { | |
| ScopeTimer sw("ProfileBuilder::PopulateFunctionTicks", FLAG_trace_profiler); | |
| for (intptr_t sample_index = 0; sample_index < samples_->length(); | |
| sample_index++) { | |
| ProcessedSample* sample = samples_->At(sample_index); | |
| // Walk the sampled PCs. | |
| for (intptr_t frame_index = 0; frame_index < sample->length(); | |
| frame_index++) { | |
| ASSERT(sample->At(frame_index) != 0); | |
| ProcessFrame(sample_index, sample, frame_index); | |
| } | |
| if (sample->truncated()) { | |
| InclusiveTickTruncatedTag(sample); | |
| } | |
| } | |
| } | |
| void ProcessFrame(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 != nullptr); | |
| const intptr_t code_index = profile_code->code_table_index(); | |
| ASSERT(profile_code != nullptr); | |
| Zone* const zone = Thread::Current()->zone(); | |
| GrowableArray<const Function*>* inlined_functions = nullptr; | |
| GrowableArray<TokenPosition>* inlined_token_positions = nullptr; | |
| TokenPosition token_position = TokenPosition::kNoSource; | |
| Code& code = Code::ZoneHandle(zone); | |
| if (profile_code->code().IsCode()) { | |
| code ^= profile_code->code().ptr(); | |
| inlined_functions_cache_->Get(pc, code, sample, frame_index, | |
| &inlined_functions, | |
| &inlined_token_positions, &token_position); | |
| if (FLAG_trace_profiler_verbose && (inlined_functions != nullptr)) { | |
| for (intptr_t i = 0; i < inlined_functions->length(); i++) { | |
| const String& name = String::Handle( | |
| zone, (*inlined_functions)[i]->QualifiedScrubbedName()); | |
| THR_Print("InlinedFunction[%" Pd "] = {%s, %s}\n", i, | |
| name.ToCString(), | |
| (*inlined_token_positions)[i].ToCString()); | |
| } | |
| } | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (profile_code->code().IsBytecode()) { | |
| const auto& bytecode = | |
| Bytecode::CheckedHandle(zone, profile_code->code().ptr()); | |
| token_position = bytecode.GetTokenIndexOfPC(pc); | |
| } | |
| #endif | |
| if (code.IsNull() || (inlined_functions == nullptr) || | |
| (inlined_functions->length() <= 1)) { | |
| ProcessFunction(sample_index, sample, frame_index, function, | |
| token_position, code_index); | |
| return; | |
| } | |
| 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()); | |
| // Append the inlined children. | |
| for (intptr_t i = inlined_functions->length() - 1; i >= 0; i--) { | |
| const Function* inlined_function = (*inlined_functions)[i]; | |
| ASSERT(inlined_function != nullptr); | |
| ASSERT(!inlined_function->IsNull()); | |
| TokenPosition inlined_token_position = (*inlined_token_positions)[i]; | |
| ProcessInlinedFunction(sample_index, sample, frame_index + i, | |
| inlined_function, inlined_token_position, | |
| code_index); | |
| } | |
| } | |
| void ProcessInlinedFunction(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 != nullptr); | |
| ProcessFunction(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 | |
| return IsExecutingFrame(sample, frame_index) || !FLAG_profile_vm; | |
| } | |
| void ProcessFunction(intptr_t sample_index, | |
| ProcessedSample* sample, | |
| intptr_t frame_index, | |
| ProfileFunction* function, | |
| TokenPosition token_position, | |
| intptr_t code_index) { | |
| if (!function->is_visible()) { | |
| return; | |
| } | |
| 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); | |
| } | |
| // 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 != nullptr); | |
| ProfileFunction* function = code->function(); | |
| function->IncInclusiveTicks(); | |
| } | |
| 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; | |
| } | |
| } | |
| 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 != nullptr); | |
| 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 != nullptr); | |
| ProfileFunction* function = code->function(); | |
| ASSERT(function != nullptr); | |
| function->Tick(true, serial, TokenPosition::kNoSource); | |
| } | |
| 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 != nullptr); | |
| 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 != nullptr); | |
| ProfileFunction* function = code->function(); | |
| ASSERT(function != nullptr); | |
| 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) { | |
| return profile_->GetCodeFromPC(pc, timestamp); | |
| } | |
| 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 thread_->isolate_group()->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 != nullptr) { | |
| return profile_code; | |
| } | |
| // We haven't seen this pc yet. | |
| // Check NativeSymbolResolver for pc. | |
| uword native_start = 0; | |
| const char* native_name = | |
| NativeSymbolResolver::LookupSymbolName(pc, &native_start); | |
| if (native_name == nullptr) { | |
| // 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 != nullptr) { | |
| NativeSymbolResolver::FreeSymbolName(native_name); | |
| native_name = nullptr; | |
| } | |
| 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 != nullptr) { | |
| NativeSymbolResolver::FreeSymbolName(native_name); | |
| native_name = nullptr; | |
| } | |
| native_start = pc; | |
| } | |
| ASSERT(pc >= native_start); | |
| ASSERT(pc < (pc + 1)); // Should not overflow. | |
| profile_code = new ProfileCode(ProfileCode::kNativeCode, native_start, | |
| pc + 1, 0, null_code_); | |
| if (native_name != nullptr) { | |
| 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 != nullptr) { | |
| 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 != nullptr) && (code->compile_timestamp() <= timestamp)) { | |
| // Code was compiled before sample was taken. | |
| return code; | |
| } | |
| if ((code == nullptr) && !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); | |
| } | |
| Thread* thread_; | |
| Isolate* isolate_; | |
| SampleFilter* filter_; | |
| SampleBlockBuffer* sample_buffer_; | |
| Profile* profile_; | |
| const AbstractCode null_code_; | |
| const Function& null_function_; | |
| bool inclusive_tree_; | |
| ProfileCodeInlinedFunctionsCache* inlined_functions_cache_; | |
| ProcessedSampleBuffer* samples_; | |
| ProfileInfoKind info_kind_; | |
| }; // ProfileBuilder. | |
| ProfileFunction* ProfileCode::SetFunctionAndName(ProfileBuilder* builder) { | |
| ASSERT(function_ == nullptr); | |
| auto table = builder->function_table(); | |
| ProfileFunction* function = nullptr; | |
| if ((kind() == kReusedCode) || (kind() == kCollectedCode)) { | |
| if (name() == nullptr) { | |
| // 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() == nullptr) { | |
| // Lazily set generated name. | |
| const intptr_t kBuffSize = 512; | |
| char buff[kBuffSize]; | |
| uword dso_base; | |
| const char* dso_name; | |
| if (NativeSymbolResolver::LookupSharedObject(start(), &dso_base, | |
| &dso_name)) { | |
| uword dso_offset = start() - dso_base; | |
| Utils::SNPrint(&buff[0], kBuffSize - 1, "[Native] %s+0x%" Px, dso_name, | |
| dso_offset); | |
| NativeSymbolResolver::FreeSymbolName(dso_name); | |
| } else { | |
| Utils::SNPrint(&buff[0], kBuffSize - 1, "[Native] %" Px, start()); | |
| } | |
| SetName(buff); | |
| } | |
| function = table->AddNative(start(), name()); | |
| } else if (kind() == kTagCode) { | |
| if (name() == nullptr) { | |
| if (UserTags::IsUserTag(start())) { | |
| const char* tag_name = UserTags::TagName( | |
| builder->thread(), builder->isolate()->group(), start()); | |
| ASSERT(tag_name != nullptr); | |
| SetName(tag_name); | |
| } else if (VMTag::IsVMTag(start()) || VMTag::IsRuntimeEntryTag(start()) || | |
| VMTag::IsNativeEntryTag(start())) { | |
| const char* tag_name = VMTag::TagName(start()); | |
| ASSERT(tag_name != nullptr); | |
| 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 != nullptr); | |
| function->AddProfileCode(code_table_index()); | |
| function_ = function; | |
| return function_; | |
| } | |
| Profile::Profile() | |
| : zone_(Thread::Current()->zone()), | |
| samples_(nullptr), | |
| live_code_(nullptr), | |
| dead_code_(nullptr), | |
| tag_code_(nullptr), | |
| functions_(nullptr), | |
| dead_code_index_offset_(-1), | |
| tag_code_index_offset_(-1), | |
| min_time_(kMaxInt64), | |
| max_time_(0), | |
| sample_count_(0) {} | |
| void Profile::Build(Thread* thread, | |
| Isolate* isolate, | |
| SampleFilter* filter, | |
| SampleBlockBuffer* sample_buffer) { | |
| thread_ = thread; | |
| isolate_ = isolate; | |
| ASSERT(isolate != nullptr); | |
| // Disable thread interrupts while processing the buffer. | |
| DisableThreadInterruptsScope dtis(thread); | |
| ProfileBuilder builder(thread, isolate, filter, sample_buffer, this); | |
| builder.Build(); | |
| } | |
| ProcessedSample* Profile::SampleAt(intptr_t index) { | |
| ASSERT(index >= 0); | |
| ASSERT(index < sample_count_); | |
| return samples_->At(index); | |
| } | |
| intptr_t Profile::NumFunctions() const { | |
| return functions_->length(); | |
| } | |
| ProfileFunction* Profile::GetFunction(intptr_t index) { | |
| ASSERT(functions_ != nullptr); | |
| return functions_->At(index); | |
| } | |
| ProfileCode* Profile::GetCode(intptr_t index) { | |
| ASSERT(live_code_ != nullptr); | |
| ASSERT(dead_code_ != nullptr); | |
| ASSERT(tag_code_ != nullptr); | |
| 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); | |
| } | |
| ProfileCode* Profile::GetCodeFromPC(uword pc, int64_t timestamp) { | |
| intptr_t index = live_code_->FindCodeIndexForPC(pc); | |
| ProfileCode* code = nullptr; | |
| if (index < 0) { | |
| index = dead_code_->FindCodeIndexForPC(pc); | |
| ASSERT(index >= 0); | |
| code = dead_code_->At(index); | |
| } else { | |
| code = live_code_->At(index); | |
| ASSERT(code != nullptr); | |
| if (code->compile_timestamp() > timestamp) { | |
| // Code is newer than sample. Fall back to dead code table. | |
| index = dead_code_->FindCodeIndexForPC(pc); | |
| ASSERT(index >= 0); | |
| code = dead_code_->At(index); | |
| } | |
| } | |
| ASSERT(code != nullptr); | |
| ASSERT(code->Contains(pc)); | |
| ASSERT(code->compile_timestamp() <= timestamp); | |
| return code; | |
| } | |
| #if !defined(PRODUCT) | |
| void Profile::PrintHeaderJSON(JSONObject* obj) { | |
| intptr_t pid = OS::ProcessId(); | |
| obj->AddProperty("samplePeriod", Profiler::CurrentConfig().period_us); | |
| obj->AddProperty("maxStackDepth", Profiler::CurrentConfig().max_depth.load()); | |
| obj->AddProperty("sampleCount", sample_count()); | |
| obj->AddPropertyTimeMicros("timeOriginMicros", min_time()); | |
| obj->AddPropertyTimeMicros("timeExtentMicros", GetTimeSpan()); | |
| obj->AddProperty64("pid", pid); | |
| 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("bail_out_deoptimizing", | |
| counters.bail_out_deoptimizing); | |
| 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); | |
| } | |
| } | |
| void Profile::ProcessSampleFrameJSON(JSONArray* stack, | |
| ProfileCodeInlinedFunctionsCache* cache, | |
| ProcessedSample* sample, | |
| intptr_t frame_index) { | |
| const uword pc = sample->At(frame_index); | |
| ProfileCode* profile_code = GetCodeFromPC(pc, sample->timestamp()); | |
| ASSERT(profile_code != nullptr); | |
| ProfileFunction* function = profile_code->function(); | |
| ASSERT(function != nullptr); | |
| // Don't show stubs in stack traces. | |
| if (!function->is_visible() || | |
| (function->kind() == ProfileFunction::kStubFunction)) { | |
| return; | |
| } | |
| GrowableArray<const Function*>* inlined_functions = nullptr; | |
| GrowableArray<TokenPosition>* inlined_token_positions = nullptr; | |
| TokenPosition token_position = TokenPosition::kNoSource; | |
| Code& code = Code::ZoneHandle(); | |
| if (profile_code->code().IsCode()) { | |
| code ^= profile_code->code().ptr(); | |
| cache->Get(pc, code, sample, frame_index, &inlined_functions, | |
| &inlined_token_positions, &token_position); | |
| if (FLAG_trace_profiler_verbose && (inlined_functions != nullptr)) { | |
| 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 == nullptr) || | |
| (inlined_functions->length() <= 1)) { | |
| PrintFunctionFrameIndexJSON(stack, function); | |
| return; | |
| } | |
| 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()); | |
| for (intptr_t i = inlined_functions->length() - 1; i >= 0; i--) { | |
| const Function* inlined_function = (*inlined_functions)[i]; | |
| ASSERT(inlined_function != nullptr); | |
| ASSERT(!inlined_function->IsNull()); | |
| ProcessInlinedFunctionFrameJSON(stack, inlined_function); | |
| } | |
| } | |
| void Profile::ProcessInlinedFunctionFrameJSON( | |
| JSONArray* stack, | |
| const Function* inlined_function) { | |
| ProfileFunction* function = functions_->LookupOrAdd(*inlined_function); | |
| ASSERT(function != nullptr); | |
| PrintFunctionFrameIndexJSON(stack, function); | |
| } | |
| void Profile::PrintFunctionFrameIndexJSON(JSONArray* stack, | |
| ProfileFunction* function) { | |
| stack->AddValue64(function->table_index()); | |
| } | |
| void Profile::PrintCodeFrameIndexJSON(JSONArray* stack, | |
| ProcessedSample* sample, | |
| intptr_t frame_index) { | |
| ProfileCode* code = | |
| GetCodeFromPC(sample->At(frame_index), sample->timestamp()); | |
| const AbstractCode codeObj = code->code(); | |
| // Ignore stub code objects. | |
| if (codeObj.IsStubCode() || codeObj.IsAllocationStubCode() || | |
| codeObj.IsTypeTestStubCode()) { | |
| return; | |
| } | |
| stack->AddValue64(code->code_table_index()); | |
| } | |
| void Profile::PrintSamplesJSON(JSONObject* obj, bool code_samples) { | |
| JSONArray samples(obj, "samples"); | |
| // Note that |cache| is zone-allocated, so it does not need to be deallocated | |
| // manually. | |
| auto* cache = new ProfileCodeInlinedFunctionsCache(); | |
| for (intptr_t sample_index = 0; sample_index < samples_->length(); | |
| sample_index++) { | |
| JSONObject sample_obj(&samples); | |
| ProcessedSample* sample = samples_->At(sample_index); | |
| sample_obj.AddProperty64("tid", OSThread::ThreadIdToIntPtr(sample->tid())); | |
| sample_obj.AddPropertyTimeMicros("timestamp", sample->timestamp()); | |
| sample_obj.AddProperty("vmTag", VMTag::TagName(sample->vm_tag())); | |
| if (VMTag::IsNativeEntryTag(sample->vm_tag())) { | |
| sample_obj.AddProperty("nativeEntryTag", true); | |
| } | |
| if (VMTag::IsRuntimeEntryTag(sample->vm_tag())) { | |
| sample_obj.AddProperty("runtimeEntryTag", true); | |
| } | |
| if (UserTags::IsUserTag(sample->user_tag())) { | |
| sample_obj.AddProperty( | |
| "userTag", | |
| UserTags::TagName(thread(), isolate()->group(), sample->user_tag())); | |
| } | |
| if (sample->truncated()) { | |
| sample_obj.AddProperty("truncated", true); | |
| } | |
| { | |
| JSONArray stack(&sample_obj, "stack"); | |
| // Walk the sampled PCs. | |
| for (intptr_t frame_index = 0; frame_index < sample->length(); | |
| frame_index++) { | |
| ASSERT(sample->At(frame_index) != 0); | |
| ProcessSampleFrameJSON(&stack, cache, sample, frame_index); | |
| } | |
| } | |
| if (code_samples) { | |
| JSONArray stack(&sample_obj, "_codeStack"); | |
| for (intptr_t frame_index = 0; frame_index < sample->length(); | |
| frame_index++) { | |
| ASSERT(sample->At(frame_index) != 0); | |
| PrintCodeFrameIndexJSON(&stack, sample, frame_index); | |
| } | |
| } | |
| if (sample->IsAllocationSample()) { | |
| sample_obj.AddProperty64("classId", sample->allocation_cid()); | |
| sample_obj.AddProperty64("identityHashCode", | |
| sample->allocation_identity_hash()); | |
| } | |
| } | |
| } | |
| void Profile::PrintProfileJSON(JSONStream* stream, bool include_code_samples) { | |
| JSONObject obj(stream); | |
| PrintProfileJSON(&obj, include_code_samples); | |
| } | |
| void Profile::PrintProfileJSON(JSONObject* obj, | |
| bool include_code_samples, | |
| bool is_event) { | |
| ScopeTimer sw("Profile::PrintProfileJSON", FLAG_trace_profiler); | |
| Thread* thread = Thread::Current(); | |
| if (is_event) { | |
| obj->AddProperty("type", "CpuSamplesEvent"); | |
| } else { | |
| obj->AddProperty("type", "CpuSamples"); | |
| } | |
| PrintHeaderJSON(obj); | |
| if (include_code_samples) { | |
| JSONArray codes(obj, "_codes"); | |
| for (intptr_t i = 0; i < live_code_->length(); i++) { | |
| ProfileCode* code = live_code_->At(i); | |
| ASSERT(code != nullptr); | |
| code->PrintToJSONArray(&codes); | |
| thread->CheckForSafepoint(); | |
| } | |
| for (intptr_t i = 0; i < dead_code_->length(); i++) { | |
| ProfileCode* code = dead_code_->At(i); | |
| ASSERT(code != nullptr); | |
| code->PrintToJSONArray(&codes); | |
| thread->CheckForSafepoint(); | |
| } | |
| for (intptr_t i = 0; i < tag_code_->length(); i++) { | |
| ProfileCode* code = tag_code_->At(i); | |
| ASSERT(code != nullptr); | |
| code->PrintToJSONArray(&codes); | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| { | |
| JSONArray functions(obj, "functions"); | |
| for (intptr_t i = 0; i < functions_->length(); i++) { | |
| ProfileFunction* function = functions_->At(i); | |
| ASSERT(function != nullptr); | |
| function->PrintToJSONArray(&functions, is_event); | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| PrintSamplesJSON(obj, include_code_samples); | |
| thread->CheckForSafepoint(); | |
| } | |
| #endif // !defined(PRODUCT) | |
| #if defined(SUPPORT_PERFETTO) | |
| namespace { | |
| void ProcessSampleFramePerfetto(Profile* profile, | |
| GrowableArray<uint64_t>& callstack, | |
| ProfileCodeInlinedFunctionsCache* cache, | |
| ProcessedSample* sample, | |
| intptr_t frame_index, | |
| const GrowableArray<uint64_t>& function_iids) { | |
| const uword pc = sample->At(frame_index); | |
| ProfileCode* profile_code = profile->GetCodeFromPC(pc, sample->timestamp()); | |
| ASSERT(profile_code != nullptr); | |
| ProfileFunction* function = profile_code->function(); | |
| ASSERT(function != nullptr); | |
| // Don't show stubs in stack traces. | |
| if (!function->is_visible() || | |
| (function->kind() == ProfileFunction::kStubFunction)) { | |
| return; | |
| } | |
| GrowableArray<const Function*>* inlined_functions = nullptr; | |
| GrowableArray<TokenPosition>* inlined_token_positions = nullptr; | |
| TokenPosition token_position = TokenPosition::kNoSource; | |
| Code& code = Code::ZoneHandle(); | |
| if (profile_code->code().IsCode()) { | |
| code ^= profile_code->code().ptr(); | |
| cache->Get(pc, code, sample, frame_index, &inlined_functions, | |
| &inlined_token_positions, &token_position); | |
| } | |
| if (code.IsNull() || (inlined_functions == nullptr) || | |
| (inlined_functions->length() <= 1)) { | |
| // This is the ID of a |Frame| that was added to the interned data table in | |
| // |ProfilerService::PrintProfilePerfetto|. See the comments in that method | |
| // for more details. | |
| callstack.Add(function_iids[function->table_index()]); | |
| return; | |
| } | |
| 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()); | |
| ProfileFunction* profile_function = | |
| profile->FindFunction(*inlined_function); | |
| ASSERT(profile_function != NULL); | |
| callstack.Add(function_iids[profile_function->table_index()]); | |
| } | |
| } | |
| } // namespace | |
| template <typename WriteBytesFunction> | |
| void Profile::PrintSamplesPerfetto( | |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet_ptr, | |
| WriteBytesFunction&& write_bytes, | |
| perfetto_utils::InternedDataBuilder& interned_data_builder, | |
| const GrowableArray<uint64_t>& function_iids) { | |
| ASSERT(packet_ptr != nullptr); | |
| auto& packet = *packet_ptr; | |
| GrowableArray<uint64_t> callstack(128); | |
| // Note that |cache| is zone-allocated, so it does not need to be deallocated | |
| // manually. | |
| auto* cache = new ProfileCodeInlinedFunctionsCache(); | |
| for (intptr_t sample_index = 0; sample_index < samples_->length(); | |
| ++sample_index) { | |
| ProcessedSample* sample = samples_->At(sample_index); | |
| // Walk the sampled PCs and intern the stack. | |
| callstack.Clear(); | |
| for (intptr_t frame_index = sample->length() - 1; frame_index >= 0; | |
| --frame_index) { | |
| ASSERT(sample->At(frame_index) != 0); | |
| ProcessSampleFramePerfetto(this, callstack, cache, sample, frame_index, | |
| function_iids); | |
| } | |
| // Empty sample (everything is invisible). | |
| if (callstack.is_empty()) { | |
| continue; | |
| } | |
| const auto callstack_iid = interned_data_builder.callstacks().Intern( | |
| {&callstack[0], callstack.length()}); | |
| perfetto_utils::SetTrustedPacketSequenceId(packet.get()); | |
| perfetto_utils::SetTimestampAndMonotonicClockId(packet.get(), | |
| sample->timestamp()); | |
| // Populate |packet| with a |PerfSample| that is linked to the |Callstack| | |
| // that we populated above. | |
| auto& perf_sample = *packet->set_perf_sample(); | |
| perf_sample.set_pid(OS::ProcessId()); | |
| perf_sample.set_tid(OSThread::ThreadIdToIntPtr(sample->tid())); | |
| perf_sample.set_callstack_iid(callstack_iid); | |
| interned_data_builder.AttachInternedDataTo(packet.get()); | |
| perfetto_utils::WritePacketBytes(&packet, write_bytes); | |
| packet.Reset(); | |
| } | |
| } | |
| #endif // defined(SUPPORT_PERFETTO) | |
| ProfileFunction* Profile::FindFunction(const Function& function) { | |
| return (functions_ != nullptr) ? functions_->Lookup(function) : nullptr; | |
| } | |
| #if defined(SUPPORT_PERFETTO) | |
| template <typename WriteBytesFunction> | |
| void Profile::PrintProfilePerfettoImpl( | |
| WriteBytesFunction&& write_bytes, | |
| perfetto_utils::InternedDataBuilder& interned_data_builder) { | |
| Thread* thread = Thread::Current(); | |
| // We allocate one heap-buffered packet and continuously follow a cycle of | |
| // resetting the buffer and writing its contents. | |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> packet; | |
| perfetto_utils::PopulateClockSnapshotPacket(packet.get()); | |
| perfetto_utils::WritePacketBytes(&packet, write_bytes); | |
| packet.Reset(); | |
| perfetto_utils::SetTrustedPacketSequenceId(packet.get()); | |
| GrowableArray<uint64_t> function_iids(functions_->length()); | |
| for (intptr_t i = 0; i < functions_->length(); ++i) { | |
| ProfileFunction* function = functions_->At(i); | |
| ASSERT(function != NULL); | |
| const uint64_t function_name_iid = | |
| interned_data_builder.function_names().Intern(function->Name()); | |
| uint64_t mapping_iid = 0; | |
| const char* resolved_script_url = function->ResolvedScriptUrl(); | |
| if (resolved_script_url != nullptr) { | |
| const Script& script_handle = | |
| Script::Handle(function->function()->script()); | |
| TokenPosition token_pos = function->function()->token_pos(); | |
| uint64_t mapping_path_iid; | |
| if (!script_handle.IsNull() && token_pos.IsReal()) { | |
| intptr_t line = -1; | |
| intptr_t column = -1; | |
| script_handle.GetTokenLocation(token_pos, &line, &column); | |
| intptr_t path_with_location_buffer_size = | |
| Utils::SNPrint(nullptr, 0, "%s:%" Pd ":%" Pd, resolved_script_url, | |
| line, column) + | |
| 1; | |
| std::unique_ptr<char[]> path_with_location = | |
| std::make_unique<char[]>(path_with_location_buffer_size); | |
| Utils::SNPrint(path_with_location.get(), path_with_location_buffer_size, | |
| "%s:%" Pd ":%" Pd, resolved_script_url, line, column); | |
| mapping_path_iid = interned_data_builder.mapping_paths().Intern( | |
| path_with_location.get()); | |
| } else { | |
| mapping_path_iid = | |
| interned_data_builder.mapping_paths().Intern(resolved_script_url); | |
| } | |
| mapping_iid = interned_data_builder.mappings().Intern( | |
| {.path_string = mapping_path_iid}); | |
| } | |
| if (mapping_iid == 0) { | |
| interned_data_builder.MarkNeedUnknownMapping(); | |
| } | |
| // Add a |Frame| to the interned data table that is linked to |function|'s | |
| // name and source location (through the interned data table). A Perfetto | |
| // |Callstack| consists of a stack of |Frame|s, so the |Callstack|s | |
| // populated by |PrintSamplesPerfetto| will refer to these |Frame|s. | |
| function_iids.Add(interned_data_builder.frames().Intern( | |
| {.mapping_iid = static_cast<uint32_t>(mapping_iid), | |
| .function_name_iid = static_cast<uint32_t>(function_name_iid)})); | |
| thread->CheckForSafepoint(); | |
| } | |
| interned_data_builder.AttachInternedDataTo(packet.get()); | |
| perfetto_utils::WritePacketBytes(&packet, write_bytes); | |
| packet.Reset(); | |
| PrintSamplesPerfetto(&packet, write_bytes, interned_data_builder, | |
| function_iids); | |
| thread->CheckForSafepoint(); | |
| } | |
| #if !defined(PRODUCT) | |
| void Profile::PrintProfilePerfetto(JSONStream* js) { | |
| ScopeTimer sw("Profile::PrintProfilePerfetto", FLAG_trace_profiler); | |
| JSONObject jsobj_topLevel(js); | |
| jsobj_topLevel.AddProperty("type", "PerfettoCpuSamples"); | |
| PrintHeaderJSON(&jsobj_topLevel); | |
| js->AppendSerializedObject("\"samples\":"); | |
| JSONBase64String jsonBase64String(js); | |
| perfetto_utils::InternedDataBuilder interned_data_builder; | |
| PrintProfilePerfettoImpl( | |
| [&](auto bytes, auto bytes_length) { | |
| jsonBase64String.AppendBytes(bytes, bytes_length); | |
| }, | |
| interned_data_builder); | |
| } | |
| #endif | |
| void Profile::PrintProfilePerfetto( | |
| perfetto_utils::InternedDataBuilder& interned_data_builder, | |
| void* file, | |
| Dart_FileWriteCallback write_bytes) { | |
| ScopeTimer sw("Profile::PrintProfilePerfetto", FLAG_trace_profiler); | |
| PrintProfilePerfettoImpl( | |
| [&](auto bytes, auto bytes_length) { | |
| write_bytes(bytes, bytes_length, file); | |
| }, | |
| interned_data_builder); | |
| } | |
| #endif // defined(SUPPORT_PERFETTO) | |
| #if !defined(PRODUCT) | |
| void ProfilerService::PrintCommonImpl(PrintFormat format, | |
| Thread* thread, | |
| JSONStream* js, | |
| SampleFilter* filter, | |
| SampleBlockBuffer* buffer, | |
| bool include_code_samples) { | |
| // We should bail out in service.cc if the profiler is disabled. | |
| ASSERT(buffer != nullptr); | |
| StackZone zone(thread); | |
| Profile profile; | |
| profile.Build(thread, thread->isolate(), filter, buffer); | |
| if (format == PrintFormat::JSON) { | |
| profile.PrintProfileJSON(js, include_code_samples); | |
| } else if (format == PrintFormat::Perfetto) { | |
| #if defined(SUPPORT_PERFETTO) | |
| // This branch will never be reached when SUPPORT_PERFETTO is not defined or | |
| // when PRODUCT is defined, because |PrintPerfetto| is not defined when | |
| // SUPPORT_PERFETTO is not defined or when PRODUCT is defined. | |
| profile.PrintProfilePerfetto(js); | |
| #else | |
| UNREACHABLE(); | |
| #endif // defined(SUPPORT_PERFETTO) | |
| } | |
| } | |
| void ProfilerService::PrintCommon(PrintFormat format, | |
| JSONStream* js, | |
| int64_t time_origin_micros, | |
| int64_t time_extent_micros, | |
| bool include_code_samples) { | |
| Thread* thread = Thread::Current(); | |
| const Isolate* isolate = thread->isolate(); | |
| NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask, | |
| time_origin_micros, time_extent_micros); | |
| PrintCommonImpl(format, thread, js, &filter, Profiler::sample_block_buffer(), | |
| include_code_samples); | |
| } | |
| void ProfilerService::PrintJSON(JSONStream* js, | |
| int64_t time_origin_micros, | |
| int64_t time_extent_micros, | |
| bool include_code_samples) { | |
| PrintCommon(PrintFormat::JSON, js, time_origin_micros, time_extent_micros, | |
| include_code_samples); | |
| } | |
| #if defined(SUPPORT_PERFETTO) | |
| void ProfilerService::PrintPerfetto(JSONStream* js, | |
| int64_t time_origin_micros, | |
| int64_t time_extent_micros) { | |
| PrintCommon(PrintFormat::Perfetto, js, time_origin_micros, | |
| time_extent_micros); | |
| } | |
| #endif // defined(SUPPORT_PERFETTO) | |
| class AllocationSampleFilter : public SampleFilter { | |
| public: | |
| AllocationSampleFilter(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::PrintAllocationJSON(JSONStream* stream, | |
| int64_t time_origin_micros, | |
| int64_t time_extent_micros) { | |
| Thread* thread = Thread::Current(); | |
| Isolate* isolate = thread->isolate(); | |
| AllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask, | |
| time_origin_micros, time_extent_micros); | |
| PrintCommonImpl(PrintFormat::JSON, thread, stream, &filter, | |
| Profiler::sample_block_buffer(), true); | |
| } | |
| 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.ptr())) { | |
| 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, | |
| 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); | |
| PrintCommonImpl(PrintFormat::JSON, thread, stream, &filter, | |
| Profiler::sample_block_buffer(), true); | |
| } | |
| void ProfilerService::ClearSamples() { | |
| SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer(); | |
| if (sample_block_buffer == nullptr) { | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Isolate* isolate = thread->isolate(); | |
| // Disable thread interrupts while processing the buffer. | |
| DisableThreadInterruptsScope dtis(thread); | |
| ClearProfileVisitor clear_profile(isolate); | |
| sample_block_buffer->VisitSamples(&clear_profile); | |
| } | |
| #endif // !defined(PRODUCT) | |
| #endif // defined(DART_INCLUDE_PROFILER) | |
| } // namespace dart |