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