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/. */
6
7 // This header contains basic definitions required to create marker types, and
8 // to add markers to the profiler buffers.
9 //
10 // In most cases, #include "mozilla/BaseProfilerMarkers.h" instead, or
11 // #include "mozilla/BaseProfilerMarkerTypes.h" for common marker types.
12
13 #ifndef BaseProfilerMarkersPrerequisites_h
14 #define BaseProfilerMarkersPrerequisites_h
15
16 namespace mozilla {
17
18 enum class StackCaptureOptions {
19 NoStack, // No stack captured.
20 Full, // Capture a full stack, including label frames, JS frames and
21 // native frames.
22 NonNative, // Capture a stack without native frames for reduced overhead.
23 };
24
25 }
26
27 #include "BaseProfilingCategory.h"
28 #include "mozilla/Maybe.h"
29 #include "mozilla/ProfileChunkedBuffer.h"
30 #include "mozilla/BaseProfilerState.h"
31 #include "mozilla/TimeStamp.h"
32 #include "mozilla/UniquePtr.h"
33 #include "mozilla/Variant.h"
34
35 #include <initializer_list>
36 #include <string_view>
37 #include <string>
38 #include <type_traits>
39 #include <utility>
40 #include <vector>
41
42 namespace mozilla {
43
44 // Return a NotNull<const CHAR*> pointing at the literal empty string `""`.
45 template <typename CHAR>
LiteralEmptyStringPointer()46 constexpr const CHAR* LiteralEmptyStringPointer() {
47 static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>,
48 "Only char and char16_t are supported in Firefox");
49 if constexpr (std::is_same_v<CHAR, char>) {
50 return "";
51 }
52 if constexpr (std::is_same_v<CHAR, char16_t>) {
53 return u"";
54 }
55 }
56
57 // Return a string_view<CHAR> pointing at the literal empty string.
58 template <typename CHAR>
LiteralEmptyStringView()59 constexpr std::basic_string_view<CHAR> LiteralEmptyStringView() {
60 static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>,
61 "Only char and char16_t are supported in Firefox");
62 // Use `operator""sv()` from <string_view>.
63 using namespace std::literals::string_view_literals;
64 if constexpr (std::is_same_v<CHAR, char>) {
65 return ""sv;
66 }
67 if constexpr (std::is_same_v<CHAR, char16_t>) {
68 return u""sv;
69 }
70 }
71
72 // General string view, optimized for short on-stack life before serialization,
73 // and between deserialization and JSON-streaming.
74 template <typename CHAR>
75 class MOZ_STACK_CLASS ProfilerStringView {
76 public:
77 // Default constructor points at "" (literal empty string).
78 constexpr ProfilerStringView() = default;
79
80 // Don't allow copy.
81 ProfilerStringView(const ProfilerStringView&) = delete;
82 ProfilerStringView& operator=(const ProfilerStringView&) = delete;
83
84 // Allow move. For consistency the moved-from string is always reset to "".
ProfilerStringView(ProfilerStringView && aOther)85 constexpr ProfilerStringView(ProfilerStringView&& aOther)
86 : mStringView(std::move(aOther.mStringView)),
87 mOwnership(aOther.mOwnership) {
88 if (mOwnership == Ownership::OwnedThroughStringView) {
89 // We now own the buffer, make the other point at the literal "".
90 aOther.mStringView = LiteralEmptyStringView<CHAR>();
91 aOther.mOwnership = Ownership::Literal;
92 }
93 }
94 constexpr ProfilerStringView& operator=(ProfilerStringView&& aOther) {
95 mStringView = std::move(aOther.mStringView);
96 mOwnership = aOther.mOwnership;
97 if (mOwnership == Ownership::OwnedThroughStringView) {
98 // We now own the buffer, make the other point at the literal "".
99 aOther.mStringView = LiteralEmptyStringView<CHAR>();
100 aOther.mOwnership = Ownership::Literal;
101 }
102 return *this;
103 }
104
~ProfilerStringView()105 ~ProfilerStringView() {
106 if (MOZ_UNLIKELY(mOwnership == Ownership::OwnedThroughStringView)) {
107 // We own the buffer pointed at by mStringView, destroy it.
108 // This is only used between deserialization and streaming.
109 delete mStringView.data();
110 }
111 }
112
113 // Implicit construction from nullptr, points at "" (literal empty string).
ProfilerStringView(decltype (nullptr))114 constexpr MOZ_IMPLICIT ProfilerStringView(decltype(nullptr)) {}
115
116 // Implicit constructor from a literal string.
117 template <size_t Np1>
ProfilerStringView(const CHAR (& aLiteralString)[Np1])118 constexpr MOZ_IMPLICIT ProfilerStringView(const CHAR (&aLiteralString)[Np1])
119 : ProfilerStringView(aLiteralString, Np1 - 1, Ownership::Literal) {}
120
121 // Constructor from a non-literal string.
ProfilerStringView(const CHAR * aString,size_t aLength)122 constexpr ProfilerStringView(const CHAR* aString, size_t aLength)
123 : ProfilerStringView(aString, aLength, Ownership::Reference) {}
124
125 // Implicit constructor from a string_view.
ProfilerStringView(const std::basic_string_view<CHAR> & aStringView)126 constexpr MOZ_IMPLICIT ProfilerStringView(
127 const std::basic_string_view<CHAR>& aStringView)
128 : ProfilerStringView(aStringView.data(), aStringView.length(),
129 Ownership::Reference) {}
130
131 // Implicit constructor from an expiring string_view. We assume that the
132 // pointed-at string will outlive this ProfilerStringView.
ProfilerStringView(std::basic_string_view<CHAR> && aStringView)133 constexpr MOZ_IMPLICIT ProfilerStringView(
134 std::basic_string_view<CHAR>&& aStringView)
135 : ProfilerStringView(aStringView.data(), aStringView.length(),
136 Ownership::Reference) {}
137
138 // Implicit constructor from std::string.
ProfilerStringView(const std::basic_string<CHAR> & aString)139 constexpr MOZ_IMPLICIT ProfilerStringView(
140 const std::basic_string<CHAR>& aString)
141 : ProfilerStringView(aString.data(), aString.length(),
142 Ownership::Reference) {}
143
144 // Construction from a raw pointer to a null-terminated string.
145 // This is a named class-static function to make it more obvious where work is
146 // being done (to determine the string length), and encourage users to instead
147 // provide a length, if already known.
148 // TODO: Find callers and convert them to constructor instead if possible.
WrapNullTerminatedString(const CHAR * aString)149 static constexpr ProfilerStringView WrapNullTerminatedString(
150 const CHAR* aString) {
151 return ProfilerStringView(
152 aString, aString ? std::char_traits<CHAR>::length(aString) : 0,
153 Ownership::Reference);
154 }
155
156 // Implicit constructor for an object with member functions `Data()`
157 // `Length()`, and `IsLiteral()`, common in xpcom strings.
158 template <
159 typename String,
160 typename DataReturnType = decltype(std::declval<const String>().Data()),
161 typename LengthReturnType =
162 decltype(std::declval<const String>().Length()),
163 typename IsLiteralReturnType =
164 decltype(std::declval<const String>().IsLiteral()),
165 typename =
166 std::enable_if_t<std::is_convertible_v<DataReturnType, const CHAR*> &&
167 std::is_integral_v<LengthReturnType> &&
168 std::is_same_v<IsLiteralReturnType, bool>>>
ProfilerStringView(const String & aString)169 constexpr MOZ_IMPLICIT ProfilerStringView(const String& aString)
170 : ProfilerStringView(
171 static_cast<const CHAR*>(aString.Data()), aString.Length(),
172 aString.IsLiteral() ? Ownership::Literal : Ownership::Reference) {}
173
StringView()174 [[nodiscard]] constexpr const std::basic_string_view<CHAR>& StringView()
175 const {
176 return mStringView;
177 }
178
Length()179 [[nodiscard]] constexpr size_t Length() const { return mStringView.length(); }
180
IsLiteral()181 [[nodiscard]] constexpr bool IsLiteral() const {
182 return mOwnership == Ownership::Literal;
183 }
IsReference()184 [[nodiscard]] constexpr bool IsReference() const {
185 return mOwnership == Ownership::Reference;
186 }
187 // No `IsOwned...()` because it's a secret, only used internally!
188
AsSpan()189 [[nodiscard]] Span<const CHAR> AsSpan() const {
190 return Span<const CHAR>(mStringView.data(), mStringView.length());
191 }
192 [[nodiscard]] operator Span<const CHAR>() const { return AsSpan(); }
193
194 private:
195 enum class Ownership { Literal, Reference, OwnedThroughStringView };
196
197 // Allow deserializer to store anything here.
198 friend ProfileBufferEntryReader::Deserializer<ProfilerStringView>;
199
ProfilerStringView(const CHAR * aString,size_t aLength,Ownership aOwnership)200 constexpr ProfilerStringView(const CHAR* aString, size_t aLength,
201 Ownership aOwnership)
202 : mStringView(aString ? std::basic_string_view<CHAR>(aString, aLength)
203 : LiteralEmptyStringView<CHAR>()),
204 mOwnership(aString ? aOwnership : Ownership::Literal) {}
205
206 // String view to an outside string (literal or reference).
207 // We may actually own the pointed-at buffer, but it is only used internally
208 // between deserialization and JSON streaming.
209 std::basic_string_view<CHAR> mStringView = LiteralEmptyStringView<CHAR>();
210
211 Ownership mOwnership = Ownership::Literal;
212 };
213
214 using ProfilerString8View = ProfilerStringView<char>;
215 using ProfilerString16View = ProfilerStringView<char16_t>;
216
217 // This compulsory marker parameter contains the required category information.
218 class MarkerCategory {
219 public:
220 // Constructor from category pair (includes both super- and sub-categories).
MarkerCategory(baseprofiler::ProfilingCategoryPair aCategoryPair)221 constexpr explicit MarkerCategory(
222 baseprofiler::ProfilingCategoryPair aCategoryPair)
223 : mCategoryPair(aCategoryPair) {}
224
225 // Returns the stored category pair.
CategoryPair()226 constexpr baseprofiler::ProfilingCategoryPair CategoryPair() const {
227 return mCategoryPair;
228 }
229
230 // Returns the super-category from the stored category pair.
GetCategory()231 baseprofiler::ProfilingCategory GetCategory() const {
232 return GetProfilingCategoryPairInfo(mCategoryPair).mCategory;
233 }
234
235 private:
236 baseprofiler::ProfilingCategoryPair mCategoryPair =
237 baseprofiler::ProfilingCategoryPair::OTHER;
238 };
239
240 namespace baseprofiler::category {
241
242 // Each category pair name constructs a MarkerCategory.
243 // E.g.: mozilla::baseprofiler::category::OTHER_Profiling
244 // Profiler macros will take the category name alone without namespace.
245 // E.g.: `PROFILER_MARKER_UNTYPED("name", OTHER_Profiling)`
246 #define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color)
247 #define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) \
248 static constexpr MarkerCategory name{ProfilingCategoryPair::name};
249 #define CATEGORY_ENUM_END_CATEGORY
250 MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY,
251 CATEGORY_ENUM_SUBCATEGORY,
252 CATEGORY_ENUM_END_CATEGORY)
253 #undef CATEGORY_ENUM_BEGIN_CATEGORY
254 #undef CATEGORY_ENUM_SUBCATEGORY
255 #undef CATEGORY_ENUM_END_CATEGORY
256
257 // Import `MarkerCategory` into this namespace. This will allow using this type
258 // dynamically in macros that prepend `::mozilla::baseprofiler::category::` to
259 // the given category, e.g.:
260 // `PROFILER_MARKER_UNTYPED("name", MarkerCategory(...))`
261 using MarkerCategory = ::mozilla::MarkerCategory;
262
263 } // namespace baseprofiler::category
264
265 // The classes below are all embedded in a `MarkerOptions` object.
266 class MarkerOptions;
267
268 // This marker option captures a given thread id.
269 // If left unspecified (by default construction) during the add-marker call, the
270 // current thread id will be used then.
271 class MarkerThreadId {
272 public:
273 // Default constructor, keeps the thread id unspecified.
274 constexpr MarkerThreadId() = default;
275
276 // Constructor from a given thread id.
MarkerThreadId(baseprofiler::BaseProfilerThreadId aThreadId)277 constexpr explicit MarkerThreadId(
278 baseprofiler::BaseProfilerThreadId aThreadId)
279 : mThreadId(aThreadId) {}
280
281 // Use the current thread's id.
CurrentThread()282 static MarkerThreadId CurrentThread() {
283 return MarkerThreadId(baseprofiler::profiler_current_thread_id());
284 }
285
286 // Use the main thread's id. This can be useful to record a marker from a
287 // possibly-unregistered thread, and display it in the main thread track.
MainThread()288 static MarkerThreadId MainThread() {
289 return MarkerThreadId(baseprofiler::profiler_main_thread_id());
290 }
291
ThreadId()292 [[nodiscard]] constexpr baseprofiler::BaseProfilerThreadId ThreadId() const {
293 return mThreadId;
294 }
295
IsUnspecified()296 [[nodiscard]] constexpr bool IsUnspecified() const {
297 return !mThreadId.IsSpecified();
298 }
299
300 private:
301 baseprofiler::BaseProfilerThreadId mThreadId;
302 };
303
304 // This marker option contains marker timing information.
305 // This class encapsulates the logic for correctly storing a marker based on its
306 // Use the static methods to create the MarkerTiming. This is a transient object
307 // that is being used to enforce the constraints of the combinations of the
308 // data.
309 class MarkerTiming {
310 public:
311 // The following static methods are used to create the MarkerTiming based on
312 // the type that it is.
313
InstantAt(const TimeStamp & aTime)314 static MarkerTiming InstantAt(const TimeStamp& aTime) {
315 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an instant marker.");
316 return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::Instant};
317 }
318
InstantNow()319 static MarkerTiming InstantNow() { return InstantAt(TimeStamp::Now()); }
320
Interval(const TimeStamp & aStartTime,const TimeStamp & aEndTime)321 static MarkerTiming Interval(const TimeStamp& aStartTime,
322 const TimeStamp& aEndTime) {
323 MOZ_ASSERT(!aStartTime.IsNull(),
324 "Start time is null for an interval marker.");
325 MOZ_ASSERT(!aEndTime.IsNull(), "End time is null for an interval marker.");
326 return MarkerTiming{aStartTime, aEndTime, MarkerTiming::Phase::Interval};
327 }
328
IntervalUntilNowFrom(const TimeStamp & aStartTime)329 static MarkerTiming IntervalUntilNowFrom(const TimeStamp& aStartTime) {
330 return Interval(aStartTime, TimeStamp::Now());
331 }
332
333 static MarkerTiming IntervalStart(const TimeStamp& aTime = TimeStamp::Now()) {
334 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval start marker.");
335 return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::IntervalStart};
336 }
337
338 static MarkerTiming IntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) {
339 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker.");
340 return MarkerTiming{TimeStamp{}, aTime, MarkerTiming::Phase::IntervalEnd};
341 }
342
343 // Set the interval end in this timing.
344 // If there was already a start time, this makes it a full interval.
345 void SetIntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) {
346 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker.");
347 mEndTime = aTime;
348 mPhase = mStartTime.IsNull() ? Phase::IntervalEnd : Phase::Interval;
349 }
350
StartTime()351 [[nodiscard]] const TimeStamp& StartTime() const { return mStartTime; }
EndTime()352 [[nodiscard]] const TimeStamp& EndTime() const { return mEndTime; }
353
354 enum class Phase : uint8_t {
355 Instant = 0,
356 Interval = 1,
357 IntervalStart = 2,
358 IntervalEnd = 3,
359 };
360
MarkerPhase()361 [[nodiscard]] Phase MarkerPhase() const {
362 MOZ_ASSERT(!IsUnspecified());
363 return mPhase;
364 }
365
366 // The following getter methods are used to put the value into the buffer for
367 // storage.
GetStartTime()368 [[nodiscard]] double GetStartTime() const {
369 MOZ_ASSERT(!IsUnspecified());
370 // If mStartTime is null (e.g., for IntervalEnd), this will output 0.0 as
371 // expected.
372 return MarkerTiming::timeStampToDouble(mStartTime);
373 }
374
GetEndTime()375 [[nodiscard]] double GetEndTime() const {
376 MOZ_ASSERT(!IsUnspecified());
377 // If mEndTime is null (e.g., for Instant or IntervalStart), this will
378 // output 0.0 as expected.
379 return MarkerTiming::timeStampToDouble(mEndTime);
380 }
381
GetPhase()382 [[nodiscard]] uint8_t GetPhase() const {
383 MOZ_ASSERT(!IsUnspecified());
384 return static_cast<uint8_t>(mPhase);
385 }
386
387 // This is a constructor for Rust FFI bindings. It must not be used outside of
388 // this! Please see the other static constructors above.
UnsafeConstruct(MarkerTiming * aMarkerTiming,const TimeStamp & aStartTime,const TimeStamp & aEndTime,Phase aPhase)389 static void UnsafeConstruct(MarkerTiming* aMarkerTiming,
390 const TimeStamp& aStartTime,
391 const TimeStamp& aEndTime, Phase aPhase) {
392 new (aMarkerTiming) MarkerTiming{aStartTime, aEndTime, aPhase};
393 }
394
395 private:
396 friend ProfileBufferEntryWriter::Serializer<MarkerTiming>;
397 friend ProfileBufferEntryReader::Deserializer<MarkerTiming>;
398 friend MarkerOptions;
399
400 // Default timing leaves it internally "unspecified", serialization getters
401 // and add-marker functions will default to `InstantNow()`.
402 constexpr MarkerTiming() = default;
403
404 // This should only be used by internal profiler code.
IsUnspecified()405 [[nodiscard]] bool IsUnspecified() const {
406 return mStartTime.IsNull() && mEndTime.IsNull();
407 }
408
409 // Full constructor, used by static factory functions.
MarkerTiming(const TimeStamp & aStartTime,const TimeStamp & aEndTime,Phase aPhase)410 constexpr MarkerTiming(const TimeStamp& aStartTime, const TimeStamp& aEndTime,
411 Phase aPhase)
412 : mStartTime(aStartTime), mEndTime(aEndTime), mPhase(aPhase) {}
413
timeStampToDouble(const TimeStamp & time)414 static double timeStampToDouble(const TimeStamp& time) {
415 if (time.IsNull()) {
416 // The Phase lets us know not to use this value.
417 return 0;
418 }
419 return (time - TimeStamp::ProcessCreation()).ToMilliseconds();
420 }
421
422 TimeStamp mStartTime;
423 TimeStamp mEndTime;
424 Phase mPhase = Phase::Instant;
425 };
426
427 // This marker option allows three cases:
428 // - By default, no stacks are captured.
429 // - The caller can request a stack capture, and the add-marker code will take
430 // care of it in the most efficient way.
431 // - The caller can still provide an existing backtrace, for cases where a
432 // marker reports something that happened elsewhere.
433 class MarkerStack {
434 public:
435 // Default constructor, no capture.
436 constexpr MarkerStack() = default;
437
438 // Disallow copy.
439 MarkerStack(const MarkerStack&) = delete;
440 MarkerStack& operator=(const MarkerStack&) = delete;
441
442 // Allow move.
MarkerStack(MarkerStack && aOther)443 MarkerStack(MarkerStack&& aOther)
444 : mCaptureOptions(aOther.mCaptureOptions),
445 mOptionalChunkedBufferStorage(
446 std::move(aOther.mOptionalChunkedBufferStorage)),
447 mChunkedBuffer(aOther.mChunkedBuffer) {
448 AssertInvariants();
449 aOther.Clear();
450 }
451 MarkerStack& operator=(MarkerStack&& aOther) {
452 mCaptureOptions = aOther.mCaptureOptions;
453 mOptionalChunkedBufferStorage =
454 std::move(aOther.mOptionalChunkedBufferStorage);
455 mChunkedBuffer = aOther.mChunkedBuffer;
456 AssertInvariants();
457 aOther.Clear();
458 return *this;
459 }
460
461 // Take ownership of a backtrace. If null or empty, equivalent to NoStack().
MarkerStack(UniquePtr<ProfileChunkedBuffer> && aExternalChunkedBuffer)462 explicit MarkerStack(UniquePtr<ProfileChunkedBuffer>&& aExternalChunkedBuffer)
463 : mOptionalChunkedBufferStorage(
464 (!aExternalChunkedBuffer || aExternalChunkedBuffer->IsEmpty())
465 ? nullptr
466 : std::move(aExternalChunkedBuffer)),
467 mChunkedBuffer(mOptionalChunkedBufferStorage.get()) {
468 AssertInvariants();
469 }
470
471 // Use an existing backtrace stored elsewhere, which the user must guarantee
472 // is alive during the add-marker call. If empty, equivalent to NoStack().
MarkerStack(ProfileChunkedBuffer & aExternalChunkedBuffer)473 explicit MarkerStack(ProfileChunkedBuffer& aExternalChunkedBuffer)
474 : mChunkedBuffer(aExternalChunkedBuffer.IsEmpty()
475 ? nullptr
476 : &aExternalChunkedBuffer) {
477 AssertInvariants();
478 }
479
480 // Don't capture a stack in this marker.
NoStack()481 static MarkerStack NoStack() {
482 return MarkerStack(StackCaptureOptions::NoStack);
483 }
484
485 // Capture a stack when adding this marker.
486 static MarkerStack Capture(
487 StackCaptureOptions aCaptureOptions = StackCaptureOptions::Full) {
488 // Actual capture will be handled inside profiler_add_marker.
489 return MarkerStack(aCaptureOptions);
490 }
491
492 // Optionally capture a stack, useful for avoiding long-winded ternaries.
MaybeCapture(bool aDoCapture)493 static MarkerStack MaybeCapture(bool aDoCapture) {
494 return aDoCapture ? Capture() : NoStack();
495 }
496
497 // Use an existing backtrace stored elsewhere, which the user must guarantee
498 // is alive during the add-marker call. If empty, equivalent to NoStack().
UseBacktrace(ProfileChunkedBuffer & aExternalChunkedBuffer)499 static MarkerStack UseBacktrace(
500 ProfileChunkedBuffer& aExternalChunkedBuffer) {
501 return MarkerStack(aExternalChunkedBuffer);
502 }
503
504 // Take ownership of a backtrace previously captured with
505 // `profiler_capture_backtrace()`. If null, equivalent to NoStack().
TakeBacktrace(UniquePtr<ProfileChunkedBuffer> && aExternalChunkedBuffer)506 static MarkerStack TakeBacktrace(
507 UniquePtr<ProfileChunkedBuffer>&& aExternalChunkedBuffer) {
508 return MarkerStack(std::move(aExternalChunkedBuffer));
509 }
510
511 // Construct with the given capture options.
WithCaptureOptions(StackCaptureOptions aCaptureOptions)512 static MarkerStack WithCaptureOptions(StackCaptureOptions aCaptureOptions) {
513 return MarkerStack(aCaptureOptions);
514 }
515
CaptureOptions()516 [[nodiscard]] StackCaptureOptions CaptureOptions() const {
517 return mCaptureOptions;
518 }
519
GetChunkedBuffer()520 ProfileChunkedBuffer* GetChunkedBuffer() const { return mChunkedBuffer; }
521
522 // Use backtrace after a request. If null, equivalent to NoStack().
UseRequestedBacktrace(ProfileChunkedBuffer * aExternalChunkedBuffer)523 void UseRequestedBacktrace(ProfileChunkedBuffer* aExternalChunkedBuffer) {
524 MOZ_RELEASE_ASSERT(mCaptureOptions != StackCaptureOptions::NoStack);
525 mCaptureOptions = StackCaptureOptions::NoStack;
526 if (aExternalChunkedBuffer && !aExternalChunkedBuffer->IsEmpty()) {
527 // We only need to use the provided buffer if it is not empty.
528 mChunkedBuffer = aExternalChunkedBuffer;
529 }
530 AssertInvariants();
531 }
532
Clear()533 void Clear() {
534 mCaptureOptions = StackCaptureOptions::NoStack;
535 mOptionalChunkedBufferStorage.reset();
536 mChunkedBuffer = nullptr;
537 AssertInvariants();
538 }
539
540 private:
MarkerStack(StackCaptureOptions aCaptureOptions)541 explicit MarkerStack(StackCaptureOptions aCaptureOptions)
542 : mCaptureOptions(aCaptureOptions) {
543 AssertInvariants();
544 }
545
546 // This should be called after every constructor and non-const function.
AssertInvariants()547 void AssertInvariants() const {
548 #ifdef DEBUG
549 if (mCaptureOptions != StackCaptureOptions::NoStack) {
550 MOZ_ASSERT(!mOptionalChunkedBufferStorage,
551 "We should not hold a buffer when capture is requested");
552 MOZ_ASSERT(!mChunkedBuffer,
553 "We should not point at a buffer when capture is requested");
554 } else {
555 if (mOptionalChunkedBufferStorage) {
556 MOZ_ASSERT(mChunkedBuffer == mOptionalChunkedBufferStorage.get(),
557 "Non-null mOptionalChunkedBufferStorage must be pointed-at "
558 "by mChunkedBuffer");
559 }
560 if (mChunkedBuffer) {
561 MOZ_ASSERT(!mChunkedBuffer->IsEmpty(),
562 "Non-null mChunkedBuffer must not be empty");
563 }
564 }
565 #endif // DEBUG
566 }
567
568 StackCaptureOptions mCaptureOptions = StackCaptureOptions::NoStack;
569
570 // Optional storage for the backtrace, in case it was captured before the
571 // add-marker call.
572 UniquePtr<ProfileChunkedBuffer> mOptionalChunkedBufferStorage;
573
574 // If not null, this points to the backtrace. It may point to a backtrace
575 // temporarily stored on the stack, or to mOptionalChunkedBufferStorage.
576 ProfileChunkedBuffer* mChunkedBuffer = nullptr;
577 };
578
579 // This marker option captures a given inner window id.
580 class MarkerInnerWindowId {
581 public:
582 // Default constructor, it leaves the id unspecified.
583 constexpr MarkerInnerWindowId() = default;
584
585 // Constructor with a specified inner window id.
MarkerInnerWindowId(uint64_t i)586 constexpr explicit MarkerInnerWindowId(uint64_t i) : mInnerWindowId(i) {}
587
588 // Constructor with either specified inner window id or Nothing.
MarkerInnerWindowId(const Maybe<uint64_t> & i)589 constexpr explicit MarkerInnerWindowId(const Maybe<uint64_t>& i)
590 : mInnerWindowId(i.valueOr(scNoId)) {}
591
592 // Explicit option with unspecified id.
NoId()593 constexpr static MarkerInnerWindowId NoId() { return MarkerInnerWindowId{}; }
594
IsUnspecified()595 [[nodiscard]] bool IsUnspecified() const { return mInnerWindowId == scNoId; }
596
Id()597 [[nodiscard]] constexpr uint64_t Id() const { return mInnerWindowId; }
598
599 private:
600 static constexpr uint64_t scNoId = 0;
601 uint64_t mInnerWindowId = scNoId;
602 };
603
604 // This class combines each of the possible marker options above.
605 class MarkerOptions {
606 public:
607 // Constructor from individual options (including none).
608 // Implicit to allow `{}` and one option type as-is.
609 // Options that are not provided here are defaulted. In particular, timing
610 // defaults to `MarkerTiming::InstantNow()` when the marker is recorded.
611 template <typename... Options>
MarkerOptions(Options &&...aOptions)612 MOZ_IMPLICIT MarkerOptions(Options&&... aOptions) {
613 (Set(std::forward<Options>(aOptions)), ...);
614 }
615
616 // Disallow copy.
617 MarkerOptions(const MarkerOptions&) = delete;
618 MarkerOptions& operator=(const MarkerOptions&) = delete;
619
620 // Allow move.
621 MarkerOptions(MarkerOptions&&) = default;
622 MarkerOptions& operator=(MarkerOptions&&) = default;
623
624 // The embedded `MarkerTiming` hasn't been specified yet.
IsTimingUnspecified()625 [[nodiscard]] bool IsTimingUnspecified() const {
626 return mTiming.IsUnspecified();
627 }
628
629 // Each option may be added in a chain by e.g.:
630 // `options.Set(MarkerThreadId(123)).Set(MarkerTiming::IntervalEnd())`.
631 // When passed to an add-marker function, it must be an rvalue, either created
632 // on the spot, or `std::move`d from storage, e.g.:
633 // `PROFILER_MARKER_UNTYPED("...", std::move(options).Set(...))`;
634 //
635 // Options can be read by their name (without "Marker"), e.g.: `o.ThreadId()`.
636 // Add "Ref" for a non-const reference, e.g.: `o.ThreadIdRef() = ...;`
637 #define FUNCTIONS_ON_MEMBER(NAME) \
638 MarkerOptions& Set(Marker##NAME&& a##NAME)& { \
639 m##NAME = std::move(a##NAME); \
640 return *this; \
641 } \
642 \
643 MarkerOptions&& Set(Marker##NAME&& a##NAME)&& { \
644 m##NAME = std::move(a##NAME); \
645 return std::move(*this); \
646 } \
647 \
648 const Marker##NAME& NAME() const { return m##NAME; } \
649 \
650 Marker##NAME& NAME##Ref() { return m##NAME; }
651
652 FUNCTIONS_ON_MEMBER(ThreadId);
653 FUNCTIONS_ON_MEMBER(Timing);
654 FUNCTIONS_ON_MEMBER(Stack);
655 FUNCTIONS_ON_MEMBER(InnerWindowId);
656 #undef FUNCTIONS_ON_MEMBER
657
658 private:
659 friend ProfileBufferEntryReader::Deserializer<MarkerOptions>;
660
661 MarkerThreadId mThreadId;
662 MarkerTiming mTiming;
663 MarkerStack mStack;
664 MarkerInnerWindowId mInnerWindowId;
665 };
666
667 } // namespace mozilla
668
669 namespace mozilla::baseprofiler::markers {
670
671 // Default marker payload types, with no extra information, not even a marker
672 // type and payload. This is intended for label-only markers.
673 struct NoPayload final {};
674
675 } // namespace mozilla::baseprofiler::markers
676
677 namespace mozilla {
678
679 class JSONWriter;
680
681 // This class collects all the information necessary to stream the JSON schema
682 // that informs the front-end how to display a type of markers.
683 // It will be created and populated in `MarkerTypeDisplay()` functions in each
684 // marker type definition, see Add/Set functions.
685 class MarkerSchema {
686 public:
687 enum class Location : unsigned {
688 MarkerChart,
689 MarkerTable,
690 // This adds markers to the main marker timeline in the header.
691 TimelineOverview,
692 // In the timeline, this is a section that breaks out markers that are
693 // related to memory. When memory counters are enabled, this is its own
694 // track, otherwise it is displayed with the main thread.
695 TimelineMemory,
696 // This adds markers to the IPC timeline area in the header.
697 TimelineIPC,
698 // This adds markers to the FileIO timeline area in the header.
699 TimelineFileIO,
700 // TODO - This is not supported yet.
701 StackChart
702 };
703
704 // Used as constructor parameter, to explicitly specify that the location (and
705 // other display options) are handled as a special case in the front-end.
706 // In this case, *no* schema will be output for this type.
707 struct SpecialFrontendLocation {};
708
709 enum class Format {
710 // ----------------------------------------------------
711 // String types.
712
713 // Show the URL, and handle PII sanitization
714 Url,
715 // Show the file path, and handle PII sanitization.
716 FilePath,
717 // Important, do not put URL or file path information here, as it will not
718 // be sanitized. Please be careful with including other types of PII here as
719 // well.
720 // e.g. "Label: Some String"
721 String,
722
723 // ----------------------------------------------------
724 // Numeric types
725
726 // For time data that represents a duration of time.
727 // e.g. "Label: 5s, 5ms, 5μs"
728 Duration,
729 // Data that happened at a specific time, relative to the start of the
730 // profile. e.g. "Label: 15.5s, 20.5ms, 30.5μs"
731 Time,
732 // The following are alternatives to display a time only in a specific unit
733 // of time.
734 Seconds, // "Label: 5s"
735 Milliseconds, // "Label: 5ms"
736 Microseconds, // "Label: 5μs"
737 Nanoseconds, // "Label: 5ns"
738 // e.g. "Label: 5.55mb, 5 bytes, 312.5kb"
739 Bytes,
740 // This should be a value between 0 and 1.
741 // "Label: 50%"
742 Percentage,
743 // The integer should be used for generic representations of numbers.
744 // Do not use it for time information.
745 // "Label: 52, 5,323, 1,234,567"
746 Integer,
747 // The decimal should be used for generic representations of numbers.
748 // Do not use it for time information.
749 // "Label: 52.23, 0.0054, 123,456.78"
750 Decimal
751 };
752
753 enum class Searchable { NotSearchable, Searchable };
754
755 // Marker schema, with a non-empty list of locations where markers should be
756 // shown.
757 // Tech note: Even though `aLocations` are templated arguments, they are
758 // assigned to an `enum class` object, so they can only be of that enum type.
759 template <typename... Locations>
MarkerSchema(Location aLocation,Locations...aLocations)760 explicit MarkerSchema(Location aLocation, Locations... aLocations)
761 : mLocations{aLocation, aLocations...} {}
762
763 // Alternative constructor for MarkerSchema.
MarkerSchema(const mozilla::MarkerSchema::Location * aLocations,size_t aLength)764 explicit MarkerSchema(const mozilla::MarkerSchema::Location* aLocations,
765 size_t aLength)
766 : mLocations(aLocations, aLocations + aLength) {}
767
768 // Marker schema for types that have special frontend handling.
769 // Nothing else should be set in this case.
770 // Implicit to allow quick return from MarkerTypeDisplay functions.
MarkerSchema(SpecialFrontendLocation)771 MOZ_IMPLICIT MarkerSchema(SpecialFrontendLocation) {}
772
773 // Caller must specify location(s) or SpecialFrontendLocation above.
774 MarkerSchema() = delete;
775
776 // Optional labels in the marker chart, the chart tooltip, and the marker
777 // table. If not provided, the marker "name" will be used. The given string
778 // can contain element keys in braces to include data elements streamed by
779 // `StreamJSONMarkerData()`. E.g.: "This is {text}"
780
781 #define LABEL_SETTER(name) \
782 MarkerSchema& Set##name(std::string a##name) { \
783 m##name = std::move(a##name); \
784 return *this; \
785 }
786
787 LABEL_SETTER(ChartLabel)
LABEL_SETTER(TooltipLabel)788 LABEL_SETTER(TooltipLabel)
789 LABEL_SETTER(TableLabel)
790
791 #undef LABEL_SETTER
792
793 MarkerSchema& SetAllLabels(std::string aText) {
794 // Here we set the same text in each label.
795 // TODO: Move to a single "label" field once the front-end allows it.
796 SetChartLabel(aText);
797 SetTooltipLabel(aText);
798 SetTableLabel(std::move(aText));
799 return *this;
800 }
801
802 // Each data element that is streamed by `StreamJSONMarkerData()` can be
803 // displayed as indicated by using one of the `Add...` function below.
804 // Each `Add...` will add a line in the full marker description. Parameters:
805 // - `aKey`: Element property name as streamed by `StreamJSONMarkerData()`.
806 // - `aLabel`: Optional prefix. Defaults to the key name.
807 // - `aFormat`: How to format the data element value, see `Format` above.
808 // - `aSearchable`: Optional, indicates if the value is used in searches,
809 // defaults to false.
810
AddKeyFormat(std::string aKey,Format aFormat)811 MarkerSchema& AddKeyFormat(std::string aKey, Format aFormat) {
812 mData.emplace_back(mozilla::VariantType<DynamicData>{},
813 DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat,
814 mozilla::Nothing{}});
815 return *this;
816 }
817
AddKeyLabelFormat(std::string aKey,std::string aLabel,Format aFormat)818 MarkerSchema& AddKeyLabelFormat(std::string aKey, std::string aLabel,
819 Format aFormat) {
820 mData.emplace_back(
821 mozilla::VariantType<DynamicData>{},
822 DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat,
823 mozilla::Nothing{}});
824 return *this;
825 }
826
AddKeyFormatSearchable(std::string aKey,Format aFormat,Searchable aSearchable)827 MarkerSchema& AddKeyFormatSearchable(std::string aKey, Format aFormat,
828 Searchable aSearchable) {
829 mData.emplace_back(mozilla::VariantType<DynamicData>{},
830 DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat,
831 mozilla::Some(aSearchable)});
832 return *this;
833 }
834
AddKeyLabelFormatSearchable(std::string aKey,std::string aLabel,Format aFormat,Searchable aSearchable)835 MarkerSchema& AddKeyLabelFormatSearchable(std::string aKey,
836 std::string aLabel, Format aFormat,
837 Searchable aSearchable) {
838 mData.emplace_back(
839 mozilla::VariantType<DynamicData>{},
840 DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat,
841 mozilla::Some(aSearchable)});
842 return *this;
843 }
844
845 // The display may also include static rows.
846
AddStaticLabelValue(std::string aLabel,std::string aValue)847 MarkerSchema& AddStaticLabelValue(std::string aLabel, std::string aValue) {
848 mData.emplace_back(mozilla::VariantType<StaticData>{},
849 StaticData{std::move(aLabel), std::move(aValue)});
850 return *this;
851 }
852
853 // Internal streaming function.
854 MFBT_API void Stream(JSONWriter& aWriter, const Span<const char>& aName) &&;
855
856 private:
857 MFBT_API static Span<const char> LocationToStringSpan(Location aLocation);
858 MFBT_API static Span<const char> FormatToStringSpan(Format aFormat);
859
860 // List of marker display locations. Empty for SpecialFrontendLocation.
861 std::vector<Location> mLocations;
862 // Labels for different places.
863 std::string mChartLabel;
864 std::string mTooltipLabel;
865 std::string mTableLabel;
866 // Main display, made of zero or more rows of key+label+format or label+value.
867 private:
868 struct DynamicData {
869 std::string mKey;
870 mozilla::Maybe<std::string> mLabel;
871 Format mFormat;
872 mozilla::Maybe<Searchable> mSearchable;
873 };
874 struct StaticData {
875 std::string mLabel;
876 std::string mValue;
877 };
878 using DataRow = mozilla::Variant<DynamicData, StaticData>;
879 using DataRowVector = std::vector<DataRow>;
880
881 DataRowVector mData;
882 };
883
884 } // namespace mozilla
885
886 #endif // BaseProfilerMarkersPrerequisites_h
887