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>> 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 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 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 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 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 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 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: 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 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 159 V8ProfilerAgentImpl::~V8ProfilerAgentImpl() { 160 if (m_profiler) m_profiler->Dispose(); 161 } 162 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 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 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 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 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 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 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 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 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 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 { 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 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 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 357 String16 V8ProfilerAgentImpl::nextProfileId() { 358 return String16::fromInteger( 359 v8::base::NoBarrier_AtomicIncrement(&s_lastProfileId, 1)); 360 } 361 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 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 394 bool V8ProfilerAgentImpl::idleStarted() { 395 m_idle = true; 396 if (m_profiler) m_profiler->SetIdle(m_idle); 397 return m_profiler; 398 } 399 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