blob: 6e3637fecd87d30c4fde55632e987aa5768d7b45 [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, profile_depth);
DECLARE_FLAG(int, profile_period);
DEFINE_FLAG(bool, trace_profiler, false, "Trace profiler.");
// Forward declarations.
class CodeRegion;
class ProfileFunction;
class ProfileFunctionTable;
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; 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_;
};
class ProfileFunction : public ZoneAllocated {
public:
enum Kind {
kDartFunction, // Dart function.
kNativeFunction, // Synthetic function for Native (C/C++).
kTagFunction, // Synthetic function for a VM or User tag.
kStubFunction, // Synthetic function for stub code.
kUnkownFunction, // A singleton function for unknown objects.
};
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),
code_objects_(new ZoneGrowableArray<intptr_t>()),
exclusive_ticks_(0),
inclusive_ticks_(0),
inclusive_tick_serial_(0) {
ASSERT((kind_ != kDartFunction) || !function_.IsNull());
ASSERT((kind_ != kDartFunction) || (table_index_ >= 0));
ASSERT(code_objects_->length() == 0);
}
const char* name() const {
ASSERT(name_ != NULL);
return name_;
}
RawFunction* function() const {
return function_.raw();
}
intptr_t index() const {
return table_index_;
}
Kind kind() const {
return kind_;
}
const char* KindToCString(Kind kind) {
switch (kind) {
case kDartFunction:
return "Dart";
case kNativeFunction:
return "Native";
case kTagFunction:
return "Tag";
case kStubFunction:
return "Stub";
case kUnkownFunction:
return "Collected";
default:
UNIMPLEMENTED();
return "";
}
}
void Dump() {
const char* n = (name_ == NULL) ? "<NULL>" : name_;
const char* fn = "";
if (!function_.IsNull()) {
fn = function_.ToQualifiedCString();
}
OS::Print("%s %s [%s]", KindToCString(kind()), n, fn);
}
void AddCodeObjectIndex(intptr_t index) {
for (intptr_t i = 0; i < code_objects_->length(); i++) {
if ((*code_objects_)[i] == index) {
return;
}
}
code_objects_->Add(index);
}
intptr_t inclusive_ticks() const {
return inclusive_ticks_;
}
void inc_inclusive_ticks() {
inclusive_ticks_++;
}
intptr_t exclusive_ticks() const {
return exclusive_ticks_;
}
void Tick(bool exclusive, intptr_t serial) {
// Assert that exclusive ticks are never passed a valid serial number.
ASSERT((exclusive && (serial == -1)) || (!exclusive && (serial != -1)));
if (!exclusive && (inclusive_tick_serial_ == serial)) {
// We've already given this object an inclusive tick for this sample.
return;
}
if (exclusive) {
exclusive_ticks_++;
} else {
inclusive_ticks_++;
// Mark the last serial we ticked the inclusive count.
inclusive_tick_serial_ = serial;
}
}
void PrintToJSONObject(JSONObject* func) {
func->AddProperty("type", "@Function");
func->AddProperty("name", name());
func->AddProperty("kind", KindToCString(kind()));
}
void 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 < code_objects_->length(); i++) {
intptr_t code_index = (*code_objects_)[i];
codes.AddValue(code_index);
}
}
}
private:
const Kind kind_;
const char* name_;
const Function& function_;
const intptr_t table_index_;
ZoneGrowableArray<intptr_t>* code_objects_;
intptr_t exclusive_ticks_;
intptr_t inclusive_ticks_;
intptr_t inclusive_tick_serial_;
};
class ProfileFunctionTable : public ValueObject {
public:
ProfileFunctionTable()
: null_function_(Function::ZoneHandle()),
table_(new ZoneGrowableArray<ProfileFunction*>()),
unknown_function_(NULL) {
}
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() {
if (unknown_function_ == NULL) {
// Construct.
unknown_function_ = Add(ProfileFunction::kUnkownFunction,
"<unknown Dart function>");
}
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_;
};
struct AddressEntry {
uword pc;
intptr_t exclusive_ticks;
intptr_t inclusive_ticks;
void tick(bool exclusive) {
if (exclusive) {
exclusive_ticks++;
} else {
inclusive_ticks++;
}
}
};
typedef bool (*RegionCompare)(uword pc, uword region_start, uword region_end);
// A contiguous address region that holds code. Each CodeRegion has a "kind"
// which describes the type of code contained inside the region. Each
// region covers the following interval: [start, end).
class CodeRegion : public ZoneAllocated {
public:
enum Kind {
kDartCode, // Live Dart code.
kCollectedCode, // Dead Dart code.
kNativeCode, // Native code.
kReusedCode, // Dead Dart code that has been reused by new kDartCode.
kTagCode, // A special kind of code representing a tag.
};
CodeRegion(Kind kind,
uword start,
uword end,
int64_t timestamp,
const Code& code)
: kind_(kind),
start_(start),
end_(end),
inclusive_ticks_(0),
exclusive_ticks_(0),
inclusive_tick_serial_(0),
name_(NULL),
compile_timestamp_(timestamp),
creation_serial_(0),
code_(Code::ZoneHandle(code.raw())),
profile_function_(NULL),
code_table_index_(-1) {
ASSERT(start_ < end_);
// Ensure all kDartCode have a valid code_ object.
ASSERT((kind != kDartCode) || (!code_.IsNull()));
}
uword start() const { return start_; }
void set_start(uword start) {
start_ = start;
}
uword end() const { return end_; }
void set_end(uword end) {
end_ = end;
}
void AdjustExtent(uword start, uword end) {
if (start < start_) {
start_ = start;
}
if (end > end_) {
end_ = end;
}
ASSERT(start_ < end_);
}
bool contains(uword pc) const {
return (pc >= start_) && (pc < end_);
}
bool overlaps(const CodeRegion* other) const {
ASSERT(other != NULL);
return other->contains(start_) ||
other->contains(end_ - 1) ||
contains(other->start()) ||
contains(other->end() - 1);
}
intptr_t creation_serial() const { return creation_serial_; }
void set_creation_serial(intptr_t serial) {
creation_serial_ = serial;
}
int64_t compile_timestamp() const { return compile_timestamp_; }
void set_compile_timestamp(int64_t timestamp) {
compile_timestamp_ = timestamp;
}
intptr_t inclusive_ticks() const { return inclusive_ticks_; }
void set_inclusive_ticks(intptr_t inclusive_ticks) {
inclusive_ticks_ = inclusive_ticks;
}
void inc_inclusive_ticks() {
inclusive_ticks_++;
}
intptr_t exclusive_ticks() const { return exclusive_ticks_; }
void set_exclusive_ticks(intptr_t exclusive_ticks) {
exclusive_ticks_ = exclusive_ticks;
}
const char* name() const { return name_; }
void SetName(const char* name) {
if (name == NULL) {
name_ = NULL;
}
intptr_t len = strlen(name);
name_ = Isolate::Current()->current_zone()->Alloc<const char>(len + 1);
strncpy(const_cast<char*>(name_), name, len);
const_cast<char*>(name_)[len] = '\0';
}
bool IsOptimizedDart() const {
return !code_.IsNull() && code_.is_optimized();
}
RawCode* code() const {
return code_.raw();
}
ProfileFunction* SetFunctionAndName(ProfileFunctionTable* table) {
ASSERT(profile_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 {
if (start() == VMTag::kRootTagId) {
SetName("Root");
} else {
ASSERT(start() == VMTag::kTruncatedTagId);
SetName("[Truncated]");
}
}
}
function = table->AddTag(start(), name());
} else {
UNREACHABLE();
}
ASSERT(function != NULL);
// Register this CodeRegion with this function.
function->AddCodeObjectIndex(code_table_index());
profile_function_ = function;
return profile_function_;
}
ProfileFunction* function() const {
ASSERT(profile_function_ != NULL);
return profile_function_;
}
void set_code_table_index(intptr_t code_table_index) {
ASSERT(code_table_index_ == -1);
ASSERT(code_table_index != -1);
code_table_index_ = code_table_index;
}
intptr_t code_table_index() const {
ASSERT(code_table_index_ != -1);
return code_table_index_;
}
Kind kind() const { return kind_; }
static const char* 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 DebugPrint() const {
OS::Print("%s [%" Px ", %" Px ") %" Pd " %" Pd64 "\n",
KindToCString(kind_),
start(),
end(),
creation_serial_,
compile_timestamp_);
}
void Tick(uword pc, bool exclusive, intptr_t serial) {
// Assert that exclusive ticks are never passed a valid serial number.
ASSERT((exclusive && (serial == -1)) || (!exclusive && (serial != -1)));
if (!exclusive && (inclusive_tick_serial_ == serial)) {
// We've already given this code object an inclusive tick for this sample.
return;
}
// Tick the code object.
if (exclusive) {
exclusive_ticks_++;
} else {
inclusive_ticks_++;
// Mark the last serial we ticked the inclusive count.
inclusive_tick_serial_ = serial;
}
TickAddress(pc, exclusive);
}
void 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.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
profile_function_->PrintToJSONObject(&func);
}
}
void 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.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
profile_function_->PrintToJSONObject(&func);
}
}
void 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.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
ASSERT(profile_function_ != NULL);
profile_function_->PrintToJSONObject(&func);
}
}
void 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());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
ASSERT(profile_function_ != NULL);
profile_function_->PrintToJSONObject(&func);
}
}
void PrintToJSONArray(JSONArray* codes) {
JSONObject obj(codes);
obj.AddProperty("kind", 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_table_.length(); i++) {
const AddressEntry& entry = address_table_[i];
ticks.AddValueF("%" Px "", entry.pc);
ticks.AddValueF("%" Pd "", entry.exclusive_ticks);
ticks.AddValueF("%" Pd "", entry.inclusive_ticks);
}
}
}
private:
void TickAddress(uword pc, bool exclusive) {
const intptr_t length = address_table_.length();
intptr_t i = 0;
for (; i < length; i++) {
AddressEntry& entry = address_table_[i];
if (entry.pc == pc) {
// Tick the address entry.
entry.tick(exclusive);
return;
}
if (entry.pc > pc) {
break;
}
}
// New address, add entry.
AddressEntry entry;
entry.pc = pc;
entry.exclusive_ticks = 0;
entry.inclusive_ticks = 0;
entry.tick(exclusive);
if (i < length) {
// Insert at i.
address_table_.InsertAt(i, entry);
} else {
// Add to end.
address_table_.Add(entry);
}
}
void 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);
}
// CodeRegion kind.
const Kind kind_;
// CodeRegion start address.
uword start_;
// CodeRegion end address.
uword end_;
// Inclusive ticks.
intptr_t inclusive_ticks_;
// Exclusive ticks.
intptr_t exclusive_ticks_;
// Inclusive tick serial number, ensures that each CodeRegion is only given
// a single inclusive tick per sample.
intptr_t inclusive_tick_serial_;
// Name of code region.
const char* name_;
// The compilation timestamp associated with this code region.
int64_t compile_timestamp_;
// Serial number at which this CodeRegion was created.
intptr_t creation_serial_;
// Dart code object (may be null).
const Code& code_;
// Pointer to ProfileFunction.
ProfileFunction* profile_function_;
// Final code table index.
intptr_t code_table_index_;
ZoneGrowableArray<AddressEntry> address_table_;
DISALLOW_COPY_AND_ASSIGN(CodeRegion);
};
// A sorted table of CodeRegions. Does not allow for overlap.
class CodeRegionTable : public ValueObject {
public:
enum TickResult {
kTicked = 0, // CodeRegion found and ticked.
kNotFound = -1, // No CodeRegion found.
kNewerCode = -2, // CodeRegion found but it was compiled after sample.
};
CodeRegionTable() :
code_region_table_(new ZoneGrowableArray<CodeRegion*>(64)) {
}
// Ticks the CodeRegion containing pc if it is alive at timestamp.
TickResult Tick(uword pc, bool exclusive, intptr_t serial,
int64_t timestamp) {
intptr_t index = FindIndex(pc);
if (index < 0) {
// Not found.
return kNotFound;
}
ASSERT(index < code_region_table_->length());
CodeRegion* region = At(index);
if (region->compile_timestamp() > timestamp) {
// Compiled after tick.
return kNewerCode;
}
region->Tick(pc, exclusive, serial);
return kTicked;
}
// Table length.
intptr_t Length() const { return code_region_table_->length(); }
// Get the CodeRegion at index.
CodeRegion* At(intptr_t index) const {
return (*code_region_table_)[index];
}
// Find the table index to the CodeRegion containing pc.
// Returns < 0 if not found.
intptr_t FindIndex(uword pc) const {
intptr_t index = FindRegionIndex(pc, &CompareLowerBound);
const CodeRegion* code_region = NULL;
if (index == code_region_table_->length()) {
// Not present.
return -1;
}
code_region = At(index);
if (code_region->contains(pc)) {
// Found at index.
return index;
}
return -2;
}
// Insert code_region into the table. Returns the table index where the
// CodeRegion was inserted. Will merge with an overlapping CodeRegion if
// one is present.
intptr_t InsertCodeRegion(CodeRegion* code_region) {
const uword start = code_region->start();
const uword end = code_region->end();
const intptr_t length = code_region_table_->length();
if (length == 0) {
code_region_table_->Add(code_region);
return length;
}
// Determine the correct place to insert or merge code_region into table.
intptr_t lo = FindRegionIndex(start, &CompareLowerBound);
intptr_t hi = FindRegionIndex(end - 1, &CompareUpperBound);
// TODO(johnmccutchan): Simplify below logic.
if ((lo == length) && (hi == length)) {
lo = length - 1;
}
if (lo == length) {
CodeRegion* region = At(hi);
if (region->overlaps(code_region)) {
HandleOverlap(region, code_region, start, end);
return hi;
}
code_region_table_->Add(code_region);
return length;
} else if (hi == length) {
CodeRegion* region = At(lo);
if (region->overlaps(code_region)) {
HandleOverlap(region, code_region, start, end);
return lo;
}
code_region_table_->Add(code_region);
return length;
} else if (lo == hi) {
CodeRegion* region = At(lo);
if (region->overlaps(code_region)) {
HandleOverlap(region, code_region, start, end);
return lo;
}
code_region_table_->InsertAt(lo, code_region);
return lo;
} else {
CodeRegion* region = At(lo);
if (region->overlaps(code_region)) {
HandleOverlap(region, code_region, start, end);
return lo;
}
region = At(hi);
if (region->overlaps(code_region)) {
HandleOverlap(region, code_region, start, end);
return hi;
}
code_region_table_->InsertAt(hi, code_region);
return hi;
}
UNREACHABLE();
}
void Verify() {
VerifyOrder();
VerifyOverlap();
}
void DebugPrint() {
OS::Print("Dumping CodeRegionTable:\n");
for (intptr_t i = 0; i < code_region_table_->length(); i++) {
CodeRegion* region = At(i);
region->DebugPrint();
}
}
private:
intptr_t FindRegionIndex(uword pc, RegionCompare comparator) const {
ASSERT(comparator != NULL);
intptr_t count = code_region_table_->length();
intptr_t first = 0;
while (count > 0) {
intptr_t it = first;
intptr_t step = count / 2;
it += step;
const CodeRegion* code_region = At(it);
if (comparator(pc, code_region->start(), code_region->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(CodeRegion* region, CodeRegion* code_region,
uword start, uword end) {
// We should never see overlapping Dart code regions.
ASSERT(region->kind() != CodeRegion::kDartCode);
// We should never see overlapping Tag code regions.
ASSERT(region->kind() != CodeRegion::kTagCode);
// When code regions overlap, they should be of the same kind.
ASSERT(region->kind() == code_region->kind());
region->AdjustExtent(start, end);
}
void VerifyOrder() {
const intptr_t length = code_region_table_->length();
if (length == 0) {
return;
}
uword last = (*code_region_table_)[0]->end();
for (intptr_t i = 1; i < length; i++) {
CodeRegion* a = (*code_region_table_)[i];
ASSERT(last <= a->start());
last = a->end();
}
}
void VerifyOverlap() {
const intptr_t length = code_region_table_->length();
for (intptr_t i = 0; i < length; i++) {
CodeRegion* a = (*code_region_table_)[i];
for (intptr_t j = i+1; j < length; j++) {
CodeRegion* b = (*code_region_table_)[j];
ASSERT(!a->contains(b->start()) &&
!a->contains(b->end() - 1) &&
!b->contains(a->start()) &&
!b->contains(a->end() - 1));
}
}
}
ZoneGrowableArray<CodeRegion*>* code_region_table_;
};
class CodeRegionTableBuilder : public SampleVisitor {
public:
CodeRegionTableBuilder(Isolate* isolate,
CodeRegionTable* live_code_table,
CodeRegionTable* dead_code_table,
CodeRegionTable* tag_code_table,
DeoptimizedCodeSet* deoptimized_code)
: SampleVisitor(isolate),
live_code_table_(live_code_table),
dead_code_table_(dead_code_table),
tag_code_table_(tag_code_table),
isolate_(isolate),
vm_isolate_(Dart::vm_isolate()),
null_code_(Code::ZoneHandle()),
deoptimized_code_(deoptimized_code) {
ASSERT(live_code_table_ != NULL);
ASSERT(dead_code_table_ != NULL);
ASSERT(tag_code_table_ != NULL);
ASSERT(isolate_ != NULL);
ASSERT(vm_isolate_ != NULL);
ASSERT(null_code_.IsNull());
frames_ = 0;
min_time_ = kMaxInt64;
max_time_ = 0;
}
void VisitSample(Sample* sample) {
int64_t timestamp = sample->timestamp();
if (timestamp > max_time_) {
max_time_ = timestamp;
}
if (timestamp < min_time_) {
min_time_ = timestamp;
}
// Make sure VM tag is created.
if (VMTag::IsNativeEntryTag(sample->vm_tag())) {
CreateTag(VMTag::kNativeTagId);
} else if (VMTag::IsRuntimeEntryTag(sample->vm_tag())) {
CreateTag(VMTag::kRuntimeTagId);
}
CreateTag(sample->vm_tag());
// Make sure user tag is created.
CreateUserTag(sample->user_tag());
// Exclusive tick for top frame if we aren't sampled from an exit frame.
if (!sample->exit_frame_sample()) {
Tick(sample->At(0), true, timestamp);
}
// Inclusive tick for all frames.
for (intptr_t i = 0; i < FLAG_profile_depth; i++) {
if (sample->At(i) == 0) {
break;
}
frames_++;
Tick(sample->At(i), false, timestamp);
}
}
intptr_t frames() const { return frames_; }
intptr_t TimeDeltaMicros() const {
return static_cast<intptr_t>(max_time_ - min_time_);
}
int64_t max_time() const { return max_time_; }
private:
void CreateTag(uword tag) {
intptr_t index = tag_code_table_->FindIndex(tag);
if (index >= 0) {
// Already created.
return;
}
CodeRegion* region = new CodeRegion(CodeRegion::kTagCode,
tag,
tag + 1,
0,
null_code_);
index = tag_code_table_->InsertCodeRegion(region);
ASSERT(index >= 0);
region->set_creation_serial(visited());
}
void CreateUserTag(uword tag) {
if (tag == 0) {
// None set.
return;
}
return CreateTag(tag);
}
void Tick(uword pc, bool exclusive, int64_t timestamp) {
CodeRegionTable::TickResult r;
intptr_t serial = exclusive ? -1 : visited();
r = live_code_table_->Tick(pc, exclusive, serial, timestamp);
if (r == CodeRegionTable::kTicked) {
// Live code found and ticked.
return;
}
if (r == CodeRegionTable::kNewerCode) {
// Code has been overwritten by newer code.
// Update shadow table of dead code regions.
r = dead_code_table_->Tick(pc, exclusive, serial, timestamp);
ASSERT(r != CodeRegionTable::kNewerCode);
if (r == CodeRegionTable::kTicked) {
// Dead code found and ticked.
return;
}
ASSERT(r == CodeRegionTable::kNotFound);
CreateAndTickDeadCodeRegion(pc, exclusive, serial);
return;
}
// Create new live CodeRegion.
ASSERT(r == CodeRegionTable::kNotFound);
CodeRegion* region = CreateCodeRegion(pc);
region->set_creation_serial(visited());
intptr_t index = live_code_table_->InsertCodeRegion(region);
ASSERT(index >= 0);
region = live_code_table_->At(index);
if (region->compile_timestamp() <= timestamp) {
region->Tick(pc, exclusive, serial);
return;
}
// We have created a new code region but it's for a CodeRegion
// compiled after the sample.
ASSERT(region->kind() == CodeRegion::kDartCode);
CreateAndTickDeadCodeRegion(pc, exclusive, serial);
}
void CreateAndTickDeadCodeRegion(uword pc, bool exclusive, intptr_t serial) {
// Need to create dead code.
CodeRegion* region = new CodeRegion(CodeRegion::kReusedCode,
pc,
pc + 1,
0,
null_code_);
intptr_t index = dead_code_table_->InsertCodeRegion(region);
region->set_creation_serial(visited());
ASSERT(index >= 0);
dead_code_table_->At(index)->Tick(pc, exclusive, serial);
}
CodeRegion* CreateCodeRegion(uword pc) {
const intptr_t kDartCodeAlignment = OS::PreferredCodeAlignment();
const intptr_t kDartCodeAlignmentMask = ~(kDartCodeAlignment - 1);
Code& code = Code::Handle(isolate_);
// Check current isolate for pc.
if (isolate_->heap()->CodeContains(pc)) {
code ^= Code::LookupCode(pc);
if (!code.IsNull()) {
deoptimized_code_->Add(code);
return new CodeRegion(CodeRegion::kDartCode,
code.EntryPoint(),
code.EntryPoint() + code.Size(),
code.compile_timestamp(),
code);
}
return new CodeRegion(CodeRegion::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 CodeRegion(CodeRegion::kDartCode,
code.EntryPoint(),
code.EntryPoint() + code.Size(),
code.compile_timestamp(),
code);
}
return new CodeRegion(CodeRegion::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 CodeRegion(CodeRegion::kNativeCode,
pc,
pc + 1,
0,
code);
}
ASSERT(pc >= native_start);
CodeRegion* code_region =
new CodeRegion(CodeRegion::kNativeCode,
native_start,
pc + 1,
0,
code);
code_region->SetName(native_name);
free(native_name);
return code_region;
}
intptr_t frames_;
int64_t min_time_;
int64_t max_time_;
CodeRegionTable* live_code_table_;
CodeRegionTable* dead_code_table_;
CodeRegionTable* tag_code_table_;
Isolate* isolate_;
Isolate* vm_isolate_;
const Code& null_code_;
DeoptimizedCodeSet* deoptimized_code_;
};
class CodeRegionFunctionMapper : public ValueObject {
public:
CodeRegionFunctionMapper(Isolate* isolate,
CodeRegionTable* live_code_table,
CodeRegionTable* dead_code_table,
CodeRegionTable* tag_code_table,
ProfileFunctionTable* function_table)
: isolate_(isolate),
live_code_table_(live_code_table),
dead_code_table_(dead_code_table),
tag_code_table_(tag_code_table),
function_table_(function_table) {
ASSERT(isolate_ != NULL);
ASSERT(live_code_table_ != NULL);
ASSERT(dead_code_table_ != NULL);
ASSERT(tag_code_table_ != NULL);
dead_code_table_offset_ = live_code_table_->Length();
tag_code_table_offset_ = dead_code_table_offset_ +
dead_code_table_->Length();
const Code& null_code = Code::ZoneHandle();
// Create the truncated tag.
intptr_t truncated_index =
tag_code_table_->FindIndex(VMTag::kTruncatedTagId);
ASSERT(truncated_index < 0);
CodeRegion* truncated =
new CodeRegion(CodeRegion::kTagCode,
VMTag::kTruncatedTagId,
VMTag::kTruncatedTagId + 1,
0,
null_code);
truncated_index = tag_code_table_->InsertCodeRegion(truncated);
ASSERT(truncated_index >= 0);
truncated->set_creation_serial(0);
// Create the root tag.
intptr_t root_index = tag_code_table_->FindIndex(VMTag::kRootTagId);
ASSERT(root_index < 0);
CodeRegion* root = new CodeRegion(CodeRegion::kTagCode,
VMTag::kRootTagId,
VMTag::kRootTagId + 1,
0,
null_code);
root_index = tag_code_table_->InsertCodeRegion(root);
ASSERT(root_index >= 0);
root->set_creation_serial(0);
}
void Map() {
// Calculate final indexes in code table for each CodeRegion.
for (intptr_t i = 0; i < live_code_table_->Length(); i++) {
const intptr_t index = i;
CodeRegion* region = live_code_table_->At(i);
ASSERT(region != NULL);
region->set_code_table_index(index);
}
for (intptr_t i = 0; i < dead_code_table_->Length(); i++) {
const intptr_t index = dead_code_table_offset_ + i;
CodeRegion* region = dead_code_table_->At(i);
ASSERT(region != NULL);
region->set_code_table_index(index);
}
for (intptr_t i = 0; i < tag_code_table_->Length(); i++) {
const intptr_t index = tag_code_table_offset_ + i;
CodeRegion* region = tag_code_table_->At(i);
ASSERT(region != NULL);
region->set_code_table_index(index);
}
// Associate a ProfileFunction with each CodeRegion.
for (intptr_t i = 0; i < live_code_table_->Length(); i++) {
CodeRegion* region = live_code_table_->At(i);
ASSERT(region != NULL);
region->SetFunctionAndName(function_table_);
}
for (intptr_t i = 0; i < dead_code_table_->Length(); i++) {
CodeRegion* region = dead_code_table_->At(i);
ASSERT(region != NULL);
region->SetFunctionAndName(function_table_);
}
for (intptr_t i = 0; i < tag_code_table_->Length(); i++) {
CodeRegion* region = tag_code_table_->At(i);
ASSERT(region != NULL);
region->SetFunctionAndName(function_table_);
}
}
private:
Isolate* isolate_;
CodeRegionTable* live_code_table_;
CodeRegionTable* dead_code_table_;
CodeRegionTable* tag_code_table_;
ProfileFunctionTable* function_table_;
intptr_t dead_code_table_offset_;
intptr_t tag_code_table_offset_;
};
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 ZoneAllocated {
public:
explicit ProfileFunctionTrieNode(intptr_t profile_function_table_index)
: profile_function_table_index_(profile_function_table_index),
count_(0),
code_objects_(new ZoneGrowableArray<ProfileFunctionTrieNodeCode>()) {
}
void Tick() {
count_++;
}
intptr_t count() const {
return count_;
}
intptr_t profile_function_table_index() const {
return profile_function_table_index_;
}
ProfileFunctionTrieNode* GetChild(intptr_t child_index) {
const intptr_t length = children_.length();
intptr_t i = 0;
while (i < length) {
ProfileFunctionTrieNode* child = children_[i];
if (child->profile_function_table_index() == child_index) {
return child;
}
if (child->profile_function_table_index() > child_index) {
break;
}
i++;
}
// Add new ProfileFunctionTrieNode, sorted by index.
ProfileFunctionTrieNode* child =
new ProfileFunctionTrieNode(child_index);
if (i < length) {
// Insert at i.
children_.InsertAt(i, child);
} else {
// Add to end.
children_.Add(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);
}
// This should only be called after the trie is completely built.
void SortByCount() {
code_objects_->Sort(ProfileFunctionTrieNodeCodeCompare);
children_.Sort(ProfileFunctionTrieNodeCompare);
intptr_t child_count = children_.length();
// Recurse.
for (intptr_t i = 0; i < child_count; i++) {
children_[i]->SortByCount();
}
}
void PrintToJSONArray(JSONArray* array) const {
ASSERT(array != NULL);
// Write CodeRegion index.
array->AddValue(profile_function_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);
}
}
private:
static int ProfileFunctionTrieNodeCodeCompare(
const ProfileFunctionTrieNodeCode* a,
const ProfileFunctionTrieNodeCode* b) {
ASSERT(a != NULL);
ASSERT(b != NULL);
return b->ticks() - a->ticks();
}
static int ProfileFunctionTrieNodeCompare(ProfileFunctionTrieNode* const* a,
ProfileFunctionTrieNode* const* b) {
ASSERT(a != NULL);
ASSERT(b != NULL);
return (*b)->count() - (*a)->count();
}
const intptr_t profile_function_table_index_;
intptr_t count_;
ZoneGrowableArray<ProfileFunctionTrieNode*> children_;
ZoneGrowableArray<ProfileFunctionTrieNodeCode>* code_objects_;
};
class ProfileFunctionTrieBuilder : public SampleVisitor {
public:
ProfileFunctionTrieBuilder(Isolate* isolate,
CodeRegionTable* live_code_table,
CodeRegionTable* dead_code_table,
CodeRegionTable* tag_code_table,
ProfileFunctionTable* function_table)
: SampleVisitor(isolate),
live_code_table_(live_code_table),
dead_code_table_(dead_code_table),
tag_code_table_(tag_code_table),
function_table_(function_table),
inclusive_tree_(false),
trace_(false),
trace_code_filter_(NULL) {
ASSERT(live_code_table_ != NULL);
ASSERT(dead_code_table_ != NULL);
ASSERT(tag_code_table_ != NULL);
ASSERT(function_table_ != NULL);
set_tag_order(ProfilerService::kUserVM);
// Verify that the truncated tag exists.
ASSERT(tag_code_table_->FindIndex(VMTag::kTruncatedTagId) >= 0);
// Verify that the root tag exists.
intptr_t root_index = tag_code_table_->FindIndex(VMTag::kRootTagId);
ASSERT(root_index >= 0);
// Setup root.
CodeRegion* region = tag_code_table_->At(root_index);
ASSERT(region != NULL);
ProfileFunction* function = region->function();
ASSERT(function != NULL);
exclusive_root_ = new ProfileFunctionTrieNode(function->index());
inclusive_root_ = new ProfileFunctionTrieNode(function->index());
}
void VisitSample(Sample* sample) {
inclusive_tree_ = false;
ProcessSampleExclusive(sample);
inclusive_tree_ = true;
ProcessSampleInclusive(sample);
}
ProfileFunctionTrieNode* exclusive_root() const {
return exclusive_root_;
}
ProfileFunctionTrieNode* inclusive_root() const {
return inclusive_root_;
}
ProfilerService::TagOrder tag_order() const {
return tag_order_;
}
bool vm_tags_emitted() const {
return (tag_order_ == ProfilerService::kUserVM) ||
(tag_order_ == ProfilerService::kVMUser) ||
(tag_order_ == ProfilerService::kVM);
}
void set_tag_order(ProfilerService::TagOrder tag_order) {
tag_order_ = tag_order;
}
private:
void ProcessSampleInclusive(Sample* sample) {
// Give the root a tick.
inclusive_root_->Tick();
ProfileFunctionTrieNode* current = inclusive_root_;
current = AppendTags(sample, current);
if (sample->truncated_trace()) {
InclusiveTickTruncatedTag();
current = AppendTruncatedTag(current);
}
// Walk the sampled PCs.
for (intptr_t i = FLAG_profile_depth - 1; i >= 0; i--) {
if (sample->At(i) == 0) {
continue;
}
current = ProcessPC(sample->At(i),
sample->timestamp(),
current,
visited(),
(i == 0),
sample->exit_frame_sample() && (i == 0),
sample->missing_frame_inserted());
}
}
void ProcessSampleExclusive(Sample* sample) {
// Give the root a tick.
exclusive_root_->Tick();
ProfileFunctionTrieNode* current = exclusive_root_;
current = AppendTags(sample, current);
// Walk the sampled PCs.
for (intptr_t i = 0; i < FLAG_profile_depth; i++) {
if (sample->At(i) == 0) {
break;
}
current = ProcessPC(sample->At(i),
sample->timestamp(),
current,
visited(),
(i == 0),
sample->exit_frame_sample() && (i == 0),
sample->missing_frame_inserted());
}
if (sample->truncated_trace()) {
current = AppendTruncatedTag(current);
}
}
ProfileFunctionTrieNode* AppendUserTag(Sample* sample,
ProfileFunctionTrieNode* current) {
intptr_t user_tag_index = FindTagIndex(sample->user_tag());
if (user_tag_index >= 0) {
current = current->GetChild(user_tag_index);
// Give the tag a tick.
current->Tick();
}
return current;
}
ProfileFunctionTrieNode* AppendTruncatedTag(
ProfileFunctionTrieNode* current) {
intptr_t truncated_tag_index = FindTagIndex(VMTag::kTruncatedTagId);
ASSERT(truncated_tag_index >= 0);
current = current->GetChild(truncated_tag_index);
current->Tick();
return current;
}
void InclusiveTickTruncatedTag() {
intptr_t index = tag_code_table_->FindIndex(VMTag::kTruncatedTagId);
CodeRegion* region = tag_code_table_->At(index);
ProfileFunction* function = region->function();
function->inc_inclusive_ticks();
}
ProfileFunctionTrieNode* AppendVMTag(Sample* sample,
ProfileFunctionTrieNode* current) {
if (VMTag::IsNativeEntryTag(sample->vm_tag())) {
// Insert a dummy kNativeTagId node.
intptr_t tag_index = FindTagIndex(VMTag::kNativeTagId);
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
} else if (VMTag::IsRuntimeEntryTag(sample->vm_tag())) {
// Insert a dummy kRuntimeTagId node.
intptr_t tag_index = FindTagIndex(VMTag::kRuntimeTagId);
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
} else {
intptr_t tag_index = FindTagIndex(sample->vm_tag());
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
}
return current;
}
ProfileFunctionTrieNode* AppendSpecificNativeRuntimeEntryVMTag(
Sample* sample, ProfileFunctionTrieNode* current) {
// Only Native and Runtime entries have a second VM tag.
if (!VMTag::IsNativeEntryTag(sample->vm_tag()) &&
!VMTag::IsRuntimeEntryTag(sample->vm_tag())) {
return current;
}
intptr_t tag_index = FindTagIndex(sample->vm_tag());
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
return current;
}
ProfileFunctionTrieNode* AppendVMTags(Sample* sample,
ProfileFunctionTrieNode* current) {
current = AppendVMTag(sample, current);
current = AppendSpecificNativeRuntimeEntryVMTag(sample, current);
return current;
}
ProfileFunctionTrieNode* AppendTags(Sample* sample,
ProfileFunctionTrieNode* current) {
// None.
if (tag_order() == ProfilerService::kNoTags) {
return current;
}
// User first.
if ((tag_order() == ProfilerService::kUserVM) ||
(tag_order() == ProfilerService::kUser)) {
current = AppendUserTag(sample, current);
// Only user.
if (tag_order() == ProfilerService::kUser) {
return current;
}
return AppendVMTags(sample, current);
}
// VM first.
ASSERT((tag_order() == ProfilerService::kVMUser) ||
(tag_order() == ProfilerService::kVM));
current = AppendVMTags(sample, current);
// Only VM.
if (tag_order() == ProfilerService::kVM) {
return current;
}
return AppendUserTag(sample, current);
}
intptr_t FindTagIndex(uword tag) const {
if (tag == 0) {
UNREACHABLE();
return -1;
}
intptr_t index = tag_code_table_->FindIndex(tag);
if (index < 0) {
UNREACHABLE();
return -1;
}
ASSERT(index >= 0);
CodeRegion* region = tag_code_table_->At(index);
ASSERT(region->contains(tag));
ProfileFunction* function = region->function();
ASSERT(function != NULL);
return function->index();
}
void Dump(ProfileFunctionTrieNode* current) {
int current_index = current->profile_function_table_index();
ProfileFunction* function = function_table_->At(current_index);
function->Dump();
OS::Print("\n");
}
ProfileFunctionTrieNode* ProcessPC(uword pc,
int64_t timestamp,
ProfileFunctionTrieNode* current,
intptr_t inclusive_serial,
bool top_frame,
bool exit_frame,
bool missing_frame_inserted) {
CodeRegion* region = FindCodeObject(pc, timestamp);
if (region == NULL) {
return current;
}
const char* region_name = region->name();
if (region_name == NULL) {
region_name = "";
}
intptr_t code_index = region->code_table_index();
const Code& code = Code::ZoneHandle(region->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.
ProfileFunction* function = region->function();
ASSERT(function != NULL);
if (trace_) {
OS::Print("[%" Px "] X - %s (%s)\n",
pc, function->name(), region_name);
}
current = ProcessFunction(function,
current,
inclusive_serial,
top_frame,
exit_frame,
code_index);
if ((trace_code_filter_ != NULL) &&
(strstr(region_name, trace_code_filter_) != NULL)) {
trace_ = true;
OS::Print("Tracing from: %" Px " [%s] ", pc,
missing_frame_inserted ? "INSERTED" : "");
Dump(current);
}
return current;
}
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());
current = ProcessInlinedFunction(inlined_function,
current,
inclusive_serial,
top_frame,
exit_frame,
code_index);
top_frame = false;
}
} else {
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 char* inline_name = inlined_function->ToQualifiedCString();
if (trace_) {
OS::Print("[%" Px "] %" Pd " - %s (%s)\n",
pc, i, inline_name, region_name);
}
current = ProcessInlinedFunction(inlined_function,
current,
inclusive_serial,
top_frame,
exit_frame,
code_index);
top_frame = false;
if ((trace_code_filter_ != NULL) &&
(strstr(region_name, trace_code_filter_) != NULL)) {
trace_ = true;
OS::Print("Tracing from: %" Px " [%s] ",
pc, missing_frame_inserted ? "INSERTED" : "");
Dump(current);
}
}
}
return current;
}
ProfileFunctionTrieNode* ProcessInlinedFunction(
Function* inlined_function,
ProfileFunctionTrieNode* current,
intptr_t inclusive_serial,
bool top_frame,
bool exit_frame,
intptr_t code_index) {
ProfileFunction* function =
function_table_->LookupOrAdd(*inlined_function);
ASSERT(function != NULL);
return ProcessFunction(function,
current,
inclusive_serial,
top_frame,
exit_frame,
code_index);
}
ProfileFunctionTrieNode* ProcessFunction(ProfileFunction* function,
ProfileFunctionTrieNode* current,
intptr_t inclusive_serial,
bool top_frame,
bool exit_frame,
intptr_t code_index) {
const bool exclusive = top_frame && !exit_frame;
if (!inclusive_tree_) {
// We process functions for the inclusive and exclusive trees.
// Only tick the function for the exclusive tree.
function->Tick(exclusive, exclusive ? -1 : inclusive_serial);
}
function->AddCodeObjectIndex(code_index);
current = current->GetChild(function->index());
current->AddCodeObjectIndex(code_index);
if (top_frame) {
if (!exit_frame || vm_tags_emitted()) {
// Only tick if this isn't an exit frame or VM tags are emitted.
current->Tick();
}
} else {
current->Tick();
}
return current;
}
CodeRegion* FindCodeObject(uword pc, int64_t timestamp) const {
intptr_t index = live_code_table_->FindIndex(pc);
if (index < 0) {
return NULL;
}
CodeRegion* region = live_code_table_->At(index);
ASSERT(region->contains(pc));
if (region->compile_timestamp() > timestamp) {
// Overwritten code, find in dead code table.
index = dead_code_table_->FindIndex(pc);
if (index < 0) {
return NULL;
}
region = dead_code_table_->At(index);
ASSERT(region->contains(pc));
ASSERT(region->compile_timestamp() <= timestamp);
return region;
}
ASSERT(region->compile_timestamp() <= timestamp);
return region;
}
ProfilerService::TagOrder tag_order_;
ProfileFunctionTrieNode* exclusive_root_;
ProfileFunctionTrieNode* inclusive_root_;
CodeRegionTable* live_code_table_;
CodeRegionTable* dead_code_table_;
CodeRegionTable* tag_code_table_;
ProfileFunctionTable* function_table_;
bool inclusive_tree_;
bool trace_;
const char* trace_code_filter_;
};
class CodeRegionTrieNode : public ZoneAllocated {
public:
explicit CodeRegionTrieNode(intptr_t code_region_index)
: code_region_index_(code_region_index),
count_(0),
children_(new ZoneGrowableArray<CodeRegionTrieNode*>()) {
}
void Tick() {
ASSERT(code_region_index_ >= 0);
count_++;
}
intptr_t count() const {
ASSERT(code_region_index_ >= 0);
return count_;
}
intptr_t code_region_index() const {
return code_region_index_;
}
ZoneGrowableArray<CodeRegionTrieNode*>& children() const {
return *children_;
}
CodeRegionTrieNode* GetChild(intptr_t child_code_region_index) {
const intptr_t length = children_->length();
intptr_t i = 0;
while (i < length) {
CodeRegionTrieNode* child = (*children_)[i];
if (child->code_region_index() == child_code_region_index) {
return child;
}
if (child->code_region_index() > child_code_region_index) {
break;
}
i++;
}
// Add new CodeRegion, sorted by CodeRegionTable index.
CodeRegionTrieNode* child = new CodeRegionTrieNode(child_code_region_index);
if (i < length) {
// Insert at i.
children_->InsertAt(i, child);
} else {
// Add to end.
children_->Add(child);
}
return child;
}
// This should only be called after the trie is completely built.
void SortByCount() {
children_->Sort(CodeRegionTrieNodeCompare);
ZoneGrowableArray<CodeRegionTrieNode*>& kids = children();
intptr_t child_count = kids.length();
// Recurse.
for (intptr_t i = 0; i < child_count; i++) {
kids[i]->SortByCount();
}
}
void PrintToJSONArray(JSONArray* array) const {
ASSERT(array != NULL);
// Write CodeRegion index.
array->AddValue(code_region_index_);
// Write count.
array->AddValue(count_);
// Write number of children.
ZoneGrowableArray<CodeRegionTrieNode*>& kids = children();
intptr_t child_count = kids.length();
array->AddValue(child_count);
// Recurse.
for (intptr_t i = 0; i < child_count; i++) {
kids[i]->PrintToJSONArray(array);
}
}
private:
static int CodeRegionTrieNodeCompare(CodeRegionTrieNode* const* a,
CodeRegionTrieNode* const* b) {
ASSERT(a != NULL);
ASSERT(b != NULL);
return (*b)->count() - (*a)->count();
}
const intptr_t code_region_index_;
intptr_t count_;
ZoneGrowableArray<CodeRegionTrieNode*>* children_;
};
class CodeRegionTrieBuilder : public SampleVisitor {
public:
CodeRegionTrieBuilder(Isolate* isolate,
CodeRegionTable* live_code_table,
CodeRegionTable* dead_code_table,
CodeRegionTable* tag_code_table)
: SampleVisitor(isolate),
live_code_table_(live_code_table),
dead_code_table_(dead_code_table),
tag_code_table_(tag_code_table) {
ASSERT(live_code_table_ != NULL);
ASSERT(dead_code_table_ != NULL);
ASSERT(tag_code_table_ != NULL);
set_tag_order(ProfilerService::kUserVM);
// Verify that the truncated tag exists.
ASSERT(tag_code_table_->FindIndex(VMTag::kTruncatedTagId) >= 0);
// Verify that the root tag exists.
intptr_t root_index = tag_code_table_->FindIndex(VMTag::kRootTagId);
ASSERT(root_index >= 0);
CodeRegion* region = tag_code_table_->At(root_index);
ASSERT(region != NULL);
exclusive_root_ = new CodeRegionTrieNode(region->code_table_index());
inclusive_root_ = new CodeRegionTrieNode(region->code_table_index());
}
void VisitSample(Sample* sample) {
ProcessSampleExclusive(sample);
ProcessSampleInclusive(sample);
}
CodeRegionTrieNode* inclusive_root() const {
return inclusive_root_;
}
CodeRegionTrieNode* exclusive_root() const {
return exclusive_root_;
}
ProfilerService::TagOrder tag_order() const {
return tag_order_;
}
bool vm_tags_emitted() const {
return (tag_order_ == ProfilerService::kUserVM) ||
(tag_order_ == ProfilerService::kVMUser) ||
(tag_order_ == ProfilerService::kVM);
}
void set_tag_order(ProfilerService::TagOrder tag_order) {
tag_order_ = tag_order;
}
private:
void ProcessSampleInclusive(Sample* sample) {
// Give the root a tick.
inclusive_root_->Tick();
CodeRegionTrieNode* current = inclusive_root_;
current = AppendTags(sample, current);
if (sample->truncated_trace()) {
current = AppendTruncatedTag(current);
}
// Walk the sampled PCs.
for (intptr_t i = FLAG_profile_depth - 1; i >= 0; i--) {
if (sample->At(i) == 0) {
continue;
}
intptr_t index = FindFinalIndex(sample->At(i), sample->timestamp());
if (index < 0) {
continue;
}
current = current->GetChild(index);
current->Tick();
}
}
void ProcessSampleExclusive(Sample* sample) {
// Give the root a tick.
exclusive_root_->Tick();
CodeRegionTrieNode* current = exclusive_root_;
current = AppendTags(sample, current);
// Walk the sampled PCs.
for (intptr_t i = 0; i < FLAG_profile_depth; i++) {
if (sample->At(i) == 0) {
break;
}
intptr_t index = FindFinalIndex(sample->At(i), sample->timestamp());
if (index < 0) {
continue;
}
current = current->GetChild(index);
if (i == 0) {
// Executing PC.
if (!sample->exit_frame_sample() || vm_tags_emitted()) {
// Only tick if this isn't an exit frame or VM tags are emitted.
current->Tick();
}
} else {
// Caller PCs.
current->Tick();
}
}
if (sample->truncated_trace()) {
current = AppendTruncatedTag(current);
}
}
CodeRegionTrieNode* AppendUserTag(Sample* sample,
CodeRegionTrieNode* current) {
intptr_t user_tag_index = FindTagIndex(sample->user_tag());
if (user_tag_index >= 0) {
current = current->GetChild(user_tag_index);
// Give the tag a tick.
current->Tick();
}
return current;
}
CodeRegionTrieNode* AppendTruncatedTag(CodeRegionTrieNode* current) {
intptr_t truncated_tag_index = FindTagIndex(VMTag::kTruncatedTagId);
ASSERT(truncated_tag_index >= 0);
current = current->GetChild(truncated_tag_index);
current->Tick();
return current;
}
CodeRegionTrieNode* AppendVMTag(Sample* sample,
CodeRegionTrieNode* current) {
if (VMTag::IsNativeEntryTag(sample->vm_tag())) {
// Insert a dummy kNativeTagId node.
intptr_t tag_index = FindTagIndex(VMTag::kNativeTagId);
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
} else if (VMTag::IsRuntimeEntryTag(sample->vm_tag())) {
// Insert a dummy kRuntimeTagId node.
intptr_t tag_index = FindTagIndex(VMTag::kRuntimeTagId);
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
} else {
intptr_t tag_index = FindTagIndex(sample->vm_tag());
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
}
return current;
}
CodeRegionTrieNode* AppendSpecificNativeRuntimeEntryVMTag(
Sample* sample, CodeRegionTrieNode* current) {
// Only Native and Runtime entries have a second VM tag.
if (!VMTag::IsNativeEntryTag(sample->vm_tag()) &&
!VMTag::IsRuntimeEntryTag(sample->vm_tag())) {
return current;
}
intptr_t tag_index = FindTagIndex(sample->vm_tag());
current = current->GetChild(tag_index);
// Give the tag a tick.
current->Tick();
return current;
}
CodeRegionTrieNode* AppendVMTags(Sample* sample,
CodeRegionTrieNode* current) {
current = AppendVMTag(sample, current);
current = AppendSpecificNativeRuntimeEntryVMTag(sample, current);
return current;
}
CodeRegionTrieNode* AppendTags(Sample* sample, CodeRegionTrieNode* current) {
// None.
if (tag_order() == ProfilerService::kNoTags) {
return current;
}
// User first.
if ((tag_order() == ProfilerService::kUserVM) ||
(tag_order() == ProfilerService::kUser)) {
current = AppendUserTag(sample, current);
// Only user.
if (tag_order() == ProfilerService::kUser) {
return current;
}
return AppendVMTags(sample, current);
}
// VM first.
ASSERT((tag_order() == ProfilerService::kVMUser) ||
(tag_order() == ProfilerService::kVM));
current = AppendVMTags(sample, current);
// Only VM.
if (tag_order() == ProfilerService::kVM) {
return current;
}
return AppendUserTag(sample, current);
}
intptr_t FindTagIndex(uword tag) const {
if (tag == 0) {
UNREACHABLE();
return -1;
}
intptr_t index = tag_code_table_->FindIndex(tag);
if (index < 0) {
UNREACHABLE();
return -1;
}
ASSERT(index >= 0);
CodeRegion* region = tag_code_table_->At(index);
ASSERT(region->contains(tag));
return region->code_table_index();
}
intptr_t FindDeadIndex(uword pc, int64_t timestamp) const {
intptr_t index = dead_code_table_->FindIndex(pc);
if (index < 0) {
OS::Print("%" Px " cannot be found\n", pc);
return -1;
}
CodeRegion* region = dead_code_table_->At(index);
ASSERT(region->contains(pc));
ASSERT(region->compile_timestamp() <= timestamp);
return region->code_table_index();
}
intptr_t FindFinalIndex(uword pc, int64_t timestamp) const {
intptr_t index = live_code_table_->FindIndex(pc);
if (index < 0) {
// Try dead code table.
return FindDeadIndex(pc, timestamp);
}
CodeRegion* region = live_code_table_->At(index);
ASSERT(region->contains(pc));
if (region->compile_timestamp() > timestamp) {
// Overwritten code, find in dead code table.
return FindDeadIndex(pc, timestamp);
}
ASSERT(region->compile_timestamp() <= timestamp);
return region->code_table_index();
}
ProfilerService::TagOrder tag_order_;
CodeRegionTrieNode* exclusive_root_;
CodeRegionTrieNode* inclusive_root_;
CodeRegionTable* live_code_table_;
CodeRegionTable* dead_code_table_;
CodeRegionTable* tag_code_table_;
};
void ProfilerService::PrintJSON(JSONStream* stream, TagOrder tag_order) {
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) {
JSONObject error(stream);
error.AddProperty("type", "Error");
error.AddProperty("text", "Isolate does not have profiling enabled.");
return;
}
SampleBuffer* sample_buffer = profiler_data->sample_buffer();
ASSERT(sample_buffer != NULL);
ScopeTimer sw("ProfilerService::PrintJSON", FLAG_trace_profiler);
{
StackZone zone(isolate);
HANDLESCOPE(isolate);
{
// Live code holds Dart, Native, and Collected CodeRegions.
CodeRegionTable live_code_table;
// Dead code holds Overwritten CodeRegions.
CodeRegionTable dead_code_table;
// Tag code holds Tag CodeRegions.
CodeRegionTable tag_code_table;
// Table holding all ProfileFunctions.
ProfileFunctionTable function_table;
// Set of deoptimized code still referenced by the profiler.
DeoptimizedCodeSet* deoptimized_code = new DeoptimizedCodeSet(isolate);
{
ScopeTimer sw("PreprocessSamples", FLAG_trace_profiler);
// Preprocess samples.
PreprocessVisitor preprocessor(isolate);
sample_buffer->VisitSamples(&preprocessor);
}
// Build CodeRegion tables.
CodeRegionTableBuilder builder(isolate,
&live_code_table,
&dead_code_table,
&tag_code_table,
deoptimized_code);
{
ScopeTimer sw("CodeRegionTableBuilder", FLAG_trace_profiler);
sample_buffer->VisitSamples(&builder);
}
intptr_t samples = builder.visited();
intptr_t frames = builder.frames();
if (FLAG_trace_profiler) {
intptr_t total_live_code_objects = live_code_table.Length();
intptr_t total_dead_code_objects = dead_code_table.Length();
intptr_t total_tag_code_objects = tag_code_table.Length();
OS::Print(
"Processed %" Pd " samples with %" Pd " frames\n", samples, frames);
OS::Print("CodeTables: live=%" Pd " dead=%" Pd " tag=%" Pd "\n",
total_live_code_objects,
total_dead_code_objects,
total_tag_code_objects);
}
if (FLAG_trace_profiler) {
ScopeTimer sw("CodeRegionTableVerify", FLAG_trace_profiler);
live_code_table.Verify();
dead_code_table.Verify();
tag_code_table.Verify();
}
{
ScopeTimer st("CodeRegionFunctionMapping", FLAG_trace_profiler);
CodeRegionFunctionMapper mapper(isolate, &live_code_table,
&dead_code_table,
&tag_code_table,
&function_table);
mapper.Map();
}
if (FLAG_trace_profiler) {
intptr_t total_functions = function_table.Length();
OS::Print("FunctionTable: size=%" Pd "\n", total_functions);
}
CodeRegionTrieBuilder code_trie_builder(isolate,
&live_code_table,
&dead_code_table,
&tag_code_table);
code_trie_builder.set_tag_order(tag_order);
{
// Build CodeRegion trie.
ScopeTimer sw("CodeRegionTrieBuilder", FLAG_trace_profiler);
sample_buffer->VisitSamples(&code_trie_builder);
code_trie_builder.exclusive_root()->SortByCount();
code_trie_builder.inclusive_root()->SortByCount();
}
if (FLAG_trace_profiler) {
OS::Print("Code Trie Root Count: E: %" Pd " I: %" Pd "\n",
code_trie_builder.exclusive_root()->count(),
code_trie_builder.inclusive_root()->count());
}
ProfileFunctionTrieBuilder function_trie_builder(isolate,
&live_code_table,
&dead_code_table,
&tag_code_table,
&function_table);
function_trie_builder.set_tag_order(tag_order);
{
// Build ProfileFunction trie.
ScopeTimer sw("ProfileFunctionTrieBuilder",
FLAG_trace_profiler);
sample_buffer->VisitSamples(&function_trie_builder);
function_trie_builder.exclusive_root()->SortByCount();
function_trie_builder.inclusive_root()->SortByCount();
}
if (FLAG_trace_profiler) {
OS::Print("Function Trie Root Count: E: %" Pd " I: %" Pd "\n",
function_trie_builder.exclusive_root()->count(),
function_trie_builder.inclusive_root()->count());
}
{
ScopeTimer sw("CpuProfileJSONStream", FLAG_trace_profiler);
// Serialize to JSON.
JSONObject obj(stream);
obj.AddProperty("type", "_CpuProfile");
obj.AddProperty("sampleCount", samples);
obj.AddProperty("samplePeriod",
static_cast<intptr_t>(FLAG_profile_period));
obj.AddProperty("stackDepth",
static_cast<intptr_t>(FLAG_profile_depth));
obj.AddProperty("timeSpan",
MicrosecondsToSeconds(builder.TimeDeltaMicros()));
{
JSONArray code_trie(&obj, "exclusiveCodeTrie");
CodeRegionTrieNode* root = code_trie_builder.exclusive_root();
ASSERT(root != NULL);
root->PrintToJSONArray(&code_trie);
}
{
JSONArray code_trie(&obj, "inclusiveCodeTrie");
CodeRegionTrieNode* root = code_trie_builder.inclusive_root();
ASSERT(root != NULL);
root->PrintToJSONArray(&code_trie);
}
{
JSONArray function_trie(&obj, "exclusiveFunctionTrie");
ProfileFunctionTrieNode* root =
function_trie_builder.exclusive_root();
ASSERT(root != NULL);
root->PrintToJSONArray(&function_trie);
}
{
JSONArray function_trie(&obj, "inclusiveFunctionTrie");
ProfileFunctionTrieNode* root =
function_trie_builder.inclusive_root();
ASSERT(root != NULL);
root->PrintToJSONArray(&function_trie);
}
{
JSONArray codes(&obj, "codes");
for (intptr_t i = 0; i < live_code_table.Length(); i++) {
CodeRegion* region = live_code_table.At(i);
ASSERT(region != NULL);
region->PrintToJSONArray(&codes);
}
for (intptr_t i = 0; i < dead_code_table.Length(); i++) {
CodeRegion* region = dead_code_table.At(i);
ASSERT(region != NULL);
region->PrintToJSONArray(&codes);
}
for (intptr_t i = 0; i < tag_code_table.Length(); i++) {
CodeRegion* region = tag_code_table.At(i);
ASSERT(region != NULL);
region->PrintToJSONArray(&codes);
}
}
{
JSONArray functions(&obj, "functions");
for (intptr_t i = 0; i < function_table.Length(); i++) {
ProfileFunction* function = function_table.At(i);
ASSERT(function != NULL);
function->PrintToJSONArray(&functions);
}
}
}
// Update the isolates set of dead code.
deoptimized_code->UpdateIsolate(isolate);
}
}
// Enable profile interrupts.
Profiler::BeginExecution(isolate);
}
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