#include "src/profiler/heap-profiler.h"
#include <fstream>
#include <optional>
#include <utility>
#include "include/v8-profiler.h"
#include "src/api/api-inl.h"
#include "src/base/platform/platform.h"
#include "src/debug/debug.h"
#include "src/heap/combined-heap.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap-layout-inl.h"
#include "src/heap/heap.h"
#include "src/objects/cpp-heap-object-wrapper-inl.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/profiler/allocation-tracker.h"
#include "src/profiler/heap-snapshot-generator-inl.h"
#include "src/profiler/sampling-heap-profiler.h"
#include "src/utils/output-stream.h"
namespace v8::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;
}));
}
std::vector<v8::Local<v8::Value>> HeapProfiler::GetDetachedJSWrapperObjects() {
heap()->CollectAllAvailableGarbage(GarbageCollectionReason::kHeapProfiler);
std::vector<v8::Local<v8::Value>> js_objects_found;
HeapObjectIterator iterator(heap());
for (Tagged<HeapObject> obj = iterator.Next(); !obj.is_null();
obj = iterator.Next()) {
if (TrustedHeapLayout::InCodeSpace(obj)) continue;
if (!IsJSApiWrapperObject(obj)) continue;
CppHeapObjectWrapper wrapper = CppHeapObjectWrapper(Cast<JSObject>(obj));
if (!wrapper.GetCppHeapWrappable(isolate(), kAnyCppHeapPointer)) continue;
v8::Local<v8::Value> data(
Utils::ToLocal(direct_handle(Cast<JSObject>(obj), isolate())));
v8::EmbedderGraph::Node::Detachedness detachedness =
GetDetachedness(data, 0);
if (detachedness != v8::EmbedderGraph::Node::Detachedness::kDetached)
continue;
js_objects_found.push_back(data);
}
return js_objects_found;
}
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,
UnorderedCppHeapExternalObjectSet&& cpp_heap_external_objects) {
if (internal_build_embedder_graph_callback_.first) {
internal_build_embedder_graph_callback_.first(
reinterpret_cast<v8::Isolate*>(isolate), graph,
internal_build_embedder_graph_callback_.second,
std::move(cpp_heap_external_objects));
}
for (const auto& cb : build_embedder_graph_callbacks_) {
cb.first(reinterpret_cast<v8::Isolate*>(isolate), graph, cb.second);
}
}
void HeapProfiler::SetInternalBuildEmbedderGraphCallback(
InternalBuildEmbedderGraphCallback callback, void* data) {
internal_build_embedder_graph_callback_ = {callback, data};
}
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);
}
const char* HeapProfiler::CopyNameForHeapSnapshot(const char* name) {
CHECK(is_taking_snapshot_);
return names_->GetCopy(name);
}
HeapSnapshot* HeapProfiler::TakeSnapshot(
const v8::HeapProfiler::HeapSnapshotOptions options) {
is_taking_snapshot_ = true;
HeapSnapshot* result =
new HeapSnapshot(this, options.snapshot_mode, options.numerics_mode);
heap()->stack().SetMarkerIfNeededAndCallback([this, &options, &result]() {
std::optional<CppClassNamesAsHeapObjectNameScope> use_cpp_class_name;
if (result->expose_internals() && heap()->cpp_heap()) {
use_cpp_class_name.emplace(heap()->cpp_heap());
}
START_ALLOW_USE_DEPRECATED()
HeapSnapshotGenerator generator(
result, options.control, options.global_object_name_resolver,
options.context_name_resolver, heap(), options.stack_state);
END_ALLOW_USE_DEPRECATED()
if (!generator.GenerateSnapshot()) {
delete result;
result = nullptr;
} else {
snapshots_.emplace_back(result);
}
});
ids_->RemoveDeadEntries();
if (native_move_listener_) {
native_move_listener_->StartListening();
}
is_tracking_object_moves_ = true;
heap()->isolate()->UpdateLogObjectRelocation();
is_taking_snapshot_ = false;
return result;
}
void HeapProfiler::WriteSnapshotToDiskAfterGC(HeapSnapshotMode snapshot_mode) {
heap()->stack().SetMarkerIfNeededAndCallback([this, snapshot_mode]() {
int64_t time = V8::GetCurrentPlatform()->CurrentClockTimeMilliseconds();
int pid = base::OS::GetCurrentProcessId();
std::string filename = "v8-heap-" + std::to_string(time) + "-" +
std::to_string(pid) + ".heapsnapshot";
if (v8_flags.heap_snapshot_path.value()) {
filename = v8_flags.heap_snapshot_path.value();
}
v8::HeapProfiler::HeapSnapshotOptions options;
std::unique_ptr<HeapSnapshot> result(
new HeapSnapshot(this, snapshot_mode, options.numerics_mode));
START_ALLOW_USE_DEPRECATED()
HeapSnapshotGenerator generator(
result.get(), options.control, options.global_object_name_resolver,
options.context_name_resolver, heap(), options.stack_state);
END_ALLOW_USE_DEPRECATED()
if (!generator.GenerateSnapshotAfterGC()) return;
i::FileOutputStream stream(filename.c_str());
if (stream.IsOpen()) {
HeapSnapshotJSONSerializer serializer(result.get());
serializer.Serialize(&stream);
PrintF("Wrote heap snapshot to %s.\n", filename.c_str());
} else {
PrintF("Failed to open heap snapshot file '%s' for writing.\n",
filename.c_str());
}
});
}
void HeapProfiler::TakeSnapshotToFile(
const v8::HeapProfiler::HeapSnapshotOptions options, std::string filename) {
HeapSnapshot* snapshot = TakeSnapshot(options);
if (!snapshot) return;
FileOutputStream stream(filename.c_str());
if (stream.IsOpen()) {
HeapSnapshotJSONSerializer serializer(snapshot);
serializer.Serialize(&stream);
} else {
RemoveSnapshot(snapshot);
}
}
std::string HeapProfiler::TakeSnapshotToString(
const v8::HeapProfiler::HeapSnapshotOptions options) {
HeapSnapshot* snapshot = TakeSnapshot(options);
StringOutputStream stream;
HeapSnapshotJSONSerializer serializer(snapshot);
serializer.Serialize(&stream);
return stream.str();
}
bool HeapProfiler::StartSamplingHeapProfiler(
uint64_t sample_interval, int stack_depth,
v8::HeapProfiler::SamplingFlags flags) {
if (sampling_heap_profiler_) 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_) {
return sampling_heap_profiler_->GetAllocationProfile();
} else {
return nullptr;
}
}
void HeapProfiler::StartHeapObjectsTracking(bool track_allocations) {
ids_->UpdateHeapObjectsMap();
if (native_move_listener_) {
native_move_listener_->StartListening();
}
is_tracking_object_moves_ = true;
heap()->isolate()->UpdateLogObjectRelocation();
DCHECK(!allocation_tracker_);
if (track_allocations) {
allocation_tracker_.reset(new AllocationTracker(ids_.get(), names_.get()));
heap()->AddHeapObjectAllocationTracker(this);
}
}
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(DirectHandle<Object> obj) {
if (!IsHeapObject(*obj)) return v8::HeapProfiler::kUnknownObjectId;
return ids_->FindEntry(Cast<HeapObject>(*obj).address());
}
SnapshotObjectId HeapProfiler::GetSnapshotObjectId(NativeObject obj) {
SnapshotObjectId id = ids_->FindEntry(reinterpret_cast<Address>(obj));
if (id == v8::HeapProfiler::kUnknownObjectId) {
id = ids_->FindMergedNativeEntry(obj);
}
return id;
}
void HeapProfilerNativeMoveListener::ObjectMoveEvent(Address from, Address to,
int size) {
profiler_->ObjectMoveEvent(from, to, size, true);
}
void HeapProfiler::ObjectMoveEvent(Address from, Address to, int size,
bool is_native_object) {
base::MutexGuard guard(&profiler_mutex_);
bool known_object = ids_->MoveObject(from, to, size);
if (!known_object && allocation_tracker_ && !is_native_object) {
allocation_tracker_->address_to_trace()->MoveObject(from, to, size);
}
}
void HeapProfiler::AllocationEvent(Address addr, int size) {
DisallowGarbageCollection no_gc;
if (allocation_tracker_) {
allocation_tracker_->AllocationEvent(addr, size);
}
}
void HeapProfiler::UpdateObjectSizeEvent(Address addr, int size) {
ids_->UpdateObjectSize(addr, size);
}
DirectHandle<HeapObject> HeapProfiler::FindHeapObjectById(SnapshotObjectId id) {
CombinedHeapObjectIterator iterator(heap(),
HeapObjectIterator::kFilterUnreachable);
for (Tagged<HeapObject> obj = iterator.Next(); !obj.is_null();
obj = iterator.Next()) {
if (ids_->FindEntry(obj.address()) == id)
return DirectHandle<HeapObject>(obj, isolate());
}
return DirectHandle<HeapObject>();
}
void HeapProfiler::ClearHeapObjectMap() {
ids_.reset(new HeapObjectsMap(heap()));
if (!allocation_tracker_) {
if (native_move_listener_) {
native_move_listener_->StopListening();
}
is_tracking_object_moves_ = false;
heap()->isolate()->UpdateLogObjectRelocation();
}
}
Heap* HeapProfiler::heap() const { return ids_->heap(); }
Isolate* HeapProfiler::isolate() const { return heap()->isolate(); }
void HeapProfiler::QueryObjects(DirectHandle<Context> context,
v8::QueryObjectPredicate* predicate,
std::vector<v8::Global<v8::Object>>* objects) {
heap()->stack().SetMarkerIfNeededAndCallback([this, predicate, objects]() {
{
HandleScope handle_scope(isolate());
std::vector<Handle<JSTypedArray>> on_heap_typed_arrays;
CombinedHeapObjectIterator heap_iterator(
heap(), HeapObjectIterator::kFilterUnreachable);
for (Tagged<HeapObject> heap_obj = heap_iterator.Next();
!heap_obj.is_null(); heap_obj = heap_iterator.Next()) {
if (IsFeedbackVector(heap_obj)) {
Cast<FeedbackVector>(heap_obj)->ClearSlots(isolate());
} else if (IsJSTypedArray(heap_obj) &&
Cast<JSTypedArray>(heap_obj)->is_on_heap()) {
on_heap_typed_arrays.push_back(
handle(Cast<JSTypedArray>(heap_obj), isolate()));
}
}
for (auto& typed_array : on_heap_typed_arrays) {
typed_array->GetBuffer(isolate());
}
}
heap()->CollectAllAvailableGarbage(GarbageCollectionReason::kHeapProfiler);
CombinedHeapObjectIterator heap_iterator(
heap(), HeapObjectIterator::kFilterUnreachable);
PtrComprCageBase cage_base(isolate());
for (Tagged<HeapObject> heap_obj = heap_iterator.Next();
!heap_obj.is_null(); heap_obj = heap_iterator.Next()) {
if (!IsJSObject(heap_obj, cage_base) ||
IsJSExternalObject(heap_obj, cage_base))
continue;
v8::Local<v8::Object> v8_obj(
Utils::ToLocal(direct_handle(Cast<JSObject>(heap_obj), isolate())));
if (!predicate->Filter(v8_obj)) continue;
objects->emplace_back(reinterpret_cast<v8::Isolate*>(isolate()), v8_obj);
}
});
}
}