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), ×tamp); 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