// Copyright 2016 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/inspector/v8-heap-profiler-agent-impl.h" #include "include/v8-context.h" #include "include/v8-inspector.h" #include "include/v8-platform.h" #include "include/v8-profiler.h" #include "include/v8-version.h" #include "src/base/platform/mutex.h" #include "src/base/platform/time.h" #include "src/inspector/injected-script.h" #include "src/inspector/inspected-context.h" #include "src/inspector/protocol/Protocol.h" #include "src/inspector/string-util.h" #include "src/inspector/v8-debugger.h" #include "src/inspector/v8-inspector-impl.h" #include "src/inspector/v8-inspector-session-impl.h" namespace v8_inspector { namespace { namespace HeapProfilerAgentState { static const char heapProfilerEnabled[] = "heapProfilerEnabled"; static const char heapObjectsTrackingEnabled[] = "heapObjectsTrackingEnabled"; static const char allocationTrackingEnabled[] = "allocationTrackingEnabled"; static const char samplingHeapProfilerEnabled[] = "samplingHeapProfilerEnabled"; static const char samplingHeapProfilerInterval[] = "samplingHeapProfilerInterval"; static const char samplingHeapProfilerFlags[] = "samplingHeapProfilerFlags"; } // namespace HeapProfilerAgentState class HeapSnapshotProgress final : public v8::ActivityControl { public: explicit HeapSnapshotProgress(protocol::HeapProfiler::Frontend* frontend) : m_frontend(frontend) {} ControlOption ReportProgressValue(uint32_t done, uint32_t total) override { m_frontend->reportHeapSnapshotProgress(done, total, protocol::Maybe()); if (done >= total) { m_frontend->reportHeapSnapshotProgress(total, total, true); } m_frontend->flush(); return kContinue; } private: protocol::HeapProfiler::Frontend* m_frontend; }; class GlobalObjectNameResolver final : public v8::HeapProfiler::ObjectNameResolver { public: explicit GlobalObjectNameResolver(V8InspectorSessionImpl* session) : m_offset(0), m_strings(10000), m_session(session) {} const char* GetName(v8::Local object) override { v8::Local creationContext; if (!object->GetCreationContext().ToLocal(&creationContext)) { return ""; } InspectedContext* context = m_session->inspector()->getContext( m_session->contextGroupId(), InspectedContext::contextId(creationContext)); if (!context) return ""; String16 name = context->origin(); size_t length = name.length(); if (m_offset + length + 1 >= m_strings.size()) return ""; for (size_t i = 0; i < length; ++i) { UChar ch = name[i]; m_strings[m_offset + i] = ch > 0xFF ? '?' : static_cast(ch); } m_strings[m_offset + length] = '\0'; char* result = &*m_strings.begin() + m_offset; m_offset += length + 1; return result; } private: size_t m_offset; std::vector m_strings; V8InspectorSessionImpl* m_session; }; class HeapSnapshotOutputStream final : public v8::OutputStream { public: explicit HeapSnapshotOutputStream(protocol::HeapProfiler::Frontend* frontend) : m_frontend(frontend) {} void EndOfStream() override {} int GetChunkSize() override { return 102400; } WriteResult WriteAsciiChunk(char* data, int size) override { m_frontend->addHeapSnapshotChunk(String16(data, size)); m_frontend->flush(); return kContinue; } private: protocol::HeapProfiler::Frontend* m_frontend; }; v8::Local objectByHeapObjectId(v8::Isolate* isolate, int id) { v8::HeapProfiler* profiler = isolate->GetHeapProfiler(); v8::Local value = profiler->FindObjectById(id); if (value.IsEmpty() || !value->IsObject()) return v8::Local(); return value.As(); } class InspectableHeapObject final : public V8InspectorSession::Inspectable { public: explicit InspectableHeapObject(int heapObjectId) : m_heapObjectId(heapObjectId) {} v8::Local get(v8::Local context) override { return objectByHeapObjectId(context->GetIsolate(), m_heapObjectId); } private: int m_heapObjectId; }; class HeapStatsStream final : public v8::OutputStream { public: explicit HeapStatsStream(protocol::HeapProfiler::Frontend* frontend) : m_frontend(frontend) {} void EndOfStream() override {} WriteResult WriteAsciiChunk(char* data, int size) override { DCHECK(false); return kAbort; } WriteResult WriteHeapStatsChunk(v8::HeapStatsUpdate* updateData, int count) override { DCHECK_GT(count, 0); auto statsDiff = std::make_unique>(); for (int i = 0; i < count; ++i) { statsDiff->emplace_back(updateData[i].index); statsDiff->emplace_back(updateData[i].count); statsDiff->emplace_back(updateData[i].size); } m_frontend->heapStatsUpdate(std::move(statsDiff)); return kContinue; } private: protocol::HeapProfiler::Frontend* m_frontend; }; } // namespace struct V8HeapProfilerAgentImpl::AsyncCallbacks { v8::base::Mutex m_mutex; bool m_canceled = false; std::vector> m_gcCallbacks; std::vector m_heapSnapshotTasks; }; class V8HeapProfilerAgentImpl::GCTask : public v8::Task { public: GCTask(v8::Isolate* isolate, std::shared_ptr asyncCallbacks) : m_isolate(isolate), m_asyncCallbacks(asyncCallbacks) {} void Run() override { std::shared_ptr asyncCallbacks = m_asyncCallbacks.lock(); if (!asyncCallbacks) return; v8::base::MutexGuard lock(&asyncCallbacks->m_mutex); if (asyncCallbacks->m_canceled) return; v8::debug::ForceGarbageCollection(m_isolate, v8::StackState::kNoHeapPointers); for (auto& callback : asyncCallbacks->m_gcCallbacks) { callback->sendSuccess(); } asyncCallbacks->m_gcCallbacks.clear(); } private: v8::Isolate* m_isolate; std::weak_ptr m_asyncCallbacks; }; struct V8HeapProfilerAgentImpl::HeapSnapshotProtocolOptions { HeapSnapshotProtocolOptions(Maybe reportProgress, Maybe treatGlobalObjectsAsRoots, Maybe captureNumericValue, Maybe exposeInternals) : m_reportProgress(reportProgress.fromMaybe(false)), m_treatGlobalObjectsAsRoots(treatGlobalObjectsAsRoots.fromMaybe(true)), m_captureNumericValue(captureNumericValue.fromMaybe(false)), m_exposeInternals(exposeInternals.fromMaybe(false)) {} bool m_reportProgress; bool m_treatGlobalObjectsAsRoots; bool m_captureNumericValue; bool m_exposeInternals; }; class V8HeapProfilerAgentImpl::HeapSnapshotTask : public v8::Task { public: HeapSnapshotTask(V8HeapProfilerAgentImpl* agent, std::shared_ptr asyncCallbacks, HeapSnapshotProtocolOptions protocolOptions, std::unique_ptr callback) : m_agent(agent), m_asyncCallbacks(asyncCallbacks), m_protocolOptions(protocolOptions), m_callback(std::move(callback)) {} void Run() override { Run(cppgc::EmbedderStackState::kNoHeapPointers); } void Run(cppgc::EmbedderStackState stackState) { Response response = Response::Success(); { // If the async callbacks object still exists and is not canceled, then // the V8HeapProfilerAgentImpl still exists, so we can safely take a // snapshot. std::shared_ptr asyncCallbacks = m_asyncCallbacks.lock(); if (!asyncCallbacks) return; v8::base::MutexGuard lock(&asyncCallbacks->m_mutex); if (asyncCallbacks->m_canceled) return; auto& heapSnapshotTasks = asyncCallbacks->m_heapSnapshotTasks; auto it = std::find(heapSnapshotTasks.begin(), heapSnapshotTasks.end(), this); if (it == heapSnapshotTasks.end()) { // This task must have already been run. This can happen because the // task was queued with PostNonNestableTask but then got run early by // takePendingHeapSnapshots. return; } heapSnapshotTasks.erase(it); response = m_agent->takeHeapSnapshotNow(m_protocolOptions, stackState); } // The rest of this function runs without the mutex, because Node expects to // be able to dispose the profiler agent during the callback, which would // deadlock if this function still held the mutex. It's safe to call the // callback without the mutex; the internal implementation of the callback // uses weak pointers to avoid doing anything dangerous if other components // have been disposed (see DomainDispatcher::Callback::sendIfActive). if (response.IsSuccess()) { m_callback->sendSuccess(); } else { m_callback->sendFailure(std::move(response)); } } private: V8HeapProfilerAgentImpl* m_agent; std::weak_ptr m_asyncCallbacks; HeapSnapshotProtocolOptions m_protocolOptions; std::unique_ptr m_callback; }; V8HeapProfilerAgentImpl::V8HeapProfilerAgentImpl( V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel, protocol::DictionaryValue* state) : m_session(session), m_isolate(session->inspector()->isolate()), m_frontend(frontendChannel), m_state(state), m_hasTimer(false), m_asyncCallbacks(std::make_shared()) {} V8HeapProfilerAgentImpl::~V8HeapProfilerAgentImpl() { v8::base::MutexGuard lock(&m_asyncCallbacks->m_mutex); m_asyncCallbacks->m_canceled = true; m_asyncCallbacks->m_gcCallbacks.clear(); m_asyncCallbacks->m_heapSnapshotTasks.clear(); } void V8HeapProfilerAgentImpl::restore() { if (m_state->booleanProperty(HeapProfilerAgentState::heapProfilerEnabled, false)) m_frontend.resetProfiles(); if (m_state->booleanProperty( HeapProfilerAgentState::heapObjectsTrackingEnabled, false)) startTrackingHeapObjectsInternal(m_state->booleanProperty( HeapProfilerAgentState::allocationTrackingEnabled, false)); if (m_state->booleanProperty( HeapProfilerAgentState::samplingHeapProfilerEnabled, false)) { double samplingInterval = m_state->doubleProperty( HeapProfilerAgentState::samplingHeapProfilerInterval, -1); DCHECK_GE(samplingInterval, 0); int flags = m_state->integerProperty( HeapProfilerAgentState::samplingHeapProfilerFlags, 0); startSampling( Maybe(samplingInterval), Maybe( flags & v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMajorGC), Maybe( flags & v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMinorGC)); } } void V8HeapProfilerAgentImpl::collectGarbage( std::unique_ptr callback) { v8::base::MutexGuard lock(&m_asyncCallbacks->m_mutex); m_asyncCallbacks->m_gcCallbacks.push_back(std::move(callback)); v8::debug::GetCurrentPlatform() ->GetForegroundTaskRunner(m_isolate) ->PostNonNestableTask( std::make_unique(m_isolate, m_asyncCallbacks)); } Response V8HeapProfilerAgentImpl::startTrackingHeapObjects( Maybe trackAllocations) { m_state->setBoolean(HeapProfilerAgentState::heapObjectsTrackingEnabled, true); bool allocationTrackingEnabled = trackAllocations.fromMaybe(false); m_state->setBoolean(HeapProfilerAgentState::allocationTrackingEnabled, allocationTrackingEnabled); startTrackingHeapObjectsInternal(allocationTrackingEnabled); return Response::Success(); } Response V8HeapProfilerAgentImpl::stopTrackingHeapObjects( Maybe reportProgress, Maybe treatGlobalObjectsAsRoots, Maybe captureNumericValue, Maybe exposeInternals) { requestHeapStatsUpdate(); takeHeapSnapshotNow( HeapSnapshotProtocolOptions( std::move(reportProgress), std::move(treatGlobalObjectsAsRoots), std::move(captureNumericValue), std::move(exposeInternals)), cppgc::EmbedderStackState::kMayContainHeapPointers); stopTrackingHeapObjectsInternal(); return Response::Success(); } Response V8HeapProfilerAgentImpl::enable() { m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, true); return Response::Success(); } Response V8HeapProfilerAgentImpl::disable() { stopTrackingHeapObjectsInternal(); if (m_state->booleanProperty( HeapProfilerAgentState::samplingHeapProfilerEnabled, false)) { v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); if (profiler) profiler->StopSamplingHeapProfiler(); } m_isolate->GetHeapProfiler()->ClearObjectIds(); m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, false); return Response::Success(); } void V8HeapProfilerAgentImpl::takeHeapSnapshot( Maybe reportProgress, Maybe treatGlobalObjectsAsRoots, Maybe captureNumericValue, Maybe exposeInternals, std::unique_ptr callback) { HeapSnapshotProtocolOptions protocolOptions( std::move(reportProgress), std::move(treatGlobalObjectsAsRoots), std::move(captureNumericValue), std::move(exposeInternals)); std::shared_ptr task_runner = v8::debug::GetCurrentPlatform()->GetForegroundTaskRunner(m_isolate); // Heap snapshots can be more accurate if we wait until the stack is empty and // run the garbage collector without conservative stack scanning, as done in // V8HeapProfilerAgentImpl::collectGarbage. However, heap snapshots can also // be requested while paused in the debugger, in which case the snapshot must // be taken immediately with conservative stack scanning enabled. if (m_session->inspector()->debugger()->isPaused() || !task_runner->NonNestableTasksEnabled()) { Response response = takeHeapSnapshotNow( protocolOptions, cppgc::EmbedderStackState::kMayContainHeapPointers); if (response.IsSuccess()) { callback->sendSuccess(); } else { callback->sendFailure(std::move(response)); } return; } std::unique_ptr task = std::make_unique( this, m_asyncCallbacks, protocolOptions, std::move(callback)); m_asyncCallbacks->m_heapSnapshotTasks.push_back(task.get()); task_runner->PostNonNestableTask(std::move(task)); } Response V8HeapProfilerAgentImpl::takeHeapSnapshotNow( const HeapSnapshotProtocolOptions& protocolOptions, cppgc::EmbedderStackState stackState) { v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); if (!profiler) return Response::ServerError("Cannot access v8 heap profiler"); std::unique_ptr progress; if (protocolOptions.m_reportProgress) progress.reset(new HeapSnapshotProgress(&m_frontend)); GlobalObjectNameResolver resolver(m_session); v8::HeapProfiler::HeapSnapshotOptions options; options.global_object_name_resolver = &resolver; options.control = progress.get(); options.snapshot_mode = protocolOptions.m_exposeInternals || // Not treating global objects as roots results into exposing // internals. !protocolOptions.m_treatGlobalObjectsAsRoots ? v8::HeapProfiler::HeapSnapshotMode::kExposeInternals : v8::HeapProfiler::HeapSnapshotMode::kRegular; options.numerics_mode = protocolOptions.m_captureNumericValue ? v8::HeapProfiler::NumericsMode::kExposeNumericValues : v8::HeapProfiler::NumericsMode::kHideNumericValues; options.stack_state = stackState; const v8::HeapSnapshot* snapshot = profiler->TakeHeapSnapshot(options); if (!snapshot) return Response::ServerError("Failed to take heap snapshot"); HeapSnapshotOutputStream stream(&m_frontend); snapshot->Serialize(&stream); const_cast(snapshot)->Delete(); return Response::Success(); } Response V8HeapProfilerAgentImpl::getObjectByHeapObjectId( const String16& heapSnapshotObjectId, Maybe objectGroup, std::unique_ptr* result) { bool ok; int id = heapSnapshotObjectId.toInteger(&ok); if (!ok) return Response::ServerError("Invalid heap snapshot object id"); v8::HandleScope handles(m_isolate); v8::Local heapObject = objectByHeapObjectId(m_isolate, id); if (heapObject.IsEmpty()) return Response::ServerError("Object is not available"); if (!m_session->inspector()->client()->isInspectableHeapObject(heapObject)) return Response::ServerError("Object is not available"); v8::Local creationContext; if (!heapObject->GetCreationContext().ToLocal(&creationContext)) { return Response::ServerError("Object is not available"); } *result = m_session->wrapObject(creationContext, heapObject, objectGroup.fromMaybe(""), false); if (!*result) return Response::ServerError("Object is not available"); return Response::Success(); } void V8HeapProfilerAgentImpl::takePendingHeapSnapshots() { // Each task will remove itself from m_heapSnapshotTasks. while (m_asyncCallbacks->m_heapSnapshotTasks.size() > 0) { m_asyncCallbacks->m_heapSnapshotTasks.front()->Run( cppgc::EmbedderStackState::kMayContainHeapPointers); } } Response V8HeapProfilerAgentImpl::addInspectedHeapObject( const String16& inspectedHeapObjectId) { bool ok; int id = inspectedHeapObjectId.toInteger(&ok); if (!ok) return Response::ServerError("Invalid heap snapshot object id"); v8::HandleScope handles(m_isolate); v8::Local heapObject = objectByHeapObjectId(m_isolate, id); if (heapObject.IsEmpty()) return Response::ServerError("Object is not available"); if (!m_session->inspector()->client()->isInspectableHeapObject(heapObject)) return Response::ServerError("Object is not available"); m_session->addInspectedObject( std::unique_ptr(new InspectableHeapObject(id))); return Response::Success(); } Response V8HeapProfilerAgentImpl::getHeapObjectId( const String16& objectId, String16* heapSnapshotObjectId) { v8::HandleScope handles(m_isolate); v8::Local value; v8::Local context; Response response = m_session->unwrapObject(objectId, &value, &context, nullptr); if (!response.IsSuccess()) return response; if (value->IsUndefined()) return Response::InternalError(); v8::SnapshotObjectId id = m_isolate->GetHeapProfiler()->GetObjectId(value); *heapSnapshotObjectId = String16::fromInteger(static_cast(id)); return Response::Success(); } void V8HeapProfilerAgentImpl::requestHeapStatsUpdate() { HeapStatsStream stream(&m_frontend); v8::SnapshotObjectId lastSeenObjectId = m_isolate->GetHeapProfiler()->GetHeapStats(&stream); m_frontend.lastSeenObjectId( lastSeenObjectId, m_session->inspector()->client()->currentTimeMS()); } // static void V8HeapProfilerAgentImpl::onTimer(void* data) { reinterpret_cast(data)->onTimerImpl(); } static constexpr v8::base::TimeDelta kDefaultTimerDelay = v8::base::TimeDelta::FromMilliseconds(50); void V8HeapProfilerAgentImpl::onTimerImpl() { v8::base::TimeTicks start = v8::base::TimeTicks::Now(); requestHeapStatsUpdate(); v8::base::TimeDelta elapsed = v8::base::TimeTicks::Now() - start; if (m_hasTimer) { // requestHeapStatsUpdate can take a long time on large heaps. To ensure // that there is still some time for the thread to make progress on running // JavaScript or doing other useful work, we'll adjust the timer delay here. const v8::base::TimeDelta minAcceptableDelay = std::max(elapsed * 2, kDefaultTimerDelay); const v8::base::TimeDelta idealDelay = std::max(elapsed * 3, kDefaultTimerDelay); const v8::base::TimeDelta maxAcceptableDelay = std::max(elapsed * 4, kDefaultTimerDelay); if (m_timerDelayInSeconds < minAcceptableDelay.InSecondsF() || m_timerDelayInSeconds > maxAcceptableDelay.InSecondsF()) { // The existing timer's speed is not very close to ideal, so cancel it and // start a new timer. m_session->inspector()->client()->cancelTimer( reinterpret_cast(this)); m_timerDelayInSeconds = idealDelay.InSecondsF(); m_session->inspector()->client()->startRepeatingTimer( m_timerDelayInSeconds, &V8HeapProfilerAgentImpl::onTimer, reinterpret_cast(this)); } } } void V8HeapProfilerAgentImpl::startTrackingHeapObjectsInternal( bool trackAllocations) { m_isolate->GetHeapProfiler()->StartTrackingHeapObjects(trackAllocations); if (!m_hasTimer) { m_hasTimer = true; m_timerDelayInSeconds = kDefaultTimerDelay.InSecondsF(); m_session->inspector()->client()->startRepeatingTimer( m_timerDelayInSeconds, &V8HeapProfilerAgentImpl::onTimer, reinterpret_cast(this)); } } void V8HeapProfilerAgentImpl::stopTrackingHeapObjectsInternal() { if (m_hasTimer) { m_session->inspector()->client()->cancelTimer( reinterpret_cast(this)); m_hasTimer = false; } m_isolate->GetHeapProfiler()->StopTrackingHeapObjects(); m_state->setBoolean(HeapProfilerAgentState::heapObjectsTrackingEnabled, false); m_state->setBoolean(HeapProfilerAgentState::allocationTrackingEnabled, false); } Response V8HeapProfilerAgentImpl::startSampling( Maybe samplingInterval, Maybe includeObjectsCollectedByMajorGC, Maybe includeObjectsCollectedByMinorGC) { v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); if (!profiler) return Response::ServerError("Cannot access v8 heap profiler"); const unsigned defaultSamplingInterval = 1 << 15; double samplingIntervalValue = samplingInterval.fromMaybe(defaultSamplingInterval); if (samplingIntervalValue <= 0.0) { return Response::ServerError("Invalid sampling interval"); } m_state->setDouble(HeapProfilerAgentState::samplingHeapProfilerInterval, samplingIntervalValue); m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled, true); int flags = v8::HeapProfiler::kSamplingForceGC; if (includeObjectsCollectedByMajorGC.fromMaybe(false)) { flags |= v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMajorGC; } if (includeObjectsCollectedByMinorGC.fromMaybe(false)) { flags |= v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMinorGC; } m_state->setInteger(HeapProfilerAgentState::samplingHeapProfilerFlags, flags); profiler->StartSamplingHeapProfiler( static_cast(samplingIntervalValue), 128, static_cast(flags)); return Response::Success(); } namespace { std::unique_ptr buildSampingHeapProfileNode(v8::Isolate* isolate, const v8::AllocationProfile::Node* node) { auto children = std::make_unique< protocol::Array>(); for (const auto* child : node->children) children->emplace_back(buildSampingHeapProfileNode(isolate, child)); size_t selfSize = 0; for (const auto& allocation : node->allocations) selfSize += allocation.size * allocation.count; std::unique_ptr callFrame = protocol::Runtime::CallFrame::create() .setFunctionName(toProtocolString(isolate, node->name)) .setScriptId(String16::fromInteger(node->script_id)) .setUrl(toProtocolString(isolate, node->script_name)) .setLineNumber(node->line_number - 1) .setColumnNumber(node->column_number - 1) .build(); std::unique_ptr result = protocol::HeapProfiler::SamplingHeapProfileNode::create() .setCallFrame(std::move(callFrame)) .setSelfSize(selfSize) .setChildren(std::move(children)) .setId(node->node_id) .build(); return result; } } // namespace Response V8HeapProfilerAgentImpl::stopSampling( std::unique_ptr* profile) { Response result = getSamplingProfile(profile); if (result.IsSuccess()) { m_isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled, false); } return result; } Response V8HeapProfilerAgentImpl::getSamplingProfile( std::unique_ptr* profile) { v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); // Need a scope as v8::AllocationProfile contains Local handles. v8::HandleScope scope(m_isolate); std::unique_ptr v8Profile( profiler->GetAllocationProfile()); if (!v8Profile) return Response::ServerError("V8 sampling heap profiler was not started."); v8::AllocationProfile::Node* root = v8Profile->GetRootNode(); auto samples = std::make_unique< protocol::Array>(); for (const auto& sample : v8Profile->GetSamples()) { samples->emplace_back( protocol::HeapProfiler::SamplingHeapProfileSample::create() .setSize(sample.size * sample.count) .setNodeId(sample.node_id) .setOrdinal(static_cast(sample.sample_id)) .build()); } *profile = protocol::HeapProfiler::SamplingHeapProfile::create() .setHead(buildSampingHeapProfileNode(m_isolate, root)) .setSamples(std::move(samples)) .build(); return Response::Success(); } } // namespace v8_inspector