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