blob: 64197073f9373f1d4a39df5e699f7959b845a972 [file] [log] [blame]
// 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/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);
DEFINE_FLAG(bool, trace_profiler, false, "Trace profiler.");
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_;
};
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),
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();
}
void ProfileFunction::Tick(bool exclusive, intptr_t inclusive_serial) {
if (exclusive) {
exclusive_ticks_++;
}
// Fall through and tick inclusive count too.
if (inclusive_serial_ == inclusive_serial) {
// Already ticked.
return;
}
inclusive_serial_ = inclusive_serial;
inclusive_ticks_++;
}
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.AddPropertyF("inclusiveTicks", "%" Pd "", inclusive_ticks());
obj.AddPropertyF("exclusiveTicks", "%" Pd "", 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);
}
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) {
}
void ProfileCode::AdjustExtent(uword start, uword end) {
if (start < start_) {
start_ = start;
}
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<const char>(len + 1);
strncpy(const_cast<char*>(name_), name, len);
const_cast<char*>(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.AddPropertyF("inclusiveTicks", "%" Pd "", inclusive_ticks());
obj.AddPropertyF("exclusiveTicks", "%" Pd "", 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.AddValueF("%" Pd "", entry.exclusive_ticks());
ticks.AddValueF("%" Pd "", entry.inclusive_ticks());
}
}
}
class ProfileFunctionTable : public ZoneAllocated {
public:
ProfileFunctionTable()
: null_function_(Function::ZoneHandle()),
table_(8),
unknown_function_(NULL) {
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);
}
intptr_t LookupIndex(const Function& function) {
ASSERT(!function.IsNull());
for (intptr_t i = 0; i < table_.length(); i++) {
ProfileFunction* profile_function = table_[i];
if (profile_function->function() == function.raw()) {
return i;
}
}
return -1;
}
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);
return profile_function;
}
ProfileFunction* Lookup(const Function& function) {
ASSERT(!function.IsNull());
intptr_t index = LookupIndex(function);
if (index == -1) {
return NULL;
}
return table_[index];
}
const Function& null_function_;
ZoneGrowableArray<ProfileFunction*> table_;
ProfileFunction* unknown_function_;
};
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 Object& obj = Object::Handle(code_.owner());
if (obj.IsFunction()) {
const String& user_name = String::Handle(code_.PrettyName());
function = table->LookupOrAdd(Function::Cast(obj));
SetName(user_name.ToCString());
} else {
// A stub.
const String& user_name = String::Handle(code_.PrettyName());
function = table->AddStub(start(), user_name.ToCString());
SetName(user_name.ToCString());
}
} 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_;
}
typedef bool (*RangeCompare)(uword pc, uword region_start, uword region_end);
class ProfileCodeTable : public ZoneAllocated {
public:
ProfileCodeTable()
: table_(8) {
}
intptr_t length() const { return table_.length(); }
ProfileCode* At(intptr_t index) const {
ASSERT(index >= 0);
ASSERT(index < length());
return table_[index];
}
// Find the table index to the ProfileCode containing pc.
// Returns < 0 if not found.
intptr_t FindCodeIndexForPC(uword pc) const {
intptr_t index = FindCodeIndex(pc, &CompareLowerBound);
if (index == length()) {
// Not present.
return -1;
}
const ProfileCode* code = At(index);
if (!code->Contains(pc)) {
// Not present.
return -1;
}
// Found at index.
return index;
}
ProfileCode* FindCodeForPC(uword pc) const {
intptr_t index = FindCodeIndexForPC(pc);
if (index < 0) {
return NULL;
}
return At(index);
}
// Insert |new_code| into the table. Returns the table index where |new_code|
// was inserted. Will merge with an overlapping ProfileCode if one is present.
intptr_t InsertCode(ProfileCode* new_code) {
const uword start = new_code->start();
const uword end = new_code->end();
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 = FindCodeIndex(start, &CompareLowerBound);
intptr_t hi = FindCodeIndex(end - 1, &CompareUpperBound);
// TODO(johnmccutchan): Simplify below logic.
if ((lo == length) && (hi == length)) {
lo = length - 1;
}
if (lo == length) {
ProfileCode* code = At(hi);
if (code->Overlaps(new_code)) {
HandleOverlap(code, new_code, start, end);
return hi;
}
table_.Add(new_code);
return length;
} else if (hi == length) {
ProfileCode* code = At(lo);
if (code->Overlaps(new_code)) {
HandleOverlap(code, new_code, start, end);
return lo;
}
table_.Add(new_code);
return length;
} else if (lo == hi) {
ProfileCode* code = At(lo);
if (code->Overlaps(new_code)) {
HandleOverlap(code, new_code, start, end);
return lo;
}
table_.InsertAt(lo, new_code);
return lo;
} else {
ProfileCode* code = At(lo);
if (code->Overlaps(new_code)) {
HandleOverlap(code, new_code, start, end);
return lo;
}
code = At(hi);
if (code->Overlaps(new_code)) {
HandleOverlap(code, new_code, start, end);
return hi;
}
table_.InsertAt(hi, new_code);
return hi;
}
UNREACHABLE();
}
private:
intptr_t FindCodeIndex(uword pc, RangeCompare comparator) const {
ASSERT(comparator != NULL);
intptr_t count = table_.length();
intptr_t first = 0;
while (count > 0) {
intptr_t it = first;
intptr_t step = count / 2;
it += step;
const ProfileCode* code = At(it);
if (comparator(pc, code->start(), code->end())) {
first = ++it;
count -= (step + 1);
} else {
count = step;
}
}
return first;
}
static bool CompareUpperBound(uword pc, uword start, uword end) {
return pc >= end;
}
static bool CompareLowerBound(uword pc, uword start, uword end) {
return end <= pc;
}
void HandleOverlap(ProfileCode* existing, ProfileCode* code,
uword start, uword end) {
// We should never see overlapping Dart code regions.
ASSERT(existing->kind() != ProfileCode::kDartCode);
// We should never see overlapping Tag code regions.
ASSERT(existing->kind() != ProfileCode::kTagCode);
// When code regions overlap, they should be of the same kind.
ASSERT(existing->kind() == code->kind());
existing->AdjustExtent(start, end);
}
void 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 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));
}
}
}
ZoneGrowableArray<ProfileCode*> table_;
};
ProfileTrieNode::ProfileTrieNode(intptr_t table_index)
: table_index_(table_index),
count_(0),
children_(0) {
ASSERT(table_index_ >= 0);
}
ProfileTrieNode::~ProfileTrieNode() {
}
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);
// 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 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 ProfileBuilder : public ValueObject {
public:
enum ProfileInfoKind {
kNone,
kOptimized,
kUnoptimized,
kNative,
kInlineStart,
kInlineFinish,
kNumProfileInfoKind,
};
ProfileBuilder(Isolate* isolate,
SampleFilter* filter,
Profile::TagOrder tag_order,
intptr_t extra_tags,
Profile* profile)
: isolate_(isolate),
vm_isolate_(Dart::vm_isolate()),
filter_(filter),
tag_order_(tag_order),
extra_tags_(extra_tags),
profile_(profile),
deoptimized_code_(new DeoptimizedCodeSet(isolate)),
null_code_(Code::ZoneHandle()),
null_function_(Function::ZoneHandle()),
tick_functions_(false),
inclusive_tree_(false),
samples_(NULL),
info_kind_(kNone) {
ASSERT(profile_ != NULL);
}
void Build() {
ScopeTimer sw("ProfileBuilder::Build", FLAG_trace_profiler);
FilterSamples();
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);
}
void FilterSamples() {
ScopeTimer sw("ProfileBuilder::FilterSamples", FLAG_trace_profiler);
MutexLocker profiler_data_lock(isolate_->profiler_data_mutex());
IsolateProfilerData* profiler_data = isolate_->profiler_data();
if (profiler_data == NULL) {
return;
}
SampleBuffer* sample_buffer = profiler_data->sample_buffer();
if (sample_buffer == NULL) {
return;
}
samples_ = sample_buffer->BuildProcessedSampleBuffer(filter_);
profile_->sample_count_ = samples_->length();
}
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);
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 = RegisterProfileCode(pc, timestamp);
ASSERT(code != NULL);
code->Tick(pc, IsExecutingFrame(sample, frame_index), sample_index);
}
}
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();
// VM & User tags.
current = AppendTags(sample->vm_tag(), sample->user_tag(), current);
ResetKind();
// Truncated tag.
if (sample->truncated()) {
current = AppendTruncatedTag(current);
}
// 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);
current = current->GetChild(index);
current->Tick();
}
}
}
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();
// VM & User tags.
current = AppendTags(sample->vm_tag(), sample->user_tag(), current);
ResetKind();
// 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();
}
current = AppendKind(code, current);
}
// Truncated tag.
if (sample->truncated()) {
current = AppendTruncatedTag(current);
}
}
}
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();
// VM & User tags.
current = AppendTags(sample->vm_tag(), sample->user_tag(), current);
// Truncated tag.
if (sample->truncated()) {
current = AppendTruncatedTag(current);
}
// 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);
}
}
}
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();
// VM & User tags.
current = AppendTags(sample->vm_tag(), sample->user_tag(), current);
ResetKind();
// 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);
}
// Truncated tag.
if (sample->truncated()) {
current = AppendTruncatedTag(current);
InclusiveTickTruncatedTag();
}
}
}
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<Function*> inlined_functions;
if (!code.IsNull()) {
intptr_t offset = pc - code.EntryPoint();
code.GetInlinedFunctionsAt(offset, &inlined_functions);
}
if (code.IsNull() || (inlined_functions.length() == 0)) {
// No inlined functions.
if (inclusive_tree_) {
current = AppendKind(code, current);
}
current = ProcessFunction(current,
sample_index,
sample,
frame_index,
function,
code_index);
if (!inclusive_tree_) {
current = AppendKind(code, current);
}
return current;
}
ASSERT(code.is_optimized());
if (inclusive_tree_) {
for (intptr_t i = inlined_functions.length() - 1; i >= 0; i--) {
Function* inlined_function = inlined_functions[i];
ASSERT(inlined_function != NULL);
ASSERT(!inlined_function->IsNull());
const bool inliner = i == (inlined_functions.length() - 1);
if (inliner) {
current = AppendKind(code, current);
}
current = ProcessInlinedFunction(current,
sample_index,
sample,
frame_index,
inlined_function,
code_index);
if (inliner) {
current = AppendKind(kInlineStart, current);
}
}
current = AppendKind(kInlineFinish, current);
} else {
// Append the inlined children.
current = AppendKind(kInlineFinish, current);
for (intptr_t i = 0; i < inlined_functions.length(); i++) {
Function* inlined_function = inlined_functions[i];
ASSERT(inlined_function != NULL);
ASSERT(!inlined_function->IsNull());
const bool inliner = i == (inlined_functions.length() - 1);
if (inliner) {
current = AppendKind(kInlineStart, current);
}
current = ProcessInlinedFunction(current,
sample_index,
sample,
frame_index + i,
inlined_function,
code_index);
if (inliner) {
current = AppendKind(code, current);
}
}
}
return current;
}
ProfileFunctionTrieNode* ProcessInlinedFunction(
ProfileFunctionTrieNode* current,
intptr_t sample_index,
ProcessedSample* sample,
intptr_t frame_index,
Function* inlined_function,
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,
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) || vm_tags_emitted();
}
ProfileFunctionTrieNode* ProcessFunction(ProfileFunctionTrieNode* current,
intptr_t sample_index,
ProcessedSample* sample,
intptr_t frame_index,
ProfileFunction* function,
intptr_t code_index) {
if (tick_functions_) {
function->Tick(IsExecutingFrame(sample, frame_index), sample_index);
}
function->AddProfileCode(code_index);
current = current->GetChild(function->table_index());
if (ShouldTickNode(sample, frame_index)) {
current->Tick();
}
current->AddCodeObjectIndex(code_index);
return current;
}
// Tick the truncated tag's inclusive tick count.
void InclusiveTickTruncatedTag() {
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) {
intptr_t user_tag_index = GetProfileCodeTagIndex(user_tag);
if (user_tag_index >= 0) {
current = current->GetChild(user_tag_index);
current->Tick();
}
return current;
}
ProfileCodeTrieNode* AppendTruncatedTag(ProfileCodeTrieNode* current) {
intptr_t truncated_tag_index =
GetProfileCodeTagIndex(VMTag::kTruncatedTagId);
ASSERT(truncated_tag_index >= 0);
current = current->GetChild(truncated_tag_index);
current->Tick();
return current;
}
ProfileCodeTrieNode* AppendVMTag(uword vm_tag,
ProfileCodeTrieNode* current) {
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();
} 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();
} else {
intptr_t tag_index = GetProfileCodeTagIndex(vm_tag);
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
}
return current;
}
ProfileCodeTrieNode* AppendSpecificNativeRuntimeEntryVMTag(
uword vm_tag, ProfileCodeTrieNode* current) {
// 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();
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) {
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();
}
return current;
}
ProfileCodeTrieNode* AppendKind(const Code& code,
ProfileCodeTrieNode* current) {
if (code.IsNull()) {
return AppendKind(kNone, current);
} else if (code.is_optimized()) {
return AppendKind(kOptimized, current);
} else {
return AppendKind(kUnoptimized, current);
}
}
ProfileCodeTrieNode* AppendVMTags(uword vm_tag,
ProfileCodeTrieNode* current) {
current = AppendVMTag(vm_tag, current);
current = AppendSpecificNativeRuntimeEntryVMTag(vm_tag, current);
return current;
}
ProfileCodeTrieNode* AppendTags(uword vm_tag,
uword user_tag,
ProfileCodeTrieNode* current) {
// None.
if (tag_order() == Profile::kNoTags) {
return current;
}
// User first.
if ((tag_order() == Profile::kUserVM) ||
(tag_order() == Profile::kUser)) {
current = AppendUserTag(user_tag, current);
// Only user.
if (tag_order() == Profile::kUser) {
return current;
}
return AppendVMTags(vm_tag, current);
}
// VM first.
ASSERT((tag_order() == Profile::kVMUser) ||
(tag_order() == Profile::kVM));
current = AppendVMTags(vm_tag, current);
// Only VM.
if (tag_order() == Profile::kVM) {
return current;
}
return AppendUserTag(user_tag, current);
}
// ProfileFunctionTrieNode
void ResetKind() {
info_kind_ = kNone;
}
ProfileFunctionTrieNode* AppendKind(ProfileInfoKind kind,
ProfileFunctionTrieNode* current) {
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();
}
return current;
}
ProfileFunctionTrieNode* AppendKind(const Code& code,
ProfileFunctionTrieNode* current) {
if (code.IsNull()) {
return AppendKind(kNone, current);
} else if (code.is_optimized()) {
return AppendKind(kOptimized, current);
} else {
return AppendKind(kUnoptimized, current);
}
}
ProfileFunctionTrieNode* AppendUserTag(uword user_tag,
ProfileFunctionTrieNode* current) {
intptr_t user_tag_index = GetProfileFunctionTagIndex(user_tag);
if (user_tag_index >= 0) {
current = current->GetChild(user_tag_index);
current->Tick();
}
return current;
}
ProfileFunctionTrieNode* AppendTruncatedTag(
ProfileFunctionTrieNode* current) {
intptr_t truncated_tag_index =
GetProfileFunctionTagIndex(VMTag::kTruncatedTagId);
ASSERT(truncated_tag_index >= 0);
current = current->GetChild(truncated_tag_index);
current->Tick();
return current;
}
ProfileFunctionTrieNode* AppendVMTag(uword vm_tag,
ProfileFunctionTrieNode* current) {
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();
} 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();
} else {
intptr_t tag_index = GetProfileFunctionTagIndex(vm_tag);
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
}
return current;
}
ProfileFunctionTrieNode* AppendSpecificNativeRuntimeEntryVMTag(
uword vm_tag, ProfileFunctionTrieNode* current) {
// 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();
return current;
}
ProfileFunctionTrieNode* AppendVMTags(uword vm_tag,
ProfileFunctionTrieNode* current) {
current = AppendVMTag(vm_tag, current);
current = AppendSpecificNativeRuntimeEntryVMTag(vm_tag, current);
return current;
}
ProfileFunctionTrieNode* AppendTags(uword vm_tag,
uword user_tag,
ProfileFunctionTrieNode* current) {
// None.
if (tag_order() == Profile::kNoTags) {
return current;
}
// User first.
if ((tag_order() == Profile::kUserVM) ||
(tag_order() == Profile::kUser)) {
current = AppendUserTag(user_tag, current);
// Only user.
if (tag_order() == Profile::kUser) {
return current;
}
return AppendVMTags(vm_tag, current);
}
// VM first.
ASSERT((tag_order() == Profile::kVMUser) ||
(tag_order() == Profile::kVM));
current = AppendVMTags(vm_tag, current);
// Only VM.
if (tag_order() == Profile::kVM) {
return current;
}
return AppendUserTag(user_tag, current);
}
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;
}
ProfileCode* CreateProfileCode(uword pc) {
const intptr_t kDartCodeAlignment = OS::PreferredCodeAlignment();
const intptr_t kDartCodeAlignmentMask = ~(kDartCodeAlignment - 1);
Code& code = Code::Handle(isolate_->current_zone());
// Check current isolate for pc.
if (isolate_->heap()->CodeContains(pc)) {
code ^= Code::LookupCode(pc);
if (!code.IsNull()) {
deoptimized_code_->Add(code);
return new ProfileCode(ProfileCode::kDartCode,
code.EntryPoint(),
code.EntryPoint() + code.Size(),
code.compile_timestamp(),
code);
}
return new ProfileCode(ProfileCode::kCollectedCode,
pc,
(pc & kDartCodeAlignmentMask) + kDartCodeAlignment,
0,
code);
}
// Check VM isolate for pc.
if (vm_isolate_->heap()->CodeContains(pc)) {
code ^= Code::LookupCodeInVmIsolate(pc);
if (!code.IsNull()) {
return new ProfileCode(ProfileCode::kDartCode,
code.EntryPoint(),
code.EntryPoint() + code.Size(),
code.compile_timestamp(),
code);
}
return new ProfileCode(ProfileCode::kCollectedCode,
pc,
(pc & kDartCodeAlignmentMask) + kDartCodeAlignment,
0,
code);
}
// Check NativeSymbolResolver for pc.
uintptr_t native_start = 0;
char* native_name = NativeSymbolResolver::LookupSymbolName(pc,
&native_start);
if (native_name == NULL) {
// No native name found.
return new ProfileCode(ProfileCode::kNativeCode,
pc,
pc + 1,
0,
code);
}
ASSERT(pc >= native_start);
ProfileCode* profile_code =
new ProfileCode(ProfileCode::kNativeCode,
native_start,
pc + 1,
0,
code);
profile_code->SetName(native_name);
free(native_name);
return profile_code;
}
ProfileCode* RegisterProfileCode(uword pc, int64_t timestamp) {
ProfileCodeTable* live_table = profile_->live_code_;
ProfileCodeTable* dead_table = profile_->dead_code_;
ProfileCode* code = live_table->FindCodeForPC(pc);
if (code == NULL) {
// Code not found.
intptr_t index = live_table->InsertCode(CreateProfileCode(pc));
ASSERT(index >= 0);
code = live_table->At(index);
if (code->compile_timestamp() <= timestamp) {
// Code was compiled before sample was taken.
return code;
}
// Code was compiled after the sample was taken. Insert code object into
// the dead code table.
index = dead_table->InsertCode(CreateProfileCodeReused(pc));
ASSERT(index >= 0);
return dead_table->At(index);
}
// Existing code found.
if (code->compile_timestamp() <= timestamp) {
// Code was compiled before sample was taken.
return code;
}
// Code was compiled after the sample was taken. Check if we have an entry
// in the dead code table.
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);
}
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;
}
Isolate* isolate_;
Isolate* vm_isolate_;
SampleFilter* filter_;
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_;
ProcessedSampleBuffer* samples_;
ProfileInfoKind info_kind_;
};
Profile::Profile(Isolate* isolate)
: isolate_(isolate),
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(SampleFilter* filter,
TagOrder tag_order,
intptr_t extra_tags) {
ProfileBuilder builder(isolate_, filter, tag_order, extra_tags, this);
builder.Build();
}
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::PrintJSON(JSONStream* stream) {
ScopeTimer sw("Profile::PrintJSON", FLAG_trace_profiler);
JSONObject obj(stream);
obj.AddProperty("type", "_CpuProfile");
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()));
{
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();
}
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();
}
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();
}
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) {
Isolate* isolate = thread->isolate();
// Disable profile interrupts while processing the buffer.
Profiler::EndExecution(isolate);
{
MutexLocker profiler_data_lock(isolate->profiler_data_mutex());
IsolateProfilerData* profiler_data = isolate->profiler_data();
if (profiler_data == NULL) {
stream->PrintError(kFeatureDisabled, NULL);
return;
}
}
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
profile.Build(filter, tag_order, extra_tags);
profile.PrintJSON(stream);
}
// Enable profile interrupts.
Profiler::BeginExecution(isolate);
}
class NoAllocationSampleFilter : public SampleFilter {
public:
explicit NoAllocationSampleFilter(Isolate* isolate)
: SampleFilter(isolate) {
}
bool FilterSample(Sample* sample) {
return !sample->is_allocation_sample();
}
};
void ProfilerService::PrintJSON(JSONStream* stream,
Profile::TagOrder tag_order,
intptr_t extra_tags) {
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
NoAllocationSampleFilter filter(isolate);
PrintJSONImpl(thread, stream, tag_order, extra_tags, &filter);
}
class ClassAllocationSampleFilter : public SampleFilter {
public:
ClassAllocationSampleFilter(Isolate* isolate, const Class& cls)
: SampleFilter(isolate),
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) {
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ClassAllocationSampleFilter filter(isolate, cls);
PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter);
}
void ProfilerService::ClearSamples() {
Isolate* isolate = Isolate::Current();
// Disable profile interrupts while processing the buffer.
Profiler::EndExecution(isolate);
MutexLocker profiler_data_lock(isolate->profiler_data_mutex());
IsolateProfilerData* profiler_data = isolate->profiler_data();
if (profiler_data == NULL) {
return;
}
SampleBuffer* sample_buffer = profiler_data->sample_buffer();
ASSERT(sample_buffer != NULL);
ClearProfileVisitor clear_profile(isolate);
sample_buffer->VisitSamples(&clear_profile);
// Enable profile interrupts.
Profiler::BeginExecution(isolate);
}
} // namespace dart