// Copyright 2015 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-profiler-agent-impl.h" #include <vector> #include "src/base/atomicops.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" #include "src/inspector/v8-stack-trace-impl.h" #include "include/v8-profiler.h" namespace v8_inspector { namespace ProfilerAgentState { static const char samplingInterval[] = "samplingInterval"; static const char userInitiatedProfiling[] = "userInitiatedProfiling"; static const char profilerEnabled[] = "profilerEnabled"; } namespace { std::unique_ptr<protocol::Array<protocol::Profiler::PositionTickInfo>> buildInspectorObjectForPositionTicks(const v8::CpuProfileNode* node) { unsigned lineCount = node->GetHitLineCount(); if (!lineCount) return nullptr; auto array = protocol::Array<protocol::Profiler::PositionTickInfo>::create(); std::vector<v8::CpuProfileNode::LineTick> entries(lineCount); if (node->GetLineTicks(&entries[0], lineCount)) { for (unsigned i = 0; i < lineCount; i++) { std::unique_ptr<protocol::Profiler::PositionTickInfo> line = protocol::Profiler::PositionTickInfo::create() .setLine(entries[i].line) .setTicks(entries[i].hit_count) .build(); array->addItem(std::move(line)); } } return array; } std::unique_ptr<protocol::Profiler::ProfileNode> buildInspectorObjectFor( v8::Isolate* isolate, const v8::CpuProfileNode* node) { v8::HandleScope handleScope(isolate); auto callFrame = protocol::Runtime::CallFrame::create() .setFunctionName(toProtocolString(node->GetFunctionName())) .setScriptId(String16::fromInteger(node->GetScriptId())) .setUrl(toProtocolString(node->GetScriptResourceName())) .setLineNumber(node->GetLineNumber() - 1) .setColumnNumber(node->GetColumnNumber() - 1) .build(); auto result = protocol::Profiler::ProfileNode::create() .setCallFrame(std::move(callFrame)) .setHitCount(node->GetHitCount()) .setId(node->GetNodeId()) .build(); const int childrenCount = node->GetChildrenCount(); if (childrenCount) { auto children = protocol::Array<int>::create(); for (int i = 0; i < childrenCount; i++) children->addItem(node->GetChild(i)->GetNodeId()); result->setChildren(std::move(children)); } const char* deoptReason = node->GetBailoutReason(); if (deoptReason && deoptReason[0] && strcmp(deoptReason, "no reason")) result->setDeoptReason(deoptReason); auto positionTicks = buildInspectorObjectForPositionTicks(node); if (positionTicks) result->setPositionTicks(std::move(positionTicks)); return result; } std::unique_ptr<protocol::Array<int>> buildInspectorObjectForSamples( v8::CpuProfile* v8profile) { auto array = protocol::Array<int>::create(); int count = v8profile->GetSamplesCount(); for (int i = 0; i < count; i++) array->addItem(v8profile->GetSample(i)->GetNodeId()); return array; } std::unique_ptr<protocol::Array<int>> buildInspectorObjectForTimestamps( v8::CpuProfile* v8profile) { auto array = protocol::Array<int>::create(); int count = v8profile->GetSamplesCount(); uint64_t lastTime = v8profile->GetStartTime(); for (int i = 0; i < count; i++) { uint64_t ts = v8profile->GetSampleTimestamp(i); array->addItem(static_cast<int>(ts - lastTime)); lastTime = ts; } return array; } void flattenNodesTree(v8::Isolate* isolate, const v8::CpuProfileNode* node, protocol::Array<protocol::Profiler::ProfileNode>* list) { list->addItem(buildInspectorObjectFor(isolate, node)); const int childrenCount = node->GetChildrenCount(); for (int i = 0; i < childrenCount; i++) flattenNodesTree(isolate, node->GetChild(i), list); } std::unique_ptr<protocol::Profiler::Profile> createCPUProfile( v8::Isolate* isolate, v8::CpuProfile* v8profile) { auto nodes = protocol::Array<protocol::Profiler::ProfileNode>::create(); flattenNodesTree(isolate, v8profile->GetTopDownRoot(), nodes.get()); return protocol::Profiler::Profile::create() .setNodes(std::move(nodes)) .setStartTime(static_cast<double>(v8profile->GetStartTime())) .setEndTime(static_cast<double>(v8profile->GetEndTime())) .setSamples(buildInspectorObjectForSamples(v8profile)) .setTimeDeltas(buildInspectorObjectForTimestamps(v8profile)) .build(); } std::unique_ptr<protocol::Debugger::Location> currentDebugLocation( V8InspectorImpl* inspector) { std::unique_ptr<V8StackTraceImpl> callStack = inspector->debugger()->captureStackTrace(false /* fullStack */); auto location = protocol::Debugger::Location::create() .setScriptId(toString16(callStack->topScriptId())) .setLineNumber(callStack->topLineNumber()) .build(); location->setColumnNumber(callStack->topColumnNumber()); return location; } volatile int s_lastProfileId = 0; } // namespace class V8ProfilerAgentImpl::ProfileDescriptor { public: ProfileDescriptor(const String16& id, const String16& title) : m_id(id), m_title(title) {} String16 m_id; String16 m_title; }; V8ProfilerAgentImpl::V8ProfilerAgentImpl( V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel, protocol::DictionaryValue* state) : m_session(session), m_isolate(m_session->inspector()->isolate()), m_profiler(nullptr), m_state(state), m_frontend(frontendChannel), m_enabled(false), m_recordingCPUProfile(false) {} V8ProfilerAgentImpl::~V8ProfilerAgentImpl() { if (m_profiler) m_profiler->Dispose(); } void V8ProfilerAgentImpl::consoleProfile(const String16& title) { if (!m_enabled) return; String16 id = nextProfileId(); m_startedProfiles.push_back(ProfileDescriptor(id, title)); startProfiling(id); m_frontend.consoleProfileStarted( id, currentDebugLocation(m_session->inspector()), title); } void V8ProfilerAgentImpl::consoleProfileEnd(const String16& title) { if (!m_enabled) return; String16 id; String16 resolvedTitle; // Take last started profile if no title was passed. if (title.isEmpty()) { if (m_startedProfiles.empty()) return; id = m_startedProfiles.back().m_id; resolvedTitle = m_startedProfiles.back().m_title; m_startedProfiles.pop_back(); } else { for (size_t i = 0; i < m_startedProfiles.size(); i++) { if (m_startedProfiles[i].m_title == title) { resolvedTitle = title; id = m_startedProfiles[i].m_id; m_startedProfiles.erase(m_startedProfiles.begin() + i); break; } } if (id.isEmpty()) return; } std::unique_ptr<protocol::Profiler::Profile> profile = stopProfiling(id, true); if (!profile) return; std::unique_ptr<protocol::Debugger::Location> location = currentDebugLocation(m_session->inspector()); m_frontend.consoleProfileFinished(id, std::move(location), std::move(profile), resolvedTitle); } Response V8ProfilerAgentImpl::enable() { if (m_enabled) return Response::OK(); m_enabled = true; DCHECK(!m_profiler); m_profiler = v8::CpuProfiler::New(m_isolate); m_state->setBoolean(ProfilerAgentState::profilerEnabled, true); return Response::OK(); } Response V8ProfilerAgentImpl::disable() { if (!m_enabled) return Response::OK(); for (size_t i = m_startedProfiles.size(); i > 0; --i) stopProfiling(m_startedProfiles[i - 1].m_id, false); m_startedProfiles.clear(); stop(nullptr); m_profiler->Dispose(); m_profiler = nullptr; m_enabled = false; m_state->setBoolean(ProfilerAgentState::profilerEnabled, false); return Response::OK(); } Response V8ProfilerAgentImpl::setSamplingInterval(int interval) { if (m_recordingCPUProfile) return Response::Error("Cannot change sampling interval when profiling."); m_state->setInteger(ProfilerAgentState::samplingInterval, interval); m_profiler->SetSamplingInterval(interval); return Response::OK(); } void V8ProfilerAgentImpl::restore() { DCHECK(!m_enabled); if (!m_state->booleanProperty(ProfilerAgentState::profilerEnabled, false)) return; m_enabled = true; DCHECK(!m_profiler); m_profiler = v8::CpuProfiler::New(m_isolate); int interval = 0; m_state->getInteger(ProfilerAgentState::samplingInterval, &interval); if (interval) m_profiler->SetSamplingInterval(interval); if (m_state->booleanProperty(ProfilerAgentState::userInitiatedProfiling, false)) { start(); } } Response V8ProfilerAgentImpl::start() { if (m_recordingCPUProfile) return Response::OK(); if (!m_enabled) return Response::Error("Profiler is not enabled"); m_recordingCPUProfile = true; m_frontendInitiatedProfileId = nextProfileId(); startProfiling(m_frontendInitiatedProfileId); m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, true); return Response::OK(); } Response V8ProfilerAgentImpl::stop( std::unique_ptr<protocol::Profiler::Profile>* profile) { if (!m_recordingCPUProfile) return Response::Error("No recording profiles found"); m_recordingCPUProfile = false; std::unique_ptr<protocol::Profiler::Profile> cpuProfile = stopProfiling(m_frontendInitiatedProfileId, !!profile); if (profile) { *profile = std::move(cpuProfile); if (!profile->get()) return Response::Error("Profile is not found"); } m_frontendInitiatedProfileId = String16(); m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, false); return Response::OK(); } String16 V8ProfilerAgentImpl::nextProfileId() { return String16::fromInteger( v8::base::NoBarrier_AtomicIncrement(&s_lastProfileId, 1)); } void V8ProfilerAgentImpl::startProfiling(const String16& title) { v8::HandleScope handleScope(m_isolate); m_profiler->StartProfiling(toV8String(m_isolate, title), true); } std::unique_ptr<protocol::Profiler::Profile> V8ProfilerAgentImpl::stopProfiling( const String16& title, bool serialize) { v8::HandleScope handleScope(m_isolate); v8::CpuProfile* profile = m_profiler->StopProfiling(toV8String(m_isolate, title)); if (!profile) return nullptr; std::unique_ptr<protocol::Profiler::Profile> result; if (serialize) result = createCPUProfile(m_isolate, profile); profile->Delete(); return result; } bool V8ProfilerAgentImpl::isRecording() const { return m_recordingCPUProfile || !m_startedProfiles.empty(); } bool V8ProfilerAgentImpl::idleStarted() { if (m_profiler) m_profiler->SetIdle(true); return m_profiler; } bool V8ProfilerAgentImpl::idleFinished() { if (m_profiler) m_profiler->SetIdle(false); return m_profiler; } void V8ProfilerAgentImpl::collectSample() { if (m_profiler) m_profiler->CollectSample(); } } // namespace v8_inspector