d7fa1ced6a
Current implementation of profile data streaming inherited its
approach to symbolization from the implementation of vm-service's
get{,Perfetto}CpuSamples methods. These methods rather expensive
as they rely on CodeLookupTable to symbolize collected samples, and
constructing CodeLookupTable requires bringing all threads to safepoint
and iterating over old-space to collect code objects. This can take
significant amount of time - especially when old-space is large (e.g.
consider 1Gb+ heaps of Dart Analysis Server).
This CL rewrites profile data streaming to use a completely different
approach in AOT mode where Dart frames are not symbolized eagerly
and instead stored in the timeline in their raw form: a pair of
an isolate group specific Mapping and a PC value relative to the start
of that mapping. At the end of streaming (or when isolate group
exits) an additional ModuleSymbols packet is emitted which provides
symbolization information for all collected frames. ModuleSymbols
mappings can be cheaply constructed from collected PCs using
ReversePc lookup tables.
TEST=expanded existing tests
Change-Id: I56ef1dd4c9a17fb0d2e9c24e51f2e4656a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/482782
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
2177 lines
70 KiB
C++
2177 lines
70 KiB
C++
// 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),
|
|
vm_isolate_(Dart::vm_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);
|
|
|
|
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();
|
|
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((*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)) {
|
|
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 vm_isolate_->group()->heap()->CodeContains(pc) ||
|
|
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_;
|
|
Isolate* vm_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("single_frame_sample_deoptimizing",
|
|
counters.single_frame_sample_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
|