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 static const char preciseCoverageStarted[] = "preciseCoverageStarted";
26 }
27
28 namespace {
29
30 std::unique_ptr<protocol::Array<protocol::Profiler::PositionTickInfo>>
buildInspectorObjectForPositionTicks(const v8::CpuProfileNode * node)31 buildInspectorObjectForPositionTicks(const v8::CpuProfileNode* node) {
32 unsigned lineCount = node->GetHitLineCount();
33 if (!lineCount) return nullptr;
34 auto array = protocol::Array<protocol::Profiler::PositionTickInfo>::create();
35 std::vector<v8::CpuProfileNode::LineTick> entries(lineCount);
36 if (node->GetLineTicks(&entries[0], lineCount)) {
37 for (unsigned i = 0; i < lineCount; i++) {
38 std::unique_ptr<protocol::Profiler::PositionTickInfo> line =
39 protocol::Profiler::PositionTickInfo::create()
40 .setLine(entries[i].line)
41 .setTicks(entries[i].hit_count)
42 .build();
43 array->addItem(std::move(line));
44 }
45 }
46 return array;
47 }
48
buildInspectorObjectFor(v8::Isolate * isolate,const v8::CpuProfileNode * node)49 std::unique_ptr<protocol::Profiler::ProfileNode> buildInspectorObjectFor(
50 v8::Isolate* isolate, const v8::CpuProfileNode* node) {
51 v8::HandleScope handleScope(isolate);
52 auto callFrame =
53 protocol::Runtime::CallFrame::create()
54 .setFunctionName(toProtocolString(node->GetFunctionName()))
55 .setScriptId(String16::fromInteger(node->GetScriptId()))
56 .setUrl(toProtocolString(node->GetScriptResourceName()))
57 .setLineNumber(node->GetLineNumber() - 1)
58 .setColumnNumber(node->GetColumnNumber() - 1)
59 .build();
60 auto result = protocol::Profiler::ProfileNode::create()
61 .setCallFrame(std::move(callFrame))
62 .setHitCount(node->GetHitCount())
63 .setId(node->GetNodeId())
64 .build();
65
66 const int childrenCount = node->GetChildrenCount();
67 if (childrenCount) {
68 auto children = protocol::Array<int>::create();
69 for (int i = 0; i < childrenCount; i++)
70 children->addItem(node->GetChild(i)->GetNodeId());
71 result->setChildren(std::move(children));
72 }
73
74 const char* deoptReason = node->GetBailoutReason();
75 if (deoptReason && deoptReason[0] && strcmp(deoptReason, "no reason"))
76 result->setDeoptReason(deoptReason);
77
78 auto positionTicks = buildInspectorObjectForPositionTicks(node);
79 if (positionTicks) result->setPositionTicks(std::move(positionTicks));
80
81 return result;
82 }
83
buildInspectorObjectForSamples(v8::CpuProfile * v8profile)84 std::unique_ptr<protocol::Array<int>> buildInspectorObjectForSamples(
85 v8::CpuProfile* v8profile) {
86 auto array = protocol::Array<int>::create();
87 int count = v8profile->GetSamplesCount();
88 for (int i = 0; i < count; i++)
89 array->addItem(v8profile->GetSample(i)->GetNodeId());
90 return array;
91 }
92
buildInspectorObjectForTimestamps(v8::CpuProfile * v8profile)93 std::unique_ptr<protocol::Array<int>> buildInspectorObjectForTimestamps(
94 v8::CpuProfile* v8profile) {
95 auto array = protocol::Array<int>::create();
96 int count = v8profile->GetSamplesCount();
97 uint64_t lastTime = v8profile->GetStartTime();
98 for (int i = 0; i < count; i++) {
99 uint64_t ts = v8profile->GetSampleTimestamp(i);
100 array->addItem(static_cast<int>(ts - lastTime));
101 lastTime = ts;
102 }
103 return array;
104 }
105
flattenNodesTree(v8::Isolate * isolate,const v8::CpuProfileNode * node,protocol::Array<protocol::Profiler::ProfileNode> * list)106 void flattenNodesTree(v8::Isolate* isolate, const v8::CpuProfileNode* node,
107 protocol::Array<protocol::Profiler::ProfileNode>* list) {
108 list->addItem(buildInspectorObjectFor(isolate, node));
109 const int childrenCount = node->GetChildrenCount();
110 for (int i = 0; i < childrenCount; i++)
111 flattenNodesTree(isolate, node->GetChild(i), list);
112 }
113
createCPUProfile(v8::Isolate * isolate,v8::CpuProfile * v8profile)114 std::unique_ptr<protocol::Profiler::Profile> createCPUProfile(
115 v8::Isolate* isolate, v8::CpuProfile* v8profile) {
116 auto nodes = protocol::Array<protocol::Profiler::ProfileNode>::create();
117 flattenNodesTree(isolate, v8profile->GetTopDownRoot(), nodes.get());
118 return protocol::Profiler::Profile::create()
119 .setNodes(std::move(nodes))
120 .setStartTime(static_cast<double>(v8profile->GetStartTime()))
121 .setEndTime(static_cast<double>(v8profile->GetEndTime()))
122 .setSamples(buildInspectorObjectForSamples(v8profile))
123 .setTimeDeltas(buildInspectorObjectForTimestamps(v8profile))
124 .build();
125 }
126
currentDebugLocation(V8InspectorImpl * inspector)127 std::unique_ptr<protocol::Debugger::Location> currentDebugLocation(
128 V8InspectorImpl* inspector) {
129 std::unique_ptr<V8StackTraceImpl> callStack =
130 inspector->debugger()->captureStackTrace(false /* fullStack */);
131 auto location = protocol::Debugger::Location::create()
132 .setScriptId(toString16(callStack->topScriptId()))
133 .setLineNumber(callStack->topLineNumber())
134 .build();
135 location->setColumnNumber(callStack->topColumnNumber());
136 return location;
137 }
138
139 volatile int s_lastProfileId = 0;
140
141 } // namespace
142
143 class V8ProfilerAgentImpl::ProfileDescriptor {
144 public:
ProfileDescriptor(const String16 & id,const String16 & title)145 ProfileDescriptor(const String16& id, const String16& title)
146 : m_id(id), m_title(title) {}
147 String16 m_id;
148 String16 m_title;
149 };
150
V8ProfilerAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * frontendChannel,protocol::DictionaryValue * state)151 V8ProfilerAgentImpl::V8ProfilerAgentImpl(
152 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
153 protocol::DictionaryValue* state)
154 : m_session(session),
155 m_isolate(m_session->inspector()->isolate()),
156 m_state(state),
157 m_frontend(frontendChannel) {}
158
~V8ProfilerAgentImpl()159 V8ProfilerAgentImpl::~V8ProfilerAgentImpl() {
160 if (m_profiler) m_profiler->Dispose();
161 }
162
consoleProfile(const String16 & title)163 void V8ProfilerAgentImpl::consoleProfile(const String16& title) {
164 if (!m_enabled) return;
165 String16 id = nextProfileId();
166 m_startedProfiles.push_back(ProfileDescriptor(id, title));
167 startProfiling(id);
168 m_frontend.consoleProfileStarted(
169 id, currentDebugLocation(m_session->inspector()), title);
170 }
171
consoleProfileEnd(const String16 & title)172 void V8ProfilerAgentImpl::consoleProfileEnd(const String16& title) {
173 if (!m_enabled) return;
174 String16 id;
175 String16 resolvedTitle;
176 // Take last started profile if no title was passed.
177 if (title.isEmpty()) {
178 if (m_startedProfiles.empty()) return;
179 id = m_startedProfiles.back().m_id;
180 resolvedTitle = m_startedProfiles.back().m_title;
181 m_startedProfiles.pop_back();
182 } else {
183 for (size_t i = 0; i < m_startedProfiles.size(); i++) {
184 if (m_startedProfiles[i].m_title == title) {
185 resolvedTitle = title;
186 id = m_startedProfiles[i].m_id;
187 m_startedProfiles.erase(m_startedProfiles.begin() + i);
188 break;
189 }
190 }
191 if (id.isEmpty()) return;
192 }
193 std::unique_ptr<protocol::Profiler::Profile> profile =
194 stopProfiling(id, true);
195 if (!profile) return;
196 std::unique_ptr<protocol::Debugger::Location> location =
197 currentDebugLocation(m_session->inspector());
198 m_frontend.consoleProfileFinished(id, std::move(location), std::move(profile),
199 resolvedTitle);
200 }
201
enable()202 Response V8ProfilerAgentImpl::enable() {
203 if (m_enabled) return Response::OK();
204 m_enabled = true;
205 m_state->setBoolean(ProfilerAgentState::profilerEnabled, true);
206 return Response::OK();
207 }
208
disable()209 Response V8ProfilerAgentImpl::disable() {
210 if (!m_enabled) return Response::OK();
211 for (size_t i = m_startedProfiles.size(); i > 0; --i)
212 stopProfiling(m_startedProfiles[i - 1].m_id, false);
213 m_startedProfiles.clear();
214 stop(nullptr);
215 stopPreciseCoverage();
216 DCHECK(!m_profiler);
217 m_enabled = false;
218 m_state->setBoolean(ProfilerAgentState::profilerEnabled, false);
219 return Response::OK();
220 }
221
setSamplingInterval(int interval)222 Response V8ProfilerAgentImpl::setSamplingInterval(int interval) {
223 if (m_profiler) {
224 return Response::Error("Cannot change sampling interval when profiling.");
225 }
226 m_state->setInteger(ProfilerAgentState::samplingInterval, interval);
227 return Response::OK();
228 }
229
restore()230 void V8ProfilerAgentImpl::restore() {
231 DCHECK(!m_enabled);
232 if (!m_state->booleanProperty(ProfilerAgentState::profilerEnabled, false))
233 return;
234 m_enabled = true;
235 DCHECK(!m_profiler);
236 if (m_state->booleanProperty(ProfilerAgentState::userInitiatedProfiling,
237 false)) {
238 start();
239 }
240 if (m_state->booleanProperty(ProfilerAgentState::preciseCoverageStarted,
241 false)) {
242 startPreciseCoverage();
243 }
244 }
245
start()246 Response V8ProfilerAgentImpl::start() {
247 if (m_recordingCPUProfile) return Response::OK();
248 if (!m_enabled) return Response::Error("Profiler is not enabled");
249 m_recordingCPUProfile = true;
250 m_frontendInitiatedProfileId = nextProfileId();
251 startProfiling(m_frontendInitiatedProfileId);
252 m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, true);
253 return Response::OK();
254 }
255
stop(std::unique_ptr<protocol::Profiler::Profile> * profile)256 Response V8ProfilerAgentImpl::stop(
257 std::unique_ptr<protocol::Profiler::Profile>* profile) {
258 if (!m_recordingCPUProfile) {
259 return Response::Error("No recording profiles found");
260 }
261 m_recordingCPUProfile = false;
262 std::unique_ptr<protocol::Profiler::Profile> cpuProfile =
263 stopProfiling(m_frontendInitiatedProfileId, !!profile);
264 if (profile) {
265 *profile = std::move(cpuProfile);
266 if (!profile->get()) return Response::Error("Profile is not found");
267 }
268 m_frontendInitiatedProfileId = String16();
269 m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, false);
270 return Response::OK();
271 }
272
startPreciseCoverage()273 Response V8ProfilerAgentImpl::startPreciseCoverage() {
274 if (!m_enabled) return Response::Error("Profiler is not enabled");
275 m_state->setBoolean(ProfilerAgentState::preciseCoverageStarted, true);
276 v8::debug::Coverage::TogglePrecise(m_isolate, true);
277 return Response::OK();
278 }
279
stopPreciseCoverage()280 Response V8ProfilerAgentImpl::stopPreciseCoverage() {
281 if (!m_enabled) return Response::Error("Profiler is not enabled");
282 m_state->setBoolean(ProfilerAgentState::preciseCoverageStarted, false);
283 v8::debug::Coverage::TogglePrecise(m_isolate, false);
284 return Response::OK();
285 }
286
287 namespace {
takeCoverage(v8::Isolate * isolate,bool reset_count,std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>> * out_result)288 Response takeCoverage(
289 v8::Isolate* isolate, bool reset_count,
290 std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
291 out_result) {
292 std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>> result =
293 protocol::Array<protocol::Profiler::ScriptCoverage>::create();
294 v8::HandleScope handle_scope(isolate);
295 v8::debug::Coverage coverage =
296 v8::debug::Coverage::Collect(isolate, reset_count);
297 for (size_t i = 0; i < coverage.ScriptCount(); i++) {
298 v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
299 v8::Local<v8::debug::Script> script = script_data.GetScript();
300 std::unique_ptr<protocol::Array<protocol::Profiler::FunctionCoverage>>
301 functions =
302 protocol::Array<protocol::Profiler::FunctionCoverage>::create();
303 for (size_t j = 0; j < script_data.FunctionCount(); j++) {
304 v8::debug::Coverage::FunctionData function_data =
305 script_data.GetFunctionData(j);
306 std::unique_ptr<protocol::Array<protocol::Profiler::CoverageRange>>
307 ranges = protocol::Array<protocol::Profiler::CoverageRange>::create();
308 // At this point we only have per-function coverage data, so there is
309 // only one range per function.
310 ranges->addItem(
311 protocol::Profiler::CoverageRange::create()
312 .setStartLineNumber(function_data.Start().GetLineNumber())
313 .setStartColumnNumber(function_data.Start().GetColumnNumber())
314 .setEndLineNumber(function_data.End().GetLineNumber())
315 .setEndColumnNumber(function_data.End().GetColumnNumber())
316 .setCount(function_data.Count())
317 .build());
318 functions->addItem(
319 protocol::Profiler::FunctionCoverage::create()
320 .setFunctionName(toProtocolString(
321 function_data.Name().FromMaybe(v8::Local<v8::String>())))
322 .setRanges(std::move(ranges))
323 .build());
324 }
325 String16 url;
326 v8::Local<v8::String> name;
327 if (script->Name().ToLocal(&name) || script->SourceURL().ToLocal(&name)) {
328 url = toProtocolString(name);
329 }
330 result->addItem(protocol::Profiler::ScriptCoverage::create()
331 .setScriptId(String16::fromInteger(script->Id()))
332 .setUrl(url)
333 .setFunctions(std::move(functions))
334 .build());
335 }
336 *out_result = std::move(result);
337 return Response::OK();
338 }
339 } // anonymous namespace
340
takePreciseCoverage(std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>> * out_result)341 Response V8ProfilerAgentImpl::takePreciseCoverage(
342 std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
343 out_result) {
344 if (!m_state->booleanProperty(ProfilerAgentState::preciseCoverageStarted,
345 false)) {
346 return Response::Error("Precise coverage has not been started.");
347 }
348 return takeCoverage(m_isolate, true, out_result);
349 }
350
getBestEffortCoverage(std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>> * out_result)351 Response V8ProfilerAgentImpl::getBestEffortCoverage(
352 std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
353 out_result) {
354 return takeCoverage(m_isolate, false, out_result);
355 }
356
nextProfileId()357 String16 V8ProfilerAgentImpl::nextProfileId() {
358 return String16::fromInteger(
359 v8::base::NoBarrier_AtomicIncrement(&s_lastProfileId, 1));
360 }
361
startProfiling(const String16 & title)362 void V8ProfilerAgentImpl::startProfiling(const String16& title) {
363 v8::HandleScope handleScope(m_isolate);
364 if (!m_startedProfilesCount) {
365 DCHECK(!m_profiler);
366 m_profiler = v8::CpuProfiler::New(m_isolate);
367 m_profiler->SetIdle(m_idle);
368 int interval =
369 m_state->integerProperty(ProfilerAgentState::samplingInterval, 0);
370 if (interval) m_profiler->SetSamplingInterval(interval);
371 }
372 ++m_startedProfilesCount;
373 m_profiler->StartProfiling(toV8String(m_isolate, title), true);
374 }
375
stopProfiling(const String16 & title,bool serialize)376 std::unique_ptr<protocol::Profiler::Profile> V8ProfilerAgentImpl::stopProfiling(
377 const String16& title, bool serialize) {
378 v8::HandleScope handleScope(m_isolate);
379 v8::CpuProfile* profile =
380 m_profiler->StopProfiling(toV8String(m_isolate, title));
381 std::unique_ptr<protocol::Profiler::Profile> result;
382 if (profile) {
383 if (serialize) result = createCPUProfile(m_isolate, profile);
384 profile->Delete();
385 }
386 --m_startedProfilesCount;
387 if (!m_startedProfilesCount) {
388 m_profiler->Dispose();
389 m_profiler = nullptr;
390 }
391 return result;
392 }
393
idleStarted()394 bool V8ProfilerAgentImpl::idleStarted() {
395 m_idle = true;
396 if (m_profiler) m_profiler->SetIdle(m_idle);
397 return m_profiler;
398 }
399
idleFinished()400 bool V8ProfilerAgentImpl::idleFinished() {
401 m_idle = false;
402 if (m_profiler) m_profiler->SetIdle(m_idle);
403 return m_profiler;
404 }
405
406 } // namespace v8_inspector
407