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 "src/base/adapters.h" 8 #include "src/debug/debug.h" 9 #include "src/deoptimizer.h" 10 #include "src/global-handles.h" 11 #include "src/objects-inl.h" 12 #include "src/profiler/cpu-profiler.h" 13 #include "src/profiler/profile-generator-inl.h" 14 #include "src/tracing/trace-event.h" 15 #include "src/tracing/traced-value.h" 16 #include "src/unicode.h" 17 18 namespace v8 { 19 namespace internal { 20 21 void SourcePositionTable::SetPosition(int pc_offset, int line) { 22 DCHECK_GE(pc_offset, 0); 23 DCHECK_GT(line, 0); // The 1-based number of the source line. 24 // Check that we are inserting in ascending order, so that the vector remains 25 // sorted. 26 DCHECK(pc_offsets_to_lines_.empty() || 27 pc_offsets_to_lines_.back().pc_offset < pc_offset); 28 if (pc_offsets_to_lines_.empty() || 29 pc_offsets_to_lines_.back().line_number != line) { 30 pc_offsets_to_lines_.push_back({pc_offset, line}); 31 } 32 } 33 34 int SourcePositionTable::GetSourceLineNumber(int pc_offset) const { 35 if (pc_offsets_to_lines_.empty()) { 36 return v8::CpuProfileNode::kNoLineNumberInfo; 37 } 38 auto it = 39 std::upper_bound(pc_offsets_to_lines_.begin(), pc_offsets_to_lines_.end(), 40 PCOffsetAndLineNumber{pc_offset, 0}); 41 if (it != pc_offsets_to_lines_.begin()) --it; 42 return it->line_number; 43 } 44 45 const char* const CodeEntry::kWasmResourceNamePrefix = "wasm "; 46 const char* const CodeEntry::kEmptyResourceName = ""; 47 const char* const CodeEntry::kEmptyBailoutReason = ""; 48 const char* const CodeEntry::kNoDeoptReason = ""; 49 50 const char* const CodeEntry::kProgramEntryName = "(program)"; 51 const char* const CodeEntry::kIdleEntryName = "(idle)"; 52 const char* const CodeEntry::kGarbageCollectorEntryName = "(garbage collector)"; 53 const char* const CodeEntry::kUnresolvedFunctionName = "(unresolved function)"; 54 55 base::LazyDynamicInstance<CodeEntry, CodeEntry::ProgramEntryCreateTrait>::type 56 CodeEntry::kProgramEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER; 57 58 base::LazyDynamicInstance<CodeEntry, CodeEntry::IdleEntryCreateTrait>::type 59 CodeEntry::kIdleEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER; 60 61 base::LazyDynamicInstance<CodeEntry, CodeEntry::GCEntryCreateTrait>::type 62 CodeEntry::kGCEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER; 63 64 base::LazyDynamicInstance<CodeEntry, 65 CodeEntry::UnresolvedEntryCreateTrait>::type 66 CodeEntry::kUnresolvedEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER; 67 68 CodeEntry* CodeEntry::ProgramEntryCreateTrait::Create() { 69 return new CodeEntry(Logger::FUNCTION_TAG, CodeEntry::kProgramEntryName); 70 } 71 72 CodeEntry* CodeEntry::IdleEntryCreateTrait::Create() { 73 return new CodeEntry(Logger::FUNCTION_TAG, CodeEntry::kIdleEntryName); 74 } 75 76 CodeEntry* CodeEntry::GCEntryCreateTrait::Create() { 77 return new CodeEntry(Logger::BUILTIN_TAG, 78 CodeEntry::kGarbageCollectorEntryName); 79 } 80 81 CodeEntry* CodeEntry::UnresolvedEntryCreateTrait::Create() { 82 return new CodeEntry(Logger::FUNCTION_TAG, 83 CodeEntry::kUnresolvedFunctionName); 84 } 85 86 uint32_t CodeEntry::GetHash() const { 87 uint32_t hash = ComputeIntegerHash(tag()); 88 if (script_id_ != v8::UnboundScript::kNoScriptId) { 89 hash ^= ComputeIntegerHash(static_cast<uint32_t>(script_id_)); 90 hash ^= ComputeIntegerHash(static_cast<uint32_t>(position_)); 91 } else { 92 hash ^= ComputeIntegerHash( 93 static_cast<uint32_t>(reinterpret_cast<uintptr_t>(name_))); 94 hash ^= ComputeIntegerHash( 95 static_cast<uint32_t>(reinterpret_cast<uintptr_t>(resource_name_))); 96 hash ^= ComputeIntegerHash(line_number_); 97 } 98 return hash; 99 } 100 101 bool CodeEntry::IsSameFunctionAs(const CodeEntry* entry) const { 102 if (this == entry) return true; 103 if (script_id_ != v8::UnboundScript::kNoScriptId) { 104 return script_id_ == entry->script_id_ && position_ == entry->position_; 105 } 106 return name_ == entry->name_ && resource_name_ == entry->resource_name_ && 107 line_number_ == entry->line_number_; 108 } 109 110 111 void CodeEntry::SetBuiltinId(Builtins::Name id) { 112 bit_field_ = TagField::update(bit_field_, CodeEventListener::BUILTIN_TAG); 113 bit_field_ = BuiltinIdField::update(bit_field_, id); 114 } 115 116 117 int CodeEntry::GetSourceLine(int pc_offset) const { 118 if (line_info_) return line_info_->GetSourceLineNumber(pc_offset); 119 return v8::CpuProfileNode::kNoLineNumberInfo; 120 } 121 122 void CodeEntry::AddInlineStack( 123 int pc_offset, std::vector<std::unique_ptr<CodeEntry>> inline_stack) { 124 EnsureRareData()->inline_locations_.insert( 125 std::make_pair(pc_offset, std::move(inline_stack))); 126 } 127 128 const std::vector<std::unique_ptr<CodeEntry>>* CodeEntry::GetInlineStack( 129 int pc_offset) const { 130 if (!rare_data_) return nullptr; 131 auto it = rare_data_->inline_locations_.find(pc_offset); 132 return it != rare_data_->inline_locations_.end() ? &it->second : nullptr; 133 } 134 135 void CodeEntry::set_deopt_info( 136 const char* deopt_reason, int deopt_id, 137 std::vector<CpuProfileDeoptFrame> inlined_frames) { 138 DCHECK(!has_deopt_info()); 139 RareData* rare_data = EnsureRareData(); 140 rare_data->deopt_reason_ = deopt_reason; 141 rare_data->deopt_id_ = deopt_id; 142 rare_data->deopt_inlined_frames_ = std::move(inlined_frames); 143 } 144 145 void CodeEntry::FillFunctionInfo(SharedFunctionInfo* shared) { 146 if (!shared->script()->IsScript()) return; 147 Script* script = Script::cast(shared->script()); 148 set_script_id(script->id()); 149 set_position(shared->StartPosition()); 150 if (shared->optimization_disabled()) { 151 set_bailout_reason(GetBailoutReason(shared->disable_optimization_reason())); 152 } 153 } 154 155 CpuProfileDeoptInfo CodeEntry::GetDeoptInfo() { 156 DCHECK(has_deopt_info()); 157 158 CpuProfileDeoptInfo info; 159 info.deopt_reason = rare_data_->deopt_reason_; 160 DCHECK_NE(kNoDeoptimizationId, rare_data_->deopt_id_); 161 if (rare_data_->deopt_inlined_frames_.empty()) { 162 info.stack.push_back(CpuProfileDeoptFrame( 163 {script_id_, static_cast<size_t>(std::max(0, position()))})); 164 } else { 165 info.stack = rare_data_->deopt_inlined_frames_; 166 } 167 return info; 168 } 169 170 CodeEntry::RareData* CodeEntry::EnsureRareData() { 171 if (!rare_data_) { 172 rare_data_.reset(new RareData()); 173 } 174 return rare_data_.get(); 175 } 176 177 void ProfileNode::CollectDeoptInfo(CodeEntry* entry) { 178 deopt_infos_.push_back(entry->GetDeoptInfo()); 179 entry->clear_deopt_info(); 180 } 181 182 ProfileNode* ProfileNode::FindChild(CodeEntry* entry, int line_number) { 183 auto map_entry = children_.find({entry, line_number}); 184 return map_entry != children_.end() ? map_entry->second : nullptr; 185 } 186 187 ProfileNode* ProfileNode::FindOrAddChild(CodeEntry* entry, int line_number) { 188 auto map_entry = children_.find({entry, line_number}); 189 if (map_entry == children_.end()) { 190 ProfileNode* node = new ProfileNode(tree_, entry, this, line_number); 191 children_[{entry, line_number}] = node; 192 children_list_.push_back(node); 193 return node; 194 } else { 195 return map_entry->second; 196 } 197 } 198 199 200 void ProfileNode::IncrementLineTicks(int src_line) { 201 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) return; 202 // Increment a hit counter of a certain source line. 203 // Add a new source line if not found. 204 auto map_entry = line_ticks_.find(src_line); 205 if (map_entry == line_ticks_.end()) { 206 line_ticks_[src_line] = 1; 207 } else { 208 line_ticks_[src_line]++; 209 } 210 } 211 212 213 bool ProfileNode::GetLineTicks(v8::CpuProfileNode::LineTick* entries, 214 unsigned int length) const { 215 if (entries == nullptr || length == 0) return false; 216 217 unsigned line_count = static_cast<unsigned>(line_ticks_.size()); 218 219 if (line_count == 0) return true; 220 if (length < line_count) return false; 221 222 v8::CpuProfileNode::LineTick* entry = entries; 223 224 for (auto p = line_ticks_.begin(); p != line_ticks_.end(); p++, entry++) { 225 entry->line = p->first; 226 entry->hit_count = p->second; 227 } 228 229 return true; 230 } 231 232 233 void ProfileNode::Print(int indent) { 234 int line_number = line_number_ != 0 ? line_number_ : entry_->line_number(); 235 base::OS::Print("%5u %*s %s:%d %d #%d", self_ticks_, indent, "", 236 entry_->name(), line_number, entry_->script_id(), id()); 237 if (entry_->resource_name()[0] != '\0') 238 base::OS::Print(" %s:%d", entry_->resource_name(), entry_->line_number()); 239 base::OS::Print("\n"); 240 for (size_t i = 0; i < deopt_infos_.size(); ++i) { 241 CpuProfileDeoptInfo& info = deopt_infos_[i]; 242 base::OS::Print("%*s;;; deopted at script_id: %d position: %" PRIuS 243 " with reason '%s'.\n", 244 indent + 10, "", info.stack[0].script_id, 245 info.stack[0].position, info.deopt_reason); 246 for (size_t index = 1; index < info.stack.size(); ++index) { 247 base::OS::Print("%*s;;; Inline point: script_id %d position: %" PRIuS 248 ".\n", 249 indent + 10, "", info.stack[index].script_id, 250 info.stack[index].position); 251 } 252 } 253 const char* bailout_reason = entry_->bailout_reason(); 254 if (bailout_reason != GetBailoutReason(BailoutReason::kNoReason) && 255 bailout_reason != CodeEntry::kEmptyBailoutReason) { 256 base::OS::Print("%*s bailed out due to '%s'\n", indent + 10, "", 257 bailout_reason); 258 } 259 for (auto child : children_) { 260 child.second->Print(indent + 2); 261 } 262 } 263 264 265 class DeleteNodesCallback { 266 public: 267 void BeforeTraversingChild(ProfileNode*, ProfileNode*) { } 268 269 void AfterAllChildrenTraversed(ProfileNode* node) { 270 delete node; 271 } 272 273 void AfterChildTraversed(ProfileNode*, ProfileNode*) { } 274 }; 275 276 ProfileTree::ProfileTree(Isolate* isolate) 277 : root_entry_(CodeEventListener::FUNCTION_TAG, "(root)"), 278 next_node_id_(1), 279 root_(new ProfileNode(this, &root_entry_, nullptr)), 280 isolate_(isolate), 281 next_function_id_(1) {} 282 283 ProfileTree::~ProfileTree() { 284 DeleteNodesCallback cb; 285 TraverseDepthFirst(&cb); 286 } 287 288 289 unsigned ProfileTree::GetFunctionId(const ProfileNode* node) { 290 CodeEntry* code_entry = node->entry(); 291 auto map_entry = function_ids_.find(code_entry); 292 if (map_entry == function_ids_.end()) { 293 return function_ids_[code_entry] = next_function_id_++; 294 } 295 return function_ids_[code_entry]; 296 } 297 298 ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path, 299 int src_line, bool update_stats) { 300 ProfileNode* node = root_; 301 CodeEntry* last_entry = nullptr; 302 for (auto it = path.rbegin(); it != path.rend(); ++it) { 303 if (*it == nullptr) continue; 304 last_entry = *it; 305 node = node->FindOrAddChild(*it, v8::CpuProfileNode::kNoLineNumberInfo); 306 } 307 if (last_entry && last_entry->has_deopt_info()) { 308 node->CollectDeoptInfo(last_entry); 309 } 310 if (update_stats) { 311 node->IncrementSelfTicks(); 312 if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) { 313 node->IncrementLineTicks(src_line); 314 } 315 } 316 return node; 317 } 318 319 ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path, 320 int src_line, bool update_stats, 321 ProfilingMode mode) { 322 ProfileNode* node = root_; 323 CodeEntry* last_entry = nullptr; 324 int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo; 325 for (auto it = path.rbegin(); it != path.rend(); ++it) { 326 if ((*it).code_entry == nullptr) continue; 327 last_entry = (*it).code_entry; 328 node = node->FindOrAddChild((*it).code_entry, parent_line_number); 329 parent_line_number = mode == ProfilingMode::kCallerLineNumbers 330 ? (*it).line_number 331 : v8::CpuProfileNode::kNoLineNumberInfo; 332 } 333 if (last_entry && last_entry->has_deopt_info()) { 334 node->CollectDeoptInfo(last_entry); 335 } 336 if (update_stats) { 337 node->IncrementSelfTicks(); 338 if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) { 339 node->IncrementLineTicks(src_line); 340 } 341 } 342 return node; 343 } 344 345 346 class Position { 347 public: 348 explicit Position(ProfileNode* node) 349 : node(node), child_idx_(0) { } 350 V8_INLINE ProfileNode* current_child() { 351 return node->children()->at(child_idx_); 352 } 353 V8_INLINE bool has_current_child() { 354 return child_idx_ < static_cast<int>(node->children()->size()); 355 } 356 V8_INLINE void next_child() { ++child_idx_; } 357 358 ProfileNode* node; 359 private: 360 int child_idx_; 361 }; 362 363 364 // Non-recursive implementation of a depth-first post-order tree traversal. 365 template <typename Callback> 366 void ProfileTree::TraverseDepthFirst(Callback* callback) { 367 std::vector<Position> stack; 368 stack.emplace_back(root_); 369 while (stack.size() > 0) { 370 Position& current = stack.back(); 371 if (current.has_current_child()) { 372 callback->BeforeTraversingChild(current.node, current.current_child()); 373 stack.emplace_back(current.current_child()); 374 } else { 375 callback->AfterAllChildrenTraversed(current.node); 376 if (stack.size() > 1) { 377 Position& parent = stack[stack.size() - 2]; 378 callback->AfterChildTraversed(parent.node, current.node); 379 parent.next_child(); 380 } 381 // Remove child from the stack. 382 stack.pop_back(); 383 } 384 } 385 } 386 387 using v8::tracing::TracedValue; 388 389 std::atomic<uint32_t> CpuProfile::last_id_; 390 391 CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title, 392 bool record_samples, ProfilingMode mode) 393 : title_(title), 394 record_samples_(record_samples), 395 mode_(mode), 396 start_time_(base::TimeTicks::HighResolutionNow()), 397 top_down_(profiler->isolate()), 398 profiler_(profiler), 399 streaming_next_sample_(0), 400 id_(++last_id_) { 401 auto value = TracedValue::Create(); 402 value->SetDouble("startTime", 403 (start_time_ - base::TimeTicks()).InMicroseconds()); 404 TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"), 405 "Profile", id_, "data", std::move(value)); 406 } 407 408 void CpuProfile::AddPath(base::TimeTicks timestamp, 409 const ProfileStackTrace& path, int src_line, 410 bool update_stats) { 411 ProfileNode* top_frame_node = 412 top_down_.AddPathFromEnd(path, src_line, update_stats, mode_); 413 414 if (record_samples_ && !timestamp.IsNull()) { 415 timestamps_.push_back(timestamp); 416 samples_.push_back(top_frame_node); 417 } 418 419 const int kSamplesFlushCount = 100; 420 const int kNodesFlushCount = 10; 421 if (samples_.size() - streaming_next_sample_ >= kSamplesFlushCount || 422 top_down_.pending_nodes_count() >= kNodesFlushCount) { 423 StreamPendingTraceEvents(); 424 } 425 } 426 427 namespace { 428 429 void BuildNodeValue(const ProfileNode* node, TracedValue* value) { 430 const CodeEntry* entry = node->entry(); 431 value->BeginDictionary("callFrame"); 432 value->SetString("functionName", entry->name()); 433 if (*entry->resource_name()) { 434 value->SetString("url", entry->resource_name()); 435 } 436 value->SetInteger("scriptId", entry->script_id()); 437 if (entry->line_number()) { 438 value->SetInteger("lineNumber", entry->line_number() - 1); 439 } 440 if (entry->column_number()) { 441 value->SetInteger("columnNumber", entry->column_number() - 1); 442 } 443 value->EndDictionary(); 444 value->SetInteger("id", node->id()); 445 if (node->parent()) { 446 value->SetInteger("parent", node->parent()->id()); 447 } 448 const char* deopt_reason = entry->bailout_reason(); 449 if (deopt_reason && deopt_reason[0] && strcmp(deopt_reason, "no reason")) { 450 value->SetString("deoptReason", deopt_reason); 451 } 452 } 453 454 } // namespace 455 456 void CpuProfile::StreamPendingTraceEvents() { 457 std::vector<const ProfileNode*> pending_nodes = top_down_.TakePendingNodes(); 458 if (pending_nodes.empty() && samples_.empty()) return; 459 auto value = TracedValue::Create(); 460 461 if (!pending_nodes.empty() || streaming_next_sample_ != samples_.size()) { 462 value->BeginDictionary("cpuProfile"); 463 if (!pending_nodes.empty()) { 464 value->BeginArray("nodes"); 465 for (auto node : pending_nodes) { 466 value->BeginDictionary(); 467 BuildNodeValue(node, value.get()); 468 value->EndDictionary(); 469 } 470 value->EndArray(); 471 } 472 if (streaming_next_sample_ != samples_.size()) { 473 value->BeginArray("samples"); 474 for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) { 475 value->AppendInteger(samples_[i]->id()); 476 } 477 value->EndArray(); 478 } 479 value->EndDictionary(); 480 } 481 if (streaming_next_sample_ != samples_.size()) { 482 value->BeginArray("timeDeltas"); 483 base::TimeTicks lastTimestamp = 484 streaming_next_sample_ ? timestamps_[streaming_next_sample_ - 1] 485 : start_time(); 486 for (size_t i = streaming_next_sample_; i < timestamps_.size(); ++i) { 487 value->AppendInteger( 488 static_cast<int>((timestamps_[i] - lastTimestamp).InMicroseconds())); 489 lastTimestamp = timestamps_[i]; 490 } 491 value->EndArray(); 492 DCHECK_EQ(samples_.size(), timestamps_.size()); 493 streaming_next_sample_ = samples_.size(); 494 } 495 496 TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"), 497 "ProfileChunk", id_, "data", std::move(value)); 498 } 499 500 void CpuProfile::FinishProfile() { 501 end_time_ = base::TimeTicks::HighResolutionNow(); 502 StreamPendingTraceEvents(); 503 auto value = TracedValue::Create(); 504 value->SetDouble("endTime", (end_time_ - base::TimeTicks()).InMicroseconds()); 505 TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"), 506 "ProfileChunk", id_, "data", std::move(value)); 507 } 508 509 void CpuProfile::Print() { 510 base::OS::Print("[Top down]:\n"); 511 top_down_.Print(); 512 } 513 514 CodeMap::CodeMap() = default; 515 516 CodeMap::~CodeMap() { 517 // First clean the free list as it's otherwise impossible to tell 518 // the slot type. 519 unsigned free_slot = free_list_head_; 520 while (free_slot != kNoFreeSlot) { 521 unsigned next_slot = code_entries_[free_slot].next_free_slot; 522 code_entries_[free_slot].entry = nullptr; 523 free_slot = next_slot; 524 } 525 for (auto slot : code_entries_) delete slot.entry; 526 } 527 528 void CodeMap::AddCode(Address addr, CodeEntry* entry, unsigned size) { 529 ClearCodesInRange(addr, addr + size); 530 unsigned index = AddCodeEntry(addr, entry); 531 code_map_.emplace(addr, CodeEntryMapInfo{index, size}); 532 DCHECK(entry->instruction_start() == kNullAddress || 533 addr == entry->instruction_start()); 534 } 535 536 void CodeMap::ClearCodesInRange(Address start, Address end) { 537 auto left = code_map_.upper_bound(start); 538 if (left != code_map_.begin()) { 539 --left; 540 if (left->first + left->second.size <= start) ++left; 541 } 542 auto right = left; 543 for (; right != code_map_.end() && right->first < end; ++right) { 544 if (!entry(right->second.index)->used()) { 545 DeleteCodeEntry(right->second.index); 546 } 547 } 548 code_map_.erase(left, right); 549 } 550 551 CodeEntry* CodeMap::FindEntry(Address addr) { 552 auto it = code_map_.upper_bound(addr); 553 if (it == code_map_.begin()) return nullptr; 554 --it; 555 Address start_address = it->first; 556 Address end_address = start_address + it->second.size; 557 CodeEntry* ret = addr < end_address ? entry(it->second.index) : nullptr; 558 if (ret && ret->instruction_start() != kNullAddress) { 559 DCHECK_EQ(start_address, ret->instruction_start()); 560 DCHECK(addr >= start_address && addr < end_address); 561 } 562 return ret; 563 } 564 565 void CodeMap::MoveCode(Address from, Address to) { 566 if (from == to) return; 567 auto it = code_map_.find(from); 568 if (it == code_map_.end()) return; 569 CodeEntryMapInfo info = it->second; 570 code_map_.erase(it); 571 DCHECK(from + info.size <= to || to + info.size <= from); 572 ClearCodesInRange(to, to + info.size); 573 code_map_.emplace(to, info); 574 575 CodeEntry* entry = code_entries_[info.index].entry; 576 entry->set_instruction_start(to); 577 } 578 579 unsigned CodeMap::AddCodeEntry(Address start, CodeEntry* entry) { 580 if (free_list_head_ == kNoFreeSlot) { 581 code_entries_.push_back(CodeEntrySlotInfo{entry}); 582 return static_cast<unsigned>(code_entries_.size()) - 1; 583 } 584 unsigned index = free_list_head_; 585 free_list_head_ = code_entries_[index].next_free_slot; 586 code_entries_[index].entry = entry; 587 return index; 588 } 589 590 void CodeMap::DeleteCodeEntry(unsigned index) { 591 delete code_entries_[index].entry; 592 code_entries_[index].next_free_slot = free_list_head_; 593 free_list_head_ = index; 594 } 595 596 void CodeMap::Print() { 597 for (const auto& pair : code_map_) { 598 base::OS::Print("%p %5d %s\n", reinterpret_cast<void*>(pair.first), 599 pair.second.size, entry(pair.second.index)->name()); 600 } 601 } 602 603 CpuProfilesCollection::CpuProfilesCollection(Isolate* isolate) 604 : profiler_(nullptr), current_profiles_semaphore_(1) {} 605 606 bool CpuProfilesCollection::StartProfiling(const char* title, 607 bool record_samples, 608 ProfilingMode mode) { 609 current_profiles_semaphore_.Wait(); 610 if (static_cast<int>(current_profiles_.size()) >= kMaxSimultaneousProfiles) { 611 current_profiles_semaphore_.Signal(); 612 return false; 613 } 614 for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) { 615 if (strcmp(profile->title(), title) == 0) { 616 // Ignore attempts to start profile with the same title... 617 current_profiles_semaphore_.Signal(); 618 // ... though return true to force it collect a sample. 619 return true; 620 } 621 } 622 current_profiles_.emplace_back( 623 new CpuProfile(profiler_, title, record_samples, mode)); 624 current_profiles_semaphore_.Signal(); 625 return true; 626 } 627 628 629 CpuProfile* CpuProfilesCollection::StopProfiling(const char* title) { 630 const int title_len = StrLength(title); 631 CpuProfile* profile = nullptr; 632 current_profiles_semaphore_.Wait(); 633 634 auto it = 635 std::find_if(current_profiles_.rbegin(), current_profiles_.rend(), 636 [&](const std::unique_ptr<CpuProfile>& p) { 637 return title_len == 0 || strcmp(p->title(), title) == 0; 638 }); 639 640 if (it != current_profiles_.rend()) { 641 (*it)->FinishProfile(); 642 profile = it->get(); 643 finished_profiles_.push_back(std::move(*it)); 644 // Convert reverse iterator to matching forward iterator. 645 current_profiles_.erase(--(it.base())); 646 } 647 648 current_profiles_semaphore_.Signal(); 649 return profile; 650 } 651 652 653 bool CpuProfilesCollection::IsLastProfile(const char* title) { 654 // Called from VM thread, and only it can mutate the list, 655 // so no locking is needed here. 656 if (current_profiles_.size() != 1) return false; 657 return StrLength(title) == 0 658 || strcmp(current_profiles_[0]->title(), title) == 0; 659 } 660 661 662 void CpuProfilesCollection::RemoveProfile(CpuProfile* profile) { 663 // Called from VM thread for a completed profile. 664 auto pos = 665 std::find_if(finished_profiles_.begin(), finished_profiles_.end(), 666 [&](const std::unique_ptr<CpuProfile>& finished_profile) { 667 return finished_profile.get() == profile; 668 }); 669 DCHECK(pos != finished_profiles_.end()); 670 finished_profiles_.erase(pos); 671 } 672 673 void CpuProfilesCollection::AddPathToCurrentProfiles( 674 base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line, 675 bool update_stats) { 676 // As starting / stopping profiles is rare relatively to this 677 // method, we don't bother minimizing the duration of lock holding, 678 // e.g. copying contents of the list to a local vector. 679 current_profiles_semaphore_.Wait(); 680 for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) { 681 profile->AddPath(timestamp, path, src_line, update_stats); 682 } 683 current_profiles_semaphore_.Signal(); 684 } 685 686 ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles) 687 : profiles_(profiles) {} 688 689 void ProfileGenerator::RecordTickSample(const TickSample& sample) { 690 ProfileStackTrace stack_trace; 691 // Conservatively reserve space for stack frames + pc + function + vm-state. 692 // There could in fact be more of them because of inlined entries. 693 stack_trace.reserve(sample.frames_count + 3); 694 695 // The ProfileNode knows nothing about all versions of generated code for 696 // the same JS function. The line number information associated with 697 // the latest version of generated code is used to find a source line number 698 // for a JS function. Then, the detected source line is passed to 699 // ProfileNode to increase the tick count for this source line. 700 const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo; 701 int src_line = no_line_info; 702 bool src_line_not_found = true; 703 704 if (sample.pc != nullptr) { 705 if (sample.has_external_callback && sample.state == EXTERNAL) { 706 // Don't use PC when in external callback code, as it can point 707 // inside a callback's code, and we will erroneously report 708 // that a callback calls itself. 709 stack_trace.push_back( 710 {FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)), 711 no_line_info}); 712 } else { 713 Address attributed_pc = reinterpret_cast<Address>(sample.pc); 714 CodeEntry* pc_entry = FindEntry(attributed_pc); 715 // If there is no pc_entry, we're likely in native code. Find out if the 716 // top of the stack (the return address) was pointing inside a JS 717 // function, meaning that we have encountered a frameless invocation. 718 if (!pc_entry && !sample.has_external_callback) { 719 attributed_pc = reinterpret_cast<Address>(sample.tos); 720 pc_entry = FindEntry(attributed_pc); 721 } 722 // If pc is in the function code before it set up stack frame or after the 723 // frame was destroyed, SafeStackFrameIterator incorrectly thinks that 724 // ebp contains the return address of the current function and skips the 725 // caller's frame. Check for this case and just skip such samples. 726 if (pc_entry) { 727 int pc_offset = 728 static_cast<int>(attributed_pc - pc_entry->instruction_start()); 729 // TODO(petermarshall): pc_offset can still be negative in some cases. 730 src_line = pc_entry->GetSourceLine(pc_offset); 731 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { 732 src_line = pc_entry->line_number(); 733 } 734 src_line_not_found = false; 735 stack_trace.push_back({pc_entry, src_line}); 736 737 if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply || 738 pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) { 739 // When current function is either the Function.prototype.apply or the 740 // Function.prototype.call builtin the top frame is either frame of 741 // the calling JS function or internal frame. 742 // In the latter case we know the caller for sure but in the 743 // former case we don't so we simply replace the frame with 744 // 'unresolved' entry. 745 if (!sample.has_external_callback) { 746 stack_trace.push_back( 747 {CodeEntry::unresolved_entry(), no_line_info}); 748 } 749 } 750 } 751 } 752 753 for (unsigned i = 0; i < sample.frames_count; ++i) { 754 Address stack_pos = reinterpret_cast<Address>(sample.stack[i]); 755 CodeEntry* entry = FindEntry(stack_pos); 756 int line_number = no_line_info; 757 if (entry) { 758 // Find out if the entry has an inlining stack associated. 759 int pc_offset = 760 static_cast<int>(stack_pos - entry->instruction_start()); 761 // TODO(petermarshall): pc_offset can still be negative in some cases. 762 const std::vector<std::unique_ptr<CodeEntry>>* inline_stack = 763 entry->GetInlineStack(pc_offset); 764 if (inline_stack) { 765 std::transform( 766 inline_stack->rbegin(), inline_stack->rend(), 767 std::back_inserter(stack_trace), 768 [=](const std::unique_ptr<CodeEntry>& ptr) { 769 return CodeEntryAndLineNumber{ptr.get(), no_line_info}; 770 }); 771 } 772 // Skip unresolved frames (e.g. internal frame) and get source line of 773 // the first JS caller. 774 if (src_line_not_found) { 775 src_line = entry->GetSourceLine(pc_offset); 776 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { 777 src_line = entry->line_number(); 778 } 779 src_line_not_found = false; 780 } 781 line_number = entry->GetSourceLine(pc_offset); 782 } 783 stack_trace.push_back({entry, line_number}); 784 } 785 } 786 787 if (FLAG_prof_browser_mode) { 788 bool no_symbolized_entries = true; 789 for (auto e : stack_trace) { 790 if (e.code_entry != nullptr) { 791 no_symbolized_entries = false; 792 break; 793 } 794 } 795 // If no frames were symbolized, put the VM state entry in. 796 if (no_symbolized_entries) { 797 stack_trace.push_back({EntryForVMState(sample.state), no_line_info}); 798 } 799 } 800 801 profiles_->AddPathToCurrentProfiles(sample.timestamp, stack_trace, src_line, 802 sample.update_stats); 803 } 804 805 CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) { 806 switch (tag) { 807 case GC: 808 return CodeEntry::gc_entry(); 809 case JS: 810 case PARSER: 811 case COMPILER: 812 case BYTECODE_COMPILER: 813 // DOM events handlers are reported as OTHER / EXTERNAL entries. 814 // To avoid confusing people, let's put all these entries into 815 // one bucket. 816 case OTHER: 817 case EXTERNAL: 818 return CodeEntry::program_entry(); 819 case IDLE: 820 return CodeEntry::idle_entry(); 821 } 822 UNREACHABLE(); 823 } 824 825 } // namespace internal 826 } // namespace v8 827