From a5be5da2e83485a81153571eff8946974dd7ff5b Mon Sep 17 00:00:00 2001 From: "yurys@chromium.org" Date: Tue, 19 Mar 2013 08:11:56 +0000 Subject: [PATCH] Allow recording individual samples in addition to the aggregated CPU profiles CPU profiler API is extended with methods that allow to retrieve individual samples from profile. Each sample is presented as a pointer to a node in the top-down profile tree. The samples will let us tie JS performance to time. BUG=None Review URL: https://codereview.chromium.org/12919002 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13980 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8-profiler.h | 20 +++++++- src/api.cc | 20 +++++++- src/cpu-profiler.cc | 16 +++--- src/cpu-profiler.h | 6 +-- src/profile-generator-inl.h | 3 +- src/profile-generator.cc | 19 ++++--- src/profile-generator.h | 19 +++++-- test/cctest/test-cpu-profiler.cc | 6 +-- test/cctest/test-profile-generator.cc | 95 +++++++++++++++++++++++++++++++++-- 9 files changed, 167 insertions(+), 37 deletions(-) diff --git a/include/v8-profiler.h b/include/v8-profiler.h index 90a3d4d..41fe8a1 100644 --- a/include/v8-profiler.h +++ b/include/v8-profiler.h @@ -105,6 +105,9 @@ class V8EXPORT CpuProfileNode { /** Returns function entry UID. */ unsigned GetCallUid() const; + /** Returns id of the node. The id is unique within the tree */ + unsigned GetNodeId() const; + /** Returns child nodes count of the node. */ int GetChildrenCount() const; @@ -131,6 +134,18 @@ class V8EXPORT CpuProfile { const CpuProfileNode* GetTopDownRoot() const; /** + * Returns number of samples recorded. The samples are not recorded unless + * |record_samples| parameter of CpuProfiler::StartProfiling is true. + */ + int GetSamplesCount() const; + + /** + * Returns profile node corresponding to the top frame the sample at + * the given index. + */ + const CpuProfileNode* GetSample(int index) const; + + /** * Deletes the profile and removes it from CpuProfiler's list. * All pointers to nodes previously returned become invalid. * Profiles with the same uid but obtained using different @@ -179,8 +194,11 @@ class V8EXPORT CpuProfiler { * title are silently ignored. While collecting a profile, functions * from all security contexts are included in it. The token-based * filtering is only performed when querying for a profile. + * + * |record_samples| parameter controls whether individual samples should + * be recorded in addition to the aggregated tree. */ - static void StartProfiling(Handle title); + static void StartProfiling(Handle title, bool record_samples = false); /** * Stops collecting CPU profile with a given title and returns it. diff --git a/src/api.cc b/src/api.cc index 64c0022..26db0de 100644 --- a/src/api.cc +++ b/src/api.cc @@ -6454,6 +6454,11 @@ unsigned CpuProfileNode::GetCallUid() const { } +unsigned CpuProfileNode::GetNodeId() const { + return reinterpret_cast(this)->id(); +} + + int CpuProfileNode::GetChildrenCount() const { i::Isolate* isolate = i::Isolate::Current(); IsDeadCheck(isolate, "v8::CpuProfileNode::GetChildrenCount"); @@ -6506,6 +6511,17 @@ const CpuProfileNode* CpuProfile::GetTopDownRoot() const { } +const CpuProfileNode* CpuProfile::GetSample(int index) const { + const i::CpuProfile* profile = reinterpret_cast(this); + return reinterpret_cast(profile->sample(index)); +} + + +int CpuProfile::GetSamplesCount() const { + return reinterpret_cast(this)->samples_count(); +} + + int CpuProfiler::GetProfilesCount() { i::Isolate* isolate = i::Isolate::Current(); IsDeadCheck(isolate, "v8::CpuProfiler::GetProfilesCount"); @@ -6535,10 +6551,10 @@ const CpuProfile* CpuProfiler::FindProfile(unsigned uid, } -void CpuProfiler::StartProfiling(Handle title) { +void CpuProfiler::StartProfiling(Handle title, bool record_samples) { i::Isolate* isolate = i::Isolate::Current(); IsDeadCheck(isolate, "v8::CpuProfiler::StartProfiling"); - i::CpuProfiler::StartProfiling(*Utils::OpenHandle(*title)); + i::CpuProfiler::StartProfiling(*Utils::OpenHandle(*title), record_samples); } diff --git a/src/cpu-profiler.cc b/src/cpu-profiler.cc index 15edc1e..043f58c 100644 --- a/src/cpu-profiler.cc +++ b/src/cpu-profiler.cc @@ -260,13 +260,14 @@ void ProfilerEventsProcessor::Run() { void CpuProfiler::StartProfiling(const char* title) { ASSERT(Isolate::Current()->cpu_profiler() != NULL); - Isolate::Current()->cpu_profiler()->StartCollectingProfile(title); + Isolate::Current()->cpu_profiler()->StartCollectingProfile(title, false); } -void CpuProfiler::StartProfiling(String* title) { +void CpuProfiler::StartProfiling(String* title, bool record_samples) { ASSERT(Isolate::Current()->cpu_profiler() != NULL); - Isolate::Current()->cpu_profiler()->StartCollectingProfile(title); + Isolate::Current()->cpu_profiler()->StartCollectingProfile( + title, record_samples); } @@ -468,16 +469,17 @@ void CpuProfiler::ResetProfiles() { profiles_ = new CpuProfilesCollection(); } -void CpuProfiler::StartCollectingProfile(const char* title) { - if (profiles_->StartProfiling(title, next_profile_uid_++)) { +void CpuProfiler::StartCollectingProfile(const char* title, + bool record_samples) { + if (profiles_->StartProfiling(title, next_profile_uid_++, record_samples)) { StartProcessorIfNotStarted(); } processor_->AddCurrentStack(); } -void CpuProfiler::StartCollectingProfile(String* title) { - StartCollectingProfile(profiles_->GetName(title)); +void CpuProfiler::StartCollectingProfile(String* title, bool record_samples) { + StartCollectingProfile(profiles_->GetName(title), record_samples); } diff --git a/src/cpu-profiler.h b/src/cpu-profiler.h index 3dc766b..2ec4c70 100644 --- a/src/cpu-profiler.h +++ b/src/cpu-profiler.h @@ -207,7 +207,7 @@ class CpuProfiler { static void TearDown(); static void StartProfiling(const char* title); - static void StartProfiling(String* title); + static void StartProfiling(String* title, bool record_samples); static CpuProfile* StopProfiling(const char* title); static CpuProfile* StopProfiling(Object* security_token, String* title); static int GetProfilesCount(); @@ -253,8 +253,8 @@ class CpuProfiler { private: CpuProfiler(); ~CpuProfiler(); - void StartCollectingProfile(const char* title); - void StartCollectingProfile(String* title); + void StartCollectingProfile(const char* title, bool record_samples); + void StartCollectingProfile(String* title, bool record_samples); void StartProcessorIfNotStarted(); CpuProfile* StopCollectingProfile(const char* title); CpuProfile* StopCollectingProfile(Object* security_token, String* title); diff --git a/src/profile-generator-inl.h b/src/profile-generator-inl.h index 6e7f499..4e6302c 100644 --- a/src/profile-generator-inl.h +++ b/src/profile-generator-inl.h @@ -74,7 +74,8 @@ ProfileNode::ProfileNode(ProfileTree* tree, CodeEntry* entry) entry_(entry), total_ticks_(0), self_ticks_(0), - children_(CodeEntriesMatch) { + children_(CodeEntriesMatch), + id_(tree->next_node_id()) { } diff --git a/src/profile-generator.cc b/src/profile-generator.cc index 2bf1724..ce07213 100644 --- a/src/profile-generator.cc +++ b/src/profile-generator.cc @@ -296,6 +296,7 @@ ProfileTree::ProfileTree() "", 0, TokenEnumerator::kNoSecurityToken), + next_node_id_(1), root_(new ProfileNode(this, &root_entry_)) { } @@ -306,7 +307,7 @@ ProfileTree::~ProfileTree() { } -void ProfileTree::AddPathFromEnd(const Vector& path) { +ProfileNode* ProfileTree::AddPathFromEnd(const Vector& path) { ProfileNode* node = root_; for (CodeEntry** entry = path.start() + path.length() - 1; entry != path.start() - 1; @@ -316,6 +317,7 @@ void ProfileTree::AddPathFromEnd(const Vector& path) { } } node->IncrementSelfTicks(); + return node; } @@ -467,7 +469,8 @@ void ProfileTree::ShortPrint() { void CpuProfile::AddPath(const Vector& path) { - top_down_.AddPathFromEnd(path); + ProfileNode* top_frame_node = top_down_.AddPathFromEnd(path); + if (record_samples_) samples_.Add(top_frame_node); } @@ -483,7 +486,7 @@ void CpuProfile::SetActualSamplingRate(double actual_sampling_rate) { CpuProfile* CpuProfile::FilteredClone(int security_token_id) { ASSERT(security_token_id != TokenEnumerator::kNoSecurityToken); - CpuProfile* clone = new CpuProfile(title_, uid_); + CpuProfile* clone = new CpuProfile(title_, uid_, false); clone->top_down_.FilteredClone(&top_down_, security_token_id); return clone; } @@ -609,7 +612,8 @@ CpuProfilesCollection::~CpuProfilesCollection() { } -bool CpuProfilesCollection::StartProfiling(const char* title, unsigned uid) { +bool CpuProfilesCollection::StartProfiling(const char* title, unsigned uid, + bool record_samples) { ASSERT(uid > 0); current_profiles_semaphore_->Wait(); if (current_profiles_.length() >= kMaxSimultaneousProfiles) { @@ -623,17 +627,12 @@ bool CpuProfilesCollection::StartProfiling(const char* title, unsigned uid) { return false; } } - current_profiles_.Add(new CpuProfile(title, uid)); + current_profiles_.Add(new CpuProfile(title, uid, record_samples)); current_profiles_semaphore_->Signal(); return true; } -bool CpuProfilesCollection::StartProfiling(String* title, unsigned uid) { - return StartProfiling(GetName(title), uid); -} - - CpuProfile* CpuProfilesCollection::StopProfiling(int security_token_id, const char* title, double actual_sampling_rate) { diff --git a/src/profile-generator.h b/src/profile-generator.h index b128dad..4ddb753 100644 --- a/src/profile-generator.h +++ b/src/profile-generator.h @@ -150,6 +150,7 @@ class ProfileNode { INLINE(const List* children() const) { return &children_list_; } double GetSelfMillis() const; double GetTotalMillis() const; + unsigned id() const { return id_; } void Print(int indent); @@ -170,6 +171,7 @@ class ProfileNode { // Mapping from CodeEntry* to ProfileNode* HashMap children_; List children_list_; + unsigned id_; DISALLOW_COPY_AND_ASSIGN(ProfileNode); }; @@ -180,7 +182,7 @@ class ProfileTree { ProfileTree(); ~ProfileTree(); - void AddPathFromEnd(const Vector& path); + ProfileNode* AddPathFromEnd(const Vector& path); void AddPathFromStart(const Vector& path); void CalculateTotalTicks(); void FilteredClone(ProfileTree* src, int security_token_id); @@ -191,6 +193,8 @@ class ProfileTree { ProfileNode* root() const { return root_; } void SetTickRatePerMs(double ticks_per_ms); + unsigned next_node_id() { return next_node_id_++; } + void ShortPrint(); void Print() { root_->Print(0); @@ -201,6 +205,7 @@ class ProfileTree { void TraverseDepthFirst(Callback* callback); CodeEntry root_entry_; + unsigned next_node_id_; ProfileNode* root_; double ms_to_ticks_scale_; @@ -210,8 +215,8 @@ class ProfileTree { class CpuProfile { public: - CpuProfile(const char* title, unsigned uid) - : title_(title), uid_(uid) { } + CpuProfile(const char* title, unsigned uid, bool record_samples) + : title_(title), uid_(uid), record_samples_(record_samples) { } // Add pc -> ... -> main() call path to the profile. void AddPath(const Vector& path); @@ -223,6 +228,9 @@ class CpuProfile { INLINE(unsigned uid() const) { return uid_; } INLINE(const ProfileTree* top_down() const) { return &top_down_; } + INLINE(int samples_count() const) { return samples_.length(); } + INLINE(ProfileNode* sample(int index) const) { return samples_.at(index); } + void UpdateTicksScale(); void ShortPrint(); @@ -231,6 +239,8 @@ class CpuProfile { private: const char* title_; unsigned uid_; + bool record_samples_; + List samples_; ProfileTree top_down_; DISALLOW_COPY_AND_ASSIGN(CpuProfile); @@ -288,8 +298,7 @@ class CpuProfilesCollection { CpuProfilesCollection(); ~CpuProfilesCollection(); - bool StartProfiling(const char* title, unsigned uid); - bool StartProfiling(String* title, unsigned uid); + bool StartProfiling(const char* title, unsigned uid, bool record_samples); CpuProfile* StopProfiling(int security_token_id, const char* title, double actual_sampling_rate); diff --git a/test/cctest/test-cpu-profiler.cc b/test/cctest/test-cpu-profiler.cc index 75e594e..d44f349 100644 --- a/test/cctest/test-cpu-profiler.cc +++ b/test/cctest/test-cpu-profiler.cc @@ -107,7 +107,7 @@ TEST(CodeEvents) { i::Factory* factory = isolate->factory(); TestSetup test_setup; CpuProfilesCollection profiles; - profiles.StartProfiling("", 1); + profiles.StartProfiling("", 1, false); ProfileGenerator generator(&profiles); ProfilerEventsProcessor processor(&generator); processor.Start(); @@ -168,7 +168,7 @@ static int CompareProfileNodes(const T* p1, const T* p2) { TEST(TickEvents) { TestSetup test_setup; CpuProfilesCollection profiles; - profiles.StartProfiling("", 1); + profiles.StartProfiling("", 1, false); ProfileGenerator generator(&profiles); ProfilerEventsProcessor processor(&generator); processor.Start(); @@ -233,7 +233,7 @@ TEST(CrashIfStoppingLastNonExistentProfile) { TEST(Issue1398) { TestSetup test_setup; CpuProfilesCollection profiles; - profiles.StartProfiling("", 1); + profiles.StartProfiling("", 1, false); ProfileGenerator generator(&profiles); ProfilerEventsProcessor processor(&generator); processor.Start(); diff --git a/test/cctest/test-profile-generator.cc b/test/cctest/test-profile-generator.cc index 22b4e05..884ca83 100644 --- a/test/cctest/test-profile-generator.cc +++ b/test/cctest/test-profile-generator.cc @@ -85,7 +85,8 @@ TEST(TokenEnumerator) { TEST(ProfileNodeFindOrAddChild) { - ProfileNode node(NULL, NULL); + ProfileTree tree; + ProfileNode node(&tree, NULL); CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0, TokenEnumerator::kNoSecurityToken); ProfileNode* childNode1 = node.FindOrAddChild(&entry1); @@ -113,7 +114,8 @@ TEST(ProfileNodeFindOrAddChild) { TEST(ProfileNodeFindOrAddChildForSameFunction) { const char* empty = ""; const char* aaa = "aaa"; - ProfileNode node(NULL, NULL); + ProfileTree tree; + ProfileNode node(&tree, NULL); CodeEntry entry1(i::Logger::FUNCTION_TAG, empty, aaa, empty, 0, TokenEnumerator::kNoSecurityToken); ProfileNode* childNode1 = node.FindOrAddChild(&entry1); @@ -607,7 +609,7 @@ class TestSetup { TEST(RecordTickSample) { TestSetup test_setup; CpuProfilesCollection profiles; - profiles.StartProfiling("", 1); + profiles.StartProfiling("", 1, false); ProfileGenerator generator(&profiles); CodeEntry* entry1 = generator.NewCodeEntry(i::Logger::FUNCTION_TAG, "aaa"); CodeEntry* entry2 = generator.NewCodeEntry(i::Logger::FUNCTION_TAG, "bbb"); @@ -713,6 +715,88 @@ TEST(SampleRateCalculator) { } +static void CheckNodeIds(ProfileNode* node, int* expectedId) { + CHECK_EQ((*expectedId)++, node->id()); + for (int i = 0; i < node->children()->length(); i++) { + CheckNodeIds(node->children()->at(i), expectedId); + } +} + +TEST(SampleIds) { + TestSetup test_setup; + CpuProfilesCollection profiles; + profiles.StartProfiling("", 1, true); + ProfileGenerator generator(&profiles); + CodeEntry* entry1 = generator.NewCodeEntry(i::Logger::FUNCTION_TAG, "aaa"); + CodeEntry* entry2 = generator.NewCodeEntry(i::Logger::FUNCTION_TAG, "bbb"); + CodeEntry* entry3 = generator.NewCodeEntry(i::Logger::FUNCTION_TAG, "ccc"); + generator.code_map()->AddCode(ToAddress(0x1500), entry1, 0x200); + generator.code_map()->AddCode(ToAddress(0x1700), entry2, 0x100); + generator.code_map()->AddCode(ToAddress(0x1900), entry3, 0x50); + + // We are building the following calls tree: + // -> aaa #3 - sample1 + // (root)#1 -> aaa #2 -> bbb #4 -> ccc #5 - sample2 + // -> ccc #6 -> aaa #7 - sample3 + TickSample sample1; + sample1.pc = ToAddress(0x1600); + sample1.stack[0] = ToAddress(0x1510); + sample1.frames_count = 1; + generator.RecordTickSample(sample1); + TickSample sample2; + sample2.pc = ToAddress(0x1925); + sample2.stack[0] = ToAddress(0x1780); + sample2.stack[1] = ToAddress(0x10000); // non-existent. + sample2.stack[2] = ToAddress(0x1620); + sample2.frames_count = 3; + generator.RecordTickSample(sample2); + TickSample sample3; + sample3.pc = ToAddress(0x1510); + sample3.stack[0] = ToAddress(0x1910); + sample3.stack[1] = ToAddress(0x1610); + sample3.frames_count = 2; + generator.RecordTickSample(sample3); + + CpuProfile* profile = + profiles.StopProfiling(TokenEnumerator::kNoSecurityToken, "", 1); + int nodeId = 1; + CheckNodeIds(profile->top_down()->root(), &nodeId); + CHECK_EQ(7, nodeId - 1); + + CHECK_EQ(3, profile->samples_count()); + int expected_id[] = {3, 5, 7}; + for (int i = 0; i < 3; i++) { + CHECK_EQ(expected_id[i], profile->sample(i)->id()); + } +} + + +TEST(NoSamples) { + TestSetup test_setup; + CpuProfilesCollection profiles; + profiles.StartProfiling("", 1, false); + ProfileGenerator generator(&profiles); + CodeEntry* entry1 = generator.NewCodeEntry(i::Logger::FUNCTION_TAG, "aaa"); + generator.code_map()->AddCode(ToAddress(0x1500), entry1, 0x200); + + // We are building the following calls tree: + // (root)#1 -> aaa #2 -> aaa #3 - sample1 + TickSample sample1; + sample1.pc = ToAddress(0x1600); + sample1.stack[0] = ToAddress(0x1510); + sample1.frames_count = 1; + generator.RecordTickSample(sample1); + + CpuProfile* profile = + profiles.StopProfiling(TokenEnumerator::kNoSecurityToken, "", 1); + int nodeId = 1; + CheckNodeIds(profile->top_down()->root(), &nodeId); + CHECK_EQ(3, nodeId - 1); + + CHECK_EQ(0, profile->samples_count()); +} + + // --- P r o f i l e r E x t e n s i o n --- class ProfilerExtension : public v8::Extension { @@ -838,11 +922,12 @@ TEST(Issue51919) { for (int i = 0; i < CpuProfilesCollection::kMaxSimultaneousProfiles; ++i) { i::Vector title = i::Vector::New(16); i::OS::SNPrintF(title, "%d", i); - CHECK(collection.StartProfiling(title.start(), i + 1)); // UID must be > 0. + // UID must be > 0. + CHECK(collection.StartProfiling(title.start(), i + 1, false)); titles[i] = title.start(); } CHECK(!collection.StartProfiling( - "maximum", CpuProfilesCollection::kMaxSimultaneousProfiles + 1)); + "maximum", CpuProfilesCollection::kMaxSimultaneousProfiles + 1, false)); for (int i = 0; i < CpuProfilesCollection::kMaxSimultaneousProfiles; ++i) i::DeleteArray(titles[i]); } -- 2.7.4