Extend CPU profiler with mapping ticks to source lines
authoryurys@chromium.org <yurys@chromium.org>
Thu, 2 Oct 2014 09:20:37 +0000 (09:20 +0000)
committeryurys@chromium.org <yurys@chromium.org>
Thu, 2 Oct 2014 09:20:37 +0000 (09:20 +0000)
The idea behind of this solution is to use the existing "relocation info" instead of consumption the CodeLinePosition events emitted by the V8 compilers.
During generation code and relocation info are generated simultaneously.
When code generation is done you each code object has associated "relocation info".
Relocation information lets V8 to mark interesting places in the generated code: the pointers that might need to be relocated (after garbage collection),
correspondences between the machine program counter and source locations for stack walking.

This patch:
1. Add more source positions info in reloc info to make it suitable for source level mapping.
The amount of data should not be increased dramatically because (1) V8 already marks interesting places in the generated code and
(2) V8 does not write redundant information (it writes a pair (pc_offset, pos) only if pos is changed and skips other).
I measured it on Octane benchmark - for unoptimized code the number of source positions may achieve 2x ('lin_solve' from NavierStokes benchmark).

2. When a sample happens, CPU profiler finds a code object by pc, then use its reloc info to match the sample to a source line.
If a source line is found that hit counter is increased by one for this line.

3. Add a new public V8 API to get the hit source lines by CDT CPU profiler.
Note that it's expected a minor patch in Blink to pack the source level info in JSON to be shown.

4.Add a test that checks how the samples are distributed through source lines.
It tests two cases: (1) relocation info created during code generation and (2) relocation info associated with precompiled function's version.

Patch from Denis Pravdin <denis.pravdin@intel.com>
BUG=None
LOG=Y
R=svenpanne@chromium.org

Review URL: https://codereview.chromium.org/616963005

Patch from Denis Pravdin <denis.pravdin@intel.com>.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24389 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

include/v8-profiler.h
src/api.cc
src/cpu-profiler.cc
src/full-codegen.cc
src/profile-generator-inl.h
src/profile-generator.cc
src/profile-generator.h
test/cctest/test-cpu-profiler.cc

index 7fc193d..d021520 100644 (file)
@@ -22,6 +22,14 @@ typedef uint32_t SnapshotObjectId;
  */
 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;
 
@@ -43,6 +51,18 @@ class V8_EXPORT CpuProfileNode {
    */
   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.
     */
index 512eaeb..883d8c5 100644 (file)
@@ -7114,6 +7114,19 @@ int CpuProfileNode::GetColumnNumber() const {
 }
 
 
+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();
index 68a565c..2cb855a 100644 (file)
@@ -201,7 +201,10 @@ void CpuProfiler::CodeCreateEvent(Logger::LogEventsAndTags tag,
   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);
@@ -215,7 +218,10 @@ void CpuProfiler::CodeCreateEvent(Logger::LogEventsAndTags tag,
   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);
@@ -231,7 +237,9 @@ void CpuProfiler::CodeCreateEvent(Logger::LogEventsAndTags tag, Code* code,
   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());
   }
@@ -256,15 +264,29 @@ void CpuProfiler::CodeCreateEvent(Logger::LogEventsAndTags tag, Code* code,
   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();
@@ -282,9 +304,9 @@ void CpuProfiler::CodeCreateEvent(Logger::LogEventsAndTags tag,
   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);
@@ -344,9 +366,9 @@ void CpuProfiler::RegExpCodeCreateEvent(Code* code, String* source) {
   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);
 }
index 35d51d9..edb43ef 100644 (file)
@@ -866,6 +866,7 @@ void FullCodeGenerator::SetExpressionPosition(Expression* expr) {
 void FullCodeGenerator::SetSourcePosition(int pos) {
   if (pos != RelocInfo::kNoPosition) {
     masm_->positions_recorder()->RecordPosition(pos);
+    masm_->positions_recorder()->WriteRecordedPositions();
   }
 }
 
index 58c124f..c3ffb26 100644 (file)
 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),
@@ -26,7 +24,9 @@ CodeEntry::CodeEntry(Logger::LogEventsAndTags tag,
       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) {
@@ -39,13 +39,16 @@ 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_
index 6017f12..4609c02 100644 (file)
@@ -132,6 +132,30 @@ HashMap::Entry* StringsStorage::GetEntry(const char* str, int len) {
 }
 
 
+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 = "";
@@ -139,6 +163,7 @@ const char* const CodeEntry::kEmptyBailoutReason = "";
 
 CodeEntry::~CodeEntry() {
   delete no_frame_ranges_;
+  delete line_info_;
 }
 
 
@@ -181,6 +206,14 @@ void CodeEntry::SetBuiltinId(Builtins::Name id) {
 }
 
 
+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);
@@ -202,6 +235,40 @@ ProfileNode* ProfileNode::FindOrAddChild(CodeEntry* entry) {
 }
 
 
+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(),
@@ -242,7 +309,8 @@ ProfileTree::~ProfileTree() {
 }
 
 
-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;
@@ -252,11 +320,15 @@ ProfileNode* ProfileTree::AddPathFromEnd(const Vector<CodeEntry*>& path) {
     }
   }
   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();
@@ -266,6 +338,9 @@ void ProfileTree::AddPathFromStart(const Vector<CodeEntry*>& path) {
     }
   }
   node->IncrementSelfTicks();
+  if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
+    node->IncrementLineTicks(src_line);
+  }
 }
 
 
@@ -327,8 +402,8 @@ CpuProfile::CpuProfile(const char* title, bool record_samples)
 
 
 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);
@@ -517,31 +592,25 @@ void CpuProfilesCollection::RemoveProfile(CpuProfile* profile) {
 
 
 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;
 }
@@ -579,6 +648,15 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
   // 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) {
@@ -595,10 +673,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
       // 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) {
@@ -606,6 +683,11 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
             }
           }
         }
+        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 ||
@@ -626,7 +708,22 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
            *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++;
     }
   }
 
@@ -644,7 +741,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
     }
   }
 
-  profiles_->AddPathToCurrentProfiles(sample.timestamp, entries);
+  profiles_->AddPathToCurrentProfiles(sample.timestamp, entries, src_line);
 }
 
 
index 5ebb92b..9135fe6 100644 (file)
@@ -5,6 +5,7 @@
 #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"
@@ -44,15 +45,35 @@ class StringsStorage {
 };
 
 
+// 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_); }
@@ -62,6 +83,7 @@ class CodeEntry {
   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; }
@@ -83,6 +105,10 @@ class CodeEntry {
   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;
@@ -99,6 +125,8 @@ class CodeEntry {
   int script_id_;
   List<OffsetRange>* no_frame_ranges_;
   const char* bailout_reason_;
+  JITLineInfoTable* line_info_;
+  Address instruction_start_;
 
   DISALLOW_COPY_AND_ASSIGN(CodeEntry);
 };
@@ -114,11 +142,15 @@ class ProfileNode {
   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);
 
@@ -139,6 +171,7 @@ class ProfileNode {
   HashMap children_;
   List<ProfileNode*> children_list_;
   unsigned id_;
+  HashMap line_ticks_;
 
   DISALLOW_COPY_AND_ASSIGN(ProfileNode);
 };
@@ -149,8 +182,11 @@ class ProfileTree {
   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_++; }
 
@@ -175,7 +211,8 @@ class CpuProfile {
   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_; }
@@ -277,16 +314,16 @@ class CpuProfilesCollection {
   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;
index 8d429d2..72edc27 100644 (file)
@@ -1064,6 +1064,104 @@ TEST(BoundFunctionCall) {
 }
 
 
+// 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"