[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: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#53298}
This commit is contained in:
parent
2ecd52ffa4
commit
ecae80cdb3
@ -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);
|
||||
|
@ -180,18 +180,16 @@ void ProfileNode::CollectDeoptInfo(CodeEntry* entry) {
|
||||
entry->clear_deopt_info();
|
||||
}
|
||||
|
||||
|
||||
ProfileNode* ProfileNode::FindChild(CodeEntry* entry) {
|
||||
auto map_entry = children_.find(entry);
|
||||
ProfileNode* ProfileNode::FindChild(CodeEntry* entry, int line_number) {
|
||||
auto map_entry = children_.find({entry, line_number});
|
||||
return map_entry != children_.end() ? map_entry->second : nullptr;
|
||||
}
|
||||
|
||||
|
||||
ProfileNode* ProfileNode::FindOrAddChild(CodeEntry* entry) {
|
||||
auto map_entry = children_.find(entry);
|
||||
ProfileNode* ProfileNode::FindOrAddChild(CodeEntry* entry, int line_number) {
|
||||
auto map_entry = children_.find({entry, line_number});
|
||||
if (map_entry == children_.end()) {
|
||||
ProfileNode* node = new ProfileNode(tree_, entry, this);
|
||||
children_[entry] = node;
|
||||
ProfileNode* node = new ProfileNode(tree_, entry, this, line_number);
|
||||
children_[{entry, line_number}] = node;
|
||||
children_list_.push_back(node);
|
||||
return node;
|
||||
} else {
|
||||
@ -234,8 +232,9 @@ bool ProfileNode::GetLineTicks(v8::CpuProfileNode::LineTick* entries,
|
||||
|
||||
|
||||
void ProfileNode::Print(int indent) {
|
||||
base::OS::Print("%5u %*s %s %d #%d", self_ticks_, indent, "", entry_->name(),
|
||||
entry_->script_id(), id());
|
||||
int line_number = line_number_ != 0 ? line_number_ : entry_->line_number();
|
||||
base::OS::Print("%5u %*s %s:%d %d #%d", self_ticks_, indent, "",
|
||||
entry_->name(), line_number, entry_->script_id(), id());
|
||||
if (entry_->resource_name()[0] != '\0')
|
||||
base::OS::Print(" %s:%d", entry_->resource_name(), entry_->line_number());
|
||||
base::OS::Print("\n");
|
||||
@ -304,7 +303,33 @@ ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path,
|
||||
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
||||
if (*it == nullptr) continue;
|
||||
last_entry = *it;
|
||||
node = node->FindOrAddChild(*it);
|
||||
node = node->FindOrAddChild(*it, v8::CpuProfileNode::kNoLineNumberInfo);
|
||||
}
|
||||
if (last_entry && last_entry->has_deopt_info()) {
|
||||
node->CollectDeoptInfo(last_entry);
|
||||
}
|
||||
if (update_stats) {
|
||||
node->IncrementSelfTicks();
|
||||
if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
|
||||
node->IncrementLineTicks(src_line);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
|
||||
int src_line, bool update_stats,
|
||||
ProfilingMode mode) {
|
||||
ProfileNode* node = root_;
|
||||
CodeEntry* last_entry = nullptr;
|
||||
int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo;
|
||||
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
||||
if ((*it).code_entry == nullptr) continue;
|
||||
last_entry = (*it).code_entry;
|
||||
node = node->FindOrAddChild((*it).code_entry, parent_line_number);
|
||||
parent_line_number = mode == ProfilingMode::kCallerLineNumbers
|
||||
? (*it).line_number
|
||||
: v8::CpuProfileNode::kNoLineNumberInfo;
|
||||
}
|
||||
if (last_entry && last_entry->has_deopt_info()) {
|
||||
node->CollectDeoptInfo(last_entry);
|
||||
@ -363,9 +388,10 @@ void ProfileTree::TraverseDepthFirst(Callback* callback) {
|
||||
using v8::tracing::TracedValue;
|
||||
|
||||
CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title,
|
||||
bool record_samples)
|
||||
bool record_samples, ProfilingMode mode)
|
||||
: title_(title),
|
||||
record_samples_(record_samples),
|
||||
mode_(mode),
|
||||
start_time_(base::TimeTicks::HighResolutionNow()),
|
||||
top_down_(profiler->isolate()),
|
||||
profiler_(profiler),
|
||||
@ -378,14 +404,16 @@ CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title,
|
||||
}
|
||||
|
||||
void CpuProfile::AddPath(base::TimeTicks timestamp,
|
||||
const std::vector<CodeEntry*>& path, int src_line,
|
||||
const ProfileStackTrace& path, int src_line,
|
||||
bool update_stats) {
|
||||
ProfileNode* top_frame_node =
|
||||
top_down_.AddPathFromEnd(path, src_line, update_stats);
|
||||
top_down_.AddPathFromEnd(path, src_line, update_stats, mode_);
|
||||
|
||||
if (record_samples_ && !timestamp.IsNull()) {
|
||||
timestamps_.push_back(timestamp);
|
||||
samples_.push_back(top_frame_node);
|
||||
}
|
||||
|
||||
const int kSamplesFlushCount = 100;
|
||||
const int kNodesFlushCount = 10;
|
||||
if (samples_.size() - streaming_next_sample_ >= kSamplesFlushCount ||
|
||||
@ -542,7 +570,8 @@ CpuProfilesCollection::CpuProfilesCollection(Isolate* isolate)
|
||||
current_profiles_semaphore_(1) {}
|
||||
|
||||
bool CpuProfilesCollection::StartProfiling(const char* title,
|
||||
bool record_samples) {
|
||||
bool record_samples,
|
||||
ProfilingMode mode) {
|
||||
current_profiles_semaphore_.Wait();
|
||||
if (static_cast<int>(current_profiles_.size()) >= kMaxSimultaneousProfiles) {
|
||||
current_profiles_semaphore_.Signal();
|
||||
@ -557,7 +586,7 @@ bool CpuProfilesCollection::StartProfiling(const char* title,
|
||||
}
|
||||
}
|
||||
current_profiles_.emplace_back(
|
||||
new CpuProfile(profiler_, title, record_samples));
|
||||
new CpuProfile(profiler_, title, record_samples, mode));
|
||||
current_profiles_semaphore_.Signal();
|
||||
return true;
|
||||
}
|
||||
@ -608,8 +637,8 @@ void CpuProfilesCollection::RemoveProfile(CpuProfile* profile) {
|
||||
}
|
||||
|
||||
void CpuProfilesCollection::AddPathToCurrentProfiles(
|
||||
base::TimeTicks timestamp, const std::vector<CodeEntry*>& path,
|
||||
int src_line, bool update_stats) {
|
||||
base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
|
||||
bool update_stats) {
|
||||
// As starting / stopping profiles is rare relatively to this
|
||||
// method, we don't bother minimizing the duration of lock holding,
|
||||
// e.g. copying contents of the list to a local vector.
|
||||
@ -624,17 +653,18 @@ ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles)
|
||||
: profiles_(profiles) {}
|
||||
|
||||
void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
std::vector<CodeEntry*> entries;
|
||||
ProfileStackTrace stack_trace;
|
||||
// Conservatively reserve space for stack frames + pc + function + vm-state.
|
||||
// There could in fact be more of them because of inlined entries.
|
||||
entries.reserve(sample.frames_count + 3);
|
||||
stack_trace.reserve(sample.frames_count + 3);
|
||||
|
||||
// The ProfileNode knows nothing about all versions of generated code for
|
||||
// the same JS function. The line number information associated with
|
||||
// the latest version of generated code is used to find a source line number
|
||||
// for a JS function. Then, the detected source line is passed to
|
||||
// ProfileNode to increase the tick count for this source line.
|
||||
int src_line = v8::CpuProfileNode::kNoLineNumberInfo;
|
||||
const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo;
|
||||
int src_line = no_line_info;
|
||||
bool src_line_not_found = true;
|
||||
|
||||
if (sample.pc != nullptr) {
|
||||
@ -642,8 +672,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
// Don't use PC when in external callback code, as it can point
|
||||
// inside callback's code, and we will erroneously report
|
||||
// that a callback calls itself.
|
||||
entries.push_back(
|
||||
FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)));
|
||||
stack_trace.push_back(
|
||||
{FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)),
|
||||
no_line_info});
|
||||
} else {
|
||||
CodeEntry* pc_entry = FindEntry(reinterpret_cast<Address>(sample.pc));
|
||||
// If there is no pc_entry we're likely in native code.
|
||||
@ -664,7 +695,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
src_line = pc_entry->line_number();
|
||||
}
|
||||
src_line_not_found = false;
|
||||
entries.push_back(pc_entry);
|
||||
stack_trace.push_back({pc_entry, src_line});
|
||||
|
||||
if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply ||
|
||||
pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) {
|
||||
@ -675,7 +706,8 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
// former case we don't so we simply replace the frame with
|
||||
// 'unresolved' entry.
|
||||
if (!sample.has_external_callback) {
|
||||
entries.push_back(CodeEntry::unresolved_entry());
|
||||
stack_trace.push_back(
|
||||
{CodeEntry::unresolved_entry(), no_line_info});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -684,6 +716,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
for (unsigned i = 0; i < sample.frames_count; ++i) {
|
||||
Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
|
||||
CodeEntry* entry = FindEntry(stack_pos);
|
||||
int line_number = no_line_info;
|
||||
if (entry) {
|
||||
// Find out if the entry has an inlining stack associated.
|
||||
int pc_offset =
|
||||
@ -693,8 +726,10 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
if (inline_stack) {
|
||||
std::transform(
|
||||
inline_stack->rbegin(), inline_stack->rend(),
|
||||
std::back_inserter(entries),
|
||||
[](const std::unique_ptr<CodeEntry>& ptr) { return ptr.get(); });
|
||||
std::back_inserter(stack_trace),
|
||||
[=](const std::unique_ptr<CodeEntry>& ptr) {
|
||||
return CodeEntryAndLineNumber{ptr.get(), no_line_info};
|
||||
});
|
||||
}
|
||||
// Skip unresolved frames (e.g. internal frame) and get source line of
|
||||
// the first JS caller.
|
||||
@ -705,26 +740,27 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
}
|
||||
src_line_not_found = false;
|
||||
}
|
||||
line_number = entry->GetSourceLine(pc_offset);
|
||||
}
|
||||
entries.push_back(entry);
|
||||
stack_trace.push_back({entry, line_number});
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAG_prof_browser_mode) {
|
||||
bool no_symbolized_entries = true;
|
||||
for (auto e : entries) {
|
||||
if (e != nullptr) {
|
||||
for (auto e : stack_trace) {
|
||||
if (e.code_entry != nullptr) {
|
||||
no_symbolized_entries = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If no frames were symbolized, put the VM state entry in.
|
||||
if (no_symbolized_entries) {
|
||||
entries.push_back(EntryForVMState(sample.state));
|
||||
stack_trace.push_back({EntryForVMState(sample.state), no_line_info});
|
||||
}
|
||||
}
|
||||
|
||||
profiles_->AddPathToCurrentProfiles(sample.timestamp, entries, src_line,
|
||||
profiles_->AddPathToCurrentProfiles(sample.timestamp, stack_trace, src_line,
|
||||
sample.update_stats);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user