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