| // Copyright 2015 The Chromium 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 "platform/v8_inspector/V8ProfilerAgentImpl.h" |
| |
| #include "platform/v8_inspector/Atomics.h" |
| #include "platform/v8_inspector/V8InspectorImpl.h" |
| #include "platform/v8_inspector/V8InspectorSessionImpl.h" |
| #include "platform/v8_inspector/V8StackTraceImpl.h" |
| #include "platform/v8_inspector/V8StringUtil.h" |
| #include <v8-profiler.h> |
| |
| #include <vector> |
| |
| #define ENSURE_V8_VERSION(major, minor) \ |
| (V8_MAJOR_VERSION * 1000 + V8_MINOR_VERSION >= (major) * 1000 + (minor)) |
| |
| 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) |
| { |
| std::unique_ptr<protocol::Array<protocol::Profiler::PositionTickInfo>> array = protocol::Array<protocol::Profiler::PositionTickInfo>::create(); |
| unsigned lineCount = node->GetHitLineCount(); |
| if (!lineCount) |
| return array; |
| |
| 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::CPUProfileNode> buildInspectorObjectFor(v8::Isolate* isolate, const v8::CpuProfileNode* node) |
| { |
| v8::HandleScope handleScope(isolate); |
| |
| std::unique_ptr<protocol::Array<protocol::Profiler::PositionTickInfo>> positionTicks = buildInspectorObjectForPositionTicks(node); |
| std::unique_ptr<protocol::Runtime::CallFrame> 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(); |
| std::unique_ptr<protocol::Profiler::CPUProfileNode> result = protocol::Profiler::CPUProfileNode::create() |
| .setCallFrame(std::move(callFrame)) |
| .setHitCount(node->GetHitCount()) |
| .setPositionTicks(std::move(positionTicks)) |
| .setDeoptReason(node->GetBailoutReason()) |
| .setId(node->GetNodeId()).build(); |
| |
| const int childrenCount = node->GetChildrenCount(); |
| if (childrenCount) { |
| std::unique_ptr<protocol::Array<int>> children = protocol::Array<int>::create(); |
| for (int i = 0; i < childrenCount; i++) |
| children->addItem(node->GetChild(i)->GetNodeId()); |
| result->setChildren(std::move(children)); |
| } |
| return result; |
| } |
| |
| std::unique_ptr<protocol::Array<int>> buildInspectorObjectForSamples(v8::CpuProfile* v8profile) |
| { |
| std::unique_ptr<protocol::Array<int>> 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) |
| { |
| std::unique_ptr<protocol::Array<int>> 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::CPUProfileNode>* 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::CPUProfile> createCPUProfile(v8::Isolate* isolate, v8::CpuProfile* v8profile) |
| { |
| std::unique_ptr<protocol::Array<protocol::Profiler::CPUProfileNode>> nodes = protocol::Array<protocol::Profiler::CPUProfileNode>::create(); |
| flattenNodesTree(isolate, v8profile->GetTopDownRoot(), nodes.get()); |
| |
| std::unique_ptr<protocol::Profiler::CPUProfile> profile = protocol::Profiler::CPUProfile::create() |
| .setNodes(std::move(nodes)) |
| .setStartTime(static_cast<double>(v8profile->GetStartTime())) |
| .setEndTime(static_cast<double>(v8profile->GetEndTime())).build(); |
| profile->setSamples(buildInspectorObjectForSamples(v8profile)); |
| profile->setTimestampDeltas(buildInspectorObjectForTimestamps(v8profile)); |
| return profile; |
| } |
| |
| std::unique_ptr<protocol::Debugger::Location> currentDebugLocation(V8InspectorImpl* inspector) |
| { |
| std::unique_ptr<V8StackTrace> callStack = inspector->captureStackTrace(1); |
| std::unique_ptr<protocol::Debugger::Location> location = protocol::Debugger::Location::create() |
| .setScriptId(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 ENSURE_V8_VERSION(5, 4) |
| if (m_profiler) |
| m_profiler->Dispose(); |
| #endif |
| } |
| |
| 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::CPUProfile> 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); |
| } |
| |
| void V8ProfilerAgentImpl::enable(ErrorString*) |
| { |
| if (m_enabled) |
| return; |
| m_enabled = true; |
| #if ENSURE_V8_VERSION(5, 4) |
| DCHECK(!m_profiler); |
| m_profiler = v8::CpuProfiler::New(m_isolate); |
| #endif |
| m_state->setBoolean(ProfilerAgentState::profilerEnabled, true); |
| } |
| |
| void V8ProfilerAgentImpl::disable(ErrorString* errorString) |
| { |
| if (!m_enabled) |
| return; |
| for (size_t i = m_startedProfiles.size(); i > 0; --i) |
| stopProfiling(m_startedProfiles[i - 1].m_id, false); |
| m_startedProfiles.clear(); |
| stop(nullptr, nullptr); |
| #if ENSURE_V8_VERSION(5, 4) |
| m_profiler->Dispose(); |
| m_profiler = nullptr; |
| #endif |
| m_enabled = false; |
| m_state->setBoolean(ProfilerAgentState::profilerEnabled, false); |
| } |
| |
| void V8ProfilerAgentImpl::setSamplingInterval(ErrorString* error, int interval) |
| { |
| if (m_recordingCPUProfile) { |
| *error = "Cannot change sampling interval when profiling."; |
| return; |
| } |
| m_state->setInteger(ProfilerAgentState::samplingInterval, interval); |
| profiler()->SetSamplingInterval(interval); |
| } |
| |
| void V8ProfilerAgentImpl::restore() |
| { |
| DCHECK(!m_enabled); |
| if (!m_state->booleanProperty(ProfilerAgentState::profilerEnabled, false)) |
| return; |
| m_enabled = true; |
| #if ENSURE_V8_VERSION(5, 4) |
| DCHECK(!m_profiler); |
| m_profiler = v8::CpuProfiler::New(m_isolate); |
| #endif |
| int interval = 0; |
| m_state->getInteger(ProfilerAgentState::samplingInterval, &interval); |
| if (interval) |
| profiler()->SetSamplingInterval(interval); |
| if (m_state->booleanProperty(ProfilerAgentState::userInitiatedProfiling, false)) { |
| ErrorString error; |
| start(&error); |
| } |
| } |
| |
| void V8ProfilerAgentImpl::start(ErrorString* error) |
| { |
| if (m_recordingCPUProfile) |
| return; |
| if (!m_enabled) { |
| *error = "Profiler is not enabled"; |
| return; |
| } |
| m_recordingCPUProfile = true; |
| m_frontendInitiatedProfileId = nextProfileId(); |
| startProfiling(m_frontendInitiatedProfileId); |
| m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, true); |
| } |
| |
| void V8ProfilerAgentImpl::stop(ErrorString* errorString, std::unique_ptr<protocol::Profiler::CPUProfile>* profile) |
| { |
| if (!m_recordingCPUProfile) { |
| if (errorString) |
| *errorString = "No recording profiles found"; |
| return; |
| } |
| m_recordingCPUProfile = false; |
| std::unique_ptr<protocol::Profiler::CPUProfile> cpuProfile = stopProfiling(m_frontendInitiatedProfileId, !!profile); |
| if (profile) { |
| *profile = std::move(cpuProfile); |
| if (!profile->get() && errorString) |
| *errorString = "Profile is not found"; |
| } |
| m_frontendInitiatedProfileId = String16(); |
| m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, false); |
| } |
| |
| String16 V8ProfilerAgentImpl::nextProfileId() |
| { |
| return String16::fromInteger(atomicIncrement(&s_lastProfileId)); |
| } |
| |
| void V8ProfilerAgentImpl::startProfiling(const String16& title) |
| { |
| v8::HandleScope handleScope(m_isolate); |
| profiler()->StartProfiling(toV8String(m_isolate, title), true); |
| } |
| |
| std::unique_ptr<protocol::Profiler::CPUProfile> V8ProfilerAgentImpl::stopProfiling(const String16& title, bool serialize) |
| { |
| v8::HandleScope handleScope(m_isolate); |
| v8::CpuProfile* profile = profiler()->StopProfiling(toV8String(m_isolate, title)); |
| if (!profile) |
| return nullptr; |
| std::unique_ptr<protocol::Profiler::CPUProfile> result; |
| if (serialize) |
| result = createCPUProfile(m_isolate, profile); |
| profile->Delete(); |
| return result; |
| } |
| |
| bool V8ProfilerAgentImpl::isRecording() const |
| { |
| return m_recordingCPUProfile || !m_startedProfiles.empty(); |
| } |
| |
| v8::CpuProfiler* V8ProfilerAgentImpl::profiler() |
| { |
| #if ENSURE_V8_VERSION(5, 4) |
| return m_profiler; |
| #else |
| return m_isolate->GetCpuProfiler(); |
| #endif |
| } |
| |
| } // namespace v8_inspector |