summaryrefslogtreecommitdiffstats
path: root/tools/profiler/public/ProfilerState.h
blob: 7a9f3f5c73874d724966b2308461fba867f221ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/* -*- 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, "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)
// *** 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);
  }

  [[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));
  }

 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);

// 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