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