Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(284)

Unified Diff: runtime/vm/profiler_service.cc

Issue 928833003: Add Function based profile tree (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/vm/profiler_service.h ('k') | runtime/vm/scope_timer.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/vm/profiler_service.cc
diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc
index a6457d6cd40ad1bd1186539ef5be1c87bf14d677..bfd4912fccdb83dd1f484bf0f092eef7010bf2aa 100644
--- a/runtime/vm/profiler_service.cc
+++ b/runtime/vm/profiler_service.cc
@@ -15,125 +15,362 @@
namespace dart {
DECLARE_FLAG(int, profile_depth);
-DECLARE_FLAG(bool, trace_profiler);
DECLARE_FLAG(int, profile_period);
-struct AddressEntry {
- uword pc;
- intptr_t exclusive_ticks;
- intptr_t inclusive_ticks;
+DEFINE_FLAG(bool, trace_profiler, false, "Trace profiler.");
- void tick(bool exclusive) {
- if (exclusive) {
- exclusive_ticks++;
- } else {
- inclusive_ticks++;
- }
+// 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);
+ }
-struct CallEntry {
- intptr_t code_table_index;
- intptr_t count;
-};
+ 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;
+ }
+ NoGCScope no_gc_scope;
+ for (intptr_t i = 0; array.Length(); i++) {
+ if (code.raw() == array.At(i)) {
+ return true;
+ }
+ }
+ return false;
+ }
-typedef bool (*RegionCompare)(uword pc, uword region_start, uword region_end);
+ 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 CodeRegionTrieNode : public ZoneAllocated {
+class ProfileFunction : public ZoneAllocated {
public:
- explicit CodeRegionTrieNode(intptr_t code_region_index)
- : code_region_index_(code_region_index),
- count_(0),
- children_(new ZoneGrowableArray<CodeRegionTrieNode*>()) {
+ 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);
}
- void Tick() {
- ASSERT(code_region_index_ >= 0);
- count_++;
+ const char* name() const {
+ ASSERT(name_ != NULL);
+ return name_;
}
- intptr_t count() const {
- ASSERT(code_region_index_ >= 0);
- return count_;
+ RawFunction* function() const {
+ return function_.raw();
}
- intptr_t code_region_index() const {
- return code_region_index_;
+ intptr_t index() const {
+ return table_index_;
}
- ZoneGrowableArray<CodeRegionTrieNode*>& children() const {
- return *children_;
+ Kind kind() const {
+ return kind_;
}
- 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;
+ 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;
}
- 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);
+ code_objects_->Add(index);
+ }
+
+ intptr_t inclusive_ticks() const {
+ return 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 {
- // Add to end.
- children_->Add(child);
+ inclusive_ticks_++;
+ // Mark the last serial we ticked the inclusive count.
+ inclusive_tick_serial_ = serial;
}
- return child;
}
- // Sort this's children and (recursively) all descendants by count.
- // 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 PrintToJSONObject(JSONObject* func) {
+ if (kind() == kNativeFunction) {
+ func->AddProperty("type", "@Function");
+ func->AddProperty("name", name());
+ func->AddProperty("kind", "Native");
+ } else if (kind() == kTagFunction) {
+ func->AddProperty("type", "@Function");
+ func->AddProperty("kind", "Tag");
+ func->AddProperty("name", name());
+ } else if (kind() == kUnkownFunction) {
+ func->AddProperty("type", "@Function");
+ func->AddProperty("name", name());
+ func->AddProperty("kind", "Collected");
+ } else if (kind() == kStubFunction) {
+ func->AddProperty("type", "@Function");
+ func->AddProperty("name", name());
+ func->AddProperty("kind", "Stub");
+ } else {
+ UNREACHABLE();
}
}
- 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);
+ 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:
- static int CodeRegionTrieNodeCompare(CodeRegionTrieNode* const* a,
- CodeRegionTrieNode* const* b) {
- ASSERT(a != NULL);
- ASSERT(b != NULL);
- return (*b)->count() - (*a)->count();
+ 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) {
}
- const intptr_t code_region_index_;
- intptr_t count_;
- ZoneGrowableArray<CodeRegionTrieNode*>* children_;
+ 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
@@ -148,7 +385,11 @@ class CodeRegion : public ZoneAllocated {
kTagCode, // A special kind of code representing a tag.
};
- CodeRegion(Kind kind, uword start, uword end, int64_t timestamp)
+ CodeRegion(Kind kind,
+ uword start,
+ uword end,
+ int64_t timestamp,
+ const Code& code)
: kind_(kind),
start_(start),
end_(end),
@@ -158,13 +399,14 @@ class CodeRegion : public ZoneAllocated {
name_(NULL),
compile_timestamp_(timestamp),
creation_serial_(0),
- address_table_(new ZoneGrowableArray<AddressEntry>()),
- callers_table_(new ZoneGrowableArray<CallEntry>()),
- callees_table_(new ZoneGrowableArray<CallEntry>()) {
+ 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;
@@ -227,6 +469,87 @@ class CodeRegion : public ZoneAllocated {
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 {
+ ASSERT(start() == 0);
+ SetName("root");
+ }
+ }
+ 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) {
@@ -273,14 +596,6 @@ class CodeRegion : public ZoneAllocated {
TickAddress(pc, exclusive);
}
- void AddCaller(intptr_t index, intptr_t count) {
- AddCallEntry(callers_table_, index, count);
- }
-
- void AddCallee(intptr_t index, intptr_t count) {
- AddCallEntry(callees_table_, index, count);
- }
-
void PrintNativeCode(JSONObject* profile_code_obj) {
ASSERT(kind() == kNativeCode);
JSONObject obj(profile_code_obj, "code");
@@ -289,14 +604,10 @@ class CodeRegion : public ZoneAllocated {
obj.AddProperty("name", name());
obj.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
- obj.AddPropertyF("id", "code/native-%" Px "", start());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
- func.AddProperty("type", "@Function");
- func.AddPropertyF("id", "functions/native-%" Px "", start());
- func.AddProperty("name", name());
- func.AddProperty("kind", "Native");
+ profile_function_->PrintToJSONObject(&func);
}
}
@@ -308,14 +619,10 @@ class CodeRegion : public ZoneAllocated {
obj.AddProperty("name", name());
obj.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
- obj.AddPropertyF("id", "code/collected-%" Px "", start());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
- func.AddProperty("type", "@Function");
- obj.AddPropertyF("id", "functions/collected-%" Px "", start());
- func.AddProperty("name", name());
- func.AddProperty("kind", "Collected");
+ profile_function_->PrintToJSONObject(&func);
}
}
@@ -327,122 +634,65 @@ class CodeRegion : public ZoneAllocated {
obj.AddProperty("name", name());
obj.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
- obj.AddPropertyF("id", "code/reused-%" Px "", start());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
- func.AddProperty("type", "@Function");
- obj.AddPropertyF("id", "functions/reused-%" Px "", start());
- func.AddProperty("name", name());
- func.AddProperty("kind", "Reused");
+ ASSERT(profile_function_ != NULL);
+ profile_function_->PrintToJSONObject(&func);
}
}
- void PrintTagCode(JSONObject* profile_code_obj) {
+ void PrintTagCode(JSONObject* profile_code_obj) {
ASSERT(kind() == kTagCode);
JSONObject obj(profile_code_obj, "code");
obj.AddProperty("type", "@Code");
obj.AddProperty("kind", "Tag");
- obj.AddPropertyF("id", "code/tag-%" Px "", start());
obj.AddProperty("name", name());
obj.AddPropertyF("start", "%" Px "", start());
obj.AddPropertyF("end", "%" Px "", end());
{
// Generate a fake function entry.
JSONObject func(&obj, "function");
- func.AddProperty("type", "@Function");
- func.AddProperty("kind", "Tag");
- obj.AddPropertyF("id", "functions/tag-%" Px "", start());
- func.AddProperty("name", name());
+ ASSERT(profile_function_ != NULL);
+ profile_function_->PrintToJSONObject(&func);
}
}
- void PrintToJSONArray(Isolate* isolate, JSONArray* events) {
- JSONObject obj(events);
+ void PrintToJSONArray(JSONArray* codes) {
+ JSONObject obj(codes);
obj.AddProperty("kind", KindToCString(kind()));
- obj.AddPropertyF("inclusive_ticks", "%" Pd "", inclusive_ticks());
- obj.AddPropertyF("exclusive_ticks", "%" Pd "", exclusive_ticks());
+ obj.AddPropertyF("inclusiveTicks", "%" Pd "", inclusive_ticks());
+ obj.AddPropertyF("exclusiveTicks", "%" Pd "", exclusive_ticks());
if (kind() == kDartCode) {
- // Look up code in Dart heap.
- Code& code = Code::Handle(isolate);
- code ^= Code::LookupCode(start());
- if (code.IsNull()) {
- // Code is a stub in the Vm isolate.
- code ^= Code::LookupCodeInVmIsolate(start());
- }
- ASSERT(!code.IsNull());
- obj.AddProperty("code", code);
+ ASSERT(!code_.IsNull());
+ obj.AddProperty("code", code_);
} else if (kind() == kCollectedCode) {
- if (name() == NULL) {
- // Lazily set generated name.
- GenerateAndSetSymbolName("[Collected]");
- }
PrintCollectedCode(&obj);
} else if (kind() == kReusedCode) {
- if (name() == NULL) {
- // Lazily set generated name.
- GenerateAndSetSymbolName("[Reused]");
- }
PrintOverwrittenCode(&obj);
} 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 {
- ASSERT(start() == 0);
- SetName("root");
- }
- }
PrintTagCode(&obj);
} else {
ASSERT(kind() == kNativeCode);
- if (name() == NULL) {
- // Lazily set generated name.
- GenerateAndSetSymbolName("[Native]");
- }
PrintNativeCode(&obj);
}
{
JSONArray ticks(&obj, "ticks");
- for (intptr_t i = 0; i < address_table_->length(); i++) {
- const AddressEntry& entry = (*address_table_)[i];
+ 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);
}
}
- {
- JSONArray callers(&obj, "callers");
- for (intptr_t i = 0; i < callers_table_->length(); i++) {
- const CallEntry& entry = (*callers_table_)[i];
- callers.AddValueF("%" Pd "", entry.code_table_index);
- callers.AddValueF("%" Pd "", entry.count);
- }
- }
- {
- JSONArray callees(&obj, "callees");
- for (intptr_t i = 0; i < callees_table_->length(); i++) {
- const CallEntry& entry = (*callees_table_)[i];
- callees.AddValueF("%" Pd "", entry.code_table_index);
- callees.AddValueF("%" Pd "", entry.count);
- }
- }
}
private:
void TickAddress(uword pc, bool exclusive) {
- const intptr_t length = address_table_->length();
+ const intptr_t length = address_table_.length();
intptr_t i = 0;
for (; i < length; i++) {
- AddressEntry& entry = (*address_table_)[i];
+ AddressEntry& entry = address_table_[i];
if (entry.pc == pc) {
// Tick the address entry.
entry.tick(exclusive);
@@ -460,35 +710,10 @@ class CodeRegion : public ZoneAllocated {
entry.tick(exclusive);
if (i < length) {
// Insert at i.
- address_table_->InsertAt(i, entry);
+ address_table_.InsertAt(i, entry);
} else {
// Add to end.
- address_table_->Add(entry);
- }
- }
-
-
- void AddCallEntry(ZoneGrowableArray<CallEntry>* table, intptr_t index,
- intptr_t count) {
- const intptr_t length = table->length();
- intptr_t i = 0;
- for (; i < length; i++) {
- CallEntry& entry = (*table)[i];
- if (entry.code_table_index == index) {
- entry.count += count;
- return;
- }
- if (entry.code_table_index > index) {
- break;
- }
- }
- CallEntry entry;
- entry.code_table_index = index;
- entry.count = count;
- if (i < length) {
- table->InsertAt(i, entry);
- } else {
- table->Add(entry);
+ address_table_.Add(entry);
}
}
@@ -519,9 +744,13 @@ class CodeRegion : public ZoneAllocated {
int64_t compile_timestamp_;
// Serial number at which this CodeRegion was created.
intptr_t creation_serial_;
- ZoneGrowableArray<AddressEntry>* address_table_;
- ZoneGrowableArray<CallEntry>* callers_table_;
- ZoneGrowableArray<CallEntry>* callees_table_;
+ // 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);
};
@@ -641,12 +870,10 @@ class CodeRegionTable : public ValueObject {
UNREACHABLE();
}
-#if defined(DEBUG)
void Verify() {
VerifyOrder();
VerifyOverlap();
}
-#endif
void DebugPrint() {
OS::Print("Dumping CodeRegionTable:\n");
@@ -695,7 +922,6 @@ class CodeRegionTable : public ValueObject {
region->AdjustExtent(start, end);
}
-#if defined(DEBUG)
void VerifyOrder() {
const intptr_t length = code_region_table_->length();
if (length == 0) {
@@ -722,124 +948,35 @@ class CodeRegionTable : public ValueObject {
}
}
}
-#endif
ZoneGrowableArray<CodeRegion*>* code_region_table_;
};
-class FixTopFrameVisitor : public SampleVisitor {
- public:
- explicit FixTopFrameVisitor(Isolate* isolate)
- : SampleVisitor(isolate),
- vm_isolate_(Dart::vm_isolate()) {
- }
-
- void VisitSample(Sample* sample) {
- if (sample->processed()) {
- // Already processed.
- return;
- }
- REUSABLE_CODE_HANDLESCOPE(isolate());
- // Mark that we've processed this sample.
- sample->set_processed(true);
- // Lookup code object for leaf frame.
- Code& code = reused_code_handle.Handle();
- code = FindCodeForPC(sample->At(0));
- sample->set_leaf_frame_is_dart(!code.IsNull());
- if (sample->pc_marker() == 0) {
- // No pc marker. Nothing to do.
- return;
- }
- if (!code.IsNull() && (code.compile_timestamp() > sample->timestamp())) {
- // Code compiled after sample. Ignore.
- return;
- }
- if (sample->leaf_frame_is_dart()) {
- CheckForMissingDartFrame(code, sample);
- }
- }
-
- private:
- void CheckForMissingDartFrame(const Code& code, Sample* sample) const {
- // Some stubs (and intrinsics) do not push a frame onto the stack leaving
- // the frame pointer in the caller.
- //
- // PC -> STUB
- // FP -> DART3 <-+
- // DART2 <-| <- TOP FRAME RETURN ADDRESS.
- // DART1 <-|
- // .....
- //
- // In this case, traversing the linked stack frames will not collect a PC
- // inside DART3. The stack will incorrectly be: STUB, DART2, DART1.
- // In Dart code, after pushing the FP onto the stack, an IP in the current
- // function is pushed onto the stack as well. This stack slot is called
- // the PC marker. We can use the PC marker to insert DART3 into the stack
- // so that it will correctly be: STUB, DART3, DART2, DART1. Note the
- // inserted PC may not accurately reflect the true return address from STUB.
- ASSERT(!code.IsNull());
- if (sample->sp() == sample->fp()) {
- // Haven't pushed pc marker yet.
- return;
- }
- uword pc_marker = sample->pc_marker();
- if (code.ContainsInstructionAt(pc_marker)) {
- // PC marker is in the same code as pc, no missing frame.
- return;
- }
- if (!ContainedInDartCodeHeaps(pc_marker)) {
- // Not a valid PC marker.
- return;
- }
- sample->InsertCallerForTopFrame(pc_marker);
- }
-
- bool ContainedInDartCodeHeaps(uword pc) const {
- return isolate()->heap()->CodeContains(pc) ||
- vm_isolate()->heap()->CodeContains(pc);
- }
-
- Isolate* vm_isolate() const {
- return vm_isolate_;
- }
-
- RawCode* FindCodeForPC(uword pc) const {
- // Check current isolate for pc.
- if (isolate()->heap()->CodeContains(pc)) {
- return Code::LookupCode(pc);
- }
- // Check VM isolate for pc.
- if (vm_isolate()->heap()->CodeContains(pc)) {
- return Code::LookupCodeInVmIsolate(pc);
- }
- return Code::null();
- }
-
- Isolate* vm_isolate_;
-};
-
-
class CodeRegionTableBuilder : public SampleVisitor {
public:
CodeRegionTableBuilder(Isolate* isolate,
CodeRegionTable* live_code_table,
CodeRegionTable* dead_code_table,
- CodeRegionTable* tag_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()) {
+ 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;
- ASSERT(isolate_ != NULL);
- ASSERT(vm_isolate_ != NULL);
}
void VisitSample(Sample* sample) {
@@ -859,7 +996,7 @@ class CodeRegionTableBuilder : public SampleVisitor {
CreateTag(sample->vm_tag());
// Make sure user tag is created.
CreateUserTag(sample->user_tag());
- // Exclusive tick for bottom frame if we aren't sampled from an exit frame.
+ // 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);
}
@@ -890,7 +1027,8 @@ class CodeRegionTableBuilder : public SampleVisitor {
CodeRegion* region = new CodeRegion(CodeRegion::kTagCode,
tag,
tag + 1,
- 0);
+ 0,
+ null_code_);
index = tag_code_table_->InsertCodeRegion(region);
ASSERT(index >= 0);
region->set_creation_serial(visited());
@@ -901,18 +1039,7 @@ class CodeRegionTableBuilder : public SampleVisitor {
// None set.
return;
}
- intptr_t index = tag_code_table_->FindIndex(tag);
- if (index >= 0) {
- // Already created.
- return;
- }
- CodeRegion* region = new CodeRegion(CodeRegion::kTagCode,
- tag,
- tag + 1,
- 0);
- index = tag_code_table_->InsertCodeRegion(region);
- ASSERT(index >= 0);
- region->set_creation_serial(visited());
+ return CreateTag(tag);
}
void Tick(uword pc, bool exclusive, int64_t timestamp) {
@@ -958,7 +1085,8 @@ class CodeRegionTableBuilder : public SampleVisitor {
CodeRegion* region = new CodeRegion(CodeRegion::kReusedCode,
pc,
pc + 1,
- 0);
+ 0,
+ null_code_);
intptr_t index = dead_code_table_->InsertCodeRegion(region);
region->set_creation_serial(visited());
ASSERT(index >= 0);
@@ -973,25 +1101,34 @@ class CodeRegionTableBuilder : public SampleVisitor {
if (isolate_->heap()->CodeContains(pc)) {
code ^= Code::LookupCode(pc);
if (!code.IsNull()) {
- return new CodeRegion(CodeRegion::kDartCode, code.EntryPoint(),
+ deoptimized_code_->Add(code);
+ return new CodeRegion(CodeRegion::kDartCode,
+ code.EntryPoint(),
code.EntryPoint() + code.Size(),
- code.compile_timestamp());
+ code.compile_timestamp(),
+ code);
}
- return new CodeRegion(CodeRegion::kCollectedCode, pc,
+ return new CodeRegion(CodeRegion::kCollectedCode,
+ pc,
(pc & kDartCodeAlignmentMask) + kDartCodeAlignment,
- 0);
+ 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(),
+ return new CodeRegion(CodeRegion::kDartCode,
+ code.EntryPoint(),
code.EntryPoint() + code.Size(),
- code.compile_timestamp());
+ code.compile_timestamp(),
+ code);
}
- return new CodeRegion(CodeRegion::kCollectedCode, pc,
+ return new CodeRegion(CodeRegion::kCollectedCode,
+ pc,
(pc & kDartCodeAlignmentMask) + kDartCodeAlignment,
- 0);
+ 0,
+ code);
}
// Check NativeSymbolResolver for pc.
uintptr_t native_start = 0;
@@ -999,11 +1136,19 @@ class CodeRegionTableBuilder : public SampleVisitor {
&native_start);
if (native_name == NULL) {
// No native name found.
- return new CodeRegion(CodeRegion::kNativeCode, pc, pc + 1, 0);
+ 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);
+ new CodeRegion(CodeRegion::kNativeCode,
+ native_start,
+ pc + 1,
+ 0,
+ code);
code_region->SetName(native_name);
free(native_name);
return code_region;
@@ -1017,19 +1162,24 @@ class CodeRegionTableBuilder : public SampleVisitor {
CodeRegionTable* tag_code_table_;
Isolate* isolate_;
Isolate* vm_isolate_;
+ const Code& null_code_;
+ DeoptimizedCodeSet* deoptimized_code_;
};
-class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
+class CodeRegionFunctionMapper : public ValueObject {
public:
- CodeRegionExclusiveTrieBuilder(Isolate* isolate,
- CodeRegionTable* live_code_table,
- CodeRegionTable* dead_code_table,
- CodeRegionTable* tag_code_table)
- : SampleVisitor(isolate),
+ 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) {
+ 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);
@@ -1039,32 +1189,261 @@ class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
intptr_t root_index = tag_code_table_->FindIndex(0);
// Verify that the "0" tag does not exist.
ASSERT(root_index < 0);
- // Insert the dummy tag CodeRegion that is used for the Trie root.
- CodeRegion* region = new CodeRegion(CodeRegion::kTagCode, 0, 1, 0);
+ // Insert the dummy tag CodeRegion as the root.
+ const Code& null_code = Code::ZoneHandle();
+ CodeRegion* region =
+ new CodeRegion(CodeRegion::kTagCode, 0, 1, 0, null_code);
root_index = tag_code_table_->InsertCodeRegion(region);
ASSERT(root_index >= 0);
region->set_creation_serial(0);
- root_ = new CodeRegionTrieNode(tag_code_table_offset_ + root_index);
+ }
+
+ 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 ProfileFunctionExclusiveTrieBuilder : public SampleVisitor {
+ public:
+ ProfileFunctionExclusiveTrieBuilder(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),
+ 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);
+
+ intptr_t root_index = tag_code_table_->FindIndex(0);
+ // Verify that the "0" tag does exist.
+ ASSERT(root_index >= 0);
+ CodeRegion* region = tag_code_table_->At(root_index);
+ ASSERT(region != NULL);
+
+ ProfileFunction* function = region->function();
+ root_ = new ProfileFunctionTrieNode(function->index());
}
void VisitSample(Sample* sample) {
// Give the root a tick.
root_->Tick();
- CodeRegionTrieNode* current = root_;
+ ProfileFunctionTrieNode* current = root_;
current = ProcessTags(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());
- current = current->GetChild(index);
- current->Tick();
+ // If we aren't sampled out of an exit frame and this is the top
+ // frame.
+ bool exclusive_tick = (i == 0) && !sample->exit_frame_sample();
+ current = ProcessPC(sample->At(i), sample->timestamp(), current,
+ visited(), exclusive_tick,
+ sample->missing_frame_inserted());
}
}
- CodeRegionTrieNode* root() const {
+ ProfileFunctionTrieNode* root() const {
return root_;
}
@@ -1077,8 +1456,8 @@ class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
}
private:
- CodeRegionTrieNode* ProcessUserTags(Sample* sample,
- CodeRegionTrieNode* current) {
+ ProfileFunctionTrieNode* ProcessUserTags(Sample* sample,
+ ProfileFunctionTrieNode* current) {
intptr_t user_tag_index = FindTagIndex(sample->user_tag());
if (user_tag_index >= 0) {
current = current->GetChild(user_tag_index);
@@ -1088,8 +1467,8 @@ class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
return current;
}
- CodeRegionTrieNode* ProcessVMTags(Sample* sample,
- CodeRegionTrieNode* current) {
+ ProfileFunctionTrieNode* ProcessVMTags(Sample* sample,
+ ProfileFunctionTrieNode* current) {
if (VMTag::IsNativeEntryTag(sample->vm_tag())) {
// Insert a dummy kNativeTagId node.
intptr_t tag_index = FindTagIndex(VMTag::kNativeTagId);
@@ -1110,7 +1489,8 @@ class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
return current;
}
- CodeRegionTrieNode* ProcessTags(Sample* sample, CodeRegionTrieNode* current) {
+ ProfileFunctionTrieNode* ProcessTags(Sample* sample,
+ ProfileFunctionTrieNode* current) {
// None.
if (tag_order() == ProfilerService::kNoTags) {
return current;
@@ -1138,104 +1518,389 @@ class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
intptr_t FindTagIndex(uword tag) const {
if (tag == 0) {
+ UNREACHABLE();
return -1;
}
intptr_t index = tag_code_table_->FindIndex(tag);
- if (index <= 0) {
+ if (index < 0) {
+ UNREACHABLE();
return -1;
}
ASSERT(index >= 0);
- ASSERT((tag_code_table_->At(index))->contains(tag));
- return tag_code_table_offset_ + index;
+ 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 exclusive,
+ 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);
+ }
+ function->Tick(exclusive, exclusive ? -1 : inclusive_serial);
+ current = current->GetChild(function->index());
+ current->AddCodeObjectIndex(code_index);
+ current->Tick();
+ 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;
+ }
+
+
+ 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);
+ }
+ ProfileFunction* function =
+ function_table_->LookupOrAdd(*inlined_function);
+ ASSERT(function != NULL);
+ function->AddCodeObjectIndex(code_index);
+ function->Tick(exclusive, exclusive ? -1 : inclusive_serial);
+ exclusive = false;
+ current = current->GetChild(function->index());
+ current->AddCodeObjectIndex(code_index);
+ current->Tick();
+ 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;
}
- intptr_t FindFinalIndex(uword pc, int64_t timestamp) const {
+ CodeRegion* FindCodeObject(uword pc, int64_t timestamp) const {
intptr_t index = live_code_table_->FindIndex(pc);
- ASSERT(index >= 0);
+ 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);
- ASSERT(index >= 0);
+ if (index < 0) {
+ return NULL;
+ }
region = dead_code_table_->At(index);
ASSERT(region->contains(pc));
ASSERT(region->compile_timestamp() <= timestamp);
- return index + dead_code_table_offset_;
+ return region;
}
ASSERT(region->compile_timestamp() <= timestamp);
- return index;
+ return region;
}
ProfilerService::TagOrder tag_order_;
- CodeRegionTrieNode* root_;
+ ProfileFunctionTrieNode* root_;
CodeRegionTable* live_code_table_;
CodeRegionTable* dead_code_table_;
CodeRegionTable* tag_code_table_;
- intptr_t dead_code_table_offset_;
- intptr_t tag_code_table_offset_;
+ ProfileFunctionTable* function_table_;
+ 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 CodeRegionTableCallersBuilder {
+class CodeRegionExclusiveTrieBuilder : public SampleVisitor {
public:
- CodeRegionTableCallersBuilder(CodeRegionTrieNode* exclusive_root,
- CodeRegionTable* live_code_table,
- CodeRegionTable* dead_code_table,
- CodeRegionTable* tag_code_table)
- : exclusive_root_(exclusive_root),
+ CodeRegionExclusiveTrieBuilder(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(exclusive_root_ != 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();
+ set_tag_order(ProfilerService::kUserVM);
+
+ intptr_t root_index = tag_code_table_->FindIndex(0);
+ // Verify that the "0" (root) tag does exist.
+ ASSERT(root_index >= 0);
+ CodeRegion* region = tag_code_table_->At(root_index);
+ ASSERT(region != NULL);
+ root_ = new CodeRegionTrieNode(region->code_table_index());
}
- void Build() {
- ProcessNode(exclusive_root_);
+ void VisitSample(Sample* sample) {
+ // Give the root a tick.
+ root_->Tick();
+ CodeRegionTrieNode* current = root_;
+ current = ProcessTags(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);
+ current->Tick();
+ }
+ }
+
+ CodeRegionTrieNode* root() const {
+ return root_;
+ }
+
+ ProfilerService::TagOrder tag_order() const {
+ return tag_order_;
+ }
+
+ void set_tag_order(ProfilerService::TagOrder tag_order) {
+ tag_order_ = tag_order;
}
private:
- void ProcessNode(CodeRegionTrieNode* parent) {
- const ZoneGrowableArray<CodeRegionTrieNode*>& children = parent->children();
- intptr_t parent_index = parent->code_region_index();
- ASSERT(parent_index >= 0);
- CodeRegion* parent_region = At(parent_index);
- ASSERT(parent_region != NULL);
- for (intptr_t i = 0; i < children.length(); i++) {
- CodeRegionTrieNode* node = children[i];
- ProcessNode(node);
- intptr_t index = node->code_region_index();
- ASSERT(index >= 0);
- CodeRegion* region = At(index);
- ASSERT(region != NULL);
- region->AddCallee(parent_index, node->count());
- parent_region->AddCaller(index, node->count());
+ CodeRegionTrieNode* ProcessUserTags(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;
}
- CodeRegion* At(intptr_t final_index) {
- ASSERT(final_index >= 0);
- if (final_index < dead_code_table_offset_) {
- return live_code_table_->At(final_index);
- } else if (final_index < tag_code_table_offset_) {
- return dead_code_table_->At(final_index - dead_code_table_offset_);
- } else {
- return tag_code_table_->At(final_index - tag_code_table_offset_);
+ CodeRegionTrieNode* ProcessVMTags(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();
+ }
+ intptr_t tag_index = FindTagIndex(sample->vm_tag());
+ current = current->GetChild(tag_index);
+ // Give the tag a tick.
+ current->Tick();
+ return current;
+ }
+
+ CodeRegionTrieNode* ProcessTags(Sample* sample, CodeRegionTrieNode* current) {
+ // None.
+ if (tag_order() == ProfilerService::kNoTags) {
+ return current;
+ }
+ // User first.
+ if ((tag_order() == ProfilerService::kUserVM) ||
+ (tag_order() == ProfilerService::kUser)) {
+ current = ProcessUserTags(sample, current);
+ // Only user.
+ if (tag_order() == ProfilerService::kUser) {
+ return current;
+ }
+ return ProcessVMTags(sample, current);
+ }
+ // VM first.
+ ASSERT((tag_order() == ProfilerService::kVMUser) ||
+ (tag_order() == ProfilerService::kVM));
+ current = ProcessVMTags(sample, current);
+ // Only VM.
+ if (tag_order() == ProfilerService::kVM) {
+ return current;
+ }
+ return ProcessUserTags(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();
}
- CodeRegionTrieNode* exclusive_root_;
+ 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* root_;
CodeRegionTable* live_code_table_;
CodeRegionTable* dead_code_table_;
CodeRegionTable* tag_code_table_;
- intptr_t dead_code_table_offset_;
- intptr_t tag_code_table_offset_;
};
@@ -1253,8 +1918,10 @@ void ProfilerService::PrintJSON(JSONStream* stream, TagOrder tag_order) {
}
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;
@@ -1262,19 +1929,25 @@ void ProfilerService::PrintJSON(JSONStream* stream, TagOrder tag_order) {
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);
+ &tag_code_table,
+ deoptimized_code);
{
- ScopeTimer sw("FixTopFrame", FLAG_trace_profiler);
- // Preprocess samples and fix the caller when the top PC is in a
- // stub or intrinsic without a frame.
- FixTopFrameVisitor fixTopFrame(isolate);
- sample_buffer->VisitSamples(&fixTopFrame);
- }
- {
- // Build CodeRegion tables.
ScopeTimer sw("CodeRegionTableBuilder", FLAG_trace_profiler);
sample_buffer->VisitSamples(&builder);
}
@@ -1290,72 +1963,130 @@ void ProfilerService::PrintJSON(JSONStream* stream, TagOrder tag_order) {
total_dead_code_objects,
total_tag_code_objects);
}
-#if defined(DEBUG)
- live_code_table.Verify();
- dead_code_table.Verify();
- tag_code_table.Verify();
+
+ 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) {
- OS::Print("CodeRegionTables verified to be ordered and not overlap.\n");
+ intptr_t total_functions = function_table.Length();
+ OS::Print("FunctionTable: size=%" Pd "\n", total_functions);
}
-#endif
- CodeRegionExclusiveTrieBuilder build_trie(isolate,
- &live_code_table,
- &dead_code_table,
- &tag_code_table);
- build_trie.set_tag_order(tag_order);
+ CodeRegionExclusiveTrieBuilder 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("CodeRegionExclusiveTrieBuilder", FLAG_trace_profiler);
- sample_buffer->VisitSamples(&build_trie);
- build_trie.root()->SortByCount();
+ sample_buffer->VisitSamples(&code_trie_builder);
+ code_trie_builder.root()->SortByCount();
}
- CodeRegionTableCallersBuilder build_callers(build_trie.root(),
- &live_code_table,
- &dead_code_table,
- &tag_code_table);
+ ProfileFunctionExclusiveTrieBuilder
+ function_trie_builder(isolate,
+ &live_code_table,
+ &dead_code_table,
+ &tag_code_table,
+ &function_table);
+ function_trie_builder.set_tag_order(tag_order);
{
- // Build CodeRegion callers.
- ScopeTimer sw("CodeRegionTableCallersBuilder", FLAG_trace_profiler);
- build_callers.Build();
+ // Build ProfileFunction trie.
+ ScopeTimer sw("ProfileFunctionExclusiveTrieBuilder",
+ FLAG_trace_profiler);
+ sample_buffer->VisitSamples(&function_trie_builder);
+ function_trie_builder.root()->SortByCount();
}
{
ScopeTimer sw("CodeTableStream", FLAG_trace_profiler);
// Serialize to JSON.
JSONObject obj(stream);
- obj.AddProperty("type", "CpuProfile");
- obj.AddProperty("id", "profile");
- obj.AddProperty("samples", samples);
- obj.AddProperty("depth", static_cast<intptr_t>(FLAG_profile_depth));
- obj.AddProperty("period", static_cast<intptr_t>(FLAG_profile_period));
+ 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 exclusive_trie(&obj, "exclusive_trie");
- CodeRegionTrieNode* root = build_trie.root();
+ JSONArray exclusive_trie(&obj, "exclusiveCodeTrie");
+ CodeRegionTrieNode* root = code_trie_builder.root();
ASSERT(root != NULL);
root->PrintToJSONArray(&exclusive_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(isolate, &codes);
+ {
+ JSONArray function_trie(&obj, "exclusiveFunctionTrie");
+ ProfileFunctionTrieNode* root = function_trie_builder.root();
+ ASSERT(root != NULL);
+ root->PrintToJSONArray(&function_trie);
}
- for (intptr_t i = 0; i < dead_code_table.Length(); i++) {
- CodeRegion* region = dead_code_table.At(i);
- ASSERT(region != NULL);
- region->PrintToJSONArray(isolate, &codes);
+ {
+ 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);
+ }
}
- for (intptr_t i = 0; i < tag_code_table.Length(); i++) {
- CodeRegion* region = tag_code_table.At(i);
- ASSERT(region != NULL);
- region->PrintToJSONArray(isolate, &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
« no previous file with comments | « runtime/vm/profiler_service.h ('k') | runtime/vm/scope_timer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698