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