1 // Copyright 2015 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/inspector/v8-profiler-agent-impl.h"
6 
7 #include <vector>
8 
9 #include "src/base/atomicops.h"
10 #include "src/inspector/protocol/Protocol.h"
11 #include "src/inspector/string-util.h"
12 #include "src/inspector/v8-debugger.h"
13 #include "src/inspector/v8-inspector-impl.h"
14 #include "src/inspector/v8-inspector-session-impl.h"
15 #include "src/inspector/v8-stack-trace-impl.h"
16 
17 #include "include/v8-profiler.h"
18 
19 namespace v8_inspector {
20 
21 namespace ProfilerAgentState {
22 static const char samplingInterval[] = "samplingInterval";
23 static const char userInitiatedProfiling[] = "userInitiatedProfiling";
24 static const char profilerEnabled[] = "profilerEnabled";
25 }
26 
27 namespace {
28 
29 std::unique_ptr<protocol::Array<protocol::Profiler::PositionTickInfo>>
buildInspectorObjectForPositionTicks(const v8::CpuProfileNode * node)30 buildInspectorObjectForPositionTicks(const v8::CpuProfileNode* node) {
31   unsigned lineCount = node->GetHitLineCount();
32   if (!lineCount) return nullptr;
33   auto array = protocol::Array<protocol::Profiler::PositionTickInfo>::create();
34   std::vector<v8::CpuProfileNode::LineTick> entries(lineCount);
35   if (node->GetLineTicks(&entries[0], lineCount)) {
36     for (unsigned i = 0; i < lineCount; i++) {
37       std::unique_ptr<protocol::Profiler::PositionTickInfo> line =
38           protocol::Profiler::PositionTickInfo::create()
39               .setLine(entries[i].line)
40               .setTicks(entries[i].hit_count)
41               .build();
42       array->addItem(std::move(line));
43     }
44   }
45   return array;
46 }
47 
buildInspectorObjectFor(v8::Isolate * isolate,const v8::CpuProfileNode * node)48 std::unique_ptr<protocol::Profiler::ProfileNode> buildInspectorObjectFor(
49     v8::Isolate* isolate, const v8::CpuProfileNode* node) {
50   v8::HandleScope handleScope(isolate);
51   auto callFrame =
52       protocol::Runtime::CallFrame::create()
53           .setFunctionName(toProtocolString(node->GetFunctionName()))
54           .setScriptId(String16::fromInteger(node->GetScriptId()))
55           .setUrl(toProtocolString(node->GetScriptResourceName()))
56           .setLineNumber(node->GetLineNumber() - 1)
57           .setColumnNumber(node->GetColumnNumber() - 1)
58           .build();
59   auto result = protocol::Profiler::ProfileNode::create()
60                     .setCallFrame(std::move(callFrame))
61                     .setHitCount(node->GetHitCount())
62                     .setId(node->GetNodeId())
63                     .build();
64 
65   const int childrenCount = node->GetChildrenCount();
66   if (childrenCount) {
67     auto children = protocol::Array<int>::create();
68     for (int i = 0; i < childrenCount; i++)
69       children->addItem(node->GetChild(i)->GetNodeId());
70     result->setChildren(std::move(children));
71   }
72 
73   const char* deoptReason = node->GetBailoutReason();
74   if (deoptReason && deoptReason[0] && strcmp(deoptReason, "no reason"))
75     result->setDeoptReason(deoptReason);
76 
77   auto positionTicks = buildInspectorObjectForPositionTicks(node);
78   if (positionTicks) result->setPositionTicks(std::move(positionTicks));
79 
80   return result;
81 }
82 
buildInspectorObjectForSamples(v8::CpuProfile * v8profile)83 std::unique_ptr<protocol::Array<int>> buildInspectorObjectForSamples(
84     v8::CpuProfile* v8profile) {
85   auto array = protocol::Array<int>::create();
86   int count = v8profile->GetSamplesCount();
87   for (int i = 0; i < count; i++)
88     array->addItem(v8profile->GetSample(i)->GetNodeId());
89   return array;
90 }
91 
buildInspectorObjectForTimestamps(v8::CpuProfile * v8profile)92 std::unique_ptr<protocol::Array<int>> buildInspectorObjectForTimestamps(
93     v8::CpuProfile* v8profile) {
94   auto array = protocol::Array<int>::create();
95   int count = v8profile->GetSamplesCount();
96   uint64_t lastTime = v8profile->GetStartTime();
97   for (int i = 0; i < count; i++) {
98     uint64_t ts = v8profile->GetSampleTimestamp(i);
99     array->addItem(static_cast<int>(ts - lastTime));
100     lastTime = ts;
101   }
102   return array;
103 }
104 
flattenNodesTree(v8::Isolate * isolate,const v8::CpuProfileNode * node,protocol::Array<protocol::Profiler::ProfileNode> * list)105 void flattenNodesTree(v8::Isolate* isolate, const v8::CpuProfileNode* node,
106                       protocol::Array<protocol::Profiler::ProfileNode>* list) {
107   list->addItem(buildInspectorObjectFor(isolate, node));
108   const int childrenCount = node->GetChildrenCount();
109   for (int i = 0; i < childrenCount; i++)
110     flattenNodesTree(isolate, node->GetChild(i), list);
111 }
112 
createCPUProfile(v8::Isolate * isolate,v8::CpuProfile * v8profile)113 std::unique_ptr<protocol::Profiler::Profile> createCPUProfile(
114     v8::Isolate* isolate, v8::CpuProfile* v8profile) {
115   auto nodes = protocol::Array<protocol::Profiler::ProfileNode>::create();
116   flattenNodesTree(isolate, v8profile->GetTopDownRoot(), nodes.get());
117   return protocol::Profiler::Profile::create()
118       .setNodes(std::move(nodes))
119       .setStartTime(static_cast<double>(v8profile->GetStartTime()))
120       .setEndTime(static_cast<double>(v8profile->GetEndTime()))
121       .setSamples(buildInspectorObjectForSamples(v8profile))
122       .setTimeDeltas(buildInspectorObjectForTimestamps(v8profile))
123       .build();
124 }
125 
currentDebugLocation(V8InspectorImpl * inspector)126 std::unique_ptr<protocol::Debugger::Location> currentDebugLocation(
127     V8InspectorImpl* inspector) {
128   std::unique_ptr<V8StackTraceImpl> callStack =
129       inspector->debugger()->captureStackTrace(false /* fullStack */);
130   auto location = protocol::Debugger::Location::create()
131                       .setScriptId(toString16(callStack->topScriptId()))
132                       .setLineNumber(callStack->topLineNumber())
133                       .build();
134   location->setColumnNumber(callStack->topColumnNumber());
135   return location;
136 }
137 
138 volatile int s_lastProfileId = 0;
139 
140 }  // namespace
141 
142 class V8ProfilerAgentImpl::ProfileDescriptor {
143  public:
ProfileDescriptor(const String16 & id,const String16 & title)144   ProfileDescriptor(const String16& id, const String16& title)
145       : m_id(id), m_title(title) {}
146   String16 m_id;
147   String16 m_title;
148 };
149 
V8ProfilerAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * frontendChannel,protocol::DictionaryValue * state)150 V8ProfilerAgentImpl::V8ProfilerAgentImpl(
151     V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
152     protocol::DictionaryValue* state)
153     : m_session(session),
154       m_isolate(m_session->inspector()->isolate()),
155       m_profiler(nullptr),
156       m_state(state),
157       m_frontend(frontendChannel),
158       m_enabled(false),
159       m_recordingCPUProfile(false) {}
160 
~V8ProfilerAgentImpl()161 V8ProfilerAgentImpl::~V8ProfilerAgentImpl() {
162   if (m_profiler) m_profiler->Dispose();
163 }
164 
consoleProfile(const String16 & title)165 void V8ProfilerAgentImpl::consoleProfile(const String16& title) {
166   if (!m_enabled) return;
167   String16 id = nextProfileId();
168   m_startedProfiles.push_back(ProfileDescriptor(id, title));
169   startProfiling(id);
170   m_frontend.consoleProfileStarted(
171       id, currentDebugLocation(m_session->inspector()), title);
172 }
173 
consoleProfileEnd(const String16 & title)174 void V8ProfilerAgentImpl::consoleProfileEnd(const String16& title) {
175   if (!m_enabled) return;
176   String16 id;
177   String16 resolvedTitle;
178   // Take last started profile if no title was passed.
179   if (title.isEmpty()) {
180     if (m_startedProfiles.empty()) return;
181     id = m_startedProfiles.back().m_id;
182     resolvedTitle = m_startedProfiles.back().m_title;
183     m_startedProfiles.pop_back();
184   } else {
185     for (size_t i = 0; i < m_startedProfiles.size(); i++) {
186       if (m_startedProfiles[i].m_title == title) {
187         resolvedTitle = title;
188         id = m_startedProfiles[i].m_id;
189         m_startedProfiles.erase(m_startedProfiles.begin() + i);
190         break;
191       }
192     }
193     if (id.isEmpty()) return;
194   }
195   std::unique_ptr<protocol::Profiler::Profile> profile =
196       stopProfiling(id, true);
197   if (!profile) return;
198   std::unique_ptr<protocol::Debugger::Location> location =
199       currentDebugLocation(m_session->inspector());
200   m_frontend.consoleProfileFinished(id, std::move(location), std::move(profile),
201                                     resolvedTitle);
202 }
203 
enable()204 Response V8ProfilerAgentImpl::enable() {
205   if (m_enabled) return Response::OK();
206   m_enabled = true;
207   DCHECK(!m_profiler);
208   m_profiler = v8::CpuProfiler::New(m_isolate);
209   m_state->setBoolean(ProfilerAgentState::profilerEnabled, true);
210   return Response::OK();
211 }
212 
disable()213 Response V8ProfilerAgentImpl::disable() {
214   if (!m_enabled) return Response::OK();
215   for (size_t i = m_startedProfiles.size(); i > 0; --i)
216     stopProfiling(m_startedProfiles[i - 1].m_id, false);
217   m_startedProfiles.clear();
218   stop(nullptr);
219   m_profiler->Dispose();
220   m_profiler = nullptr;
221   m_enabled = false;
222   m_state->setBoolean(ProfilerAgentState::profilerEnabled, false);
223   return Response::OK();
224 }
225 
setSamplingInterval(int interval)226 Response V8ProfilerAgentImpl::setSamplingInterval(int interval) {
227   if (m_recordingCPUProfile)
228     return Response::Error("Cannot change sampling interval when profiling.");
229   m_state->setInteger(ProfilerAgentState::samplingInterval, interval);
230   m_profiler->SetSamplingInterval(interval);
231   return Response::OK();
232 }
233 
restore()234 void V8ProfilerAgentImpl::restore() {
235   DCHECK(!m_enabled);
236   if (!m_state->booleanProperty(ProfilerAgentState::profilerEnabled, false))
237     return;
238   m_enabled = true;
239   DCHECK(!m_profiler);
240   m_profiler = v8::CpuProfiler::New(m_isolate);
241   int interval = 0;
242   m_state->getInteger(ProfilerAgentState::samplingInterval, &interval);
243   if (interval) m_profiler->SetSamplingInterval(interval);
244   if (m_state->booleanProperty(ProfilerAgentState::userInitiatedProfiling,
245                                false)) {
246     start();
247   }
248 }
249 
start()250 Response V8ProfilerAgentImpl::start() {
251   if (m_recordingCPUProfile) return Response::OK();
252   if (!m_enabled) return Response::Error("Profiler is not enabled");
253   m_recordingCPUProfile = true;
254   m_frontendInitiatedProfileId = nextProfileId();
255   startProfiling(m_frontendInitiatedProfileId);
256   m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, true);
257   return Response::OK();
258 }
259 
stop(std::unique_ptr<protocol::Profiler::Profile> * profile)260 Response V8ProfilerAgentImpl::stop(
261     std::unique_ptr<protocol::Profiler::Profile>* profile) {
262   if (!m_recordingCPUProfile)
263     return Response::Error("No recording profiles found");
264   m_recordingCPUProfile = false;
265   std::unique_ptr<protocol::Profiler::Profile> cpuProfile =
266       stopProfiling(m_frontendInitiatedProfileId, !!profile);
267   if (profile) {
268     *profile = std::move(cpuProfile);
269     if (!profile->get()) return Response::Error("Profile is not found");
270   }
271   m_frontendInitiatedProfileId = String16();
272   m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, false);
273   return Response::OK();
274 }
275 
nextProfileId()276 String16 V8ProfilerAgentImpl::nextProfileId() {
277   return String16::fromInteger(
278       v8::base::NoBarrier_AtomicIncrement(&s_lastProfileId, 1));
279 }
280 
startProfiling(const String16 & title)281 void V8ProfilerAgentImpl::startProfiling(const String16& title) {
282   v8::HandleScope handleScope(m_isolate);
283   m_profiler->StartProfiling(toV8String(m_isolate, title), true);
284 }
285 
stopProfiling(const String16 & title,bool serialize)286 std::unique_ptr<protocol::Profiler::Profile> V8ProfilerAgentImpl::stopProfiling(
287     const String16& title, bool serialize) {
288   v8::HandleScope handleScope(m_isolate);
289   v8::CpuProfile* profile =
290       m_profiler->StopProfiling(toV8String(m_isolate, title));
291   if (!profile) return nullptr;
292   std::unique_ptr<protocol::Profiler::Profile> result;
293   if (serialize) result = createCPUProfile(m_isolate, profile);
294   profile->Delete();
295   return result;
296 }
297 
isRecording() const298 bool V8ProfilerAgentImpl::isRecording() const {
299   return m_recordingCPUProfile || !m_startedProfiles.empty();
300 }
301 
idleStarted()302 bool V8ProfilerAgentImpl::idleStarted() {
303   if (m_profiler) m_profiler->SetIdle(true);
304   return m_profiler;
305 }
306 
idleFinished()307 bool V8ProfilerAgentImpl::idleFinished() {
308   if (m_profiler) m_profiler->SetIdle(false);
309   return m_profiler;
310 }
311 
collectSample()312 void V8ProfilerAgentImpl::collectSample() {
313   if (m_profiler) m_profiler->CollectSample();
314 }
315 
316 }  // namespace v8_inspector
317