Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; 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/. */
// This header contains most functions that give information about the Profiler:
// Whether it is active or not, paused, and the selected features.
// It is safe to include unconditionally, but uses of structs and functions must
// be guarded by `#ifdef MOZ_GECKO_PROFILER`.
#ifndef ProfilerState_h
#define ProfilerState_h
#include <mozilla/DefineEnum.h>
#include <mozilla/EnumSet.h>
#include "mozilla/ProfilerUtils.h"
#include <functional>
//---------------------------------------------------------------------------
// Profiler features
//---------------------------------------------------------------------------
#if defined(__APPLE__) && defined(__aarch64__)
# define POWER_HELP "Sample per process power use"
#elif defined(__APPLE__) && defined(__x86_64__)
# define POWER_HELP \
"Record the power used by the entire system with each sample."
#elif defined(__linux__) && defined(__x86_64__)
# define POWER_HELP \
"Record the power used by the entire system with each sample. " \
"Only available with Intel CPUs and requires setting " \
"the sysctl kernel.perf_event_paranoid to 0."
#elif defined(_MSC_VER)
# define POWER_HELP \
"Record the value of every energy meter available on the system with " \
"each sample. Only available on Windows 11 with Intel CPUs."
#else
# define POWER_HELP "Not supported on this platform."
#endif
// Higher-order macro containing all the feature info in one place. Define
// |MACRO| appropriately to extract the relevant parts. Note that the number
// values are used internally only and so can be changed without consequence.
// Any changes to this list should also be applied to the feature list in
// toolkit/components/extensions/schemas/geckoProfiler.json.
// *** Synchronize with lists in BaseProfilerState.h and geckoProfiler.json ***
#define PROFILER_FOR_EACH_FEATURE(MACRO) \
MACRO(0, "java", Java, "Profile Java code, Android only") \
\
MACRO(1, "js", JS, \
"Get the JS engine to expose the JS stack to the profiler") \
\
MACRO(2, "mainthreadio", MainThreadIO, "Add main thread file I/O") \
\
MACRO(3, "fileio", FileIO, \
"Add file I/O from all profiled threads, implies mainthreadio") \
\
MACRO(4, "fileioall", FileIOAll, \
"Add file I/O from all threads, implies fileio") \
\
MACRO(5, "noiostacks", NoIOStacks, \
"File I/O markers do not capture stacks, to reduce overhead") \
\
MACRO(6, "screenshots", Screenshots, \
"Take a snapshot of the window on every composition") \
\
MACRO(7, "seqstyle", SequentialStyle, \
"Disable parallel traversal in styling") \
\
MACRO(8, "stackwalk", StackWalk, \
"Walk the C++ stack, not available on all platforms") \
\
MACRO(9, "jsallocations", JSAllocations, \
"Have the JavaScript engine track allocations") \
\
MACRO(10, "nostacksampling", NoStackSampling, \
"Disable all stack sampling: Cancels \"js\", \"stackwalk\" and " \
"labels") \
\
MACRO(11, "preferencereads", PreferenceReads, \
"Track when preferences are read") \
\
MACRO(12, "nativeallocations", NativeAllocations, \
"Collect the stacks from a smaller subset of all native " \
"allocations, biasing towards collecting larger allocations") \
\
MACRO(13, "ipcmessages", IPCMessages, \
"Have the IPC layer track cross-process messages") \
\
MACRO(14, "audiocallbacktracing", AudioCallbackTracing, \
"Audio callback tracing") \
\
MACRO(15, "cpu", CPUUtilization, "CPU utilization") \
\
MACRO(16, "notimerresolutionchange", NoTimerResolutionChange, \
"Do not adjust the timer resolution for sampling, so that other " \
"Firefox timers do not get affected") \
\
MACRO(17, "cpuallthreads", CPUAllThreads, \
"Sample the CPU utilization of all registered threads") \
\
MACRO(18, "samplingallthreads", SamplingAllThreads, \
"Sample the stacks of all registered threads") \
\
MACRO(19, "markersallthreads", MarkersAllThreads, \
"Record markers from all registered threads") \
\
MACRO(20, "unregisteredthreads", UnregisteredThreads, \
"Discover and profile unregistered threads -- beware: expensive!") \
\
MACRO(21, "processcpu", ProcessCPU, \
"Sample the CPU utilization of each process") \
\
MACRO(22, "power", Power, POWER_HELP)
// *** Synchronize with lists in BaseProfilerState.h and geckoProfiler.json ***
struct ProfilerFeature {
#define DECLARE(n_, str_, Name_, desc_) \
static constexpr uint32_t Name_ = (1u << n_); \
[[nodiscard]] static constexpr bool Has##Name_(uint32_t aFeatures) { \
return aFeatures & Name_; \
} \
static constexpr void Set##Name_(uint32_t& aFeatures) { \
aFeatures |= Name_; \
} \
static constexpr void Clear##Name_(uint32_t& aFeatures) { \
aFeatures &= ~Name_; \
}
// Define a bitfield constant, a getter, and two setters for each feature.
PROFILER_FOR_EACH_FEATURE(DECLARE)
#undef DECLARE
};
// clang-format off
MOZ_DEFINE_ENUM_CLASS(ProfilingState,(
// A callback will be invoked ...
AlreadyActive, // if the profiler is active when the callback is added.
RemovingCallback, // when the callback is removed.
Started, // after the profiler has started.
Pausing, // before the profiler is paused.
Resumed, // after the profiler has resumed.
GeneratingProfile, // before a profile is created.
Stopping, // before the profiler stops (unless restarting afterward).
ShuttingDown // before the profiler is shut down.
));
// clang-format on
[[nodiscard]] inline static const char* ProfilingStateToString(
ProfilingState aProfilingState) {
switch (aProfilingState) {
case ProfilingState::AlreadyActive:
return "Profiler already active";
case ProfilingState::RemovingCallback:
return "Callback being removed";
case ProfilingState::Started:
return "Profiler started";
case ProfilingState::Pausing:
return "Profiler pausing";
case ProfilingState::Resumed:
return "Profiler resumed";
case ProfilingState::GeneratingProfile:
return "Generating profile";
case ProfilingState::Stopping:
return "Profiler stopping";
case ProfilingState::ShuttingDown:
return "Profiler shutting down";
default:
MOZ_ASSERT_UNREACHABLE("Unexpected ProfilingState enum value");
return "?";
}
}
using ProfilingStateSet = mozilla::EnumSet<ProfilingState>;
[[nodiscard]] constexpr ProfilingStateSet AllProfilingStates() {
ProfilingStateSet set;
using Value = std::underlying_type_t<ProfilingState>;
for (Value stateValue = 0;
stateValue <= static_cast<Value>(kHighestProfilingState); ++stateValue) {
set += static_cast<ProfilingState>(stateValue);
}
return set;
}
// Type of callbacks to be invoked at certain state changes.
// It must NOT call profiler_add/remove_state_change_callback().
using ProfilingStateChangeCallback = std::function<void(ProfilingState)>;
#ifndef MOZ_GECKO_PROFILER
[[nodiscard]] inline bool profiler_is_active() { return false; }
[[nodiscard]] inline bool profiler_is_active_and_unpaused() { return false; }
[[nodiscard]] inline bool profiler_feature_active(uint32_t aFeature) {
return false;
}
[[nodiscard]] inline bool profiler_is_locked_on_current_thread() {
return false;
}
inline void profiler_add_state_change_callback(
ProfilingStateSet aProfilingStateSet,
ProfilingStateChangeCallback&& aCallback, uintptr_t aUniqueIdentifier = 0) {
}
inline void profiler_remove_state_change_callback(uintptr_t aUniqueIdentifier) {
}
#else // !MOZ_GECKO_PROFILER
# include "mozilla/Atomics.h"
# include "mozilla/Maybe.h"
# include <stdint.h>
namespace mozilla::profiler::detail {
// RacyFeatures is only defined in this header file so that its methods can
// be inlined into profiler_is_active(). Please do not use anything from the
// detail namespace outside the profiler.
// Within the profiler's code, the preferred way to check profiler activeness
// and features is via ActivePS(). However, that requires locking gPSMutex.
// There are some hot operations where absolute precision isn't required, so we
// duplicate the activeness/feature state in a lock-free manner in this class.
class RacyFeatures {
public:
static void SetActive(uint32_t aFeatures) {
sActiveAndFeatures = Active | aFeatures;
}
static void SetInactive() { sActiveAndFeatures = 0; }
static void SetPaused() { sActiveAndFeatures |= Paused; }
static void SetUnpaused() { sActiveAndFeatures &= ~Paused; }
static void SetSamplingPaused() { sActiveAndFeatures |= SamplingPaused; }
static void SetSamplingUnpaused() { sActiveAndFeatures &= ~SamplingPaused; }
[[nodiscard]] static mozilla::Maybe<uint32_t> FeaturesIfActive() {
if (uint32_t af = sActiveAndFeatures; af & Active) {
// Active, remove the Active&Paused bits to get all features.
return Some(af & ~(Active | Paused | SamplingPaused));
}
return Nothing();
}
[[nodiscard]] static mozilla::Maybe<uint32_t> FeaturesIfActiveAndUnpaused() {
if (uint32_t af = sActiveAndFeatures; (af & (Active | Paused)) == Active) {
// Active but not fully paused, remove the Active and sampling-paused bits
// to get all features.
return Some(af & ~(Active | SamplingPaused));
}
return Nothing();
}
// This implementation must be kept in sync with `gecko_profiler::is_active`
// in the Profiler Rust API.
[[nodiscard]] static bool IsActive() {
return uint32_t(sActiveAndFeatures) & Active;
}
[[nodiscard]] static bool IsActiveWithFeature(uint32_t aFeature) {
uint32_t af = sActiveAndFeatures; // copy it first
return (af & Active) && (af & aFeature);
}
// True if profiler is active, and not fully paused.
// Note that periodic sampling *could* be paused!
// This implementation must be kept in sync with
// `gecko_profiler::can_accept_markers` in the Profiler Rust API.
[[nodiscard]] static bool IsActiveAndUnpaused() {
uint32_t af = sActiveAndFeatures; // copy it first
return (af & Active) && !(af & Paused);
}
// True if profiler is active, and sampling is not paused (though generic
// `SetPaused()` or specific `SetSamplingPaused()`).
[[nodiscard]] static bool IsActiveAndSamplingUnpaused() {
uint32_t af = sActiveAndFeatures; // copy it first
return (af & Active) && !(af & (Paused | SamplingPaused));
}
private:
static constexpr uint32_t Active = 1u << 31;
static constexpr uint32_t Paused = 1u << 30;
static constexpr uint32_t SamplingPaused = 1u << 29;
// Ensure Active/Paused don't overlap with any of the feature bits.
# define NO_OVERLAP(n_, str_, Name_, desc_) \
static_assert(ProfilerFeature::Name_ != SamplingPaused, \
"bad feature value");
PROFILER_FOR_EACH_FEATURE(NO_OVERLAP);
# undef NO_OVERLAP
// We combine the active bit with the feature bits so they can be read or
// written in a single atomic operation. Accesses to this atomic are not
// recorded by web replay as they may occur at non-deterministic points.
static mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed>
sActiveAndFeatures;
};
} // namespace mozilla::profiler::detail
//---------------------------------------------------------------------------
// Get information from the profiler
//---------------------------------------------------------------------------
// Is the profiler active? Note: the return value of this function can become
// immediately out-of-date. E.g. the profile might be active but then
// profiler_stop() is called immediately afterward. One common and reasonable
// pattern of usage is the following:
//
// if (profiler_is_active()) {
// ExpensiveData expensiveData = CreateExpensiveData();
// PROFILER_OPERATION(expensiveData);
// }
//
// where PROFILER_OPERATION is a no-op if the profiler is inactive. In this
// case the profiler_is_active() check is just an optimization -- it prevents
// us calling CreateExpensiveData() unnecessarily in most cases, but the
// expensive data will end up being created but not used if another thread
// stops the profiler between the CreateExpensiveData() and PROFILER_OPERATION
// calls.
[[nodiscard]] inline bool profiler_is_active() {
return mozilla::profiler::detail::RacyFeatures::IsActive();
}
// Same as profiler_is_active(), but also checks if the profiler is not paused.
[[nodiscard]] inline bool profiler_is_active_and_unpaused() {
return mozilla::profiler::detail::RacyFeatures::IsActiveAndUnpaused();
}
// Is the profiler active and paused? Returns false if the profiler is inactive.
[[nodiscard]] bool profiler_is_paused();
// Is the profiler active and sampling is paused? Returns false if the profiler
// is inactive.
[[nodiscard]] bool profiler_is_sampling_paused();
// Get all the features supported by the profiler that are accepted by
// profiler_start(). The result is the same whether the profiler is active or
// not.
[[nodiscard]] uint32_t profiler_get_available_features();
// Returns the full feature set if the profiler is active.
// Note: the return value can become immediately out-of-date, much like the
// return value of profiler_is_active().
[[nodiscard]] inline mozilla::Maybe<uint32_t> profiler_features_if_active() {
return mozilla::profiler::detail::RacyFeatures::FeaturesIfActive();
}
// Returns the full feature set if the profiler is active and unpaused.
// Note: the return value can become immediately out-of-date, much like the
// return value of profiler_is_active().
[[nodiscard]] inline mozilla::Maybe<uint32_t>
profiler_features_if_active_and_unpaused() {
return mozilla::profiler::detail::RacyFeatures::FeaturesIfActiveAndUnpaused();
}
// Check if a profiler feature (specified via the ProfilerFeature type) is
// active. Returns false if the profiler is inactive. Note: the return value
// can become immediately out-of-date, much like the return value of
// profiler_is_active().
[[nodiscard]] bool profiler_feature_active(uint32_t aFeature);
// Returns true if any of the profiler mutexes are currently locked *on the
// current thread*. This may be used by re-entrant code that may call profiler
// functions while the same of a different profiler mutex is locked, which could
// deadlock.
[[nodiscard]] bool profiler_is_locked_on_current_thread();
// Install a callback to be invoked at any of the given profiling state changes.
// An optional non-zero identifier may be given, to allow later removal of the
// callback, the caller is responsible for making sure it's really unique (e.g.,
// by using a pointer to an object it owns.)
void profiler_add_state_change_callback(
ProfilingStateSet aProfilingStateSet,
ProfilingStateChangeCallback&& aCallback, uintptr_t aUniqueIdentifier = 0);
// Remove the callback with the given non-zero identifier.
void profiler_remove_state_change_callback(uintptr_t aUniqueIdentifier);
#endif // MOZ_GECKO_PROFILER
#endif // ProfilerState_h