| // Copyright 2009-2010 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/profiler/heap-profiler.h" |
| |
| #include "src/api/api-inl.h" |
| #include "src/debug/debug.h" |
| #include "src/heap/combined-heap.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/profiler/allocation-tracker.h" |
| #include "src/profiler/heap-snapshot-generator-inl.h" |
| #include "src/profiler/sampling-heap-profiler.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| HeapProfiler::HeapProfiler(Heap* heap) |
| : ids_(new HeapObjectsMap(heap)), |
| names_(new StringsStorage()), |
| is_tracking_object_moves_(false), |
| is_taking_snapshot_(false) {} |
| |
| HeapProfiler::~HeapProfiler() = default; |
| |
| void HeapProfiler::DeleteAllSnapshots() { |
| snapshots_.clear(); |
| MaybeClearStringsStorage(); |
| } |
| |
| void HeapProfiler::MaybeClearStringsStorage() { |
| if (snapshots_.empty() && !sampling_heap_profiler_ && !allocation_tracker_ && |
| !is_taking_snapshot_) { |
| names_.reset(new StringsStorage()); |
| } |
| } |
| |
| void HeapProfiler::RemoveSnapshot(HeapSnapshot* snapshot) { |
| snapshots_.erase( |
| std::find_if(snapshots_.begin(), snapshots_.end(), |
| [&](const std::unique_ptr<HeapSnapshot>& entry) { |
| return entry.get() == snapshot; |
| })); |
| } |
| |
| void HeapProfiler::AddBuildEmbedderGraphCallback( |
| v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data) { |
| build_embedder_graph_callbacks_.push_back({callback, data}); |
| } |
| |
| void HeapProfiler::RemoveBuildEmbedderGraphCallback( |
| v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data) { |
| auto it = std::find(build_embedder_graph_callbacks_.begin(), |
| build_embedder_graph_callbacks_.end(), |
| std::make_pair(callback, data)); |
| if (it != build_embedder_graph_callbacks_.end()) |
| build_embedder_graph_callbacks_.erase(it); |
| } |
| |
| void HeapProfiler::BuildEmbedderGraph(Isolate* isolate, |
| v8::EmbedderGraph* graph) { |
| for (const auto& cb : build_embedder_graph_callbacks_) { |
| cb.first(reinterpret_cast<v8::Isolate*>(isolate), graph, cb.second); |
| } |
| } |
| |
| void HeapProfiler::SetGetDetachednessCallback( |
| v8::HeapProfiler::GetDetachednessCallback callback, void* data) { |
| get_detachedness_callback_ = {callback, data}; |
| } |
| |
| v8::EmbedderGraph::Node::Detachedness HeapProfiler::GetDetachedness( |
| const v8::Local<v8::Value> v8_value, uint16_t class_id) { |
| DCHECK(HasGetDetachednessCallback()); |
| return get_detachedness_callback_.first( |
| reinterpret_cast<v8::Isolate*>(heap()->isolate()), v8_value, class_id, |
| get_detachedness_callback_.second); |
| } |
| |
| HeapSnapshot* HeapProfiler::TakeSnapshot( |
| v8::ActivityControl* control, |
| v8::HeapProfiler::ObjectNameResolver* resolver, |
| bool treat_global_objects_as_roots) { |
| is_taking_snapshot_ = true; |
| HeapSnapshot* result = new HeapSnapshot(this, treat_global_objects_as_roots); |
| { |
| HeapSnapshotGenerator generator(result, control, resolver, heap()); |
| if (!generator.GenerateSnapshot()) { |
| delete result; |
| result = nullptr; |
| } else { |
| snapshots_.emplace_back(result); |
| } |
| } |
| ids_->RemoveDeadEntries(); |
| is_tracking_object_moves_ = true; |
| is_taking_snapshot_ = false; |
| |
| heap()->isolate()->debug()->feature_tracker()->Track( |
| DebugFeatureTracker::kHeapSnapshot); |
| |
| return result; |
| } |
| |
| bool HeapProfiler::StartSamplingHeapProfiler( |
| uint64_t sample_interval, int stack_depth, |
| v8::HeapProfiler::SamplingFlags flags) { |
| if (sampling_heap_profiler_.get()) { |
| return false; |
| } |
| sampling_heap_profiler_.reset(new SamplingHeapProfiler( |
| heap(), names_.get(), sample_interval, stack_depth, flags)); |
| return true; |
| } |
| |
| |
| void HeapProfiler::StopSamplingHeapProfiler() { |
| sampling_heap_profiler_.reset(); |
| MaybeClearStringsStorage(); |
| } |
| |
| |
| v8::AllocationProfile* HeapProfiler::GetAllocationProfile() { |
| if (sampling_heap_profiler_.get()) { |
| return sampling_heap_profiler_->GetAllocationProfile(); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| |
| void HeapProfiler::StartHeapObjectsTracking(bool track_allocations) { |
| ids_->UpdateHeapObjectsMap(); |
| is_tracking_object_moves_ = true; |
| DCHECK(!allocation_tracker_); |
| if (track_allocations) { |
| allocation_tracker_.reset(new AllocationTracker(ids_.get(), names_.get())); |
| heap()->AddHeapObjectAllocationTracker(this); |
| heap()->isolate()->debug()->feature_tracker()->Track( |
| DebugFeatureTracker::kAllocationTracking); |
| } |
| } |
| |
| SnapshotObjectId HeapProfiler::PushHeapObjectsStats(OutputStream* stream, |
| int64_t* timestamp_us) { |
| return ids_->PushHeapObjectsStats(stream, timestamp_us); |
| } |
| |
| void HeapProfiler::StopHeapObjectsTracking() { |
| ids_->StopHeapObjectsTracking(); |
| if (allocation_tracker_) { |
| allocation_tracker_.reset(); |
| MaybeClearStringsStorage(); |
| heap()->RemoveHeapObjectAllocationTracker(this); |
| } |
| } |
| |
| int HeapProfiler::GetSnapshotsCount() const { |
| return static_cast<int>(snapshots_.size()); |
| } |
| |
| bool HeapProfiler::IsTakingSnapshot() const { return is_taking_snapshot_; } |
| |
| HeapSnapshot* HeapProfiler::GetSnapshot(int index) { |
| return snapshots_.at(index).get(); |
| } |
| |
| SnapshotObjectId HeapProfiler::GetSnapshotObjectId(Handle<Object> obj) { |
| if (!obj->IsHeapObject()) |
| return v8::HeapProfiler::kUnknownObjectId; |
| return ids_->FindEntry(HeapObject::cast(*obj).address()); |
| } |
| |
| SnapshotObjectId HeapProfiler::GetSnapshotObjectId(NativeObject obj) { |
| // Try to find id of regular native node first. |
| SnapshotObjectId id = ids_->FindEntry(reinterpret_cast<Address>(obj)); |
| // In case no id has been found, check whether there exists an entry where the |
| // native objects has been merged into a V8 entry. |
| if (id == v8::HeapProfiler::kUnknownObjectId) { |
| id = ids_->FindMergedNativeEntry(obj); |
| } |
| return id; |
| } |
| |
| void HeapProfiler::ObjectMoveEvent(Address from, Address to, int size) { |
| base::MutexGuard guard(&profiler_mutex_); |
| bool known_object = ids_->MoveObject(from, to, size); |
| if (!known_object && allocation_tracker_) { |
| allocation_tracker_->address_to_trace()->MoveObject(from, to, size); |
| } |
| } |
| |
| void HeapProfiler::AllocationEvent(Address addr, int size) { |
| DisallowHeapAllocation no_allocation; |
| if (allocation_tracker_) { |
| allocation_tracker_->AllocationEvent(addr, size); |
| } |
| } |
| |
| |
| void HeapProfiler::UpdateObjectSizeEvent(Address addr, int size) { |
| ids_->UpdateObjectSize(addr, size); |
| } |
| |
| Handle<HeapObject> HeapProfiler::FindHeapObjectById(SnapshotObjectId id) { |
| HeapObject object; |
| CombinedHeapObjectIterator iterator(heap(), |
| HeapObjectIterator::kFilterUnreachable); |
| // Make sure that object with the given id is still reachable. |
| for (HeapObject obj = iterator.Next(); !obj.is_null(); |
| obj = iterator.Next()) { |
| if (ids_->FindEntry(obj.address()) == id) { |
| DCHECK(object.is_null()); |
| object = obj; |
| // Can't break -- kFilterUnreachable requires full heap traversal. |
| } |
| } |
| |
| return !object.is_null() ? Handle<HeapObject>(object, isolate()) |
| : Handle<HeapObject>(); |
| } |
| |
| |
| void HeapProfiler::ClearHeapObjectMap() { |
| ids_.reset(new HeapObjectsMap(heap())); |
| if (!allocation_tracker_) is_tracking_object_moves_ = false; |
| } |
| |
| |
| Heap* HeapProfiler::heap() const { return ids_->heap(); } |
| |
| Isolate* HeapProfiler::isolate() const { return heap()->isolate(); } |
| |
| void HeapProfiler::QueryObjects(Handle<Context> context, |
| debug::QueryObjectPredicate* predicate, |
| PersistentValueVector<v8::Object>* objects) { |
| { |
| HandleScope handle_scope(isolate()); |
| std::vector<Handle<JSTypedArray>> on_heap_typed_arrays; |
| CombinedHeapObjectIterator heap_iterator( |
| heap(), HeapObjectIterator::kFilterUnreachable); |
| for (HeapObject heap_obj = heap_iterator.Next(); !heap_obj.is_null(); |
| heap_obj = heap_iterator.Next()) { |
| if (heap_obj.IsFeedbackVector()) { |
| FeedbackVector::cast(heap_obj).ClearSlots(isolate()); |
| } else if (heap_obj.IsJSTypedArray() && |
| JSTypedArray::cast(heap_obj).is_on_heap()) { |
| // Cannot call typed_array->GetBuffer() here directly because it may |
| // trigger GC. Defer that call by collecting the object in a vector. |
| on_heap_typed_arrays.push_back( |
| handle(JSTypedArray::cast(heap_obj), isolate())); |
| } |
| } |
| for (auto& typed_array : on_heap_typed_arrays) { |
| // Convert the on-heap typed array into off-heap typed array, so that |
| // its ArrayBuffer becomes valid and can be returned in the result. |
| typed_array->GetBuffer(); |
| } |
| } |
| // We should return accurate information about live objects, so we need to |
| // collect all garbage first. |
| heap()->CollectAllAvailableGarbage(GarbageCollectionReason::kHeapProfiler); |
| CombinedHeapObjectIterator heap_iterator( |
| heap(), HeapObjectIterator::kFilterUnreachable); |
| for (HeapObject heap_obj = heap_iterator.Next(); !heap_obj.is_null(); |
| heap_obj = heap_iterator.Next()) { |
| if (!heap_obj.IsJSObject() || heap_obj.IsExternal(isolate())) continue; |
| v8::Local<v8::Object> v8_obj( |
| Utils::ToLocal(handle(JSObject::cast(heap_obj), isolate()))); |
| if (!predicate->Filter(v8_obj)) continue; |
| objects->Append(v8_obj); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |