GeckoProfiler.cpp (25613B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/GeckoProfiler-inl.h" 8 9 #include "mozilla/DebugOnly.h" 10 #include "mozilla/Sprintf.h" 11 12 #include "gc/GC.h" 13 #include "gc/PublicIterators.h" 14 #include "jit/BaselineJIT.h" 15 #include "jit/JitcodeMap.h" 16 #include "jit/JitRuntime.h" 17 #include "jit/JSJitFrameIter.h" 18 #include "jit/PerfSpewer.h" 19 #include "js/experimental/SourceHook.h" 20 #include "vm/FrameIter.h" // js::OnlyJSJitFrameIter 21 #include "vm/JitActivation.h" 22 #include "vm/JSScript.h" 23 #include "vm/MutexIDs.h" 24 25 #include "gc/Marking-inl.h" 26 #include "jit/JSJitFrameIter-inl.h" 27 28 using namespace js; 29 using mozilla::Utf8Unit; 30 31 GeckoProfilerThread::GeckoProfilerThread() 32 : profilingStack_(nullptr), profilingStackIfEnabled_(nullptr) {} 33 34 GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt) 35 : rt(rt), 36 scriptSources_(mutexid::GeckoProfilerScriptSources), 37 slowAssertions(false), 38 enabled_(false), 39 eventMarker_(nullptr), 40 intervalMarker_(nullptr), 41 flowMarker_(nullptr), 42 terminatingFlowMarker_(nullptr) { 43 MOZ_ASSERT(rt != nullptr); 44 } 45 46 void GeckoProfilerThread::setProfilingStack(ProfilingStack* profilingStack, 47 bool enabled) { 48 profilingStack_ = profilingStack; 49 profilingStackIfEnabled_ = enabled ? profilingStack : nullptr; 50 } 51 52 void GeckoProfilerRuntime::setEventMarker(void (*fn)(mozilla::MarkerCategory, 53 const char*, 54 const char*)) { 55 eventMarker_ = fn; 56 } 57 58 void GeckoProfilerRuntime::setIntervalMarker(void (*fn)( 59 mozilla::MarkerCategory, const char*, mozilla::TimeStamp, const char*)) { 60 intervalMarker_ = fn; 61 } 62 63 void GeckoProfilerRuntime::setFlowMarker(void (*fn)(mozilla::MarkerCategory, 64 const char*, uint64_t)) { 65 flowMarker_ = fn; 66 } 67 68 void GeckoProfilerRuntime::setTerminatingFlowMarker( 69 void (*fn)(mozilla::MarkerCategory, const char*, uint64_t)) { 70 terminatingFlowMarker_ = fn; 71 } 72 73 // Get a pointer to the top-most profiling frame, given the exit frame pointer. 74 static jit::JitFrameLayout* GetTopProfilingJitFrame(jit::JitActivation* act) { 75 // If there is no exit frame set, just return. 76 if (!act->hasExitFP()) { 77 return nullptr; 78 } 79 80 // Skip wasm frames that might be in the way. 81 OnlyJSJitFrameIter iter(act); 82 if (iter.done()) { 83 return nullptr; 84 } 85 86 // Skip if the activation has no JS frames. This can happen if there's only a 87 // TrampolineNative frame because these are skipped by the profiling frame 88 // iterator. 89 jit::JSJitProfilingFrameIterator jitIter( 90 (jit::CommonFrameLayout*)iter.frame().fp()); 91 if (jitIter.done()) { 92 return nullptr; 93 } 94 95 return jitIter.framePtr(); 96 } 97 98 void GeckoProfilerRuntime::enable(bool enabled) { 99 JSContext* cx = rt->mainContextFromAnyThread(); 100 MOZ_ASSERT(cx->geckoProfiler().infraInstalled()); 101 102 if (enabled_ == enabled) { 103 return; 104 } 105 106 /* 107 * Ensure all future generated code will be instrumented, or that all 108 * currently instrumented code is discarded 109 */ 110 ReleaseAllJITCode(rt->gcContext()); 111 112 // This function is called when the Gecko profiler makes a new Sampler 113 // (and thus, a new circular buffer). Set all current entries in the 114 // JitcodeGlobalTable as expired and reset the buffer range start. 115 if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable()) { 116 rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired(); 117 } 118 rt->setProfilerSampleBufferRangeStart(0); 119 120 // Ensure that lastProfilingFrame is null for the main thread. 121 if (cx->jitActivation) { 122 cx->jitActivation->setLastProfilingFrame(nullptr); 123 cx->jitActivation->setLastProfilingCallSite(nullptr); 124 } 125 126 enabled_ = enabled; 127 128 scriptSources_.writeLock()->clear(); 129 130 /* Toggle Gecko Profiler-related jumps on baseline jitcode. 131 * The call to |ReleaseAllJITCode| above will release most baseline jitcode, 132 * but not jitcode for scripts with active frames on the stack. These scripts 133 * need to have their profiler state toggled so they behave properly. 134 */ 135 jit::ToggleBaselineProfiling(cx, enabled); 136 137 // Update lastProfilingFrame to point to the top-most JS jit-frame currently 138 // on stack. 139 if (cx->jitActivation) { 140 // Walk through all activations, and set their lastProfilingFrame 141 // appropriately. 142 if (enabled) { 143 jit::JitActivation* jitActivation = cx->jitActivation; 144 while (jitActivation) { 145 auto* lastProfilingFrame = GetTopProfilingJitFrame(jitActivation); 146 jitActivation->setLastProfilingFrame(lastProfilingFrame); 147 jitActivation->setLastProfilingCallSite(nullptr); 148 jitActivation = jitActivation->prevJitActivation(); 149 } 150 } else { 151 jit::JitActivation* jitActivation = cx->jitActivation; 152 while (jitActivation) { 153 jitActivation->setLastProfilingFrame(nullptr); 154 jitActivation->setLastProfilingCallSite(nullptr); 155 jitActivation = jitActivation->prevJitActivation(); 156 } 157 } 158 } 159 160 // WebAssembly code does not need to be released, but profiling string 161 // labels have to be generated so that they are available during async 162 // profiling stack iteration. 163 for (RealmsIter r(rt); !r.done(); r.next()) { 164 r->wasm.ensureProfilingLabels(enabled); 165 } 166 167 #ifdef JS_STRUCTURED_SPEW 168 // Enable the structured spewer if the environment variable is set. 169 if (enabled) { 170 cx->spewer().enableSpewing(); 171 } else { 172 cx->spewer().disableSpewing(); 173 } 174 #endif 175 } 176 177 /* Lookup the string for the function/script, creating one if necessary */ 178 const char* GeckoProfilerRuntime::profileString(JSContext* cx, 179 BaseScript* script) { 180 ProfileStringMap::AddPtr s = strings().lookupForAdd(script); 181 182 if (!s) { 183 UniqueChars str = allocProfileString(cx, script); 184 if (!str) { 185 return nullptr; 186 } 187 MOZ_ASSERT(script->hasBytecode()); 188 if (!strings().add(s, script, std::move(str))) { 189 ReportOutOfMemory(cx); 190 return nullptr; 191 } 192 } 193 194 return s->value().get(); 195 } 196 197 void GeckoProfilerRuntime::onScriptFinalized(BaseScript* script) { 198 /* 199 * This function is called whenever a script is destroyed, regardless of 200 * whether profiling has been turned on, so don't invoke a function on an 201 * invalid hash set. Also, even if profiling was enabled but then turned 202 * off, we still want to remove the string, so no check of enabled() is 203 * done. 204 */ 205 if (ProfileStringMap::Ptr entry = strings().lookup(script)) { 206 strings().remove(entry); 207 } 208 } 209 210 void GeckoProfilerRuntime::markEvent(const char* event, const char* details, 211 JS::ProfilingCategoryPair jsPair) { 212 MOZ_ASSERT(enabled()); 213 if (eventMarker_) { 214 JS::AutoSuppressGCAnalysis nogc; 215 mozilla::MarkerCategory category( 216 static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair)); 217 eventMarker_(category, event, details); 218 } 219 } 220 221 void GeckoProfilerRuntime::markInterval(const char* event, 222 mozilla::TimeStamp start, 223 const char* details, 224 JS::ProfilingCategoryPair jsPair) { 225 MOZ_ASSERT(enabled()); 226 if (intervalMarker_) { 227 JS::AutoSuppressGCAnalysis nogc; 228 mozilla::MarkerCategory category( 229 static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair)); 230 intervalMarker_(category, event, start, details); 231 } 232 } 233 234 void GeckoProfilerRuntime::markFlow(const char* markerName, uint64_t flowId, 235 JS::ProfilingCategoryPair jsPair) { 236 MOZ_ASSERT(enabled()); 237 if (flowMarker_) { 238 JS::AutoSuppressGCAnalysis nogc; 239 mozilla::MarkerCategory category( 240 static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair)); 241 flowMarker_(category, markerName, flowId); 242 } 243 } 244 245 void GeckoProfilerRuntime::markTerminatingFlow( 246 const char* markerName, uint64_t flowId, JS::ProfilingCategoryPair jsPair) { 247 MOZ_ASSERT(enabled()); 248 if (terminatingFlowMarker_) { 249 JS::AutoSuppressGCAnalysis nogc; 250 mozilla::MarkerCategory category( 251 static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair)); 252 terminatingFlowMarker_(category, markerName, flowId); 253 } 254 } 255 256 bool GeckoProfilerThread::enter(JSContext* cx, JSScript* script) { 257 const char* dynamicString = 258 cx->runtime()->geckoProfiler().profileString(cx, script); 259 if (dynamicString == nullptr) { 260 return false; 261 } 262 263 if (!cx->runtime()->geckoProfiler().insertScriptSource( 264 script->scriptSource())) { 265 ReportOutOfMemory(cx); 266 return false; 267 } 268 269 #ifdef DEBUG 270 // In debug builds, assert the JS profiling stack frames already on the 271 // stack have a non-null pc. Only look at the top frames to avoid quadratic 272 // behavior. 273 uint32_t sp = profilingStack_->stackPointer; 274 if (sp > 0 && sp - 1 < profilingStack_->stackCapacity()) { 275 size_t start = (sp > 4) ? sp - 4 : 0; 276 for (size_t i = start; i < sp - 1; i++) { 277 MOZ_ASSERT_IF(profilingStack_->frames[i].isJsFrame(), 278 profilingStack_->frames[i].pc()); 279 } 280 } 281 #endif 282 283 profilingStack_->pushJsFrame( 284 "", dynamicString, script, script->code(), 285 script->realm()->creationOptions().profilerRealmID(), 286 script->scriptSource()->id()); 287 return true; 288 } 289 290 void GeckoProfilerThread::exit(JSContext* cx, JSScript* script) { 291 profilingStack_->pop(); 292 293 #ifdef DEBUG 294 /* Sanity check to make sure push/pop balanced */ 295 uint32_t sp = profilingStack_->stackPointer; 296 if (sp < profilingStack_->stackCapacity()) { 297 JSRuntime* rt = script->runtimeFromMainThread(); 298 const char* dynamicString = rt->geckoProfiler().profileString(cx, script); 299 /* Can't fail lookup because we should already be in the set */ 300 MOZ_ASSERT(dynamicString); 301 302 // Bug 822041 303 if (!profilingStack_->frames[sp].isJsFrame()) { 304 fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n"); 305 fprintf(stderr, " frames=%p size=%u/%u\n", (void*)profilingStack_->frames, 306 uint32_t(profilingStack_->stackPointer), 307 profilingStack_->stackCapacity()); 308 for (int32_t i = sp; i >= 0; i--) { 309 ProfilingStackFrame& frame = profilingStack_->frames[i]; 310 if (frame.isJsFrame()) { 311 fprintf(stderr, " [%d] JS %s\n", i, frame.dynamicString()); 312 } else { 313 fprintf(stderr, " [%d] Label %s\n", i, frame.dynamicString()); 314 } 315 } 316 } 317 318 ProfilingStackFrame& frame = profilingStack_->frames[sp]; 319 MOZ_ASSERT(frame.isJsFrame()); 320 MOZ_ASSERT(frame.script() == script); 321 MOZ_ASSERT(strcmp((const char*)frame.dynamicString(), dynamicString) == 0); 322 } 323 #endif 324 } 325 326 /* 327 * Serializes the script/function pair into a "descriptive string" which is 328 * allowed to fail. This function cannot trigger a GC because it could finalize 329 * some scripts, resize the hash table of profile strings, and invalidate the 330 * AddPtr held while invoking allocProfileString. 331 */ 332 /* static */ 333 UniqueChars GeckoProfilerRuntime::allocProfileString(JSContext* cx, 334 BaseScript* script) { 335 // Note: this profiler string is regexp-matched by 336 // profiler code. Most recently at 337 // https://github.com/firefox-devtools/profiler/blob/245b1a400c5c368ccc13641d0335398bafa0e870/src/profile-logic/process-profile.js#L520-L525 338 339 // If the script has a function, try calculating its name. 340 JSAtom* name = nullptr; 341 size_t nameLength = 0; 342 JSFunction* func = script->function(); 343 if (func && func->fullDisplayAtom()) { 344 name = func->fullDisplayAtom(); 345 nameLength = JS::GetDeflatedUTF8StringLength(name); 346 } 347 348 // Calculate filename length. We cap this to a reasonable limit to avoid 349 // performance impact of strlen/alloc/memcpy. 350 constexpr size_t MaxFilenameLength = 200; 351 const char* filenameStr = script->filename() ? script->filename() : "(null)"; 352 size_t filenameLength = js_strnlen(filenameStr, MaxFilenameLength); 353 354 // Calculate line + column length. 355 bool hasLineAndColumn = false; 356 size_t lineAndColumnLength = 0; 357 char lineAndColumnStr[30]; 358 if (name || script->isFunction() || script->isForEval()) { 359 lineAndColumnLength = 360 SprintfLiteral(lineAndColumnStr, "%u:%u", script->lineno(), 361 script->column().oneOriginValue()); 362 hasLineAndColumn = true; 363 } 364 365 // Full profile string for scripts with functions is: 366 // FuncName (FileName:Lineno:Column) 367 // Full profile string for scripts without functions is: 368 // FileName:Lineno:Column 369 // Full profile string for scripts without functions and without lines is: 370 // FileName 371 372 // Calculate full string length. 373 size_t fullLength = 0; 374 if (name) { 375 MOZ_ASSERT(hasLineAndColumn); 376 fullLength = nameLength + 2 + filenameLength + 1 + lineAndColumnLength + 1; 377 } else if (hasLineAndColumn) { 378 fullLength = filenameLength + 1 + lineAndColumnLength; 379 } else { 380 fullLength = filenameLength; 381 } 382 383 // Allocate string. 384 UniqueChars str(cx->pod_malloc<char>(fullLength + 1)); 385 if (!str) { 386 return nullptr; 387 } 388 389 size_t cur = 0; 390 391 // Fill string with function name if needed. 392 if (name) { 393 mozilla::DebugOnly<size_t> written = JS::DeflateStringToUTF8Buffer( 394 name, mozilla::Span(str.get() + cur, nameLength)); 395 MOZ_ASSERT(written == nameLength); 396 cur += nameLength; 397 str[cur++] = ' '; 398 str[cur++] = '('; 399 } 400 401 // Fill string with filename chars. 402 memcpy(str.get() + cur, filenameStr, filenameLength); 403 cur += filenameLength; 404 405 // Fill line + column chars. 406 if (hasLineAndColumn) { 407 str[cur++] = ':'; 408 memcpy(str.get() + cur, lineAndColumnStr, lineAndColumnLength); 409 cur += lineAndColumnLength; 410 } 411 412 // Terminal ')' if necessary. 413 if (name) { 414 str[cur++] = ')'; 415 } 416 417 MOZ_ASSERT(cur == fullLength); 418 str[cur] = 0; 419 420 return str; 421 } 422 423 void GeckoProfilerThread::trace(JSTracer* trc) { 424 if (profilingStack_) { 425 size_t size = profilingStack_->stackSize(); 426 for (size_t i = 0; i < size; i++) { 427 profilingStack_->frames[i].trace(trc); 428 } 429 } 430 } 431 432 void GeckoProfilerRuntime::fixupStringsMapAfterMovingGC() { 433 for (ProfileStringMap::Enum e(strings()); !e.empty(); e.popFront()) { 434 BaseScript* script = e.front().key(); 435 if (IsForwarded(script)) { 436 script = Forwarded(script); 437 e.rekeyFront(script); 438 } 439 } 440 } 441 442 #ifdef JSGC_HASH_TABLE_CHECKS 443 void GeckoProfilerRuntime::checkStringsMapAfterMovingGC() { 444 CheckTableAfterMovingGC(strings(), [](const auto& entry) { 445 BaseScript* script = entry.key(); 446 CheckGCThingAfterMovingGC(script); 447 return script; 448 }); 449 } 450 #endif 451 452 // Get all script sources as a list of ProfilerJSSourceData. 453 js::ProfilerJSSources GeckoProfilerRuntime::getProfilerScriptSources() { 454 js::ProfilerJSSources result; 455 456 auto guard = scriptSources_.readLock(); 457 for (auto iter = guard->iter(); !iter.done(); iter.next()) { 458 const RefPtr<ScriptSource>& scriptSource = iter.get(); 459 MOZ_ASSERT(scriptSource); 460 461 bool hasSourceText; 462 bool retrievableSource; 463 ScriptSource::getSourceProperties(scriptSource, &hasSourceText, 464 &retrievableSource); 465 466 uint32_t sourceId = scriptSource->id(); 467 468 // Get filename for all source types. Create single copy to be moved. 469 const char* filename = scriptSource->filename(); 470 size_t filenameLen = 0; 471 JS::UniqueChars filenameCopy; 472 if (filename) { 473 filenameLen = strlen(filename); 474 filenameCopy.reset(static_cast<char*>(js_malloc(filenameLen + 1))); 475 if (filenameCopy) { 476 strcpy(filenameCopy.get(), filename); 477 } 478 } 479 480 if (retrievableSource) { 481 (void)result.append(ProfilerJSSourceData::CreateRetrievableFile( 482 sourceId, std::move(filenameCopy), filenameLen)); 483 continue; 484 } 485 486 if (!hasSourceText) { 487 (void)result.append( 488 ProfilerJSSourceData(sourceId, std::move(filenameCopy), filenameLen)); 489 continue; 490 } 491 492 size_t sourceLength = scriptSource->length(); 493 if (sourceLength == 0) { 494 (void)result.append( 495 ProfilerJSSourceData(sourceId, JS::UniqueTwoByteChars(), 0, 496 std::move(filenameCopy), filenameLen)); 497 continue; 498 } 499 500 SubstringCharsResult sourceResult(JS::UniqueChars(nullptr)); 501 size_t charsLength = 0; 502 503 if (scriptSource->shouldUnwrapEventHandlerBody()) { 504 sourceResult = scriptSource->functionBodyStringChars(&charsLength); 505 506 if (charsLength == 0) { 507 (void)result.append( 508 ProfilerJSSourceData(sourceId, JS::UniqueTwoByteChars(), 0, 509 std::move(filenameCopy), filenameLen)); 510 continue; 511 } 512 } else { 513 sourceResult = scriptSource->substringChars(0, sourceLength); 514 charsLength = sourceLength; 515 } 516 517 // Convert SubstringCharsResult to ProfilerJSSourceData. 518 // Note: The returned buffers are NOT null-terminated. The length is 519 // tracked separately in charsLength and passed to ProfilerJSSourceData. 520 if (sourceResult.is<JS::UniqueChars>()) { 521 auto& utf8Chars = sourceResult.as<JS::UniqueChars>(); 522 if (!utf8Chars) { 523 continue; 524 } 525 (void)result.append( 526 ProfilerJSSourceData(sourceId, std::move(utf8Chars), charsLength, 527 std::move(filenameCopy), filenameLen)); 528 } else { 529 auto& utf16Chars = sourceResult.as<JS::UniqueTwoByteChars>(); 530 if (!utf16Chars) { 531 continue; 532 } 533 (void)result.append( 534 ProfilerJSSourceData(sourceId, std::move(utf16Chars), charsLength, 535 std::move(filenameCopy), filenameLen)); 536 } 537 } 538 539 return result; 540 } 541 542 void ProfilingStackFrame::trace(JSTracer* trc) { 543 if (isJsFrame()) { 544 JSScript* s = rawScript(); 545 TraceNullableRoot(trc, &s, "ProfilingStackFrame script"); 546 spOrScript = s; 547 } 548 } 549 550 GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker( 551 JSContext* cx, bool hasProfilerFrame) 552 : profiler(&cx->geckoProfiler()) { 553 if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) { 554 profiler = nullptr; 555 return; 556 } 557 558 uint32_t sp = profiler->profilingStack_->stackPointer; 559 if (sp >= profiler->profilingStack_->stackCapacity()) { 560 profiler = nullptr; 561 return; 562 } 563 564 spBefore_ = sp; 565 if (sp == 0) { 566 return; 567 } 568 569 ProfilingStackFrame& frame = profiler->profilingStack_->frames[sp - 1]; 570 MOZ_ASSERT(!frame.isOSRFrame()); 571 frame.setIsOSRFrame(true); 572 } 573 574 GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker() { 575 if (profiler == nullptr) { 576 return; 577 } 578 579 uint32_t sp = profiler->stackPointer(); 580 MOZ_ASSERT(spBefore_ == sp); 581 if (sp == 0) { 582 return; 583 } 584 585 ProfilingStackFrame& frame = profiler->stack()[sp - 1]; 586 MOZ_ASSERT(frame.isOSRFrame()); 587 frame.setIsOSRFrame(false); 588 } 589 590 JS_PUBLIC_API JSScript* ProfilingStackFrame::script() const { 591 MOZ_ASSERT(isJsFrame()); 592 auto* script = reinterpret_cast<JSScript*>(spOrScript.operator void*()); 593 if (!script) { 594 return nullptr; 595 } 596 597 // If profiling is supressed then we can't trust the script pointers to be 598 // valid as they could be in the process of being moved by a compacting GC 599 // (although it's still OK to get the runtime from them). 600 JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread(); 601 if (!cx->isProfilerSamplingEnabled()) { 602 return nullptr; 603 } 604 605 MOZ_ASSERT(!IsForwarded(script)); 606 return script; 607 } 608 609 JS_PUBLIC_API JSFunction* ProfilingStackFrame::function() const { 610 JSScript* script = this->script(); 611 return script ? script->function() : nullptr; 612 } 613 614 JS_PUBLIC_API jsbytecode* ProfilingStackFrame::pc() const { 615 MOZ_ASSERT(isJsFrame()); 616 if (pcOffsetIfJS_ == NullPCOffset) { 617 return nullptr; 618 } 619 620 JSScript* script = this->script(); 621 return script ? script->offsetToPC(pcOffsetIfJS_) : nullptr; 622 } 623 624 /* static */ 625 int32_t ProfilingStackFrame::pcToOffset(JSScript* aScript, jsbytecode* aPc) { 626 return aPc ? aScript->pcToOffset(aPc) : NullPCOffset; 627 } 628 629 void ProfilingStackFrame::setPC(jsbytecode* pc) { 630 MOZ_ASSERT(isJsFrame()); 631 JSScript* script = this->script(); 632 MOZ_ASSERT( 633 script); // This should not be called while profiling is suppressed. 634 pcOffsetIfJS_ = pcToOffset(script, pc); 635 } 636 637 JS_PUBLIC_API uint32_t ProfilingStackFrame::sourceId() const { 638 return sourceId_; 639 } 640 641 JS_PUBLIC_API void js::SetContextProfilingStack( 642 JSContext* cx, ProfilingStack* profilingStack) { 643 cx->geckoProfiler().setProfilingStack( 644 profilingStack, cx->runtime()->geckoProfiler().enabled()); 645 } 646 647 JS_PUBLIC_API void js::EnableContextProfilingStack(JSContext* cx, 648 bool enabled) { 649 cx->geckoProfiler().enable(enabled); 650 cx->runtime()->geckoProfiler().enable(enabled); 651 } 652 653 JS_PUBLIC_API void js::RegisterContextProfilerMarkers( 654 JSContext* cx, 655 void (*eventMarker)(mozilla::MarkerCategory, const char*, const char*), 656 void (*intervalMarker)(mozilla::MarkerCategory, const char*, 657 mozilla::TimeStamp, const char*), 658 void (*flowMarker)(mozilla::MarkerCategory, const char*, uint64_t), 659 void (*terminatingFlowMarker)(mozilla::MarkerCategory, const char*, 660 uint64_t)) { 661 MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled()); 662 cx->runtime()->geckoProfiler().setEventMarker(eventMarker); 663 cx->runtime()->geckoProfiler().setIntervalMarker(intervalMarker); 664 cx->runtime()->geckoProfiler().setFlowMarker(flowMarker); 665 cx->runtime()->geckoProfiler().setTerminatingFlowMarker( 666 terminatingFlowMarker); 667 } 668 669 JS_PUBLIC_API js::ProfilerJSSources js::GetProfilerScriptSources( 670 JSRuntime* rt) { 671 return rt->geckoProfiler().getProfilerScriptSources(); 672 } 673 674 JS_PUBLIC_API ProfilerJSSourceData 675 js::RetrieveProfilerSourceContent(JSContext* cx, const char* filename) { 676 MOZ_ASSERT(filename && strlen(filename)); 677 if (!cx) { 678 return ProfilerJSSourceData(); // Return unavailable 679 } 680 681 // Check if source hook is available 682 if (!cx->runtime()->sourceHook.ref()) { 683 return ProfilerJSSourceData(); // Return unavailable 684 } 685 686 size_t sourceLength = 0; 687 char* utf8Source = nullptr; 688 689 bool loadSuccess = cx->runtime()->sourceHook->load( 690 cx, filename, nullptr, &utf8Source, &sourceLength); 691 692 if (!loadSuccess) { 693 // Clear the pending exception that have been set by the source hook. 694 JS_ClearPendingException(cx); 695 return ProfilerJSSourceData(); // Return unavailable 696 } 697 698 if (utf8Source) { 699 return ProfilerJSSourceData(JS::UniqueChars(utf8Source), sourceLength); 700 } 701 702 // Hook returned success but no source data. Return unavailable. 703 return ProfilerJSSourceData(); 704 } 705 706 AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx) 707 : cx_(cx), previouslyEnabled_(cx->isProfilerSamplingEnabled()) { 708 if (previouslyEnabled_) { 709 cx_->disableProfilerSampling(); 710 } 711 } 712 713 AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() { 714 if (previouslyEnabled_) { 715 cx_->enableProfilerSampling(); 716 } 717 } 718 719 namespace JS { 720 721 // clang-format off 722 723 // ProfilingSubcategory_X: 724 // One enum for each category X, listing that category's subcategories. This 725 // allows the sProfilingCategoryInfo macro construction below to look up a 726 // per-category index for a subcategory. 727 #define SUBCATEGORY_ENUMS_BEGIN_CATEGORY(name, labelAsString, color) \ 728 enum class ProfilingSubcategory_##name : uint32_t { 729 #define SUBCATEGORY_ENUMS_SUBCATEGORY(category, name, labelAsString) \ 730 name, 731 #define SUBCATEGORY_ENUMS_END_CATEGORY \ 732 }; 733 MOZ_PROFILING_CATEGORY_LIST(SUBCATEGORY_ENUMS_BEGIN_CATEGORY, 734 SUBCATEGORY_ENUMS_SUBCATEGORY, 735 SUBCATEGORY_ENUMS_END_CATEGORY) 736 #undef SUBCATEGORY_ENUMS_BEGIN_CATEGORY 737 #undef SUBCATEGORY_ENUMS_SUBCATEGORY 738 #undef SUBCATEGORY_ENUMS_END_CATEGORY 739 740 // sProfilingCategoryPairInfo: 741 // A list of ProfilingCategoryPairInfos with the same order as 742 // ProfilingCategoryPair, which can be used to map a ProfilingCategoryPair to 743 // its information. 744 #define CATEGORY_INFO_BEGIN_CATEGORY(name, labelAsString, color) 745 #define CATEGORY_INFO_SUBCATEGORY(category, name, labelAsString) \ 746 {ProfilingCategory::category, \ 747 uint32_t(ProfilingSubcategory_##category::name), labelAsString}, 748 #define CATEGORY_INFO_END_CATEGORY 749 const ProfilingCategoryPairInfo sProfilingCategoryPairInfo[] = { 750 MOZ_PROFILING_CATEGORY_LIST(CATEGORY_INFO_BEGIN_CATEGORY, 751 CATEGORY_INFO_SUBCATEGORY, 752 CATEGORY_INFO_END_CATEGORY) 753 }; 754 #undef CATEGORY_INFO_BEGIN_CATEGORY 755 #undef CATEGORY_INFO_SUBCATEGORY 756 #undef CATEGORY_INFO_END_CATEGORY 757 758 // clang-format on 759 760 JS_PUBLIC_API const ProfilingCategoryPairInfo& GetProfilingCategoryPairInfo( 761 ProfilingCategoryPair aCategoryPair) { 762 static_assert( 763 std::size(sProfilingCategoryPairInfo) == 764 uint32_t(ProfilingCategoryPair::COUNT), 765 "sProfilingCategoryPairInfo and ProfilingCategory need to have the " 766 "same order and the same length"); 767 768 uint32_t categoryPairIndex = uint32_t(aCategoryPair); 769 MOZ_RELEASE_ASSERT(categoryPairIndex <= 770 uint32_t(ProfilingCategoryPair::LAST)); 771 return sProfilingCategoryPairInfo[categoryPairIndex]; 772 } 773 774 } // namespace JS