Commit ecae80cd authored by Peter Marshall's avatar Peter Marshall Committed by Commit Bot

[cpu-profiler] Add a new profiling mode with a more detailed call tree.

The current profiling mode (called kLeafNodeLineNumbers in this CL)
produces a tree, with each node representing a stack frame that is seen
in one or more samples taken during profiling. These nodes refer to a
particular function in a stack trace, but not to a particular line or
callsite within that function.

This CL adds a new more (called kCallerLineNumbers) which produces a
different profile tree, where each stack trace seen during profiling,
including the line number, has a unique path in the tree.

The profile tree was previously keyed on CodeEntry*. Now it is keyed on
the pair of CodeEntry* and line_number, meaning it has distinct nodes
for those combinations which exist, and each distinct stack trace that
was sampled is represented in the tree.

For optimized code where we have inline frames, there are no line
numbers for the inline frames in the stack trace, causing duplicate
branches in the tree with kNoLineNumberInfo as the reported line number.
This will be addressed in follow-ups.

Bug: v8:7018
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: I512e221508f5b50ec028306d212263b514a9fb24
Reviewed-on: https://chromium-review.googlesource.com/1013493
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53298}
parent 2ecd52ff
......@@ -277,6 +277,16 @@ class V8_EXPORT CpuProfile {
void Delete();
};
enum CpuProfilingMode {
// In the resulting CpuProfile tree, intermediate nodes in a stack trace
// (from the root to a leaf) will have line numbers that point to the start
// line of the function, rather than the line of the callsite of the child.
kLeafNodeLineNumbers,
// In the resulting CpuProfile tree, nodes are separated based on the line
// number of their callsite in their parent.
kCallerLineNumbers,
};
/**
* Interface for controlling CPU profiling. Instance of the
* profiler can be created using v8::CpuProfiler::New method.
......@@ -320,6 +330,13 @@ class V8_EXPORT CpuProfiler {
* |record_samples| parameter controls whether individual samples should
* be recorded in addition to the aggregated tree.
*/
void StartProfiling(Local<String> title, CpuProfilingMode mode,
bool record_samples = false);
/**
* The same as StartProfiling above, but the CpuProfilingMode defaults to
* kLeafNodeLineNumbers mode, which was the previous default behavior of the
* profiler.
*/
void StartProfiling(Local<String> title, bool record_samples = false);
/**
......
......@@ -9949,7 +9949,7 @@ const char* CpuProfileNode::GetScriptResourceNameStr() const {
}
int CpuProfileNode::GetLineNumber() const {
return reinterpret_cast<const i::ProfileNode*>(this)->entry()->line_number();
return reinterpret_cast<const i::ProfileNode*>(this)->line_number();
}
......@@ -10087,9 +10087,14 @@ void CpuProfiler::CollectSample() {
void CpuProfiler::StartProfiling(Local<String> title, bool record_samples) {
reinterpret_cast<i::CpuProfiler*>(this)->StartProfiling(
*Utils::OpenHandle(*title), record_samples);
*Utils::OpenHandle(*title), record_samples, kLeafNodeLineNumbers);
}
void CpuProfiler::StartProfiling(Local<String> title, CpuProfilingMode mode,
bool record_samples) {
reinterpret_cast<i::CpuProfiler*>(this)->StartProfiling(
*Utils::OpenHandle(*title), record_samples, mode);
}
CpuProfile* CpuProfiler::StopProfiling(Local<String> title) {
return reinterpret_cast<CpuProfile*>(
......
......@@ -345,20 +345,20 @@ void CpuProfiler::CollectSample() {
}
}
void CpuProfiler::StartProfiling(const char* title, bool record_samples) {
if (profiles_->StartProfiling(title, record_samples)) {
void CpuProfiler::StartProfiling(const char* title, bool record_samples,
ProfilingMode mode) {
if (profiles_->StartProfiling(title, record_samples, mode)) {
TRACE_EVENT0("v8", "CpuProfiler::StartProfiling");
StartProcessorIfNotStarted();
}
}
void CpuProfiler::StartProfiling(String* title, bool record_samples) {
StartProfiling(profiles_->GetName(title), record_samples);
void CpuProfiler::StartProfiling(String* title, bool record_samples,
ProfilingMode mode) {
StartProfiling(profiles_->GetName(title), record_samples, mode);
isolate_->debug()->feature_tracker()->Track(DebugFeatureTracker::kProfiler);
}
void CpuProfiler::StartProcessorIfNotStarted() {
if (processor_) {
processor_->AddCurrentStack(isolate_);
......
......@@ -197,10 +197,13 @@ class CpuProfiler : public CodeEventObserver {
static void CollectSample(Isolate* isolate);
typedef v8::CpuProfilingMode ProfilingMode;
void set_sampling_interval(base::TimeDelta value);
void CollectSample();
void StartProfiling(const char* title, bool record_samples = false);
void StartProfiling(String* title, bool record_samples);
void StartProfiling(const char* title, bool record_samples = false,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers);
void StartProfiling(String* title, bool record_samples, ProfilingMode mode);
CpuProfile* StopProfiling(const char* title);
CpuProfile* StopProfiling(String* title);
int GetProfilesCount();
......
......@@ -33,10 +33,11 @@ inline CodeEntry* ProfileGenerator::FindEntry(Address address) {
}
ProfileNode::ProfileNode(ProfileTree* tree, CodeEntry* entry,
ProfileNode* parent)
ProfileNode* parent, int line_number)
: tree_(tree),
entry_(entry),
self_ticks_(0),
line_number_(line_number),
parent_(parent),
id_(tree->next_node_id()) {
tree_->EnqueueNode(this);
......
This diff is collapsed.
......@@ -189,15 +189,24 @@ class CodeEntry {
DISALLOW_COPY_AND_ASSIGN(CodeEntry);
};
struct CodeEntryAndLineNumber {
CodeEntry* code_entry;
int line_number;
};
typedef std::vector<CodeEntryAndLineNumber> ProfileStackTrace;
class ProfileTree;
class ProfileNode {
public:
inline ProfileNode(ProfileTree* tree, CodeEntry* entry, ProfileNode* parent);
inline ProfileNode(ProfileTree* tree, CodeEntry* entry, ProfileNode* parent,
int line_number = 0);
ProfileNode* FindChild(CodeEntry* entry);
ProfileNode* FindOrAddChild(CodeEntry* entry);
ProfileNode* FindChild(
CodeEntry* entry,
int line_number = v8::CpuProfileNode::kNoLineNumberInfo);
ProfileNode* FindOrAddChild(CodeEntry* entry, int line_number = 0);
void IncrementSelfTicks() { ++self_ticks_; }
void IncreaseSelfTicks(unsigned amount) { self_ticks_ += amount; }
void IncrementLineTicks(int src_line);
......@@ -208,6 +217,10 @@ class ProfileNode {
unsigned id() const { return id_; }
unsigned function_id() const;
ProfileNode* parent() const { return parent_; }
int line_number() const {
return line_number_ != 0 ? line_number_ : entry_->line_number();
}
unsigned int GetHitLineCount() const {
return static_cast<unsigned int>(line_ticks_.size());
}
......@@ -222,20 +235,25 @@ class ProfileNode {
void Print(int indent);
private:
struct CodeEntryEqual {
bool operator()(CodeEntry* entry1, CodeEntry* entry2) const {
return entry1 == entry2 || entry1->IsSameFunctionAs(entry2);
struct Equals {
bool operator()(CodeEntryAndLineNumber lhs,
CodeEntryAndLineNumber rhs) const {
return lhs.code_entry->IsSameFunctionAs(rhs.code_entry) &&
lhs.line_number == rhs.line_number;
}
};
struct CodeEntryHash {
std::size_t operator()(CodeEntry* entry) const { return entry->GetHash(); }
struct Hasher {
std::size_t operator()(CodeEntryAndLineNumber pair) const {
return pair.code_entry->GetHash() ^ ComputeIntegerHash(pair.line_number);
}
};
ProfileTree* tree_;
CodeEntry* entry_;
unsigned self_ticks_;
std::unordered_map<CodeEntry*, ProfileNode*, CodeEntryHash, CodeEntryEqual>
std::unordered_map<CodeEntryAndLineNumber, ProfileNode*, Hasher, Equals>
children_;
int line_number_;
std::vector<ProfileNode*> children_list_;
ProfileNode* parent_;
unsigned id_;
......@@ -253,10 +271,17 @@ class ProfileTree {
explicit ProfileTree(Isolate* isolate);
~ProfileTree();
typedef v8::CpuProfilingMode ProfilingMode;
ProfileNode* AddPathFromEnd(
const std::vector<CodeEntry*>& path,
int src_line = v8::CpuProfileNode::kNoLineNumberInfo,
bool update_stats = true);
ProfileNode* AddPathFromEnd(
const ProfileStackTrace& path,
int src_line = v8::CpuProfileNode::kNoLineNumberInfo,
bool update_stats = true,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers);
ProfileNode* root() const { return root_; }
unsigned next_node_id() { return next_node_id_++; }
unsigned GetFunctionId(const ProfileNode* node);
......@@ -293,10 +318,13 @@ class ProfileTree {
class CpuProfile {
public:
CpuProfile(CpuProfiler* profiler, const char* title, bool record_samples);
typedef v8::CpuProfilingMode ProfilingMode;
CpuProfile(CpuProfiler* profiler, const char* title, bool record_samples,
ProfilingMode mode);
// Add pc -> ... -> main() call path to the profile.
void AddPath(base::TimeTicks timestamp, const std::vector<CodeEntry*>& path,
void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path,
int src_line, bool update_stats);
void FinishProfile();
......@@ -322,6 +350,7 @@ class CpuProfile {
const char* title_;
bool record_samples_;
ProfilingMode mode_;
base::TimeTicks start_time_;
base::TimeTicks end_time_;
std::vector<ProfileNode*> samples_;
......@@ -361,8 +390,11 @@ class CpuProfilesCollection {
public:
explicit CpuProfilesCollection(Isolate* isolate);
typedef v8::CpuProfilingMode ProfilingMode;
void set_cpu_profiler(CpuProfiler* profiler) { profiler_ = profiler; }
bool StartProfiling(const char* title, bool record_samples);
bool StartProfiling(const char* title, bool record_samples,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers);
CpuProfile* StopProfiling(const char* title);
std::vector<std::unique_ptr<CpuProfile>>* profiles() {
return &finished_profiles_;
......@@ -373,8 +405,8 @@ class CpuProfilesCollection {
// Called from profile generator thread.
void AddPathToCurrentProfiles(base::TimeTicks timestamp,
const std::vector<CodeEntry*>& path,
int src_line, bool update_stats);
const ProfileStackTrace& path, int src_line,
bool update_stats);
// Limits the number of profiles that can be simultaneously collected.
static const int kMaxSimultaneousProfiles = 100;
......
......@@ -75,6 +75,7 @@
# BUG(5193). The cpu profiler tests are notoriously flaky.
'test-profile-generator/RecordStackTraceAtStartProfiling': [SKIP],
'test-cpu-profiler/CollectCpuProfile': [SKIP],
'test-cpu-profiler/CollectCpuProfileCallerLineNumbers': [FAIL, PASS],
'test-cpu-profiler/CollectCpuProfileSamples': [SKIP],
'test-cpu-profiler/CollectDeoptEvents': [SKIP],
'test-cpu-profiler/CpuProfileDeepStack': [SKIP],
......
......@@ -428,11 +428,14 @@ class ProfilerHelper {
profiler_->Dispose();
}
typedef v8::CpuProfilingMode ProfilingMode;
v8::CpuProfile* Run(v8::Local<v8::Function> function,
v8::Local<v8::Value> argv[], int argc,
unsigned min_js_samples = 0,
unsigned min_external_samples = 0,
bool collect_samples = false);
bool collect_samples = false,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers);
v8::CpuProfiler* profiler() { return profiler_; }
......@@ -445,11 +448,11 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function,
v8::Local<v8::Value> argv[], int argc,
unsigned min_js_samples,
unsigned min_external_samples,
bool collect_samples) {
bool collect_samples, ProfilingMode mode) {
v8::Local<v8::String> profile_name = v8_str("my_profile");
profiler_->SetSamplingInterval(100);
profiler_->StartProfiling(profile_name, collect_samples);
profiler_->StartProfiling(profile_name, mode, collect_samples);
v8::internal::CpuProfiler* iprofiler =
reinterpret_cast<v8::internal::CpuProfiler*>(profiler_);
......@@ -509,7 +512,6 @@ static const v8::CpuProfileNode* GetChild(v8::Local<v8::Context> context,
return result;
}
static void CheckSimpleBranch(v8::Local<v8::Context> context,
const v8::CpuProfileNode* node,
const char* names[], int length) {
......@@ -519,7 +521,6 @@ static void CheckSimpleBranch(v8::Local<v8::Context> context,
}
}
static const ProfileNode* GetSimpleBranch(v8::Local<v8::Context> context,
v8::CpuProfile* profile,
const char* names[], int length) {
......@@ -530,6 +531,41 @@ static const ProfileNode* GetSimpleBranch(v8::Local<v8::Context> context,
return reinterpret_cast<const ProfileNode*>(node);
}
struct NameLinePair {
const char* name;
int line_number;
};
static const v8::CpuProfileNode* FindChild(const v8::CpuProfileNode* node,
NameLinePair pair) {
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
const v8::CpuProfileNode* child = node->GetChild(i);
// The name and line number must match, or if the requested line number was
// -1, then match any function of the same name.
if (strcmp(child->GetFunctionNameStr(), pair.name) == 0 &&
(child->GetLineNumber() == pair.line_number ||
pair.line_number == -1)) {
return child;
}
}
return nullptr;
}
static const v8::CpuProfileNode* GetChild(const v8::CpuProfileNode* node,
NameLinePair pair) {
const v8::CpuProfileNode* result = FindChild(node, pair);
if (!result) FATAL("Failed to GetChild: %s:%d", pair.name, pair.line_number);
return result;
}
static void CheckBranch(const v8::CpuProfileNode* node, NameLinePair path[],
int length) {
for (int i = 0; i < length; i++) {
NameLinePair pair = path[i];
node = GetChild(node, pair);
}
}
static const char* cpu_profiler_test_source =
"%NeverOptimizeFunction(loop);\n"
"%NeverOptimizeFunction(delay);\n"
......@@ -610,6 +646,40 @@ TEST(CollectCpuProfile) {
profile->Delete();
}
TEST(CollectCpuProfileCallerLineNumbers) {
i::FLAG_allow_natives_syntax = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
CompileRun(cpu_profiler_test_source);
v8::Local<v8::Function> function = GetFunction(env.local(), "start");
int32_t profiling_interval_ms = 200;
v8::Local<v8::Value> args[] = {
v8::Integer::New(env->GetIsolate(), profiling_interval_ms)};
ProfilerHelper helper(env.local());
helper.Run(function, args, arraysize(args), 1000, 0, false,
v8::CpuProfilingMode::kCallerLineNumbers);
v8::CpuProfile* profile =
helper.Run(function, args, arraysize(args), 1000, 0, false,
v8::CpuProfilingMode::kCallerLineNumbers);
const v8::CpuProfileNode* root = profile->GetTopDownRoot();
const v8::CpuProfileNode* start_node = GetChild(root, {"start", 27});
const v8::CpuProfileNode* foo_node = GetChild(start_node, {"foo", 30});
NameLinePair bar_branch[] = {{"bar", 23}, {"delay", 19}, {"loop", 18}};
CheckBranch(foo_node, bar_branch, arraysize(bar_branch));
NameLinePair baz_branch[] = {{"baz", 25}, {"delay", 20}, {"loop", 18}};
CheckBranch(foo_node, baz_branch, arraysize(baz_branch));
NameLinePair delay_at22_branch[] = {{"delay", 22}, {"loop", 18}};
CheckBranch(foo_node, delay_at22_branch, arraysize(delay_at22_branch));
NameLinePair delay_at24_branch[] = {{"delay", 24}, {"loop", 18}};
CheckBranch(foo_node, delay_at24_branch, arraysize(delay_at24_branch));
profile->Delete();
}
static const char* hot_deopt_no_frame_entry_test_source =
"%NeverOptimizeFunction(foo);\n"
"%NeverOptimizeFunction(start);\n"
......
......@@ -64,6 +64,25 @@ TEST(ProfileNodeFindOrAddChild) {
CHECK_EQ(childNode3, node->FindOrAddChild(&entry3));
}
TEST(ProfileNodeFindOrAddChildWithLineNumber) {
CcTest::InitializeVM();
ProfileTree tree(CcTest::i_isolate());
ProfileNode* root = tree.root();
CodeEntry a(i::CodeEventListener::FUNCTION_TAG, "a");
ProfileNode* a_node = root->FindOrAddChild(&a, -1);
// a --(22)--> child1
// --(23)--> child1
CodeEntry child1(i::CodeEventListener::FUNCTION_TAG, "child1");
ProfileNode* child1_node = a_node->FindOrAddChild(&child1, 22);
CHECK(child1_node);
CHECK_EQ(child1_node, a_node->FindOrAddChild(&child1, 22));
ProfileNode* child2_node = a_node->FindOrAddChild(&child1, 23);
CHECK(child2_node);
CHECK_NE(child1_node, child2_node);
}
TEST(ProfileNodeFindOrAddChildForSameFunction) {
CcTest::InitializeVM();
......@@ -172,6 +191,29 @@ TEST(ProfileTreeAddPathFromEnd) {
CHECK_EQ(1u, node4->self_ticks());
}
TEST(ProfileTreeAddPathFromEndWithLineNumbers) {
CcTest::InitializeVM();
CodeEntry a(i::CodeEventListener::FUNCTION_TAG, "a");
CodeEntry b(i::CodeEventListener::FUNCTION_TAG, "b");
CodeEntry c(i::CodeEventListener::FUNCTION_TAG, "c");
ProfileTree tree(CcTest::i_isolate());
ProfileTreeTestHelper helper(&tree);
ProfileStackTrace path = {{&c, 5}, {&b, 3}, {&a, 1}};
tree.AddPathFromEnd(path, v8::CpuProfileNode::kNoLineNumberInfo, true,
v8::CpuProfilingMode::kCallerLineNumbers);
ProfileNode* a_node =
tree.root()->FindChild(&a, v8::CpuProfileNode::kNoLineNumberInfo);
tree.Print();
CHECK(a_node);
ProfileNode* b_node = a_node->FindChild(&b, 1);
CHECK(b_node);
ProfileNode* c_node = b_node->FindChild(&c, 3);
CHECK(c_node);
}
TEST(ProfileTreeCalculateTotalTicks) {
CcTest::InitializeVM();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment