/* -*- 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 #include #include "mozilla/ProfilerUtils.h" #include //--------------------------------------------------------------------------- // 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, "nomarkerstacks", NoMarkerStacks, \ "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, "nativeallocations", NativeAllocations, \ "Collect the stacks from a smaller subset of all native " \ "allocations, biasing towards collecting larger allocations") \ \ MACRO(12, "ipcmessages", IPCMessages, \ "Have the IPC layer track cross-process messages") \ \ MACRO(13, "audiocallbacktracing", AudioCallbackTracing, \ "Audio callback tracing") \ \ MACRO(14, "cpu", CPUUtilization, "CPU utilization") \ \ MACRO(15, "notimerresolutionchange", NoTimerResolutionChange, \ "Do not adjust the timer resolution for sampling, so that other " \ "Firefox timers do not get affected") \ \ MACRO(16, "cpuallthreads", CPUAllThreads, \ "Sample the CPU utilization of all registered threads") \ \ MACRO(17, "samplingallthreads", SamplingAllThreads, \ "Sample the stacks of all registered threads") \ \ MACRO(18, "markersallthreads", MarkersAllThreads, \ "Record markers from all registered threads") \ \ MACRO(19, "unregisteredthreads", UnregisteredThreads, \ "Discover and profile unregistered threads -- beware: expensive!") \ \ MACRO(20, "processcpu", ProcessCPU, \ "Sample the CPU utilization of each process") \ \ MACRO(21, "power", Power, POWER_HELP) \ \ MACRO(22, "cpufreq", CPUFrequency, \ "Record the clock frequency of " \ "every CPU core for every profiler sample.") \ \ MACRO(23, "bandwidth", Bandwidth, \ "Record the network bandwidth used for every profiler sample.") \ MACRO( \ 24, "memory", Memory, \ "Track the memory allocations and deallocations per process over time.") // *** 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 [[nodiscard]] static constexpr bool ShouldInstallMemoryHooks( uint32_t aFeatures) { return ProfilerFeature::HasMemory(aFeatures) || ProfilerFeature::HasNativeAllocations(aFeatures); } }; // 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; [[nodiscard]] constexpr ProfilingStateSet AllProfilingStates() { ProfilingStateSet set; using Value = std::underlying_type_t; for (Value stateValue = 0; stateValue <= static_cast(kHighestProfilingState); ++stateValue) { set += static_cast(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; #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_is_collecting_markers() { return false; } [[nodiscard]] inline bool profiler_is_etw_collecting_markers() { 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 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 SetETWCollectionActive() { sActiveAndFeatures |= ETWCollectionEnabled; } static void SetETWCollectionInactive() { sActiveAndFeatures &= ~ETWCollectionEnabled; } 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 Maybe 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 Maybe 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); } [[nodiscard]] static bool IsActiveWithoutFeature(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)); } [[nodiscard]] static bool IsCollectingMarkers() { uint32_t af = sActiveAndFeatures; // copy it first return ((af & Active) && !(af & Paused)) || (af & ETWCollectionEnabled); } [[nodiscard]] static bool IsETWCollecting() { uint32_t af = sActiveAndFeatures; // copy it first return (af & ETWCollectionEnabled); } private: static constexpr uint32_t Active = 1u << 31; static constexpr uint32_t Paused = 1u << 30; static constexpr uint32_t SamplingPaused = 1u << 29; static constexpr uint32_t ETWCollectionEnabled = 1u << 28; // 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. static Atomic 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(); } // Same as profiler_is_active_and_unpaused(), but also checks if the ETW is // collecting markers. [[nodiscard]] inline bool profiler_is_collecting_markers() { return mozilla::profiler::detail::RacyFeatures::IsCollectingMarkers(); } // Reports if our ETW tracelogger is running. [[nodiscard]] inline bool profiler_is_etw_collecting_markers() { return mozilla::profiler::detail::RacyFeatures::IsETWCollecting(); } // 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 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 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); // Check if the profiler is active without a feature (specified via the // ProfilerFeature type). Note: the return value can become immediately // out-of-date, much like the return value of profiler_is_active(). [[nodiscard]] bool profiler_active_without_feature(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