*/
class V8_EXPORT CpuProfileNode {
public:
+ struct LineTick {
+ /** The 1-based number of the source line where the function originates. */
+ int line;
+
+ /** The count of samples associated with the source line. */
+ unsigned int hit_count;
+ };
+
/** Returns function name (empty string for anonymous functions.) */
Handle<String> GetFunctionName() const;
*/
int GetColumnNumber() const;
+ /**
+ * Returns the number of the function's source lines that collect the samples.
+ */
+ unsigned int GetHitLineCount() const;
+
+ /** Returns the set of source lines that collect the samples.
+ * The caller allocates buffer and responsible for releasing it.
+ * True if all available entries are copied, otherwise false.
+ * The function copies nothing if buffer is not large enough.
+ */
+ bool GetLineTicks(LineTick* entries, unsigned int length) const;
+
/** Returns bailout reason for the function
* if the optimization was disabled for it.
*/
}
+unsigned int CpuProfileNode::GetHitLineCount() const {
+ const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
+ return node->GetHitLineCount();
+}
+
+
+bool CpuProfileNode::GetLineTicks(LineTick* entries,
+ unsigned int length) const {
+ const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
+ return node->GetLineTicks(entries, length);
+}
+
+
const char* CpuProfileNode::GetBailoutReason() const {
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
return node->entry()->bailout_reason();
CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
rec->start = code->address();
- rec->entry = profiles_->NewCodeEntry(tag, profiles_->GetFunctionName(name));
+ rec->entry = profiles_->NewCodeEntry(
+ tag, profiles_->GetFunctionName(name), CodeEntry::kEmptyNamePrefix,
+ CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
+ CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start());
rec->size = code->ExecutableSize();
rec->shared = NULL;
processor_->Enqueue(evt_rec);
CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
rec->start = code->address();
- rec->entry = profiles_->NewCodeEntry(tag, profiles_->GetFunctionName(name));
+ rec->entry = profiles_->NewCodeEntry(
+ tag, profiles_->GetFunctionName(name), CodeEntry::kEmptyNamePrefix,
+ CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
+ CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start());
rec->size = code->ExecutableSize();
rec->shared = NULL;
processor_->Enqueue(evt_rec);
rec->start = code->address();
rec->entry = profiles_->NewCodeEntry(
tag, profiles_->GetFunctionName(shared->DebugName()),
- CodeEntry::kEmptyNamePrefix, profiles_->GetName(script_name));
+ CodeEntry::kEmptyNamePrefix, profiles_->GetName(script_name),
+ CpuProfileNode::kNoLineNumberInfo, CpuProfileNode::kNoColumnNumberInfo,
+ NULL, code->instruction_start());
if (info) {
rec->entry->set_no_frame_ranges(info->ReleaseNoFrameRanges());
}
CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
rec->start = code->address();
+ Script* script = Script::cast(shared->script());
+ JITLineInfoTable* line_table = NULL;
+ if (script) {
+ line_table = new JITLineInfoTable();
+ for (RelocIterator it(code); !it.done(); it.next()) {
+ RelocInfo::Mode mode = it.rinfo()->rmode();
+ if (RelocInfo::IsPosition(mode)) {
+ int position = static_cast<int>(it.rinfo()->data());
+ if (position >= 0) {
+ int pc_offset = static_cast<int>(it.rinfo()->pc() - code->address());
+ int line_number = script->GetLineNumber(position) + 1;
+ line_table->SetPosition(pc_offset, line_number);
+ }
+ }
+ }
+ }
rec->entry = profiles_->NewCodeEntry(
tag, profiles_->GetFunctionName(shared->DebugName()),
CodeEntry::kEmptyNamePrefix, profiles_->GetName(script_name), line,
- column);
+ column, line_table, code->instruction_start());
if (info) {
rec->entry->set_no_frame_ranges(info->ReleaseNoFrameRanges());
}
- DCHECK(Script::cast(shared->script()));
- Script* script = Script::cast(shared->script());
rec->entry->set_script_id(script->id()->value());
rec->size = code->ExecutableSize();
rec->shared = shared->address();
CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
rec->start = code->address();
rec->entry = profiles_->NewCodeEntry(
- tag,
- profiles_->GetName(args_count),
- "args_count: ");
+ tag, profiles_->GetName(args_count), "args_count: ",
+ CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
+ CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start());
rec->size = code->ExecutableSize();
rec->shared = NULL;
processor_->Enqueue(evt_rec);
CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
rec->start = code->address();
rec->entry = profiles_->NewCodeEntry(
- Logger::REG_EXP_TAG,
- profiles_->GetName(source),
- "RegExp: ");
+ Logger::REG_EXP_TAG, profiles_->GetName(source), "RegExp: ",
+ CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
+ CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start());
rec->size = code->ExecutableSize();
processor_->Enqueue(evt_rec);
}
void FullCodeGenerator::SetSourcePosition(int pos) {
if (pos != RelocInfo::kNoPosition) {
masm_->positions_recorder()->RecordPosition(pos);
+ masm_->positions_recorder()->WriteRecordedPositions();
}
}
namespace v8 {
namespace internal {
-CodeEntry::CodeEntry(Logger::LogEventsAndTags tag,
- const char* name,
- const char* name_prefix,
- const char* resource_name,
- int line_number,
- int column_number)
+CodeEntry::CodeEntry(Logger::LogEventsAndTags tag, const char* name,
+ const char* name_prefix, const char* resource_name,
+ int line_number, int column_number,
+ JITLineInfoTable* line_info, Address instruction_start)
: tag_(tag),
builtin_id_(Builtins::builtin_count),
name_prefix_(name_prefix),
shared_id_(0),
script_id_(v8::UnboundScript::kNoScriptId),
no_frame_ranges_(NULL),
- bailout_reason_(kEmptyBailoutReason) { }
+ bailout_reason_(kEmptyBailoutReason),
+ line_info_(line_info),
+ instruction_start_(instruction_start) {}
bool CodeEntry::is_js_function_tag(Logger::LogEventsAndTags tag) {
}
+static bool LineTickMatch(void* a, void* b) { return a == b; }
+
+
ProfileNode::ProfileNode(ProfileTree* tree, CodeEntry* entry)
: tree_(tree),
entry_(entry),
self_ticks_(0),
children_(CodeEntriesMatch),
- id_(tree->next_node_id()) { }
-
+ id_(tree->next_node_id()),
+ line_ticks_(LineTickMatch) {}
} } // namespace v8::internal
#endif // V8_PROFILE_GENERATOR_INL_H_
}
+JITLineInfoTable::JITLineInfoTable() {}
+
+
+JITLineInfoTable::~JITLineInfoTable() {}
+
+
+void JITLineInfoTable::SetPosition(int pc_offset, int line) {
+ DCHECK(pc_offset >= 0);
+ DCHECK(line > 0); // The 1-based number of the source line.
+ if (GetSourceLineNumber(pc_offset) != line) {
+ pc_offset_map_.insert(std::make_pair(pc_offset, line));
+ }
+}
+
+int JITLineInfoTable::GetSourceLineNumber(int pc_offset) const {
+ PcOffsetMap::const_iterator it = pc_offset_map_.lower_bound(pc_offset);
+ if (it == pc_offset_map_.end()) {
+ if (pc_offset_map_.empty()) return v8::CpuProfileNode::kNoLineNumberInfo;
+ return (--pc_offset_map_.end())->second;
+ }
+ return it->second;
+}
+
+
const char* const CodeEntry::kEmptyNamePrefix = "";
const char* const CodeEntry::kEmptyResourceName = "";
const char* const CodeEntry::kEmptyBailoutReason = "";
CodeEntry::~CodeEntry() {
delete no_frame_ranges_;
+ delete line_info_;
}
}
+int CodeEntry::GetSourceLine(int pc_offset) const {
+ if (line_info_ && !line_info_->empty()) {
+ return line_info_->GetSourceLineNumber(pc_offset);
+ }
+ return v8::CpuProfileNode::kNoLineNumberInfo;
+}
+
+
ProfileNode* ProfileNode::FindChild(CodeEntry* entry) {
HashMap::Entry* map_entry =
children_.Lookup(entry, CodeEntryHash(entry), false);
}
+void ProfileNode::IncrementLineTicks(int src_line) {
+ if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) return;
+ // Increment a hit counter of a certain source line.
+ // Add a new source line if not found.
+ HashMap::Entry* e =
+ line_ticks_.Lookup(reinterpret_cast<void*>(src_line), src_line, true);
+ DCHECK(e);
+ e->value = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(e->value) + 1);
+}
+
+
+bool ProfileNode::GetLineTicks(v8::CpuProfileNode::LineTick* entries,
+ unsigned int length) const {
+ if (entries == NULL || length == 0) return false;
+
+ unsigned line_count = line_ticks_.occupancy();
+
+ if (line_count == 0) return true;
+ if (length < line_count) return false;
+
+ v8::CpuProfileNode::LineTick* entry = entries;
+
+ for (HashMap::Entry* p = line_ticks_.Start(); p != NULL;
+ p = line_ticks_.Next(p), entry++) {
+ entry->line =
+ static_cast<unsigned int>(reinterpret_cast<uintptr_t>(p->key));
+ entry->hit_count =
+ static_cast<unsigned int>(reinterpret_cast<uintptr_t>(p->value));
+ }
+
+ return true;
+}
+
+
void ProfileNode::Print(int indent) {
base::OS::Print("%5u %*s %s%s %d #%d %s", self_ticks_, indent, "",
entry_->name_prefix(), entry_->name(), entry_->script_id(),
}
-ProfileNode* ProfileTree::AddPathFromEnd(const Vector<CodeEntry*>& path) {
+ProfileNode* ProfileTree::AddPathFromEnd(const Vector<CodeEntry*>& path,
+ int src_line) {
ProfileNode* node = root_;
for (CodeEntry** entry = path.start() + path.length() - 1;
entry != path.start() - 1;
}
}
node->IncrementSelfTicks();
+ if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
+ node->IncrementLineTicks(src_line);
+ }
return node;
}
-void ProfileTree::AddPathFromStart(const Vector<CodeEntry*>& path) {
+void ProfileTree::AddPathFromStart(const Vector<CodeEntry*>& path,
+ int src_line) {
ProfileNode* node = root_;
for (CodeEntry** entry = path.start();
entry != path.start() + path.length();
}
}
node->IncrementSelfTicks();
+ if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
+ node->IncrementLineTicks(src_line);
+ }
}
void CpuProfile::AddPath(base::TimeTicks timestamp,
- const Vector<CodeEntry*>& path) {
- ProfileNode* top_frame_node = top_down_.AddPathFromEnd(path);
+ const Vector<CodeEntry*>& path, int src_line) {
+ ProfileNode* top_frame_node = top_down_.AddPathFromEnd(path, src_line);
if (record_samples_) {
timestamps_.Add(timestamp);
samples_.Add(top_frame_node);
void CpuProfilesCollection::AddPathToCurrentProfiles(
- base::TimeTicks timestamp, const Vector<CodeEntry*>& path) {
+ base::TimeTicks timestamp, const Vector<CodeEntry*>& path, int src_line) {
// 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.
current_profiles_semaphore_.Wait();
for (int i = 0; i < current_profiles_.length(); ++i) {
- current_profiles_[i]->AddPath(timestamp, path);
+ current_profiles_[i]->AddPath(timestamp, path, src_line);
}
current_profiles_semaphore_.Signal();
}
CodeEntry* CpuProfilesCollection::NewCodeEntry(
- Logger::LogEventsAndTags tag,
- const char* name,
- const char* name_prefix,
- const char* resource_name,
- int line_number,
- int column_number) {
- CodeEntry* code_entry = new CodeEntry(tag,
- name,
- name_prefix,
- resource_name,
- line_number,
- column_number);
+ Logger::LogEventsAndTags tag, const char* name, const char* name_prefix,
+ const char* resource_name, int line_number, int column_number,
+ JITLineInfoTable* line_info, Address instruction_start) {
+ CodeEntry* code_entry =
+ new CodeEntry(tag, name, name_prefix, resource_name, line_number,
+ column_number, line_info, instruction_start);
code_entries_.Add(code_entry);
return code_entry;
}
// entries vector with NULL values.
CodeEntry** entry = entries.start();
memset(entry, 0, entries.length() * sizeof(*entry));
+
+ // 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;
+ bool src_line_not_found = true;
+
if (sample.pc != NULL) {
if (sample.has_external_callback && sample.state == EXTERNAL &&
sample.top_frame_type == StackFrame::EXIT) {
// frame. Check for this case and just skip such samples.
if (pc_entry) {
List<OffsetRange>* ranges = pc_entry->no_frame_ranges();
+ int pc_offset =
+ static_cast<int>(sample.pc - pc_entry->instruction_start());
if (ranges) {
- Code* code = Code::cast(HeapObject::FromAddress(start));
- int pc_offset = static_cast<int>(
- sample.pc - code->instruction_start());
for (int i = 0; i < ranges->length(); i++) {
OffsetRange& range = ranges->at(i);
if (range.from <= pc_offset && pc_offset < range.to) {
}
}
}
+ src_line = pc_entry->GetSourceLine(pc_offset);
+ if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
+ src_line = pc_entry->line_number();
+ }
+ src_line_not_found = false;
*entry++ = pc_entry;
if (pc_entry->builtin_id() == Builtins::kFunctionCall ||
*stack_end = stack_pos + sample.frames_count;
stack_pos != stack_end;
++stack_pos) {
- *entry++ = code_map_.FindEntry(*stack_pos);
+ Address start = NULL;
+ *entry = code_map_.FindEntry(*stack_pos, &start);
+
+ // Skip unresolved frames (e.g. internal frame) and get source line of
+ // the first JS caller.
+ if (src_line_not_found && *entry) {
+ int pc_offset =
+ static_cast<int>(*stack_pos - (*entry)->instruction_start());
+ src_line = (*entry)->GetSourceLine(pc_offset);
+ if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
+ src_line = (*entry)->line_number();
+ }
+ src_line_not_found = false;
+ }
+
+ entry++;
}
}
}
}
- profiles_->AddPathToCurrentProfiles(sample.timestamp, entries);
+ profiles_->AddPathToCurrentProfiles(sample.timestamp, entries, src_line);
}
#ifndef V8_PROFILE_GENERATOR_H_
#define V8_PROFILE_GENERATOR_H_
+#include <map>
#include "include/v8-profiler.h"
#include "src/allocation.h"
#include "src/hashmap.h"
};
+// Provides a mapping from the offsets within generated code to
+// the source line.
+class JITLineInfoTable : public Malloced {
+ public:
+ JITLineInfoTable();
+ ~JITLineInfoTable();
+
+ void SetPosition(int pc_offset, int line);
+ int GetSourceLineNumber(int pc_offset) const;
+
+ bool empty() const { return pc_offset_map_.empty(); }
+
+ private:
+ // pc_offset -> source line
+ typedef std::map<int, int> PcOffsetMap;
+ PcOffsetMap pc_offset_map_;
+ DISALLOW_COPY_AND_ASSIGN(JITLineInfoTable);
+};
+
class CodeEntry {
public:
// CodeEntry doesn't own name strings, just references them.
- inline CodeEntry(Logger::LogEventsAndTags tag,
- const char* name,
+ inline CodeEntry(Logger::LogEventsAndTags tag, const char* name,
const char* name_prefix = CodeEntry::kEmptyNamePrefix,
const char* resource_name = CodeEntry::kEmptyResourceName,
int line_number = v8::CpuProfileNode::kNoLineNumberInfo,
- int column_number = v8::CpuProfileNode::kNoColumnNumberInfo);
+ int column_number = v8::CpuProfileNode::kNoColumnNumberInfo,
+ JITLineInfoTable* line_info = NULL,
+ Address instruction_start = NULL);
~CodeEntry();
bool is_js_function() const { return is_js_function_tag(tag_); }
const char* resource_name() const { return resource_name_; }
int line_number() const { return line_number_; }
int column_number() const { return column_number_; }
+ const JITLineInfoTable* line_info() const { return line_info_; }
void set_shared_id(int shared_id) { shared_id_ = shared_id; }
int script_id() const { return script_id_; }
void set_script_id(int script_id) { script_id_ = script_id; }
uint32_t GetCallUid() const;
bool IsSameAs(CodeEntry* entry) const;
+ int GetSourceLine(int pc_offset) const;
+
+ Address instruction_start() const { return instruction_start_; }
+
static const char* const kEmptyNamePrefix;
static const char* const kEmptyResourceName;
static const char* const kEmptyBailoutReason;
int script_id_;
List<OffsetRange>* no_frame_ranges_;
const char* bailout_reason_;
+ JITLineInfoTable* line_info_;
+ Address instruction_start_;
DISALLOW_COPY_AND_ASSIGN(CodeEntry);
};
ProfileNode* FindOrAddChild(CodeEntry* entry);
void IncrementSelfTicks() { ++self_ticks_; }
void IncreaseSelfTicks(unsigned amount) { self_ticks_ += amount; }
+ void IncrementLineTicks(int src_line);
CodeEntry* entry() const { return entry_; }
unsigned self_ticks() const { return self_ticks_; }
const List<ProfileNode*>* children() const { return &children_list_; }
unsigned id() const { return id_; }
+ unsigned int GetHitLineCount() const { return line_ticks_.occupancy(); }
+ bool GetLineTicks(v8::CpuProfileNode::LineTick* entries,
+ unsigned int length) const;
void Print(int indent);
HashMap children_;
List<ProfileNode*> children_list_;
unsigned id_;
+ HashMap line_ticks_;
DISALLOW_COPY_AND_ASSIGN(ProfileNode);
};
ProfileTree();
~ProfileTree();
- ProfileNode* AddPathFromEnd(const Vector<CodeEntry*>& path);
- void AddPathFromStart(const Vector<CodeEntry*>& path);
+ ProfileNode* AddPathFromEnd(
+ const Vector<CodeEntry*>& path,
+ int src_line = v8::CpuProfileNode::kNoLineNumberInfo);
+ void AddPathFromStart(const Vector<CodeEntry*>& path,
+ int src_line = v8::CpuProfileNode::kNoLineNumberInfo);
ProfileNode* root() const { return root_; }
unsigned next_node_id() { return next_node_id_++; }
CpuProfile(const char* title, bool record_samples);
// Add pc -> ... -> main() call path to the profile.
- void AddPath(base::TimeTicks timestamp, const Vector<CodeEntry*>& path);
+ void AddPath(base::TimeTicks timestamp, const Vector<CodeEntry*>& path,
+ int src_line);
void CalculateTotalTicksAndSamplingRate();
const char* title() const { return title_; }
void RemoveProfile(CpuProfile* profile);
CodeEntry* NewCodeEntry(
- Logger::LogEventsAndTags tag,
- const char* name,
+ Logger::LogEventsAndTags tag, const char* name,
const char* name_prefix = CodeEntry::kEmptyNamePrefix,
const char* resource_name = CodeEntry::kEmptyResourceName,
int line_number = v8::CpuProfileNode::kNoLineNumberInfo,
- int column_number = v8::CpuProfileNode::kNoColumnNumberInfo);
+ int column_number = v8::CpuProfileNode::kNoColumnNumberInfo,
+ JITLineInfoTable* line_info = NULL, Address instruction_start = NULL);
// Called from profile generator thread.
- void AddPathToCurrentProfiles(
- base::TimeTicks timestamp, const Vector<CodeEntry*>& path);
+ void AddPathToCurrentProfiles(base::TimeTicks timestamp,
+ const Vector<CodeEntry*>& path, int src_line);
// Limits the number of profiles that can be simultaneously collected.
static const int kMaxSimultaneousProfiles = 100;
}
+// This tests checks distribution of the samples through the source lines.
+TEST(TickLines) {
+ CcTest::InitializeVM();
+ LocalContext env;
+ i::FLAG_turbo_source_positions = true;
+ i::Isolate* isolate = CcTest::i_isolate();
+ i::Factory* factory = isolate->factory();
+ i::HandleScope scope(isolate);
+
+ i::EmbeddedVector<char, 512> script;
+
+ const char* func_name = "func";
+ i::SNPrintF(script,
+ "function %s() {\n"
+ " var n = 0;\n"
+ " var m = 100*100;\n"
+ " while (m > 1) {\n"
+ " m--;\n"
+ " n += m * m * m;\n"
+ " }\n"
+ "}\n"
+ "%s();\n",
+ func_name, func_name);
+
+ CompileRun(script.start());
+
+ i::Handle<i::JSFunction> func = v8::Utils::OpenHandle(
+ *v8::Local<v8::Function>::Cast((*env)->Global()->Get(v8_str(func_name))));
+ CHECK_NE(NULL, func->shared());
+ CHECK_NE(NULL, func->shared()->code());
+ i::Code* code = NULL;
+ if (func->code()->is_optimized_code()) {
+ code = func->code();
+ } else {
+ CHECK(func->shared()->code() == func->code() || !i::FLAG_crankshaft);
+ code = func->shared()->code();
+ }
+ CHECK_NE(NULL, code);
+ i::Address code_address = code->instruction_start();
+ CHECK_NE(NULL, code_address);
+
+ CpuProfilesCollection* profiles = new CpuProfilesCollection(isolate->heap());
+ profiles->StartProfiling("", false);
+ ProfileGenerator generator(profiles);
+ SmartPointer<ProfilerEventsProcessor> processor(new ProfilerEventsProcessor(
+ &generator, NULL, v8::base::TimeDelta::FromMicroseconds(100)));
+ processor->Start();
+ CpuProfiler profiler(isolate, profiles, &generator, processor.get());
+
+ // Enqueue code creation events.
+ i::Handle<i::String> str = factory->NewStringFromAsciiChecked(func_name);
+ int line = 1;
+ int column = 1;
+ profiler.CodeCreateEvent(i::Logger::FUNCTION_TAG, code, func->shared(), NULL,
+ *str, line, column);
+
+ // Enqueue a tick event to enable code events processing.
+ EnqueueTickSampleEvent(processor.get(), code_address);
+
+ processor->StopSynchronously();
+
+ CpuProfile* profile = profiles->StopProfiling("");
+ CHECK_NE(NULL, profile);
+
+ // Check the state of profile generator.
+ CodeEntry* func_entry = generator.code_map()->FindEntry(code_address);
+ CHECK_NE(NULL, func_entry);
+ CHECK_EQ(func_name, func_entry->name());
+ const i::JITLineInfoTable* line_info = func_entry->line_info();
+ CHECK_NE(NULL, line_info);
+ CHECK(!line_info->empty());
+
+ // Check the hit source lines using V8 Public APIs.
+ const i::ProfileTree* tree = profile->top_down();
+ ProfileNode* root = tree->root();
+ CHECK_NE(NULL, root);
+ ProfileNode* func_node = root->FindChild(func_entry);
+ CHECK_NE(NULL, func_node);
+
+ // Add 10 faked ticks to source line #5.
+ int hit_line = 5;
+ int hit_count = 10;
+ for (int i = 0; i < hit_count; i++) func_node->IncrementLineTicks(hit_line);
+
+ unsigned int line_count = func_node->GetHitLineCount();
+ CHECK_EQ(2, line_count); // Expect two hit source lines - #1 and #5.
+ ScopedVector<v8::CpuProfileNode::LineTick> entries(line_count);
+ CHECK(func_node->GetLineTicks(&entries[0], line_count));
+ int value = 0;
+ for (int i = 0; i < entries.length(); i++)
+ if (entries[i].line == hit_line) {
+ value = entries[i].hit_count;
+ break;
+ }
+ CHECK_EQ(hit_count, value);
+}
+
+
static const char* call_function_test_source = "function bar(iterations) {\n"
"}\n"
"function start(duration) {\n"