Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / mozglue / baseprofiler / public / BaseProfilerState.h
blobd524e364da5252e0f85756560328e1b96c084fe4
1 /* -*- Mode: C++; tab-width: 2; 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/. */
7 // The Gecko Profiler is an always-on profiler that takes fast and low overhead
8 // samples of the program execution using only userspace functionality for
9 // portability. The goal of this module is to provide performance data in a
10 // generic cross-platform way without requiring custom tools or kernel support.
12 // Samples are collected to form a timeline with optional timeline event
13 // (markers) used for filtering. The samples include both native stacks and
14 // platform-independent "label stack" frames.
16 #ifndef BaseProfilerState_h
17 #define BaseProfilerState_h
19 // This header contains most functions that give information about the Base
20 // Profiler: Whether it is active or not, paused, the selected features, and
21 // some generic process and thread information.
22 // It is safe to include unconditionally, but uses of structs and functions must
23 // be guarded by `#ifdef MOZ_GECKO_PROFILER`.
25 #include "mozilla/BaseProfilerUtils.h"
27 #ifndef MOZ_GECKO_PROFILER
29 # define AUTO_PROFILER_STATS(name)
31 namespace mozilla::baseprofiler {
33 [[nodiscard]] inline bool profiler_is_active() { return false; }
34 [[nodiscard]] inline bool profiler_is_active_and_unpaused() { return false; }
36 } // namespace mozilla::baseprofiler
38 #else // !MOZ_GECKO_PROFILER
40 # include "mozilla/Atomics.h"
41 # include "mozilla/Maybe.h"
43 # include <stdint.h>
44 # include <string>
46 // Uncomment the following line to display profiler runtime statistics at
47 // shutdown.
48 // # define PROFILER_RUNTIME_STATS
50 # ifdef PROFILER_RUNTIME_STATS
51 # include "mozilla/TimeStamp.h"
52 # endif
54 namespace mozilla::baseprofiler {
56 # ifdef PROFILER_RUNTIME_STATS
57 // This class gathers durations and displays some basic stats when destroyed.
58 // It is intended to be used as a static variable (see `AUTO_PROFILER_STATS`
59 // below), to display stats at the end of the program.
60 class StaticBaseProfilerStats {
61 public:
62 explicit StaticBaseProfilerStats(const char* aName) : mName(aName) {}
64 ~StaticBaseProfilerStats() {
65 // Using unsigned long long for computations and printfs.
66 using ULL = unsigned long long;
67 ULL n = static_cast<ULL>(mNumberDurations);
68 if (n != 0) {
69 ULL sumNs = static_cast<ULL>(mSumDurationsNs);
70 printf(
71 "[%d] Profiler stats `%s`: %llu ns / %llu = %llu ns, max %llu ns\n",
72 int(profiler_current_process_id().ToNumber()), mName, sumNs, n,
73 sumNs / n, static_cast<ULL>(mLongestDurationNs));
74 } else {
75 printf("[%d] Profiler stats `%s`: (nothing)\n",
76 int(profiler_current_process_id().ToNumber()), mName);
80 void AddDurationFrom(TimeStamp aStart) {
81 DurationNs duration = static_cast<DurationNs>(
82 (TimeStamp::Now() - aStart).ToMicroseconds() * 1000 + 0.5);
83 mSumDurationsNs += duration;
84 ++mNumberDurations;
85 // Update mLongestDurationNs if this one is longer.
86 for (;;) {
87 DurationNs longest = mLongestDurationNs;
88 if (MOZ_LIKELY(longest >= duration)) {
89 // This duration is not the longest, nothing to do.
90 break;
92 if (MOZ_LIKELY(mLongestDurationNs.compareExchange(longest, duration))) {
93 // Successfully updated `mLongestDurationNs` with the new value.
94 break;
96 // Otherwise someone else just updated `mLongestDurationNs`, we need to
97 // try again by looping.
101 private:
102 using DurationNs = uint64_t;
103 using Count = uint32_t;
105 Atomic<DurationNs> mSumDurationsNs{0};
106 Atomic<DurationNs> mLongestDurationNs{0};
107 Atomic<Count> mNumberDurations{0};
108 const char* mName;
111 // RAII object that measure its scoped lifetime duration and reports it to a
112 // `StaticBaseProfilerStats`.
113 class MOZ_RAII AutoProfilerStats {
114 public:
115 explicit AutoProfilerStats(StaticBaseProfilerStats& aStats)
116 : mStats(aStats), mStart(TimeStamp::Now()) {}
118 ~AutoProfilerStats() { mStats.AddDurationFrom(mStart); }
120 private:
121 StaticBaseProfilerStats& mStats;
122 TimeStamp mStart;
125 // Macro that should be used to collect basic statistics from measurements of
126 // block durations, from where this macro is, until the end of its enclosing
127 // scope. The name is used in the static variable name and when displaying stats
128 // at the end of the program; Another location could use the same name but their
129 // stats will not be combined, so use different name if these locations should
130 // be distinguished.
131 # define AUTO_PROFILER_STATS(name) \
132 static ::mozilla::baseprofiler::StaticBaseProfilerStats sStat##name( \
133 #name); \
134 ::mozilla::baseprofiler::AutoProfilerStats autoStat##name(sStat##name);
136 # else // PROFILER_RUNTIME_STATS
138 # define AUTO_PROFILER_STATS(name)
140 # endif // PROFILER_RUNTIME_STATS else
142 //---------------------------------------------------------------------------
143 // Profiler features
144 //---------------------------------------------------------------------------
146 # if defined(__APPLE__) && defined(__aarch64__)
147 # define POWER_HELP "Sample per process power use"
148 # elif defined(__APPLE__) && defined(__x86_64__)
149 # define POWER_HELP \
150 "Record the power used by the entire system with each sample."
151 # elif defined(__linux__) && defined(__x86_64__)
152 # define POWER_HELP \
153 "Record the power used by the entire system with each sample. " \
154 "Only available with Intel CPUs and requires setting " \
155 "the sysctl kernel.perf_event_paranoid to 0."
156 # elif defined(_MSC_VER)
157 # define POWER_HELP \
158 "Record the value of every energy meter available on the system with " \
159 "each sample. Only available on Windows 11 with Intel CPUs."
160 # else
161 # define POWER_HELP "Not supported on this platform."
162 # endif
164 // Higher-order macro containing all the feature info in one place. Define
165 // |MACRO| appropriately to extract the relevant parts. Note that the number
166 // values are used internally only and so can be changed without consequence.
167 // Any changes to this list should also be applied to the feature list in
168 // toolkit/components/extensions/schemas/geckoProfiler.json.
169 // *** Synchronize with lists in ProfilerState.h and geckoProfiler.json ***
170 # define BASE_PROFILER_FOR_EACH_FEATURE(MACRO) \
171 MACRO(0, "java", Java, "Profile Java code, Android only") \
173 MACRO(1, "js", JS, \
174 "Get the JS engine to expose the JS stack to the profiler") \
176 MACRO(2, "mainthreadio", MainThreadIO, "Add main thread file I/O") \
178 MACRO(3, "fileio", FileIO, \
179 "Add file I/O from all profiled threads, implies mainthreadio") \
181 MACRO(4, "fileioall", FileIOAll, \
182 "Add file I/O from all threads, implies fileio") \
184 MACRO(5, "nomarkerstacks", NoMarkerStacks, \
185 "Markers do not capture stacks, to reduce overhead") \
187 MACRO(6, "screenshots", Screenshots, \
188 "Take a snapshot of the window on every composition") \
190 MACRO(7, "seqstyle", SequentialStyle, \
191 "Disable parallel traversal in styling") \
193 MACRO(8, "stackwalk", StackWalk, \
194 "Walk the C++ stack, not available on all platforms") \
196 MACRO(9, "jsallocations", JSAllocations, \
197 "Have the JavaScript engine track allocations") \
199 MACRO(10, "nostacksampling", NoStackSampling, \
200 "Disable all stack sampling: Cancels \"js\", \"stackwalk\" and " \
201 "labels") \
203 MACRO(11, "nativeallocations", NativeAllocations, \
204 "Collect the stacks from a smaller subset of all native " \
205 "allocations, biasing towards collecting larger allocations") \
207 MACRO(12, "ipcmessages", IPCMessages, \
208 "Have the IPC layer track cross-process messages") \
210 MACRO(13, "audiocallbacktracing", AudioCallbackTracing, \
211 "Audio callback tracing") \
213 MACRO(14, "cpu", CPUUtilization, "CPU utilization") \
215 MACRO(15, "notimerresolutionchange", NoTimerResolutionChange, \
216 "Do not adjust the timer resolution for fast sampling, so that " \
217 "other Firefox timers do not get affected") \
219 MACRO(16, "cpuallthreads", CPUAllThreads, \
220 "Sample the CPU utilization of all registered threads") \
222 MACRO(17, "samplingallthreads", SamplingAllThreads, \
223 "Sample the stacks of all registered threads") \
225 MACRO(18, "markersallthreads", MarkersAllThreads, \
226 "Record markers from all registered threads") \
228 MACRO(19, "unregisteredthreads", UnregisteredThreads, \
229 "Discover and profile unregistered threads -- beware: expensive!") \
231 MACRO(20, "processcpu", ProcessCPU, \
232 "Sample the CPU utilization of each process") \
234 MACRO(21, "power", Power, POWER_HELP) \
236 MACRO(22, "cpufreq", CPUFrequency, \
237 "Record the clock frequency of " \
238 "every CPU core for every profiler sample.")
239 // *** Synchronize with lists in ProfilerState.h and geckoProfiler.json ***
241 struct ProfilerFeature {
242 # define DECLARE(n_, str_, Name_, desc_) \
243 static constexpr uint32_t Name_ = (1u << n_); \
244 [[nodiscard]] static constexpr bool Has##Name_(uint32_t aFeatures) { \
245 return aFeatures & Name_; \
247 static constexpr void Set##Name_(uint32_t& aFeatures) { \
248 aFeatures |= Name_; \
250 static constexpr void Clear##Name_(uint32_t& aFeatures) { \
251 aFeatures &= ~Name_; \
254 // Define a bitfield constant, a getter, and two setters for each feature.
255 BASE_PROFILER_FOR_EACH_FEATURE(DECLARE)
257 # undef DECLARE
260 namespace detail {
262 // RacyFeatures is only defined in this header file so that its methods can
263 // be inlined into profiler_is_active(). Please do not use anything from the
264 // detail namespace outside the profiler.
266 // Within the profiler's code, the preferred way to check profiler activeness
267 // and features is via ActivePS(). However, that requires locking gPSMutex.
268 // There are some hot operations where absolute precision isn't required, so we
269 // duplicate the activeness/feature state in a lock-free manner in this class.
270 class RacyFeatures {
271 public:
272 MFBT_API static void SetActive(uint32_t aFeatures);
274 MFBT_API static void SetInactive();
276 MFBT_API static void SetPaused();
278 MFBT_API static void SetUnpaused();
280 MFBT_API static void SetSamplingPaused();
282 MFBT_API static void SetSamplingUnpaused();
284 [[nodiscard]] MFBT_API static mozilla::Maybe<uint32_t> FeaturesIfActive() {
285 if (uint32_t af = sActiveAndFeatures; af & Active) {
286 // Active, remove the Active&Paused bits to get all features.
287 return Some(af & ~(Active | Paused | SamplingPaused));
289 return Nothing();
292 [[nodiscard]] MFBT_API static bool IsActive();
294 [[nodiscard]] MFBT_API static bool IsActiveWithFeature(uint32_t aFeature);
296 [[nodiscard]] MFBT_API static bool IsActiveWithoutFeature(uint32_t aFeature);
298 // True if profiler is active, and not fully paused.
299 // Note that periodic sampling *could* be paused!
300 [[nodiscard]] MFBT_API static bool IsActiveAndUnpaused();
302 // True if profiler is active, and sampling is not paused (though generic
303 // `SetPaused()` or specific `SetSamplingPaused()`).
304 [[nodiscard]] MFBT_API static bool IsActiveAndSamplingUnpaused();
306 private:
307 static constexpr uint32_t Active = 1u << 31;
308 static constexpr uint32_t Paused = 1u << 30;
309 static constexpr uint32_t SamplingPaused = 1u << 29;
311 // Ensure Active/Paused don't overlap with any of the feature bits.
312 # define NO_OVERLAP(n_, str_, Name_, desc_) \
313 static_assert(ProfilerFeature::Name_ != SamplingPaused, \
314 "bad feature value");
316 BASE_PROFILER_FOR_EACH_FEATURE(NO_OVERLAP);
318 # undef NO_OVERLAP
320 // We combine the active bit with the feature bits so they can be read or
321 // written in a single atomic operation.
322 // TODO: Could this be MFBT_DATA for better inlining optimization?
323 static Atomic<uint32_t, MemoryOrdering::Relaxed> sActiveAndFeatures;
326 MFBT_API bool IsThreadBeingProfiled();
328 } // namespace detail
330 //---------------------------------------------------------------------------
331 // Get information from the profiler
332 //---------------------------------------------------------------------------
334 // Is the profiler active? Note: the return value of this function can become
335 // immediately out-of-date. E.g. the profile might be active but then
336 // profiler_stop() is called immediately afterward. One common and reasonable
337 // pattern of usage is the following:
339 // if (profiler_is_active()) {
340 // ExpensiveData expensiveData = CreateExpensiveData();
341 // PROFILER_OPERATION(expensiveData);
342 // }
344 // where PROFILER_OPERATION is a no-op if the profiler is inactive. In this
345 // case the profiler_is_active() check is just an optimization -- it prevents
346 // us calling CreateExpensiveData() unnecessarily in most cases, but the
347 // expensive data will end up being created but not used if another thread
348 // stops the profiler between the CreateExpensiveData() and PROFILER_OPERATION
349 // calls.
350 [[nodiscard]] inline bool profiler_is_active() {
351 return baseprofiler::detail::RacyFeatures::IsActive();
354 // Same as profiler_is_active(), but also checks if the profiler is not paused.
355 [[nodiscard]] inline bool profiler_is_active_and_unpaused() {
356 return baseprofiler::detail::RacyFeatures::IsActiveAndUnpaused();
359 // Is the profiler active and unpaused, and is the current thread being
360 // profiled? (Same caveats and recommented usage as profiler_is_active().)
361 [[nodiscard]] inline bool profiler_thread_is_being_profiled() {
362 return baseprofiler::detail::RacyFeatures::IsActiveAndUnpaused() &&
363 baseprofiler::detail::IsThreadBeingProfiled();
366 // Is the profiler active and paused? Returns false if the profiler is inactive.
367 [[nodiscard]] MFBT_API bool profiler_is_paused();
369 // Is the profiler active and sampling is paused? Returns false if the profiler
370 // is inactive.
371 [[nodiscard]] MFBT_API bool profiler_is_sampling_paused();
373 // Is the current thread sleeping?
374 [[nodiscard]] MFBT_API bool profiler_thread_is_sleeping();
376 // Get all the features supported by the profiler that are accepted by
377 // profiler_start(). The result is the same whether the profiler is active or
378 // not.
379 [[nodiscard]] MFBT_API uint32_t profiler_get_available_features();
381 // Returns the full feature set if the profiler is active.
382 // Note: the return value can become immediately out-of-date, much like the
383 // return value of profiler_is_active().
384 [[nodiscard]] inline mozilla::Maybe<uint32_t> profiler_features_if_active() {
385 return baseprofiler::detail::RacyFeatures::FeaturesIfActive();
388 // Check if a profiler feature (specified via the ProfilerFeature type) is
389 // active. Returns false if the profiler is inactive. Note: the return value
390 // can become immediately out-of-date, much like the return value of
391 // profiler_is_active().
392 [[nodiscard]] MFBT_API bool profiler_feature_active(uint32_t aFeature);
394 // Check if the profiler is active without a feature (specified via the
395 // ProfilerFeature type). Note: the return value can become immediately
396 // out-of-date, much like the return value of profiler_is_active().
397 [[nodiscard]] MFBT_API bool profiler_active_without_feature(uint32_t aFeature);
399 // Returns true if any of the profiler mutexes are currently locked *on the
400 // current thread*. This may be used by re-entrant code that may call profiler
401 // functions while the same of a different profiler mutex is locked, which could
402 // deadlock.
403 [[nodiscard]] bool profiler_is_locked_on_current_thread();
405 } // namespace mozilla::baseprofiler
407 #endif // !MOZ_GECKO_PROFILER
409 #endif // BaseProfilerState_h