591 lines
19 KiB
C++
591 lines
19 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "vm/GeckoProfiler-inl.h"
|
|
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/Sprintf.h"
|
|
|
|
#include "gc/GC.h"
|
|
#include "gc/PublicIterators.h"
|
|
#include "jit/BaselineJIT.h"
|
|
#include "jit/JitcodeMap.h"
|
|
#include "jit/JitRuntime.h"
|
|
#include "jit/JSJitFrameIter.h"
|
|
#include "jit/PerfSpewer.h"
|
|
#include "js/ProfilingStack.h"
|
|
#include "vm/FrameIter.h" // js::OnlyJSJitFrameIter
|
|
#include "vm/JitActivation.h"
|
|
#include "vm/JSScript.h"
|
|
|
|
#include "gc/Marking-inl.h"
|
|
#include "jit/JSJitFrameIter-inl.h"
|
|
|
|
using namespace js;
|
|
|
|
GeckoProfilerThread::GeckoProfilerThread()
|
|
: profilingStack_(nullptr), profilingStackIfEnabled_(nullptr) {}
|
|
|
|
GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt)
|
|
: rt(rt),
|
|
slowAssertions(false),
|
|
enabled_(false),
|
|
eventMarker_(nullptr),
|
|
intervalMarker_(nullptr) {
|
|
MOZ_ASSERT(rt != nullptr);
|
|
}
|
|
|
|
void GeckoProfilerThread::setProfilingStack(ProfilingStack* profilingStack,
|
|
bool enabled) {
|
|
profilingStack_ = profilingStack;
|
|
profilingStackIfEnabled_ = enabled ? profilingStack : nullptr;
|
|
}
|
|
|
|
void GeckoProfilerRuntime::setEventMarker(void (*fn)(mozilla::MarkerCategory,
|
|
const char*,
|
|
const char*)) {
|
|
eventMarker_ = fn;
|
|
}
|
|
|
|
void GeckoProfilerRuntime::setIntervalMarker(void (*fn)(
|
|
mozilla::MarkerCategory, const char*, mozilla::TimeStamp, const char*)) {
|
|
intervalMarker_ = fn;
|
|
}
|
|
|
|
// Get a pointer to the top-most profiling frame, given the exit frame pointer.
|
|
static jit::JitFrameLayout* GetTopProfilingJitFrame(jit::JitActivation* act) {
|
|
// If there is no exit frame set, just return.
|
|
if (!act->hasExitFP()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Skip wasm frames that might be in the way.
|
|
OnlyJSJitFrameIter iter(act);
|
|
if (iter.done()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Skip if the activation has no JS frames. This can happen if there's only a
|
|
// TrampolineNative frame because these are skipped by the profiling frame
|
|
// iterator.
|
|
jit::JSJitProfilingFrameIterator jitIter(
|
|
(jit::CommonFrameLayout*)iter.frame().fp());
|
|
if (jitIter.done()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return jitIter.framePtr();
|
|
}
|
|
|
|
void GeckoProfilerRuntime::enable(bool enabled) {
|
|
JSContext* cx = rt->mainContextFromAnyThread();
|
|
MOZ_ASSERT(cx->geckoProfiler().infraInstalled());
|
|
|
|
if (enabled_ == enabled) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Ensure all future generated code will be instrumented, or that all
|
|
* currently instrumented code is discarded
|
|
*/
|
|
ReleaseAllJITCode(rt->gcContext());
|
|
|
|
// This function is called when the Gecko profiler makes a new Sampler
|
|
// (and thus, a new circular buffer). Set all current entries in the
|
|
// JitcodeGlobalTable as expired and reset the buffer range start.
|
|
if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable()) {
|
|
rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired();
|
|
}
|
|
rt->setProfilerSampleBufferRangeStart(0);
|
|
|
|
// Ensure that lastProfilingFrame is null for the main thread.
|
|
if (cx->jitActivation) {
|
|
cx->jitActivation->setLastProfilingFrame(nullptr);
|
|
cx->jitActivation->setLastProfilingCallSite(nullptr);
|
|
}
|
|
|
|
enabled_ = enabled;
|
|
|
|
/* Toggle Gecko Profiler-related jumps on baseline jitcode.
|
|
* The call to |ReleaseAllJITCode| above will release most baseline jitcode,
|
|
* but not jitcode for scripts with active frames on the stack. These scripts
|
|
* need to have their profiler state toggled so they behave properly.
|
|
*/
|
|
jit::ToggleBaselineProfiling(cx, enabled);
|
|
|
|
// Update lastProfilingFrame to point to the top-most JS jit-frame currently
|
|
// on stack.
|
|
if (cx->jitActivation) {
|
|
// Walk through all activations, and set their lastProfilingFrame
|
|
// appropriately.
|
|
if (enabled) {
|
|
jit::JitActivation* jitActivation = cx->jitActivation;
|
|
while (jitActivation) {
|
|
auto* lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
|
|
jitActivation->setLastProfilingFrame(lastProfilingFrame);
|
|
jitActivation->setLastProfilingCallSite(nullptr);
|
|
jitActivation = jitActivation->prevJitActivation();
|
|
}
|
|
} else {
|
|
jit::JitActivation* jitActivation = cx->jitActivation;
|
|
while (jitActivation) {
|
|
jitActivation->setLastProfilingFrame(nullptr);
|
|
jitActivation->setLastProfilingCallSite(nullptr);
|
|
jitActivation = jitActivation->prevJitActivation();
|
|
}
|
|
}
|
|
}
|
|
|
|
// WebAssembly code does not need to be released, but profiling string
|
|
// labels have to be generated so that they are available during async
|
|
// profiling stack iteration.
|
|
for (RealmsIter r(rt); !r.done(); r.next()) {
|
|
r->wasm.ensureProfilingLabels(enabled);
|
|
}
|
|
|
|
#ifdef JS_STRUCTURED_SPEW
|
|
// Enable the structured spewer if the environment variable is set.
|
|
if (enabled) {
|
|
cx->spewer().enableSpewing();
|
|
} else {
|
|
cx->spewer().disableSpewing();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Lookup the string for the function/script, creating one if necessary */
|
|
const char* GeckoProfilerRuntime::profileString(JSContext* cx,
|
|
BaseScript* script) {
|
|
ProfileStringMap::AddPtr s = strings().lookupForAdd(script);
|
|
|
|
if (!s) {
|
|
UniqueChars str = allocProfileString(cx, script);
|
|
if (!str) {
|
|
return nullptr;
|
|
}
|
|
MOZ_ASSERT(script->hasBytecode());
|
|
if (!strings().add(s, script, std::move(str))) {
|
|
ReportOutOfMemory(cx);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return s->value().get();
|
|
}
|
|
|
|
void GeckoProfilerRuntime::onScriptFinalized(BaseScript* script) {
|
|
/*
|
|
* This function is called whenever a script is destroyed, regardless of
|
|
* whether profiling has been turned on, so don't invoke a function on an
|
|
* invalid hash set. Also, even if profiling was enabled but then turned
|
|
* off, we still want to remove the string, so no check of enabled() is
|
|
* done.
|
|
*/
|
|
if (ProfileStringMap::Ptr entry = strings().lookup(script)) {
|
|
strings().remove(entry);
|
|
}
|
|
}
|
|
|
|
void GeckoProfilerRuntime::markEvent(const char* event, const char* details,
|
|
JS::ProfilingCategoryPair jsPair) {
|
|
MOZ_ASSERT(enabled());
|
|
if (eventMarker_) {
|
|
JS::AutoSuppressGCAnalysis nogc;
|
|
mozilla::MarkerCategory category(
|
|
static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
|
|
eventMarker_(category, event, details);
|
|
}
|
|
}
|
|
|
|
void GeckoProfilerRuntime::markInterval(const char* event,
|
|
mozilla::TimeStamp start,
|
|
const char* details,
|
|
JS::ProfilingCategoryPair jsPair) {
|
|
MOZ_ASSERT(enabled());
|
|
if (intervalMarker_) {
|
|
JS::AutoSuppressGCAnalysis nogc;
|
|
mozilla::MarkerCategory category(
|
|
static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
|
|
intervalMarker_(category, event, start, details);
|
|
}
|
|
}
|
|
|
|
bool GeckoProfilerThread::enter(JSContext* cx, JSScript* script) {
|
|
const char* dynamicString =
|
|
cx->runtime()->geckoProfiler().profileString(cx, script);
|
|
if (dynamicString == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// In debug builds, assert the JS profiling stack frames already on the
|
|
// stack have a non-null pc. Only look at the top frames to avoid quadratic
|
|
// behavior.
|
|
uint32_t sp = profilingStack_->stackPointer;
|
|
if (sp > 0 && sp - 1 < profilingStack_->stackCapacity()) {
|
|
size_t start = (sp > 4) ? sp - 4 : 0;
|
|
for (size_t i = start; i < sp - 1; i++) {
|
|
MOZ_ASSERT_IF(profilingStack_->frames[i].isJsFrame(),
|
|
profilingStack_->frames[i].pc());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
profilingStack_->pushJsFrame(
|
|
"", dynamicString, script, script->code(),
|
|
script->realm()->creationOptions().profilerRealmID());
|
|
return true;
|
|
}
|
|
|
|
void GeckoProfilerThread::exit(JSContext* cx, JSScript* script) {
|
|
profilingStack_->pop();
|
|
|
|
#ifdef DEBUG
|
|
/* Sanity check to make sure push/pop balanced */
|
|
uint32_t sp = profilingStack_->stackPointer;
|
|
if (sp < profilingStack_->stackCapacity()) {
|
|
JSRuntime* rt = script->runtimeFromMainThread();
|
|
const char* dynamicString = rt->geckoProfiler().profileString(cx, script);
|
|
/* Can't fail lookup because we should already be in the set */
|
|
MOZ_ASSERT(dynamicString);
|
|
|
|
// Bug 822041
|
|
if (!profilingStack_->frames[sp].isJsFrame()) {
|
|
fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
|
|
fprintf(stderr, " frames=%p size=%u/%u\n", (void*)profilingStack_->frames,
|
|
uint32_t(profilingStack_->stackPointer),
|
|
profilingStack_->stackCapacity());
|
|
for (int32_t i = sp; i >= 0; i--) {
|
|
ProfilingStackFrame& frame = profilingStack_->frames[i];
|
|
if (frame.isJsFrame()) {
|
|
fprintf(stderr, " [%d] JS %s\n", i, frame.dynamicString());
|
|
} else {
|
|
fprintf(stderr, " [%d] Label %s\n", i, frame.dynamicString());
|
|
}
|
|
}
|
|
}
|
|
|
|
ProfilingStackFrame& frame = profilingStack_->frames[sp];
|
|
MOZ_ASSERT(frame.isJsFrame());
|
|
MOZ_ASSERT(frame.script() == script);
|
|
MOZ_ASSERT(strcmp((const char*)frame.dynamicString(), dynamicString) == 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Serializes the script/function pair into a "descriptive string" which is
|
|
* allowed to fail. This function cannot trigger a GC because it could finalize
|
|
* some scripts, resize the hash table of profile strings, and invalidate the
|
|
* AddPtr held while invoking allocProfileString.
|
|
*/
|
|
/* static */
|
|
UniqueChars GeckoProfilerRuntime::allocProfileString(JSContext* cx,
|
|
BaseScript* script) {
|
|
// Note: this profiler string is regexp-matched by
|
|
// profiler code. Most recently at
|
|
// https://github.com/icecat-devtools/profiler/blob/245b1a400c5c368ccc13641d0335398bafa0e870/src/profile-logic/process-profile.js#L520-L525
|
|
|
|
// If the script has a function, try calculating its name.
|
|
JSAtom* name = nullptr;
|
|
size_t nameLength = 0;
|
|
JSFunction* func = script->function();
|
|
if (func && func->fullDisplayAtom()) {
|
|
name = func->fullDisplayAtom();
|
|
nameLength = JS::GetDeflatedUTF8StringLength(name);
|
|
}
|
|
|
|
// Calculate filename length. We cap this to a reasonable limit to avoid
|
|
// performance impact of strlen/alloc/memcpy.
|
|
constexpr size_t MaxFilenameLength = 200;
|
|
const char* filenameStr = script->filename() ? script->filename() : "(null)";
|
|
size_t filenameLength = js_strnlen(filenameStr, MaxFilenameLength);
|
|
|
|
// Calculate line + column length.
|
|
bool hasLineAndColumn = false;
|
|
size_t lineAndColumnLength = 0;
|
|
char lineAndColumnStr[30];
|
|
if (name || script->isFunction() || script->isForEval()) {
|
|
lineAndColumnLength =
|
|
SprintfLiteral(lineAndColumnStr, "%u:%u", script->lineno(),
|
|
script->column().oneOriginValue());
|
|
hasLineAndColumn = true;
|
|
}
|
|
|
|
// Full profile string for scripts with functions is:
|
|
// FuncName (FileName:Lineno:Column)
|
|
// Full profile string for scripts without functions is:
|
|
// FileName:Lineno:Column
|
|
// Full profile string for scripts without functions and without lines is:
|
|
// FileName
|
|
|
|
// Calculate full string length.
|
|
size_t fullLength = 0;
|
|
if (name) {
|
|
MOZ_ASSERT(hasLineAndColumn);
|
|
fullLength = nameLength + 2 + filenameLength + 1 + lineAndColumnLength + 1;
|
|
} else if (hasLineAndColumn) {
|
|
fullLength = filenameLength + 1 + lineAndColumnLength;
|
|
} else {
|
|
fullLength = filenameLength;
|
|
}
|
|
|
|
// Allocate string.
|
|
UniqueChars str(cx->pod_malloc<char>(fullLength + 1));
|
|
if (!str) {
|
|
return nullptr;
|
|
}
|
|
|
|
size_t cur = 0;
|
|
|
|
// Fill string with function name if needed.
|
|
if (name) {
|
|
mozilla::DebugOnly<size_t> written = JS::DeflateStringToUTF8Buffer(
|
|
name, mozilla::Span(str.get() + cur, nameLength));
|
|
MOZ_ASSERT(written == nameLength);
|
|
cur += nameLength;
|
|
str[cur++] = ' ';
|
|
str[cur++] = '(';
|
|
}
|
|
|
|
// Fill string with filename chars.
|
|
memcpy(str.get() + cur, filenameStr, filenameLength);
|
|
cur += filenameLength;
|
|
|
|
// Fill line + column chars.
|
|
if (hasLineAndColumn) {
|
|
str[cur++] = ':';
|
|
memcpy(str.get() + cur, lineAndColumnStr, lineAndColumnLength);
|
|
cur += lineAndColumnLength;
|
|
}
|
|
|
|
// Terminal ')' if necessary.
|
|
if (name) {
|
|
str[cur++] = ')';
|
|
}
|
|
|
|
MOZ_ASSERT(cur == fullLength);
|
|
str[cur] = 0;
|
|
|
|
return str;
|
|
}
|
|
|
|
void GeckoProfilerThread::trace(JSTracer* trc) {
|
|
if (profilingStack_) {
|
|
size_t size = profilingStack_->stackSize();
|
|
for (size_t i = 0; i < size; i++) {
|
|
profilingStack_->frames[i].trace(trc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GeckoProfilerRuntime::fixupStringsMapAfterMovingGC() {
|
|
for (ProfileStringMap::Enum e(strings()); !e.empty(); e.popFront()) {
|
|
BaseScript* script = e.front().key();
|
|
if (IsForwarded(script)) {
|
|
script = Forwarded(script);
|
|
e.rekeyFront(script);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef JSGC_HASH_TABLE_CHECKS
|
|
void GeckoProfilerRuntime::checkStringsMapAfterMovingGC() {
|
|
CheckTableAfterMovingGC(strings(), [](const auto& entry) {
|
|
BaseScript* script = entry.key();
|
|
CheckGCThingAfterMovingGC(script);
|
|
return script;
|
|
});
|
|
}
|
|
#endif
|
|
|
|
void ProfilingStackFrame::trace(JSTracer* trc) {
|
|
if (isJsFrame()) {
|
|
JSScript* s = rawScript();
|
|
TraceNullableRoot(trc, &s, "ProfilingStackFrame script");
|
|
spOrScript = s;
|
|
}
|
|
}
|
|
|
|
GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(
|
|
JSContext* cx, bool hasProfilerFrame)
|
|
: profiler(&cx->geckoProfiler()) {
|
|
if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) {
|
|
profiler = nullptr;
|
|
return;
|
|
}
|
|
|
|
uint32_t sp = profiler->profilingStack_->stackPointer;
|
|
if (sp >= profiler->profilingStack_->stackCapacity()) {
|
|
profiler = nullptr;
|
|
return;
|
|
}
|
|
|
|
spBefore_ = sp;
|
|
if (sp == 0) {
|
|
return;
|
|
}
|
|
|
|
ProfilingStackFrame& frame = profiler->profilingStack_->frames[sp - 1];
|
|
MOZ_ASSERT(!frame.isOSRFrame());
|
|
frame.setIsOSRFrame(true);
|
|
}
|
|
|
|
GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker() {
|
|
if (profiler == nullptr) {
|
|
return;
|
|
}
|
|
|
|
uint32_t sp = profiler->stackPointer();
|
|
MOZ_ASSERT(spBefore_ == sp);
|
|
if (sp == 0) {
|
|
return;
|
|
}
|
|
|
|
ProfilingStackFrame& frame = profiler->stack()[sp - 1];
|
|
MOZ_ASSERT(frame.isOSRFrame());
|
|
frame.setIsOSRFrame(false);
|
|
}
|
|
|
|
JS_PUBLIC_API JSScript* ProfilingStackFrame::script() const {
|
|
MOZ_ASSERT(isJsFrame());
|
|
auto* script = reinterpret_cast<JSScript*>(spOrScript.operator void*());
|
|
if (!script) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If profiling is supressed then we can't trust the script pointers to be
|
|
// valid as they could be in the process of being moved by a compacting GC
|
|
// (although it's still OK to get the runtime from them).
|
|
JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread();
|
|
if (!cx->isProfilerSamplingEnabled()) {
|
|
return nullptr;
|
|
}
|
|
|
|
MOZ_ASSERT(!IsForwarded(script));
|
|
return script;
|
|
}
|
|
|
|
JS_PUBLIC_API JSFunction* ProfilingStackFrame::function() const {
|
|
JSScript* script = this->script();
|
|
return script ? script->function() : nullptr;
|
|
}
|
|
|
|
JS_PUBLIC_API jsbytecode* ProfilingStackFrame::pc() const {
|
|
MOZ_ASSERT(isJsFrame());
|
|
if (pcOffsetIfJS_ == NullPCOffset) {
|
|
return nullptr;
|
|
}
|
|
|
|
JSScript* script = this->script();
|
|
return script ? script->offsetToPC(pcOffsetIfJS_) : nullptr;
|
|
}
|
|
|
|
/* static */
|
|
int32_t ProfilingStackFrame::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
|
|
return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
|
|
}
|
|
|
|
void ProfilingStackFrame::setPC(jsbytecode* pc) {
|
|
MOZ_ASSERT(isJsFrame());
|
|
JSScript* script = this->script();
|
|
MOZ_ASSERT(
|
|
script); // This should not be called while profiling is suppressed.
|
|
pcOffsetIfJS_ = pcToOffset(script, pc);
|
|
}
|
|
|
|
JS_PUBLIC_API void js::SetContextProfilingStack(
|
|
JSContext* cx, ProfilingStack* profilingStack) {
|
|
cx->geckoProfiler().setProfilingStack(
|
|
profilingStack, cx->runtime()->geckoProfiler().enabled());
|
|
}
|
|
|
|
JS_PUBLIC_API void js::EnableContextProfilingStack(JSContext* cx,
|
|
bool enabled) {
|
|
cx->geckoProfiler().enable(enabled);
|
|
cx->runtime()->geckoProfiler().enable(enabled);
|
|
}
|
|
|
|
JS_PUBLIC_API void js::RegisterContextProfilingEventMarker(
|
|
JSContext* cx,
|
|
void (*mark)(mozilla::MarkerCategory, const char*, const char*),
|
|
void (*interval)(mozilla::MarkerCategory, const char*, mozilla::TimeStamp,
|
|
const char*)) {
|
|
MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
|
|
cx->runtime()->geckoProfiler().setEventMarker(mark);
|
|
cx->runtime()->geckoProfiler().setIntervalMarker(interval);
|
|
}
|
|
|
|
AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx)
|
|
: cx_(cx), previouslyEnabled_(cx->isProfilerSamplingEnabled()) {
|
|
if (previouslyEnabled_) {
|
|
cx_->disableProfilerSampling();
|
|
}
|
|
}
|
|
|
|
AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() {
|
|
if (previouslyEnabled_) {
|
|
cx_->enableProfilerSampling();
|
|
}
|
|
}
|
|
|
|
namespace JS {
|
|
|
|
// clang-format off
|
|
|
|
// ProfilingSubcategory_X:
|
|
// One enum for each category X, listing that category's subcategories. This
|
|
// allows the sProfilingCategoryInfo macro construction below to look up a
|
|
// per-category index for a subcategory.
|
|
#define SUBCATEGORY_ENUMS_BEGIN_CATEGORY(name, labelAsString, color) \
|
|
enum class ProfilingSubcategory_##name : uint32_t {
|
|
#define SUBCATEGORY_ENUMS_SUBCATEGORY(category, name, labelAsString) \
|
|
name,
|
|
#define SUBCATEGORY_ENUMS_END_CATEGORY \
|
|
};
|
|
MOZ_PROFILING_CATEGORY_LIST(SUBCATEGORY_ENUMS_BEGIN_CATEGORY,
|
|
SUBCATEGORY_ENUMS_SUBCATEGORY,
|
|
SUBCATEGORY_ENUMS_END_CATEGORY)
|
|
#undef SUBCATEGORY_ENUMS_BEGIN_CATEGORY
|
|
#undef SUBCATEGORY_ENUMS_SUBCATEGORY
|
|
#undef SUBCATEGORY_ENUMS_END_CATEGORY
|
|
|
|
// sProfilingCategoryPairInfo:
|
|
// A list of ProfilingCategoryPairInfos with the same order as
|
|
// ProfilingCategoryPair, which can be used to map a ProfilingCategoryPair to
|
|
// its information.
|
|
#define CATEGORY_INFO_BEGIN_CATEGORY(name, labelAsString, color)
|
|
#define CATEGORY_INFO_SUBCATEGORY(category, name, labelAsString) \
|
|
{ProfilingCategory::category, \
|
|
uint32_t(ProfilingSubcategory_##category::name), labelAsString},
|
|
#define CATEGORY_INFO_END_CATEGORY
|
|
const ProfilingCategoryPairInfo sProfilingCategoryPairInfo[] = {
|
|
MOZ_PROFILING_CATEGORY_LIST(CATEGORY_INFO_BEGIN_CATEGORY,
|
|
CATEGORY_INFO_SUBCATEGORY,
|
|
CATEGORY_INFO_END_CATEGORY)
|
|
};
|
|
#undef CATEGORY_INFO_BEGIN_CATEGORY
|
|
#undef CATEGORY_INFO_SUBCATEGORY
|
|
#undef CATEGORY_INFO_END_CATEGORY
|
|
|
|
// clang-format on
|
|
|
|
JS_PUBLIC_API const ProfilingCategoryPairInfo& GetProfilingCategoryPairInfo(
|
|
ProfilingCategoryPair aCategoryPair) {
|
|
static_assert(
|
|
std::size(sProfilingCategoryPairInfo) ==
|
|
uint32_t(ProfilingCategoryPair::COUNT),
|
|
"sProfilingCategoryPairInfo and ProfilingCategory need to have the "
|
|
"same order and the same length");
|
|
|
|
uint32_t categoryPairIndex = uint32_t(aCategoryPair);
|
|
MOZ_RELEASE_ASSERT(categoryPairIndex <=
|
|
uint32_t(ProfilingCategoryPair::LAST));
|
|
return sProfilingCategoryPairInfo[categoryPairIndex];
|
|
}
|
|
|
|
} // namespace JS
|