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 "include/v8-profiler.h"
10#include "src/base/atomicops.h"
11#include "src/base/platform/time.h"
12#include "src/debug/debug-interface.h"
13#include "src/inspector/protocol/Protocol.h"
14#include "src/inspector/string-util.h"
15#include "src/inspector/v8-debugger.h"
16#include "src/inspector/v8-inspector-impl.h"
17#include "src/inspector/v8-inspector-session-impl.h"
18#include "src/inspector/v8-stack-trace-impl.h"
19
20namespace v8_inspector {
21
22namespace ProfilerAgentState {
23static const char samplingInterval[] = "samplingInterval";
24static const char userInitiatedProfiling[] = "userInitiatedProfiling";
25static const char profilerEnabled[] = "profilerEnabled";
26static const char preciseCoverageStarted[] = "preciseCoverageStarted";
27static const char preciseCoverageCallCount[] = "preciseCoverageCallCount";
28static const char preciseCoverageDetailed[] = "preciseCoverageDetailed";
29static const char preciseCoverageAllowTriggeredUpdates[] =
30    "preciseCoverageAllowTriggeredUpdates";
31static const char typeProfileStarted[] = "typeProfileStarted";
32}  // namespace ProfilerAgentState
33
34namespace {
35
36String16 resourceNameToUrl(V8InspectorImpl* inspector,
37                           v8::Local<v8::String> v8Name) {
38  String16 name = toProtocolString(inspector->isolate(), v8Name);
39  if (!inspector) return name;
40  std::unique_ptr<StringBuffer> url =
41      inspector->client()->resourceNameToUrl(toStringView(name));
42  return url ? toString16(url->string()) : name;
43}
44
45std::unique_ptr<protocol::Array<protocol::Profiler::PositionTickInfo>>
46buildInspectorObjectForPositionTicks(const v8::CpuProfileNode* node) {
47  unsigned lineCount = node->GetHitLineCount();
48  if (!lineCount) return nullptr;
49  auto array =
50      std::make_unique<protocol::Array<protocol::Profiler::PositionTickInfo>>();
51  std::vector<v8::CpuProfileNode::LineTick> entries(lineCount);
52  if (node->GetLineTicks(&entries[0], lineCount)) {
53    for (unsigned i = 0; i < lineCount; i++) {
54      std::unique_ptr<protocol::Profiler::PositionTickInfo> line =
55          protocol::Profiler::PositionTickInfo::create()
56              .setLine(entries[i].line)
57              .setTicks(entries[i].hit_count)
58              .build();
59      array->emplace_back(std::move(line));
60    }
61  }
62  return array;
63}
64
65std::unique_ptr<protocol::Profiler::ProfileNode> buildInspectorObjectFor(
66    V8InspectorImpl* inspector, const v8::CpuProfileNode* node) {
67  v8::Isolate* isolate = inspector->isolate();
68  v8::HandleScope handleScope(isolate);
69  auto callFrame =
70      protocol::Runtime::CallFrame::create()
71          .setFunctionName(toProtocolString(isolate, node->GetFunctionName()))
72          .setScriptId(String16::fromInteger(node->GetScriptId()))
73          .setUrl(resourceNameToUrl(inspector, node->GetScriptResourceName()))
74          .setLineNumber(node->GetLineNumber() - 1)
75          .setColumnNumber(node->GetColumnNumber() - 1)
76          .build();
77  auto result = protocol::Profiler::ProfileNode::create()
78                    .setCallFrame(std::move(callFrame))
79                    .setHitCount(node->GetHitCount())
80                    .setId(node->GetNodeId())
81                    .build();
82
83  const int childrenCount = node->GetChildrenCount();
84  if (childrenCount) {
85    auto children = std::make_unique<protocol::Array<int>>();
86    for (int i = 0; i < childrenCount; i++)
87      children->emplace_back(node->GetChild(i)->GetNodeId());
88    result->setChildren(std::move(children));
89  }
90
91  const char* deoptReason = node->GetBailoutReason();
92  if (deoptReason && deoptReason[0] && strcmp(deoptReason, "no reason"))
93    result->setDeoptReason(deoptReason);
94
95  auto positionTicks = buildInspectorObjectForPositionTicks(node);
96  if (positionTicks) result->setPositionTicks(std::move(positionTicks));
97
98  return result;
99}
100
101std::unique_ptr<protocol::Array<int>> buildInspectorObjectForSamples(
102    v8::CpuProfile* v8profile) {
103  auto array = std::make_unique<protocol::Array<int>>();
104  int count = v8profile->GetSamplesCount();
105  for (int i = 0; i < count; i++)
106    array->emplace_back(v8profile->GetSample(i)->GetNodeId());
107  return array;
108}
109
110std::unique_ptr<protocol::Array<int>> buildInspectorObjectForTimestamps(
111    v8::CpuProfile* v8profile) {
112  auto array = std::make_unique<protocol::Array<int>>();
113  int count = v8profile->GetSamplesCount();
114  uint64_t lastTime = v8profile->GetStartTime();
115  for (int i = 0; i < count; i++) {
116    uint64_t ts = v8profile->GetSampleTimestamp(i);
117    array->emplace_back(static_cast<int>(ts - lastTime));
118    lastTime = ts;
119  }
120  return array;
121}
122
123void flattenNodesTree(V8InspectorImpl* inspector,
124                      const v8::CpuProfileNode* node,
125                      protocol::Array<protocol::Profiler::ProfileNode>* list) {
126  list->emplace_back(buildInspectorObjectFor(inspector, node));
127  const int childrenCount = node->GetChildrenCount();
128  for (int i = 0; i < childrenCount; i++)
129    flattenNodesTree(inspector, node->GetChild(i), list);
130}
131
132std::unique_ptr<protocol::Profiler::Profile> createCPUProfile(
133    V8InspectorImpl* inspector, v8::CpuProfile* v8profile) {
134  auto nodes =
135      std::make_unique<protocol::Array<protocol::Profiler::ProfileNode>>();
136  flattenNodesTree(inspector, v8profile->GetTopDownRoot(), nodes.get());
137  return protocol::Profiler::Profile::create()
138      .setNodes(std::move(nodes))
139      .setStartTime(static_cast<double>(v8profile->GetStartTime()))
140      .setEndTime(static_cast<double>(v8profile->GetEndTime()))
141      .setSamples(buildInspectorObjectForSamples(v8profile))
142      .setTimeDeltas(buildInspectorObjectForTimestamps(v8profile))
143      .build();
144}
145
146std::unique_ptr<protocol::Debugger::Location> currentDebugLocation(
147    V8InspectorImpl* inspector) {
148  auto stackTrace = V8StackTraceImpl::capture(inspector->debugger(), 1);
149  CHECK(stackTrace);
150  CHECK(!stackTrace->isEmpty());
151  return protocol::Debugger::Location::create()
152      .setScriptId(String16::fromInteger(stackTrace->topScriptId()))
153      .setLineNumber(stackTrace->topLineNumber())
154      .setColumnNumber(stackTrace->topColumnNumber())
155      .build();
156}
157
158volatile int s_lastProfileId = 0;
159
160}  // namespace
161
162class V8ProfilerAgentImpl::ProfileDescriptor {
163 public:
164  ProfileDescriptor(const String16& id, const String16& title)
165      : m_id(id), m_title(title) {}
166  String16 m_id;
167  String16 m_title;
168};
169
170V8ProfilerAgentImpl::V8ProfilerAgentImpl(
171    V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
172    protocol::DictionaryValue* state)
173    : m_session(session),
174      m_isolate(m_session->inspector()->isolate()),
175      m_state(state),
176      m_frontend(frontendChannel) {}
177
178V8ProfilerAgentImpl::~V8ProfilerAgentImpl() {
179  if (m_profiler) m_profiler->Dispose();
180}
181
182void V8ProfilerAgentImpl::consoleProfile(const String16& title) {
183  if (!m_enabled) return;
184  String16 id = nextProfileId();
185  m_startedProfiles.push_back(ProfileDescriptor(id, title));
186  startProfiling(id);
187  m_frontend.consoleProfileStarted(
188      id, currentDebugLocation(m_session->inspector()), title);
189}
190
191void V8ProfilerAgentImpl::consoleProfileEnd(const String16& title) {
192  if (!m_enabled) return;
193  String16 id;
194  String16 resolvedTitle;
195  // Take last started profile if no title was passed.
196  if (title.isEmpty()) {
197    if (m_startedProfiles.empty()) return;
198    id = m_startedProfiles.back().m_id;
199    resolvedTitle = m_startedProfiles.back().m_title;
200    m_startedProfiles.pop_back();
201  } else {
202    for (size_t i = 0; i < m_startedProfiles.size(); i++) {
203      if (m_startedProfiles[i].m_title == title) {
204        resolvedTitle = title;
205        id = m_startedProfiles[i].m_id;
206        m_startedProfiles.erase(m_startedProfiles.begin() + i);
207        break;
208      }
209    }
210    if (id.isEmpty()) return;
211  }
212  std::unique_ptr<protocol::Profiler::Profile> profile =
213      stopProfiling(id, true);
214  if (!profile) return;
215  m_frontend.consoleProfileFinished(
216      id, currentDebugLocation(m_session->inspector()), std::move(profile),
217      resolvedTitle);
218}
219
220Response V8ProfilerAgentImpl::enable() {
221  if (!m_enabled) {
222    m_enabled = true;
223    m_state->setBoolean(ProfilerAgentState::profilerEnabled, true);
224  }
225
226  return Response::Success();
227}
228
229Response V8ProfilerAgentImpl::disable() {
230  if (m_enabled) {
231    for (size_t i = m_startedProfiles.size(); i > 0; --i)
232      stopProfiling(m_startedProfiles[i - 1].m_id, false);
233    m_startedProfiles.clear();
234    stop(nullptr);
235    stopPreciseCoverage();
236    DCHECK(!m_profiler);
237    m_enabled = false;
238    m_state->setBoolean(ProfilerAgentState::profilerEnabled, false);
239  }
240
241  return Response::Success();
242}
243
244Response V8ProfilerAgentImpl::setSamplingInterval(int interval) {
245  if (m_profiler) {
246    return Response::ServerError(
247        "Cannot change sampling interval when profiling.");
248  }
249  m_state->setInteger(ProfilerAgentState::samplingInterval, interval);
250  return Response::Success();
251}
252
253void V8ProfilerAgentImpl::restore() {
254  DCHECK(!m_enabled);
255  if (m_state->booleanProperty(ProfilerAgentState::profilerEnabled, false)) {
256    m_enabled = true;
257    DCHECK(!m_profiler);
258    if (m_state->booleanProperty(ProfilerAgentState::userInitiatedProfiling,
259                                 false)) {
260      start();
261    }
262    if (m_state->booleanProperty(ProfilerAgentState::preciseCoverageStarted,
263                                 false)) {
264      bool callCount = m_state->booleanProperty(
265          ProfilerAgentState::preciseCoverageCallCount, false);
266      bool detailed = m_state->booleanProperty(
267          ProfilerAgentState::preciseCoverageDetailed, false);
268      bool updatesAllowed = m_state->booleanProperty(
269          ProfilerAgentState::preciseCoverageAllowTriggeredUpdates, false);
270      double timestamp;
271      startPreciseCoverage(Maybe<bool>(callCount), Maybe<bool>(detailed),
272                           Maybe<bool>(updatesAllowed), &timestamp);
273    }
274  }
275}
276
277Response V8ProfilerAgentImpl::start() {
278  if (m_recordingCPUProfile) return Response::Success();
279  if (!m_enabled) return Response::ServerError("Profiler is not enabled");
280  m_recordingCPUProfile = true;
281  m_frontendInitiatedProfileId = nextProfileId();
282  startProfiling(m_frontendInitiatedProfileId);
283  m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, true);
284  return Response::Success();
285}
286
287Response V8ProfilerAgentImpl::stop(
288    std::unique_ptr<protocol::Profiler::Profile>* profile) {
289  if (!m_recordingCPUProfile) {
290    return Response::ServerError("No recording profiles found");
291  }
292  m_recordingCPUProfile = false;
293  std::unique_ptr<protocol::Profiler::Profile> cpuProfile =
294      stopProfiling(m_frontendInitiatedProfileId, !!profile);
295  if (profile) {
296    *profile = std::move(cpuProfile);
297    if (!profile->get()) return Response::ServerError("Profile is not found");
298  }
299  m_frontendInitiatedProfileId = String16();
300  m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, false);
301  return Response::Success();
302}
303
304Response V8ProfilerAgentImpl::startPreciseCoverage(
305    Maybe<bool> callCount, Maybe<bool> detailed,
306    Maybe<bool> allowTriggeredUpdates, double* out_timestamp) {
307  if (!m_enabled) return Response::ServerError("Profiler is not enabled");
308  *out_timestamp = v8::base::TimeTicks::Now().since_origin().InSecondsF();
309  bool callCountValue = callCount.fromMaybe(false);
310  bool detailedValue = detailed.fromMaybe(false);
311  bool allowTriggeredUpdatesValue = allowTriggeredUpdates.fromMaybe(false);
312  m_state->setBoolean(ProfilerAgentState::preciseCoverageStarted, true);
313  m_state->setBoolean(ProfilerAgentState::preciseCoverageCallCount,
314                      callCountValue);
315  m_state->setBoolean(ProfilerAgentState::preciseCoverageDetailed,
316                      detailedValue);
317  m_state->setBoolean(ProfilerAgentState::preciseCoverageAllowTriggeredUpdates,
318                      allowTriggeredUpdatesValue);
319  // BlockCount is a superset of PreciseCount. It includes block-granularity
320  // coverage data if it exists (at the time of writing, that's the case for
321  // each function recompiled after the BlockCount mode has been set); and
322  // function-granularity coverage data otherwise.
323  using C = v8::debug::Coverage;
324  using Mode = v8::debug::CoverageMode;
325  Mode mode = callCountValue
326                  ? (detailedValue ? Mode::kBlockCount : Mode::kPreciseCount)
327                  : (detailedValue ? Mode::kBlockBinary : Mode::kPreciseBinary);
328  C::SelectMode(m_isolate, mode);
329  return Response::Success();
330}
331
332Response V8ProfilerAgentImpl::stopPreciseCoverage() {
333  if (!m_enabled) return Response::ServerError("Profiler is not enabled");
334  m_state->setBoolean(ProfilerAgentState::preciseCoverageStarted, false);
335  m_state->setBoolean(ProfilerAgentState::preciseCoverageCallCount, false);
336  m_state->setBoolean(ProfilerAgentState::preciseCoverageDetailed, false);
337  v8::debug::Coverage::SelectMode(m_isolate,
338                                  v8::debug::CoverageMode::kBestEffort);
339  return Response::Success();
340}
341
342namespace {
343std::unique_ptr<protocol::Profiler::CoverageRange> createCoverageRange(
344    int start, int end, int count) {
345  return protocol::Profiler::CoverageRange::create()
346      .setStartOffset(start)
347      .setEndOffset(end)
348      .setCount(count)
349      .build();
350}
351
352Response coverageToProtocol(
353    V8InspectorImpl* inspector, const v8::debug::Coverage& coverage,
354    std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
355        out_result) {
356  auto result =
357      std::make_unique<protocol::Array<protocol::Profiler::ScriptCoverage>>();
358  v8::Isolate* isolate = inspector->isolate();
359  for (size_t i = 0; i < coverage.ScriptCount(); i++) {
360    v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
361    v8::Local<v8::debug::Script> script = script_data.GetScript();
362    auto functions = std::make_unique<
363        protocol::Array<protocol::Profiler::FunctionCoverage>>();
364    for (size_t j = 0; j < script_data.FunctionCount(); j++) {
365      v8::debug::Coverage::FunctionData function_data =
366          script_data.GetFunctionData(j);
367      auto ranges = std::make_unique<
368          protocol::Array<protocol::Profiler::CoverageRange>>();
369
370      // Add function range.
371      ranges->emplace_back(createCoverageRange(function_data.StartOffset(),
372                                               function_data.EndOffset(),
373                                               function_data.Count()));
374
375      // Process inner blocks.
376      for (size_t k = 0; k < function_data.BlockCount(); k++) {
377        v8::debug::Coverage::BlockData block_data =
378            function_data.GetBlockData(k);
379        ranges->emplace_back(createCoverageRange(block_data.StartOffset(),
380                                                 block_data.EndOffset(),
381                                                 block_data.Count()));
382      }
383
384      functions->emplace_back(
385          protocol::Profiler::FunctionCoverage::create()
386              .setFunctionName(toProtocolString(
387                  isolate,
388                  function_data.Name().FromMaybe(v8::Local<v8::String>())))
389              .setRanges(std::move(ranges))
390              .setIsBlockCoverage(function_data.HasBlockCoverage())
391              .build());
392    }
393    String16 url;
394    v8::Local<v8::String> name;
395    if (script->SourceURL().ToLocal(&name) && name->Length()) {
396      url = toProtocolString(isolate, name);
397    } else if (script->Name().ToLocal(&name) && name->Length()) {
398      url = resourceNameToUrl(inspector, name);
399    }
400    result->emplace_back(protocol::Profiler::ScriptCoverage::create()
401                             .setScriptId(String16::fromInteger(script->Id()))
402                             .setUrl(url)
403                             .setFunctions(std::move(functions))
404                             .build());
405  }
406  *out_result = std::move(result);
407  return Response::Success();
408}
409}  // anonymous namespace
410
411Response V8ProfilerAgentImpl::takePreciseCoverage(
412    std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
413        out_result,
414    double* out_timestamp) {
415  if (!m_state->booleanProperty(ProfilerAgentState::preciseCoverageStarted,
416                                false)) {
417    return Response::ServerError("Precise coverage has not been started.");
418  }
419  v8::HandleScope handle_scope(m_isolate);
420  v8::debug::Coverage coverage = v8::debug::Coverage::CollectPrecise(m_isolate);
421  *out_timestamp = v8::base::TimeTicks::Now().since_origin().InSecondsF();
422  return coverageToProtocol(m_session->inspector(), coverage, out_result);
423}
424
425void V8ProfilerAgentImpl::triggerPreciseCoverageDeltaUpdate(
426    const String16& occasion) {
427  if (!m_state->booleanProperty(ProfilerAgentState::preciseCoverageStarted,
428                                false)) {
429    return;
430  }
431  if (!m_state->booleanProperty(
432          ProfilerAgentState::preciseCoverageAllowTriggeredUpdates, false)) {
433    return;
434  }
435  v8::HandleScope handle_scope(m_isolate);
436  v8::debug::Coverage coverage = v8::debug::Coverage::CollectPrecise(m_isolate);
437  std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>
438      out_result;
439  coverageToProtocol(m_session->inspector(), coverage, &out_result);
440  double now = v8::base::TimeTicks::Now().since_origin().InSecondsF();
441  m_frontend.preciseCoverageDeltaUpdate(now, occasion, std::move(out_result));
442}
443
444Response V8ProfilerAgentImpl::getBestEffortCoverage(
445    std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
446        out_result) {
447  v8::HandleScope handle_scope(m_isolate);
448  v8::debug::Coverage coverage =
449      v8::debug::Coverage::CollectBestEffort(m_isolate);
450  return coverageToProtocol(m_session->inspector(), coverage, out_result);
451}
452
453namespace {
454std::unique_ptr<protocol::Array<protocol::Profiler::ScriptTypeProfile>>
455typeProfileToProtocol(V8InspectorImpl* inspector,
456                      const v8::debug::TypeProfile& type_profile) {
457  auto result = std::make_unique<
458      protocol::Array<protocol::Profiler::ScriptTypeProfile>>();
459  v8::Isolate* isolate = inspector->isolate();
460  for (size_t i = 0; i < type_profile.ScriptCount(); i++) {
461    v8::debug::TypeProfile::ScriptData script_data =
462        type_profile.GetScriptData(i);
463    v8::Local<v8::debug::Script> script = script_data.GetScript();
464    auto entries = std::make_unique<
465        protocol::Array<protocol::Profiler::TypeProfileEntry>>();
466
467    for (const auto& entry : script_data.Entries()) {
468      auto types =
469          std::make_unique<protocol::Array<protocol::Profiler::TypeObject>>();
470      for (const auto& type : entry.Types()) {
471        types->emplace_back(
472            protocol::Profiler::TypeObject::create()
473                .setName(toProtocolString(
474                    isolate, type.FromMaybe(v8::Local<v8::String>())))
475                .build());
476      }
477      entries->emplace_back(protocol::Profiler::TypeProfileEntry::create()
478                                .setOffset(entry.SourcePosition())
479                                .setTypes(std::move(types))
480                                .build());
481    }
482    String16 url;
483    v8::Local<v8::String> name;
484    if (script->SourceURL().ToLocal(&name) && name->Length()) {
485      url = toProtocolString(isolate, name);
486    } else if (script->Name().ToLocal(&name) && name->Length()) {
487      url = resourceNameToUrl(inspector, name);
488    }
489    result->emplace_back(protocol::Profiler::ScriptTypeProfile::create()
490                             .setScriptId(String16::fromInteger(script->Id()))
491                             .setUrl(url)
492                             .setEntries(std::move(entries))
493                             .build());
494  }
495  return result;
496}
497}  // anonymous namespace
498
499Response V8ProfilerAgentImpl::startTypeProfile() {
500  m_state->setBoolean(ProfilerAgentState::typeProfileStarted, true);
501  v8::debug::TypeProfile::SelectMode(m_isolate,
502                                     v8::debug::TypeProfileMode::kCollect);
503  return Response::Success();
504}
505
506Response V8ProfilerAgentImpl::stopTypeProfile() {
507  m_state->setBoolean(ProfilerAgentState::typeProfileStarted, false);
508  v8::debug::TypeProfile::SelectMode(m_isolate,
509                                     v8::debug::TypeProfileMode::kNone);
510  return Response::Success();
511}
512
513Response V8ProfilerAgentImpl::takeTypeProfile(
514    std::unique_ptr<protocol::Array<protocol::Profiler::ScriptTypeProfile>>*
515        out_result) {
516  if (!m_state->booleanProperty(ProfilerAgentState::typeProfileStarted,
517                                false)) {
518    return Response::ServerError("Type profile has not been started.");
519  }
520  v8::HandleScope handle_scope(m_isolate);
521  v8::debug::TypeProfile type_profile =
522      v8::debug::TypeProfile::Collect(m_isolate);
523  *out_result = typeProfileToProtocol(m_session->inspector(), type_profile);
524  return Response::Success();
525}
526
527String16 V8ProfilerAgentImpl::nextProfileId() {
528  return String16::fromInteger(
529      v8::base::Relaxed_AtomicIncrement(&s_lastProfileId, 1));
530}
531
532void V8ProfilerAgentImpl::startProfiling(const String16& title) {
533  v8::HandleScope handleScope(m_isolate);
534  if (!m_startedProfilesCount) {
535    DCHECK(!m_profiler);
536    m_profiler = v8::CpuProfiler::New(m_isolate);
537    int interval =
538        m_state->integerProperty(ProfilerAgentState::samplingInterval, 0);
539    if (interval) m_profiler->SetSamplingInterval(interval);
540  }
541  ++m_startedProfilesCount;
542  m_profiler->StartProfiling(toV8String(m_isolate, title), true);
543}
544
545std::unique_ptr<protocol::Profiler::Profile> V8ProfilerAgentImpl::stopProfiling(
546    const String16& title, bool serialize) {
547  v8::HandleScope handleScope(m_isolate);
548  v8::CpuProfile* profile =
549      m_profiler->StopProfiling(toV8String(m_isolate, title));
550  std::unique_ptr<protocol::Profiler::Profile> result;
551  if (profile) {
552    if (serialize) result = createCPUProfile(m_session->inspector(), profile);
553    profile->Delete();
554  }
555  --m_startedProfilesCount;
556  if (!m_startedProfilesCount) {
557    m_profiler->Dispose();
558    m_profiler = nullptr;
559  }
560  return result;
561}
562
563}  // namespace v8_inspector
564