1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/profiler/profile-generator.h"
6
7 #include <algorithm>
8
9 #include "include/v8-profiler.h"
10 #include "src/base/lazy-instance.h"
11 #include "src/codegen/source-position.h"
12 #include "src/objects/shared-function-info-inl.h"
13 #include "src/profiler/cpu-profiler.h"
14 #include "src/profiler/profile-generator-inl.h"
15 #include "src/profiler/profiler-stats.h"
16 #include "src/tracing/trace-event.h"
17 #include "src/tracing/traced-value.h"
18
19 namespace v8 {
20 namespace internal {
21
SetPosition(int pc_offset,int line,int inlining_id)22 void SourcePositionTable::SetPosition(int pc_offset, int line,
23 int inlining_id) {
24 DCHECK_GE(pc_offset, 0);
25 DCHECK_GT(line, 0); // The 1-based number of the source line.
26 // It's possible that we map multiple source positions to a pc_offset in
27 // optimized code. Usually these map to the same line, so there is no
28 // difference here as we only store line number and not line/col in the form
29 // of a script offset. Ignore any subsequent sets to the same offset.
30 if (!pc_offsets_to_lines_.empty() &&
31 pc_offsets_to_lines_.back().pc_offset == pc_offset) {
32 return;
33 }
34 // Check that we are inserting in ascending order, so that the vector remains
35 // sorted.
36 DCHECK(pc_offsets_to_lines_.empty() ||
37 pc_offsets_to_lines_.back().pc_offset < pc_offset);
38 if (pc_offsets_to_lines_.empty() ||
39 pc_offsets_to_lines_.back().line_number != line ||
40 pc_offsets_to_lines_.back().inlining_id != inlining_id) {
41 pc_offsets_to_lines_.push_back({pc_offset, line, inlining_id});
42 }
43 }
44
GetSourceLineNumber(int pc_offset) const45 int SourcePositionTable::GetSourceLineNumber(int pc_offset) const {
46 if (pc_offsets_to_lines_.empty()) {
47 return v8::CpuProfileNode::kNoLineNumberInfo;
48 }
49 auto it = std::lower_bound(
50 pc_offsets_to_lines_.begin(), pc_offsets_to_lines_.end(),
51 SourcePositionTuple{pc_offset, 0, SourcePosition::kNotInlined});
52 if (it != pc_offsets_to_lines_.begin()) --it;
53 return it->line_number;
54 }
55
GetInliningId(int pc_offset) const56 int SourcePositionTable::GetInliningId(int pc_offset) const {
57 if (pc_offsets_to_lines_.empty()) {
58 return SourcePosition::kNotInlined;
59 }
60 auto it = std::lower_bound(
61 pc_offsets_to_lines_.begin(), pc_offsets_to_lines_.end(),
62 SourcePositionTuple{pc_offset, 0, SourcePosition::kNotInlined});
63 if (it != pc_offsets_to_lines_.begin()) --it;
64 return it->inlining_id;
65 }
66
Size() const67 size_t SourcePositionTable::Size() const {
68 return sizeof(*this) + pc_offsets_to_lines_.capacity() *
69 sizeof(decltype(pc_offsets_to_lines_)::value_type);
70 }
71
print() const72 void SourcePositionTable::print() const {
73 base::OS::Print(" - source position table at %p\n", this);
74 for (const SourcePositionTuple& pos_info : pc_offsets_to_lines_) {
75 base::OS::Print(" %d --> line_number: %d inlining_id: %d\n",
76 pos_info.pc_offset, pos_info.line_number,
77 pos_info.inlining_id);
78 }
79 }
80
81 const char* const CodeEntry::kEmptyResourceName = "";
82 const char* const CodeEntry::kEmptyBailoutReason = "";
83 const char* const CodeEntry::kNoDeoptReason = "";
84
85 const char* const CodeEntry::kProgramEntryName = "(program)";
86 const char* const CodeEntry::kIdleEntryName = "(idle)";
87 const char* const CodeEntry::kGarbageCollectorEntryName = "(garbage collector)";
88 const char* const CodeEntry::kUnresolvedFunctionName = "(unresolved function)";
89 const char* const CodeEntry::kRootEntryName = "(root)";
90
91 // static
program_entry()92 CodeEntry* CodeEntry::program_entry() {
93 static base::LeakyObject<CodeEntry> kProgramEntry(
94 CodeEventListener::FUNCTION_TAG, CodeEntry::kProgramEntryName,
95 CodeEntry::kEmptyResourceName, v8::CpuProfileNode::kNoLineNumberInfo,
96 v8::CpuProfileNode::kNoColumnNumberInfo, nullptr, false,
97 CodeEntry::CodeType::OTHER);
98 return kProgramEntry.get();
99 }
100
101 // static
idle_entry()102 CodeEntry* CodeEntry::idle_entry() {
103 static base::LeakyObject<CodeEntry> kIdleEntry(
104 CodeEventListener::FUNCTION_TAG, CodeEntry::kIdleEntryName,
105 CodeEntry::kEmptyResourceName, v8::CpuProfileNode::kNoLineNumberInfo,
106 v8::CpuProfileNode::kNoColumnNumberInfo, nullptr, false,
107 CodeEntry::CodeType::OTHER);
108 return kIdleEntry.get();
109 }
110
111 // static
gc_entry()112 CodeEntry* CodeEntry::gc_entry() {
113 static base::LeakyObject<CodeEntry> kGcEntry(
114 CodeEventListener::BUILTIN_TAG, CodeEntry::kGarbageCollectorEntryName,
115 CodeEntry::kEmptyResourceName, v8::CpuProfileNode::kNoLineNumberInfo,
116 v8::CpuProfileNode::kNoColumnNumberInfo, nullptr, false,
117 CodeEntry::CodeType::OTHER);
118 return kGcEntry.get();
119 }
120
121 // static
unresolved_entry()122 CodeEntry* CodeEntry::unresolved_entry() {
123 static base::LeakyObject<CodeEntry> kUnresolvedEntry(
124 CodeEventListener::FUNCTION_TAG, CodeEntry::kUnresolvedFunctionName,
125 CodeEntry::kEmptyResourceName, v8::CpuProfileNode::kNoLineNumberInfo,
126 v8::CpuProfileNode::kNoColumnNumberInfo, nullptr, false,
127 CodeEntry::CodeType::OTHER);
128 return kUnresolvedEntry.get();
129 }
130
131 // static
root_entry()132 CodeEntry* CodeEntry::root_entry() {
133 static base::LeakyObject<CodeEntry> kRootEntry(
134 CodeEventListener::FUNCTION_TAG, CodeEntry::kRootEntryName,
135 CodeEntry::kEmptyResourceName, v8::CpuProfileNode::kNoLineNumberInfo,
136 v8::CpuProfileNode::kNoColumnNumberInfo, nullptr, false,
137 CodeEntry::CodeType::OTHER);
138 return kRootEntry.get();
139 }
140
GetHash() const141 uint32_t CodeEntry::GetHash() const {
142 uint32_t hash = 0;
143 if (script_id_ != v8::UnboundScript::kNoScriptId) {
144 hash ^= ComputeUnseededHash(static_cast<uint32_t>(script_id_));
145 hash ^= ComputeUnseededHash(static_cast<uint32_t>(position_));
146 } else {
147 hash ^= ComputeUnseededHash(
148 static_cast<uint32_t>(reinterpret_cast<uintptr_t>(name_)));
149 hash ^= ComputeUnseededHash(
150 static_cast<uint32_t>(reinterpret_cast<uintptr_t>(resource_name_)));
151 hash ^= ComputeUnseededHash(line_number_);
152 }
153 return hash;
154 }
155
IsSameFunctionAs(const CodeEntry * entry) const156 bool CodeEntry::IsSameFunctionAs(const CodeEntry* entry) const {
157 if (this == entry) return true;
158 if (script_id_ != v8::UnboundScript::kNoScriptId) {
159 return script_id_ == entry->script_id_ && position_ == entry->position_;
160 }
161 return name_ == entry->name_ && resource_name_ == entry->resource_name_ &&
162 line_number_ == entry->line_number_;
163 }
164
SetBuiltinId(Builtin id)165 void CodeEntry::SetBuiltinId(Builtin id) {
166 bit_field_ = TagField::update(bit_field_, CodeEventListener::BUILTIN_TAG);
167 bit_field_ = BuiltinField::update(bit_field_, id);
168 }
169
GetSourceLine(int pc_offset) const170 int CodeEntry::GetSourceLine(int pc_offset) const {
171 if (line_info_) return line_info_->GetSourceLineNumber(pc_offset);
172 return v8::CpuProfileNode::kNoLineNumberInfo;
173 }
174
SetInlineStacks(std::unordered_set<CodeEntry *,Hasher,Equals> inline_entries,std::unordered_map<int,std::vector<CodeEntryAndLineNumber>> inline_stacks)175 void CodeEntry::SetInlineStacks(
176 std::unordered_set<CodeEntry*, Hasher, Equals> inline_entries,
177 std::unordered_map<int, std::vector<CodeEntryAndLineNumber>>
178 inline_stacks) {
179 EnsureRareData()->inline_entries_ = std::move(inline_entries);
180 rare_data_->inline_stacks_ = std::move(inline_stacks);
181 }
182
GetInlineStack(int pc_offset) const183 const std::vector<CodeEntryAndLineNumber>* CodeEntry::GetInlineStack(
184 int pc_offset) const {
185 if (!line_info_) return nullptr;
186
187 int inlining_id = line_info_->GetInliningId(pc_offset);
188 if (inlining_id == SourcePosition::kNotInlined) return nullptr;
189 DCHECK(rare_data_);
190
191 auto it = rare_data_->inline_stacks_.find(inlining_id);
192 return it != rare_data_->inline_stacks_.end() ? &it->second : nullptr;
193 }
194
set_deopt_info(const char * deopt_reason,int deopt_id,std::vector<CpuProfileDeoptFrame> inlined_frames)195 void CodeEntry::set_deopt_info(
196 const char* deopt_reason, int deopt_id,
197 std::vector<CpuProfileDeoptFrame> inlined_frames) {
198 RareData* rare_data = EnsureRareData();
199 rare_data->deopt_reason_ = deopt_reason;
200 rare_data->deopt_id_ = deopt_id;
201 rare_data->deopt_inlined_frames_ = std::move(inlined_frames);
202 }
203
FillFunctionInfo(SharedFunctionInfo shared)204 void CodeEntry::FillFunctionInfo(SharedFunctionInfo shared) {
205 if (!shared.script().IsScript()) return;
206 Script script = Script::cast(shared.script());
207 set_script_id(script.id());
208 set_position(shared.StartPosition());
209 if (shared.optimization_disabled()) {
210 set_bailout_reason(GetBailoutReason(shared.disabled_optimization_reason()));
211 }
212 }
213
EstimatedSize() const214 size_t CodeEntry::EstimatedSize() const {
215 size_t estimated_size = 0;
216 if (rare_data_) {
217 estimated_size += sizeof(rare_data_.get());
218
219 for (const auto& inline_entry : rare_data_->inline_entries_) {
220 estimated_size += inline_entry->EstimatedSize();
221 }
222 estimated_size += rare_data_->inline_entries_.size() *
223 sizeof(decltype(rare_data_->inline_entries_)::value_type);
224
225 for (const auto& inline_stack_pair : rare_data_->inline_stacks_) {
226 estimated_size += inline_stack_pair.second.size() *
227 sizeof(decltype(inline_stack_pair.second)::value_type);
228 }
229 estimated_size +=
230 rare_data_->inline_stacks_.size() *
231 (sizeof(decltype(rare_data_->inline_stacks_)::key_type) +
232 sizeof(decltype(rare_data_->inline_stacks_)::value_type));
233
234 estimated_size +=
235 rare_data_->deopt_inlined_frames_.capacity() *
236 sizeof(decltype(rare_data_->deopt_inlined_frames_)::value_type);
237 }
238
239 if (line_info_) {
240 estimated_size += line_info_.get()->Size();
241 }
242 return sizeof(*this) + estimated_size;
243 }
244
GetDeoptInfo()245 CpuProfileDeoptInfo CodeEntry::GetDeoptInfo() {
246 DCHECK(has_deopt_info());
247
248 CpuProfileDeoptInfo info;
249 info.deopt_reason = rare_data_->deopt_reason_;
250 DCHECK_NE(kNoDeoptimizationId, rare_data_->deopt_id_);
251 if (rare_data_->deopt_inlined_frames_.empty()) {
252 info.stack.push_back(CpuProfileDeoptFrame(
253 {script_id_, static_cast<size_t>(std::max(0, position()))}));
254 } else {
255 info.stack = rare_data_->deopt_inlined_frames_;
256 }
257 return info;
258 }
259
EnsureRareData()260 CodeEntry::RareData* CodeEntry::EnsureRareData() {
261 if (!rare_data_) {
262 rare_data_.reset(new RareData());
263 }
264 return rare_data_.get();
265 }
266
ReleaseStrings(StringsStorage & strings)267 void CodeEntry::ReleaseStrings(StringsStorage& strings) {
268 DCHECK_EQ(ref_count_, 0UL);
269
270 if (name_) {
271 strings.Release(name_);
272 name_ = nullptr;
273 }
274 if (resource_name_) {
275 strings.Release(resource_name_);
276 resource_name_ = nullptr;
277 }
278 }
279
print() const280 void CodeEntry::print() const {
281 base::OS::Print("CodeEntry: at %p\n", this);
282
283 base::OS::Print(" - name: %s\n", name_);
284 base::OS::Print(" - resource_name: %s\n", resource_name_);
285 base::OS::Print(" - line_number: %d\n", line_number_);
286 base::OS::Print(" - column_number: %d\n", column_number_);
287 base::OS::Print(" - script_id: %d\n", script_id_);
288 base::OS::Print(" - position: %d\n", position_);
289
290 if (line_info_) {
291 line_info_->print();
292 }
293
294 if (rare_data_) {
295 base::OS::Print(" - deopt_reason: %s\n", rare_data_->deopt_reason_);
296 base::OS::Print(" - bailout_reason: %s\n", rare_data_->bailout_reason_);
297 base::OS::Print(" - deopt_id: %d\n", rare_data_->deopt_id_);
298
299 if (!rare_data_->inline_stacks_.empty()) {
300 base::OS::Print(" - inline stacks:\n");
301 for (auto it = rare_data_->inline_stacks_.begin();
302 it != rare_data_->inline_stacks_.end(); it++) {
303 base::OS::Print(" inlining_id: [%d]\n", it->first);
304 for (const auto& e : it->second) {
305 base::OS::Print(" %s --> %d\n", e.code_entry->name(),
306 e.line_number);
307 }
308 }
309 } else {
310 base::OS::Print(" - inline stacks: (empty)\n");
311 }
312
313 if (!rare_data_->deopt_inlined_frames_.empty()) {
314 base::OS::Print(" - deopt inlined frames:\n");
315 for (const CpuProfileDeoptFrame& frame :
316 rare_data_->deopt_inlined_frames_) {
317 base::OS::Print("script_id: %d position: %zu\n", frame.script_id,
318 frame.position);
319 }
320 } else {
321 base::OS::Print(" - deopt inlined frames: (empty)\n");
322 }
323 }
324 base::OS::Print("\n");
325 }
326
~ProfileNode()327 ProfileNode::~ProfileNode() {
328 if (tree_->code_entries()) tree_->code_entries()->DecRef(entry_);
329 }
330
source_type() const331 CpuProfileNode::SourceType ProfileNode::source_type() const {
332 // Handle metadata and VM state code entry types.
333 if (entry_ == CodeEntry::program_entry() ||
334 entry_ == CodeEntry::idle_entry() || entry_ == CodeEntry::gc_entry() ||
335 entry_ == CodeEntry::root_entry()) {
336 return CpuProfileNode::kInternal;
337 }
338 if (entry_ == CodeEntry::unresolved_entry())
339 return CpuProfileNode::kUnresolved;
340
341 // Otherwise, resolve based on logger tag.
342 switch (entry_->tag()) {
343 case CodeEventListener::EVAL_TAG:
344 case CodeEventListener::SCRIPT_TAG:
345 case CodeEventListener::LAZY_COMPILE_TAG:
346 case CodeEventListener::FUNCTION_TAG:
347 return CpuProfileNode::kScript;
348 case CodeEventListener::BUILTIN_TAG:
349 case CodeEventListener::HANDLER_TAG:
350 case CodeEventListener::BYTECODE_HANDLER_TAG:
351 case CodeEventListener::NATIVE_FUNCTION_TAG:
352 case CodeEventListener::NATIVE_SCRIPT_TAG:
353 case CodeEventListener::NATIVE_LAZY_COMPILE_TAG:
354 return CpuProfileNode::kBuiltin;
355 case CodeEventListener::CALLBACK_TAG:
356 return CpuProfileNode::kCallback;
357 case CodeEventListener::REG_EXP_TAG:
358 case CodeEventListener::STUB_TAG:
359 case CodeEventListener::CODE_CREATION_EVENT:
360 case CodeEventListener::CODE_DISABLE_OPT_EVENT:
361 case CodeEventListener::CODE_MOVE_EVENT:
362 case CodeEventListener::CODE_DELETE_EVENT:
363 case CodeEventListener::CODE_MOVING_GC:
364 case CodeEventListener::SHARED_FUNC_MOVE_EVENT:
365 case CodeEventListener::SNAPSHOT_CODE_NAME_EVENT:
366 case CodeEventListener::TICK_EVENT:
367 case CodeEventListener::NUMBER_OF_LOG_EVENTS:
368 return CpuProfileNode::kInternal;
369 }
370 }
371
CollectDeoptInfo(CodeEntry * entry)372 void ProfileNode::CollectDeoptInfo(CodeEntry* entry) {
373 deopt_infos_.push_back(entry->GetDeoptInfo());
374 entry->clear_deopt_info();
375 }
376
FindChild(CodeEntry * entry,int line_number)377 ProfileNode* ProfileNode::FindChild(CodeEntry* entry, int line_number) {
378 auto map_entry = children_.find({entry, line_number});
379 return map_entry != children_.end() ? map_entry->second : nullptr;
380 }
381
FindOrAddChild(CodeEntry * entry,int line_number)382 ProfileNode* ProfileNode::FindOrAddChild(CodeEntry* entry, int line_number) {
383 auto map_entry = children_.find({entry, line_number});
384 if (map_entry == children_.end()) {
385 ProfileNode* node = new ProfileNode(tree_, entry, this, line_number);
386 children_[{entry, line_number}] = node;
387 children_list_.push_back(node);
388 return node;
389 } else {
390 return map_entry->second;
391 }
392 }
393
394
IncrementLineTicks(int src_line)395 void ProfileNode::IncrementLineTicks(int src_line) {
396 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) return;
397 // Increment a hit counter of a certain source line.
398 // Add a new source line if not found.
399 auto map_entry = line_ticks_.find(src_line);
400 if (map_entry == line_ticks_.end()) {
401 line_ticks_[src_line] = 1;
402 } else {
403 line_ticks_[src_line]++;
404 }
405 }
406
407
GetLineTicks(v8::CpuProfileNode::LineTick * entries,unsigned int length) const408 bool ProfileNode::GetLineTicks(v8::CpuProfileNode::LineTick* entries,
409 unsigned int length) const {
410 if (entries == nullptr || length == 0) return false;
411
412 unsigned line_count = static_cast<unsigned>(line_ticks_.size());
413
414 if (line_count == 0) return true;
415 if (length < line_count) return false;
416
417 v8::CpuProfileNode::LineTick* entry = entries;
418
419 for (auto p = line_ticks_.begin(); p != line_ticks_.end(); p++, entry++) {
420 entry->line = p->first;
421 entry->hit_count = p->second;
422 }
423
424 return true;
425 }
426
Print(int indent) const427 void ProfileNode::Print(int indent) const {
428 int line_number = line_number_ != 0 ? line_number_ : entry_->line_number();
429 base::OS::Print("%5u %*s %s:%d %d %d #%d", self_ticks_, indent, "",
430 entry_->name(), line_number, source_type(),
431 entry_->script_id(), id());
432 if (entry_->resource_name()[0] != '\0')
433 base::OS::Print(" %s:%d", entry_->resource_name(), entry_->line_number());
434 base::OS::Print("\n");
435 for (const CpuProfileDeoptInfo& info : deopt_infos_) {
436 base::OS::Print(
437 "%*s;;; deopted at script_id: %d position: %zu with reason '%s'.\n",
438 indent + 10, "", info.stack[0].script_id, info.stack[0].position,
439 info.deopt_reason);
440 for (size_t index = 1; index < info.stack.size(); ++index) {
441 base::OS::Print("%*s;;; Inline point: script_id %d position: %zu.\n",
442 indent + 10, "", info.stack[index].script_id,
443 info.stack[index].position);
444 }
445 }
446 const char* bailout_reason = entry_->bailout_reason();
447 if (bailout_reason != GetBailoutReason(BailoutReason::kNoReason) &&
448 bailout_reason != CodeEntry::kEmptyBailoutReason) {
449 base::OS::Print("%*s bailed out due to '%s'\n", indent + 10, "",
450 bailout_reason);
451 }
452 for (auto child : children_) {
453 child.second->Print(indent + 2);
454 }
455 }
456
457 class DeleteNodesCallback {
458 public:
BeforeTraversingChild(ProfileNode *,ProfileNode *)459 void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
460
AfterAllChildrenTraversed(ProfileNode * node)461 void AfterAllChildrenTraversed(ProfileNode* node) { delete node; }
462
AfterChildTraversed(ProfileNode *,ProfileNode *)463 void AfterChildTraversed(ProfileNode*, ProfileNode*) { }
464 };
465
ProfileTree(Isolate * isolate,CodeEntryStorage * storage)466 ProfileTree::ProfileTree(Isolate* isolate, CodeEntryStorage* storage)
467 : next_node_id_(1),
468 isolate_(isolate),
469 code_entries_(storage),
470 root_(new ProfileNode(this, CodeEntry::root_entry(), nullptr)) {}
471
~ProfileTree()472 ProfileTree::~ProfileTree() {
473 DeleteNodesCallback cb;
474 TraverseDepthFirst(&cb);
475 }
476
AddPathFromEnd(const std::vector<CodeEntry * > & path,int src_line,bool update_stats)477 ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path,
478 int src_line, bool update_stats) {
479 ProfileNode* node = root_;
480 CodeEntry* last_entry = nullptr;
481 for (auto it = path.rbegin(); it != path.rend(); ++it) {
482 if (*it == nullptr) continue;
483 last_entry = *it;
484 node = node->FindOrAddChild(*it, v8::CpuProfileNode::kNoLineNumberInfo);
485 }
486 if (last_entry && last_entry->has_deopt_info()) {
487 node->CollectDeoptInfo(last_entry);
488 }
489 if (update_stats) {
490 node->IncrementSelfTicks();
491 if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
492 node->IncrementLineTicks(src_line);
493 }
494 }
495 return node;
496 }
497
AddPathFromEnd(const ProfileStackTrace & path,int src_line,bool update_stats,ProfilingMode mode)498 ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
499 int src_line, bool update_stats,
500 ProfilingMode mode) {
501 ProfileNode* node = root_;
502 CodeEntry* last_entry = nullptr;
503 int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo;
504 for (auto it = path.rbegin(); it != path.rend(); ++it) {
505 if (it->code_entry == nullptr) continue;
506 last_entry = it->code_entry;
507 node = node->FindOrAddChild(it->code_entry, parent_line_number);
508 parent_line_number = mode == ProfilingMode::kCallerLineNumbers
509 ? it->line_number
510 : v8::CpuProfileNode::kNoLineNumberInfo;
511 }
512 if (last_entry && last_entry->has_deopt_info()) {
513 node->CollectDeoptInfo(last_entry);
514 }
515 if (update_stats) {
516 node->IncrementSelfTicks();
517 if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
518 node->IncrementLineTicks(src_line);
519 }
520 }
521 return node;
522 }
523
524 class Position {
525 public:
Position(ProfileNode * node)526 explicit Position(ProfileNode* node)
527 : node(node), child_idx_(0) { }
current_child()528 V8_INLINE ProfileNode* current_child() {
529 return node->children()->at(child_idx_);
530 }
has_current_child()531 V8_INLINE bool has_current_child() {
532 return child_idx_ < static_cast<int>(node->children()->size());
533 }
next_child()534 V8_INLINE void next_child() { ++child_idx_; }
535
536 ProfileNode* node;
537 private:
538 int child_idx_;
539 };
540
541
542 // Non-recursive implementation of a depth-first post-order tree traversal.
543 template <typename Callback>
TraverseDepthFirst(Callback * callback)544 void ProfileTree::TraverseDepthFirst(Callback* callback) {
545 std::vector<Position> stack;
546 stack.emplace_back(root_);
547 while (stack.size() > 0) {
548 Position& current = stack.back();
549 if (current.has_current_child()) {
550 callback->BeforeTraversingChild(current.node, current.current_child());
551 stack.emplace_back(current.current_child());
552 } else {
553 callback->AfterAllChildrenTraversed(current.node);
554 if (stack.size() > 1) {
555 Position& parent = stack[stack.size() - 2];
556 callback->AfterChildTraversed(parent.node, current.node);
557 parent.next_child();
558 }
559 // Remove child from the stack.
560 stack.pop_back();
561 }
562 }
563 }
564
OnMoveEvent(Address from_address,Address to_address)565 void ContextFilter::OnMoveEvent(Address from_address, Address to_address) {
566 if (native_context_address() != from_address) return;
567
568 set_native_context_address(to_address);
569 }
570
571 using v8::tracing::TracedValue;
572
573 std::atomic<ProfilerId> CpuProfilesCollection::last_id_{0};
574
CpuProfile(CpuProfiler * profiler,ProfilerId id,const char * title,CpuProfilingOptions options,std::unique_ptr<DiscardedSamplesDelegate> delegate)575 CpuProfile::CpuProfile(CpuProfiler* profiler, ProfilerId id, const char* title,
576 CpuProfilingOptions options,
577 std::unique_ptr<DiscardedSamplesDelegate> delegate)
578 : title_(title),
579 options_(options),
580 delegate_(std::move(delegate)),
581 start_time_(base::TimeTicks::Now()),
582 top_down_(profiler->isolate(), profiler->code_entries()),
583 profiler_(profiler),
584 streaming_next_sample_(0),
585 id_(id) {
586 // The startTime timestamp is not converted to Perfetto's clock domain and
587 // will get out of sync with other timestamps Perfetto knows about, including
588 // the automatic trace event "ts" timestamp. startTime is included for
589 // backward compatibility with the tracing protocol but the value of "ts"
590 // should be used instead (it is recorded nearly immediately after).
591 auto value = TracedValue::Create();
592 value->SetDouble("startTime", start_time_.since_origin().InMicroseconds());
593 TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
594 "Profile", id_, "data", std::move(value));
595
596 DisallowHeapAllocation no_gc;
597 if (delegate_) {
598 delegate_->SetId(id_);
599 }
600 if (options_.has_filter_context()) {
601 i::Address raw_filter_context =
602 reinterpret_cast<i::Address>(options_.raw_filter_context());
603 context_filter_.set_native_context_address(raw_filter_context);
604 }
605 }
606
CheckSubsample(base::TimeDelta source_sampling_interval)607 bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) {
608 DCHECK_GE(source_sampling_interval, base::TimeDelta());
609
610 // If the sampling source's sampling interval is 0, record as many samples
611 // are possible irrespective of the profile's sampling interval. Manually
612 // taken samples (via CollectSample) fall into this case as well.
613 if (source_sampling_interval.IsZero()) return true;
614
615 next_sample_delta_ -= source_sampling_interval;
616 if (next_sample_delta_ <= base::TimeDelta()) {
617 next_sample_delta_ =
618 base::TimeDelta::FromMicroseconds(options_.sampling_interval_us());
619 return true;
620 }
621 return false;
622 }
623
AddPath(base::TimeTicks timestamp,const ProfileStackTrace & path,int src_line,bool update_stats,base::TimeDelta sampling_interval,StateTag state_tag,EmbedderStateTag embedder_state_tag)624 void CpuProfile::AddPath(base::TimeTicks timestamp,
625 const ProfileStackTrace& path, int src_line,
626 bool update_stats, base::TimeDelta sampling_interval,
627 StateTag state_tag,
628 EmbedderStateTag embedder_state_tag) {
629 if (!CheckSubsample(sampling_interval)) return;
630
631 ProfileNode* top_frame_node =
632 top_down_.AddPathFromEnd(path, src_line, update_stats, options_.mode());
633
634 bool should_record_sample =
635 !timestamp.IsNull() && timestamp >= start_time_ &&
636 (options_.max_samples() == CpuProfilingOptions::kNoSampleLimit ||
637 samples_.size() < options_.max_samples());
638
639 if (should_record_sample) {
640 samples_.push_back(
641 {top_frame_node, timestamp, src_line, state_tag, embedder_state_tag});
642 }
643
644 if (!should_record_sample && delegate_ != nullptr) {
645 const auto task_runner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
646 reinterpret_cast<v8::Isolate*>(profiler_->isolate()));
647
648 task_runner->PostTask(std::make_unique<CpuProfileMaxSamplesCallbackTask>(
649 std::move(delegate_)));
650 // std::move ensures that the delegate_ will be null on the next sample,
651 // so we don't post a task multiple times.
652 }
653
654 const int kSamplesFlushCount = 100;
655 const int kNodesFlushCount = 10;
656 if (samples_.size() - streaming_next_sample_ >= kSamplesFlushCount ||
657 top_down_.pending_nodes_count() >= kNodesFlushCount) {
658 StreamPendingTraceEvents();
659 }
660 }
661
662 namespace {
663
BuildNodeValue(const ProfileNode * node,TracedValue * value)664 void BuildNodeValue(const ProfileNode* node, TracedValue* value) {
665 const CodeEntry* entry = node->entry();
666 value->BeginDictionary("callFrame");
667 value->SetString("functionName", entry->name());
668 if (*entry->resource_name()) {
669 value->SetString("url", entry->resource_name());
670 }
671 value->SetInteger("scriptId", entry->script_id());
672 if (entry->line_number()) {
673 value->SetInteger("lineNumber", entry->line_number() - 1);
674 }
675 if (entry->column_number()) {
676 value->SetInteger("columnNumber", entry->column_number() - 1);
677 }
678 value->SetString("codeType", entry->code_type_string());
679 value->EndDictionary();
680 value->SetInteger("id", node->id());
681 if (node->parent()) {
682 value->SetInteger("parent", node->parent()->id());
683 }
684 const char* deopt_reason = entry->bailout_reason();
685 if (deopt_reason && deopt_reason[0] && strcmp(deopt_reason, "no reason")) {
686 value->SetString("deoptReason", deopt_reason);
687 }
688 }
689
690 } // namespace
691
StreamPendingTraceEvents()692 void CpuProfile::StreamPendingTraceEvents() {
693 std::vector<const ProfileNode*> pending_nodes = top_down_.TakePendingNodes();
694 if (pending_nodes.empty() && samples_.empty()) return;
695 auto value = TracedValue::Create();
696
697 if (!pending_nodes.empty() || streaming_next_sample_ != samples_.size()) {
698 value->BeginDictionary("cpuProfile");
699 if (!pending_nodes.empty()) {
700 value->BeginArray("nodes");
701 for (auto node : pending_nodes) {
702 value->BeginDictionary();
703 BuildNodeValue(node, value.get());
704 value->EndDictionary();
705 }
706 value->EndArray();
707 }
708 if (streaming_next_sample_ != samples_.size()) {
709 value->BeginArray("samples");
710 for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) {
711 value->AppendInteger(samples_[i].node->id());
712 }
713 value->EndArray();
714 }
715 value->EndDictionary();
716 }
717 if (streaming_next_sample_ != samples_.size()) {
718 // timeDeltas are computed within CLOCK_MONOTONIC. However, trace event
719 // "ts" timestamps are converted to CLOCK_BOOTTIME by Perfetto. To get
720 // absolute timestamps in CLOCK_BOOTTIME from timeDeltas, add them to
721 // the "ts" timestamp from the initial "Profile" trace event sent by
722 // CpuProfile::CpuProfile().
723 //
724 // Note that if the system is suspended and resumed while samples_ is
725 // captured, timeDeltas derived after resume will not be convertible to
726 // correct CLOCK_BOOTTIME time values (for instance, producing
727 // CLOCK_BOOTTIME time values in the middle of the suspended period).
728 value->BeginArray("timeDeltas");
729 base::TimeTicks lastTimestamp =
730 streaming_next_sample_ ? samples_[streaming_next_sample_ - 1].timestamp
731 : start_time();
732 for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) {
733 value->AppendInteger(static_cast<int>(
734 (samples_[i].timestamp - lastTimestamp).InMicroseconds()));
735 lastTimestamp = samples_[i].timestamp;
736 }
737 value->EndArray();
738 bool has_non_zero_lines =
739 std::any_of(samples_.begin() + streaming_next_sample_, samples_.end(),
740 [](const SampleInfo& sample) { return sample.line != 0; });
741 if (has_non_zero_lines) {
742 value->BeginArray("lines");
743 for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) {
744 value->AppendInteger(samples_[i].line);
745 }
746 value->EndArray();
747 }
748 streaming_next_sample_ = samples_.size();
749 }
750
751 TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
752 "ProfileChunk", id_, "data", std::move(value));
753 }
754
FinishProfile()755 void CpuProfile::FinishProfile() {
756 end_time_ = base::TimeTicks::Now();
757 // Stop tracking context movements after profiling stops.
758 context_filter_.set_native_context_address(kNullAddress);
759 StreamPendingTraceEvents();
760 auto value = TracedValue::Create();
761 // The endTime timestamp is not converted to Perfetto's clock domain and will
762 // get out of sync with other timestamps Perfetto knows about, including the
763 // automatic trace event "ts" timestamp. endTime is included for backward
764 // compatibility with the tracing protocol: its presence in "data" is used by
765 // devtools to identify the last ProfileChunk but the value of "ts" should be
766 // used instead (it is recorded nearly immediately after).
767 value->SetDouble("endTime", end_time_.since_origin().InMicroseconds());
768 TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
769 "ProfileChunk", id_, "data", std::move(value));
770 }
771
Print() const772 void CpuProfile::Print() const {
773 base::OS::Print("[Top down]:\n");
774 top_down_.Print();
775 ProfilerStats::Instance()->Print();
776 ProfilerStats::Instance()->Clear();
777 }
778
AddRef(CodeEntry * entry)779 void CodeEntryStorage::AddRef(CodeEntry* entry) {
780 if (entry->is_ref_counted()) entry->AddRef();
781 }
782
DecRef(CodeEntry * entry)783 void CodeEntryStorage::DecRef(CodeEntry* entry) {
784 if (entry->is_ref_counted() && entry->DecRef() == 0) {
785 if (entry->rare_data_) {
786 for (auto* inline_entry : entry->rare_data_->inline_entries_) {
787 DecRef(inline_entry);
788 }
789 }
790 entry->ReleaseStrings(function_and_resource_names_);
791 delete entry;
792 }
793 }
794
CodeMap(CodeEntryStorage & storage)795 CodeMap::CodeMap(CodeEntryStorage& storage) : code_entries_(storage) {}
796
~CodeMap()797 CodeMap::~CodeMap() { Clear(); }
798
Clear()799 void CodeMap::Clear() {
800 for (auto& slot : code_map_) {
801 if (CodeEntry* entry = slot.second.entry) {
802 code_entries_.DecRef(entry);
803 } else {
804 // We expect all entries in the code mapping to contain a CodeEntry.
805 UNREACHABLE();
806 }
807 }
808
809 code_map_.clear();
810 }
811
AddCode(Address addr,CodeEntry * entry,unsigned size)812 void CodeMap::AddCode(Address addr, CodeEntry* entry, unsigned size) {
813 code_map_.emplace(addr, CodeEntryMapInfo{entry, size});
814 entry->set_instruction_start(addr);
815 }
816
RemoveCode(CodeEntry * entry)817 bool CodeMap::RemoveCode(CodeEntry* entry) {
818 auto range = code_map_.equal_range(entry->instruction_start());
819 for (auto i = range.first; i != range.second; ++i) {
820 if (i->second.entry == entry) {
821 code_entries_.DecRef(entry);
822 code_map_.erase(i);
823 return true;
824 }
825 }
826 return false;
827 }
828
ClearCodesInRange(Address start,Address end)829 void CodeMap::ClearCodesInRange(Address start, Address end) {
830 auto left = code_map_.upper_bound(start);
831 if (left != code_map_.begin()) {
832 --left;
833 if (left->first + left->second.size <= start) ++left;
834 }
835 auto right = left;
836 for (; right != code_map_.end() && right->first < end; ++right) {
837 code_entries_.DecRef(right->second.entry);
838 }
839 code_map_.erase(left, right);
840 }
841
FindEntry(Address addr,Address * out_instruction_start)842 CodeEntry* CodeMap::FindEntry(Address addr, Address* out_instruction_start) {
843 // Note that an address may correspond to multiple CodeEntry objects. An
844 // arbitrary selection is made (as per multimap spec) in the event of a
845 // collision.
846 auto it = code_map_.upper_bound(addr);
847 if (it == code_map_.begin()) return nullptr;
848 --it;
849 Address start_address = it->first;
850 Address end_address = start_address + it->second.size;
851 CodeEntry* ret = addr < end_address ? it->second.entry : nullptr;
852 DCHECK(!ret || (addr >= start_address && addr < end_address));
853 if (ret && out_instruction_start) *out_instruction_start = start_address;
854 return ret;
855 }
856
MoveCode(Address from,Address to)857 void CodeMap::MoveCode(Address from, Address to) {
858 if (from == to) return;
859
860 auto range = code_map_.equal_range(from);
861 // Instead of iterating until |range.second|, iterate the number of elements.
862 // This is because the |range.second| may no longer be the element past the
863 // end of the equal elements range after insertions.
864 size_t distance = std::distance(range.first, range.second);
865 auto it = range.first;
866 while (distance--) {
867 CodeEntryMapInfo& info = it->second;
868 DCHECK(info.entry);
869 DCHECK_EQ(info.entry->instruction_start(), from);
870 info.entry->set_instruction_start(to);
871
872 DCHECK(from + info.size <= to || to + info.size <= from);
873 code_map_.emplace(to, info);
874 it++;
875 }
876
877 code_map_.erase(range.first, it);
878 }
879
Print()880 void CodeMap::Print() {
881 for (const auto& pair : code_map_) {
882 base::OS::Print("%p %5d %s\n", reinterpret_cast<void*>(pair.first),
883 pair.second.size, pair.second.entry->name());
884 }
885 }
886
GetEstimatedMemoryUsage() const887 size_t CodeMap::GetEstimatedMemoryUsage() const {
888 size_t map_size = 0;
889 for (const auto& pair : code_map_) {
890 map_size += sizeof(pair.first) + sizeof(pair.second) +
891 pair.second.entry->EstimatedSize();
892 }
893 return sizeof(*this) + map_size;
894 }
895
CpuProfilesCollection(Isolate * isolate)896 CpuProfilesCollection::CpuProfilesCollection(Isolate* isolate)
897 : profiler_(nullptr), current_profiles_semaphore_(1), isolate_(isolate) {
898 USE(isolate_);
899 }
900
StartProfilingForTesting(ProfilerId id)901 CpuProfilingResult CpuProfilesCollection::StartProfilingForTesting(
902 ProfilerId id) {
903 return StartProfiling(id);
904 }
905
StartProfiling(const char * title,CpuProfilingOptions options,std::unique_ptr<DiscardedSamplesDelegate> delegate)906 CpuProfilingResult CpuProfilesCollection::StartProfiling(
907 const char* title, CpuProfilingOptions options,
908 std::unique_ptr<DiscardedSamplesDelegate> delegate) {
909 return StartProfiling(++last_id_, title, options, std::move(delegate));
910 }
911
StartProfiling(ProfilerId id,const char * title,CpuProfilingOptions options,std::unique_ptr<DiscardedSamplesDelegate> delegate)912 CpuProfilingResult CpuProfilesCollection::StartProfiling(
913 ProfilerId id, const char* title, CpuProfilingOptions options,
914 std::unique_ptr<DiscardedSamplesDelegate> delegate) {
915 current_profiles_semaphore_.Wait();
916
917 if (static_cast<int>(current_profiles_.size()) >= kMaxSimultaneousProfiles) {
918 current_profiles_semaphore_.Signal();
919 return {
920 0,
921 CpuProfilingStatus::kErrorTooManyProfilers,
922 };
923 }
924
925 for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
926 if ((profile->title() != nullptr && title != nullptr &&
927 strcmp(profile->title(), title) == 0) ||
928 profile->id() == id) {
929 // Ignore attempts to start profile with the same title or id
930 current_profiles_semaphore_.Signal();
931 // ... though return kAlreadyStarted to force it collect a sample.
932 return {
933 profile->id(),
934 CpuProfilingStatus::kAlreadyStarted,
935 };
936 }
937 }
938
939 CpuProfile* profile =
940 new CpuProfile(profiler_, id, title, options, std::move(delegate));
941 current_profiles_.emplace_back(profile);
942 current_profiles_semaphore_.Signal();
943
944 return {
945 profile->id(),
946 CpuProfilingStatus::kStarted,
947 };
948 }
949
StopProfiling(ProfilerId id)950 CpuProfile* CpuProfilesCollection::StopProfiling(ProfilerId id) {
951 current_profiles_semaphore_.Wait();
952 CpuProfile* profile = nullptr;
953
954 auto it = std::find_if(
955 current_profiles_.rbegin(), current_profiles_.rend(),
956 [=](const std::unique_ptr<CpuProfile>& p) { return id == p->id(); });
957
958 if (it != current_profiles_.rend()) {
959 (*it)->FinishProfile();
960 profile = it->get();
961 finished_profiles_.push_back(std::move(*it));
962 // Convert reverse iterator to matching forward iterator.
963 current_profiles_.erase(--(it.base()));
964 }
965 current_profiles_semaphore_.Signal();
966 return profile;
967 }
968
Lookup(const char * title)969 CpuProfile* CpuProfilesCollection::Lookup(const char* title) {
970 // Called from VM thread, and only it can mutate the list,
971 // so no locking is needed here.
972 DCHECK_EQ(ThreadId::Current(), isolate_->thread_id());
973 if (title == nullptr) {
974 return nullptr;
975 }
976 // http://crbug/51594, edge case console.profile may provide an empty title
977 // and must not crash
978 const bool empty_title = title[0] == '\0';
979 auto it = std::find_if(
980 current_profiles_.rbegin(), current_profiles_.rend(),
981 [&](const std::unique_ptr<CpuProfile>& p) {
982 return (empty_title ||
983 (p->title() != nullptr && strcmp(p->title(), title) == 0));
984 });
985 if (it != current_profiles_.rend()) {
986 return it->get();
987 }
988
989 return nullptr;
990 }
991
IsLastProfileLeft(ProfilerId id)992 bool CpuProfilesCollection::IsLastProfileLeft(ProfilerId id) {
993 // Called from VM thread, and only it can mutate the list,
994 // so no locking is needed here.
995 DCHECK_EQ(ThreadId::Current(), isolate_->thread_id());
996 if (current_profiles_.size() != 1) return false;
997 return id == current_profiles_[0]->id();
998 }
999
RemoveProfile(CpuProfile * profile)1000 void CpuProfilesCollection::RemoveProfile(CpuProfile* profile) {
1001 // Called from VM thread for a completed profile.
1002 DCHECK_EQ(ThreadId::Current(), isolate_->thread_id());
1003 auto pos =
1004 std::find_if(finished_profiles_.begin(), finished_profiles_.end(),
1005 [&](const std::unique_ptr<CpuProfile>& finished_profile) {
1006 return finished_profile.get() == profile;
1007 });
1008 DCHECK(pos != finished_profiles_.end());
1009 finished_profiles_.erase(pos);
1010 }
1011
1012 namespace {
1013
GreatestCommonDivisor(int64_t a,int64_t b)1014 int64_t GreatestCommonDivisor(int64_t a, int64_t b) {
1015 return b ? GreatestCommonDivisor(b, a % b) : a;
1016 }
1017
1018 } // namespace
1019
GetCommonSamplingInterval() const1020 base::TimeDelta CpuProfilesCollection::GetCommonSamplingInterval() const {
1021 DCHECK(profiler_);
1022
1023 int64_t base_sampling_interval_us =
1024 profiler_->sampling_interval().InMicroseconds();
1025 if (base_sampling_interval_us == 0) return base::TimeDelta();
1026
1027 int64_t interval_us = 0;
1028 for (const auto& profile : current_profiles_) {
1029 // Snap the profile's requested sampling interval to the next multiple of
1030 // the base sampling interval.
1031 int64_t profile_interval_us =
1032 std::max<int64_t>(
1033 (profile->sampling_interval_us() + base_sampling_interval_us - 1) /
1034 base_sampling_interval_us,
1035 1) *
1036 base_sampling_interval_us;
1037 interval_us = GreatestCommonDivisor(interval_us, profile_interval_us);
1038 }
1039 return base::TimeDelta::FromMicroseconds(interval_us);
1040 }
1041
AddPathToCurrentProfiles(base::TimeTicks timestamp,const ProfileStackTrace & path,int src_line,bool update_stats,base::TimeDelta sampling_interval,StateTag state,EmbedderStateTag embedder_state_tag,Address native_context_address,Address embedder_native_context_address)1042 void CpuProfilesCollection::AddPathToCurrentProfiles(
1043 base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
1044 bool update_stats, base::TimeDelta sampling_interval, StateTag state,
1045 EmbedderStateTag embedder_state_tag, Address native_context_address,
1046 Address embedder_native_context_address) {
1047 // As starting / stopping profiles is rare relatively to this
1048 // method, we don't bother minimizing the duration of lock holding,
1049 // e.g. copying contents of the list to a local vector.
1050 current_profiles_semaphore_.Wait();
1051 const ProfileStackTrace empty_path;
1052 for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
1053 ContextFilter& context_filter = profile->context_filter();
1054 // If the context filter check failed, omit the contents of the stack.
1055 bool accepts_context = context_filter.Accept(native_context_address);
1056 bool accepts_embedder_context =
1057 context_filter.Accept(embedder_native_context_address);
1058
1059 // if FilterContext is set, do not propagate StateTag if not accepted.
1060 // GC is exception because native context address is guaranteed to be empty.
1061 DCHECK(state != StateTag::GC || native_context_address == kNullAddress);
1062 if (!accepts_context && state != StateTag::GC) {
1063 state = StateTag::IDLE;
1064 }
1065 profile->AddPath(timestamp, accepts_context ? path : empty_path, src_line,
1066 update_stats, sampling_interval, state,
1067 accepts_embedder_context ? embedder_state_tag
1068 : EmbedderStateTag::EMPTY);
1069 }
1070 current_profiles_semaphore_.Signal();
1071 }
1072
UpdateNativeContextAddressForCurrentProfiles(Address from,Address to)1073 void CpuProfilesCollection::UpdateNativeContextAddressForCurrentProfiles(
1074 Address from, Address to) {
1075 current_profiles_semaphore_.Wait();
1076 for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
1077 profile->context_filter().OnMoveEvent(from, to);
1078 }
1079 current_profiles_semaphore_.Signal();
1080 }
1081
1082 } // namespace internal
1083 } // namespace v8
1084