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