BaseProfilerMarkersPrerequisites.h (43120B)
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 "mozilla/BaseProfileJSONWriter.h" 28 #include "mozilla/BaseProfilingCategory.h" 29 #include "mozilla/Maybe.h" 30 #include "mozilla/ProfileChunkedBuffer.h" 31 #include "mozilla/BaseProfilerState.h" 32 #include "mozilla/TimeStamp.h" 33 #include "mozilla/UniquePtr.h" 34 #include "mozilla/Variant.h" 35 36 #include <initializer_list> 37 #include <string_view> 38 #include <string> 39 #include <type_traits> 40 #include <utility> 41 #include <vector> 42 43 // The header <X11/X.h> defines "None" as a macro that expands to "0L". 44 // This is terrible because we have an enum variant named "None" too in this 45 // file. To work around this, we undefine the macro "None". 46 #ifdef None 47 # undef None 48 #endif 49 50 namespace mozilla { 51 52 // Return a NotNull<const CHAR*> pointing at the literal empty string `""`. 53 template <typename CHAR> 54 constexpr const CHAR* LiteralEmptyStringPointer() { 55 static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>, 56 "Only char and char16_t are supported in Firefox"); 57 if constexpr (std::is_same_v<CHAR, char>) { 58 return ""; 59 } 60 if constexpr (std::is_same_v<CHAR, char16_t>) { 61 return u""; 62 } 63 } 64 65 // Return a string_view<CHAR> pointing at the literal empty string. 66 template <typename CHAR> 67 constexpr std::basic_string_view<CHAR> LiteralEmptyStringView() { 68 static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>, 69 "Only char and char16_t are supported in Firefox"); 70 // Use `operator""sv()` from <string_view>. 71 using namespace std::literals::string_view_literals; 72 if constexpr (std::is_same_v<CHAR, char>) { 73 return ""sv; 74 } 75 if constexpr (std::is_same_v<CHAR, char16_t>) { 76 return u""sv; 77 } 78 } 79 80 // General string view, optimized for short on-stack life before serialization, 81 // and between deserialization and JSON-streaming. 82 template <typename CHAR> 83 class MOZ_STACK_CLASS ProfilerStringView { 84 public: 85 // Default constructor points at "" (literal empty string). 86 constexpr ProfilerStringView() = default; 87 88 // Don't allow copy. 89 ProfilerStringView(const ProfilerStringView&) = delete; 90 ProfilerStringView& operator=(const ProfilerStringView&) = delete; 91 92 // Allow move. For consistency the moved-from string is always reset to "". 93 constexpr ProfilerStringView(ProfilerStringView&& aOther) 94 : mStringView(std::move(aOther.mStringView)), 95 mOwnership(aOther.mOwnership) { 96 if (mOwnership == Ownership::OwnedThroughStringView) { 97 // We now own the buffer, make the other point at the literal "". 98 aOther.mStringView = LiteralEmptyStringView<CHAR>(); 99 aOther.mOwnership = Ownership::Literal; 100 } 101 } 102 constexpr ProfilerStringView& operator=(ProfilerStringView&& aOther) { 103 mStringView = std::move(aOther.mStringView); 104 mOwnership = aOther.mOwnership; 105 if (mOwnership == Ownership::OwnedThroughStringView) { 106 // We now own the buffer, make the other point at the literal "". 107 aOther.mStringView = LiteralEmptyStringView<CHAR>(); 108 aOther.mOwnership = Ownership::Literal; 109 } 110 return *this; 111 } 112 113 ~ProfilerStringView() { 114 if (MOZ_UNLIKELY(mOwnership == Ownership::OwnedThroughStringView)) { 115 // We own the buffer pointed at by mStringView, destroy it. 116 // This is only used between deserialization and streaming. 117 delete mStringView.data(); 118 } 119 } 120 121 // Implicit construction from nullptr, points at "" (literal empty string). 122 constexpr MOZ_IMPLICIT ProfilerStringView(decltype(nullptr)) {} 123 124 // Implicit constructor from a literal string. 125 template <size_t Np1> 126 constexpr MOZ_IMPLICIT ProfilerStringView(const CHAR (&aLiteralString)[Np1]) 127 : ProfilerStringView(aLiteralString, Np1 - 1, Ownership::Literal) {} 128 129 // Constructor from a non-literal string. 130 constexpr ProfilerStringView(const CHAR* aString, size_t aLength) 131 : ProfilerStringView(aString, aLength, Ownership::Reference) {} 132 133 // Implicit constructor from a string_view. 134 constexpr MOZ_IMPLICIT ProfilerStringView( 135 const std::basic_string_view<CHAR>& aStringView) 136 : ProfilerStringView(aStringView.data(), aStringView.length(), 137 Ownership::Reference) {} 138 139 // Implicit constructor from an expiring string_view. We assume that the 140 // pointed-at string will outlive this ProfilerStringView. 141 constexpr MOZ_IMPLICIT ProfilerStringView( 142 std::basic_string_view<CHAR>&& aStringView) 143 : ProfilerStringView(aStringView.data(), aStringView.length(), 144 Ownership::Reference) {} 145 146 // Implicit constructor from std::string. 147 constexpr MOZ_IMPLICIT ProfilerStringView( 148 const std::basic_string<CHAR>& aString) 149 : ProfilerStringView(aString.data(), aString.length(), 150 Ownership::Reference) {} 151 152 // Construction from a raw pointer to a null-terminated string. 153 // This is a named class-static function to make it more obvious where work is 154 // being done (to determine the string length), and encourage users to instead 155 // provide a length, if already known. 156 // TODO: Find callers and convert them to constructor instead if possible. 157 static constexpr ProfilerStringView WrapNullTerminatedString( 158 const CHAR* aString) { 159 return ProfilerStringView( 160 aString, aString ? std::char_traits<CHAR>::length(aString) : 0, 161 Ownership::Reference); 162 } 163 164 // Implicit constructor for an object with member functions `Data()` 165 // `Length()`, and `IsLiteral()`, common in xpcom strings. 166 template < 167 typename String, 168 typename DataReturnType = decltype(std::declval<const String>().Data()), 169 typename LengthReturnType = 170 decltype(std::declval<const String>().Length()), 171 typename IsLiteralReturnType = 172 decltype(std::declval<const String>().IsLiteral()), 173 typename = 174 std::enable_if_t<std::is_convertible_v<DataReturnType, const CHAR*> && 175 std::is_integral_v<LengthReturnType> && 176 std::is_same_v<IsLiteralReturnType, bool>>> 177 constexpr MOZ_IMPLICIT ProfilerStringView(const String& aString) 178 : ProfilerStringView( 179 static_cast<const CHAR*>(aString.Data()), aString.Length(), 180 aString.IsLiteral() ? Ownership::Literal : Ownership::Reference) {} 181 182 [[nodiscard]] constexpr const std::basic_string_view<CHAR>& StringView() 183 const { 184 return mStringView; 185 } 186 187 [[nodiscard]] constexpr size_t Length() const { return mStringView.length(); } 188 189 [[nodiscard]] constexpr bool IsLiteral() const { 190 return mOwnership == Ownership::Literal; 191 } 192 [[nodiscard]] constexpr bool IsReference() const { 193 return mOwnership == Ownership::Reference; 194 } 195 // No `IsOwned...()` because it's a secret, only used internally! 196 197 [[nodiscard]] Span<const CHAR> AsSpan() const { 198 return Span<const CHAR>(mStringView.data(), mStringView.length()); 199 } 200 [[nodiscard]] operator Span<const CHAR>() const { return AsSpan(); } 201 202 private: 203 enum class Ownership { Literal, Reference, OwnedThroughStringView }; 204 205 // Allow deserializer to store anything here. 206 friend ProfileBufferEntryReader::Deserializer<ProfilerStringView>; 207 208 constexpr ProfilerStringView(const CHAR* aString, size_t aLength, 209 Ownership aOwnership) 210 : mStringView(aString ? std::basic_string_view<CHAR>(aString, aLength) 211 : LiteralEmptyStringView<CHAR>()), 212 mOwnership(aString ? aOwnership : Ownership::Literal) {} 213 214 // String view to an outside string (literal or reference). 215 // We may actually own the pointed-at buffer, but it is only used internally 216 // between deserialization and JSON streaming. 217 std::basic_string_view<CHAR> mStringView = LiteralEmptyStringView<CHAR>(); 218 219 Ownership mOwnership = Ownership::Literal; 220 }; 221 222 using ProfilerString8View = ProfilerStringView<char>; 223 using ProfilerString16View = ProfilerStringView<char16_t>; 224 225 // This compulsory marker parameter contains the required category information. 226 class MarkerCategory { 227 public: 228 // Constructor from category pair (includes both super- and sub-categories). 229 constexpr explicit MarkerCategory( 230 baseprofiler::ProfilingCategoryPair aCategoryPair) 231 : mCategoryPair(aCategoryPair) {} 232 233 // Returns the stored category pair. 234 constexpr baseprofiler::ProfilingCategoryPair CategoryPair() const { 235 return mCategoryPair; 236 } 237 238 // Returns the super-category from the stored category pair. 239 baseprofiler::ProfilingCategory GetCategory() const { 240 return GetProfilingCategoryPairInfo(mCategoryPair).mCategory; 241 } 242 243 private: 244 baseprofiler::ProfilingCategoryPair mCategoryPair = 245 baseprofiler::ProfilingCategoryPair::OTHER; 246 }; 247 248 namespace baseprofiler::category { 249 250 // Each category pair name constructs a MarkerCategory. 251 // E.g.: mozilla::baseprofiler::category::OTHER_Profiling 252 // Profiler macros will take the category name alone without namespace. 253 // E.g.: `PROFILER_MARKER_UNTYPED("name", OTHER_Profiling)` 254 #define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) 255 #define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) \ 256 static constexpr MarkerCategory name{ProfilingCategoryPair::name}; 257 #define CATEGORY_ENUM_END_CATEGORY 258 MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY, 259 CATEGORY_ENUM_SUBCATEGORY, 260 CATEGORY_ENUM_END_CATEGORY) 261 #undef CATEGORY_ENUM_BEGIN_CATEGORY 262 #undef CATEGORY_ENUM_SUBCATEGORY 263 #undef CATEGORY_ENUM_END_CATEGORY 264 265 // Import `MarkerCategory` into this namespace. This will allow using this type 266 // dynamically in macros that prepend `::mozilla::baseprofiler::category::` to 267 // the given category, e.g.: 268 // `PROFILER_MARKER_UNTYPED("name", MarkerCategory(...))` 269 using MarkerCategory = ::mozilla::MarkerCategory; 270 271 } // namespace baseprofiler::category 272 273 // The classes below are all embedded in a `MarkerOptions` object. 274 class MarkerOptions; 275 276 // This marker option captures a given thread id. 277 // If left unspecified (by default construction) during the add-marker call, the 278 // current thread id will be used then. 279 class MarkerThreadId { 280 public: 281 // Default constructor, keeps the thread id unspecified. 282 constexpr MarkerThreadId() = default; 283 284 // Constructor from a given thread id. 285 constexpr explicit MarkerThreadId( 286 baseprofiler::BaseProfilerThreadId aThreadId) 287 : mThreadId(aThreadId) {} 288 289 // Use the current thread's id. 290 static MarkerThreadId CurrentThread() { 291 return MarkerThreadId(baseprofiler::profiler_current_thread_id()); 292 } 293 294 // Use the main thread's id. This can be useful to record a marker from a 295 // possibly-unregistered thread, and display it in the main thread track. 296 static MarkerThreadId MainThread() { 297 return MarkerThreadId(baseprofiler::profiler_main_thread_id()); 298 } 299 300 [[nodiscard]] constexpr baseprofiler::BaseProfilerThreadId ThreadId() const { 301 return mThreadId; 302 } 303 304 [[nodiscard]] constexpr bool IsUnspecified() const { 305 return !mThreadId.IsSpecified(); 306 } 307 308 private: 309 baseprofiler::BaseProfilerThreadId mThreadId; 310 }; 311 312 // This marker option contains marker timing information. 313 // This class encapsulates the logic for correctly storing a marker based on its 314 // Use the static methods to create the MarkerTiming. This is a transient object 315 // that is being used to enforce the constraints of the combinations of the 316 // data. 317 class MarkerTiming { 318 public: 319 // The following static methods are used to create the MarkerTiming based on 320 // the type that it is. 321 322 static MarkerTiming InstantAt(const TimeStamp& aTime) { 323 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an instant marker."); 324 return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::Instant}; 325 } 326 327 static MarkerTiming InstantNow() { return InstantAt(TimeStamp::Now()); } 328 329 static MarkerTiming Interval(const TimeStamp& aStartTime, 330 const TimeStamp& aEndTime) { 331 MOZ_ASSERT(!aStartTime.IsNull(), 332 "Start time is null for an interval marker."); 333 MOZ_ASSERT(!aEndTime.IsNull(), "End time is null for an interval marker."); 334 return MarkerTiming{aStartTime, aEndTime, MarkerTiming::Phase::Interval}; 335 } 336 337 static MarkerTiming IntervalUntilNowFrom(const TimeStamp& aStartTime) { 338 return Interval(aStartTime, TimeStamp::Now()); 339 } 340 341 static MarkerTiming IntervalStart(const TimeStamp& aTime = TimeStamp::Now()) { 342 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval start marker."); 343 return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::IntervalStart}; 344 } 345 346 static MarkerTiming IntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) { 347 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker."); 348 return MarkerTiming{TimeStamp{}, aTime, MarkerTiming::Phase::IntervalEnd}; 349 } 350 351 // Set the interval end in this timing. 352 // If there was already a start time, this makes it a full interval. 353 void SetIntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) { 354 MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker."); 355 mEndTime = aTime; 356 mPhase = mStartTime.IsNull() ? Phase::IntervalEnd : Phase::Interval; 357 } 358 359 [[nodiscard]] const TimeStamp& StartTime() const { return mStartTime; } 360 [[nodiscard]] const TimeStamp& EndTime() const { return mEndTime; } 361 362 // The phase differentiates Instant markers from Interval markers. 363 // Interval markers can either carry both timestamps on a single marker, 364 // or they can be split into individual Start and End markers, which are 365 // associated with each other via the marker name. 366 // 367 // The numeric representation of this enum value is also exposed in the 368 // ETW trace event's Phase field. 369 enum class Phase : uint8_t { 370 Instant = 0, 371 Interval = 1, 372 IntervalStart = 2, 373 IntervalEnd = 3, 374 }; 375 376 [[nodiscard]] Phase MarkerPhase() const { 377 MOZ_ASSERT(!IsUnspecified()); 378 return mPhase; 379 } 380 381 // The following getter methods are used to put the value into the buffer for 382 // storage. 383 [[nodiscard]] double GetStartTime() const { 384 MOZ_ASSERT(!IsUnspecified()); 385 // If mStartTime is null (e.g., for IntervalEnd), this will output 0.0 as 386 // expected. 387 return MarkerTiming::timeStampToDouble(mStartTime); 388 } 389 390 [[nodiscard]] double GetEndTime() const { 391 MOZ_ASSERT(!IsUnspecified()); 392 // If mEndTime is null (e.g., for Instant or IntervalStart), this will 393 // output 0.0 as expected. 394 return MarkerTiming::timeStampToDouble(mEndTime); 395 } 396 397 [[nodiscard]] uint8_t GetPhase() const { 398 MOZ_ASSERT(!IsUnspecified()); 399 return static_cast<uint8_t>(mPhase); 400 } 401 402 // This is a constructor for Rust FFI bindings. It must not be used outside of 403 // this! Please see the other static constructors above. 404 static void UnsafeConstruct(MarkerTiming* aMarkerTiming, 405 const TimeStamp& aStartTime, 406 const TimeStamp& aEndTime, Phase aPhase) { 407 new (aMarkerTiming) MarkerTiming{aStartTime, aEndTime, aPhase}; 408 } 409 410 private: 411 friend ProfileBufferEntryWriter::Serializer<MarkerTiming>; 412 friend ProfileBufferEntryReader::Deserializer<MarkerTiming>; 413 friend MarkerOptions; 414 415 // Default timing leaves it internally "unspecified", serialization getters 416 // and add-marker functions will default to `InstantNow()`. 417 constexpr MarkerTiming() = default; 418 419 // This should only be used by internal profiler code. 420 [[nodiscard]] bool IsUnspecified() const { 421 return mStartTime.IsNull() && mEndTime.IsNull(); 422 } 423 424 // Full constructor, used by static factory functions. 425 constexpr MarkerTiming(const TimeStamp& aStartTime, const TimeStamp& aEndTime, 426 Phase aPhase) 427 : mStartTime(aStartTime), mEndTime(aEndTime), mPhase(aPhase) {} 428 429 static double timeStampToDouble(const TimeStamp& time) { 430 if (time.IsNull()) { 431 // The Phase lets us know not to use this value. 432 return 0; 433 } 434 return (time - TimeStamp::ProcessCreation()).ToMilliseconds(); 435 } 436 437 TimeStamp mStartTime; 438 TimeStamp mEndTime; 439 Phase mPhase = Phase::Instant; 440 }; 441 442 // This marker option allows three cases: 443 // - By default, no stacks are captured. 444 // - The caller can request a stack capture, and the add-marker code will take 445 // care of it in the most efficient way. 446 // - The caller can still provide an existing backtrace, for cases where a 447 // marker reports something that happened elsewhere. 448 class MarkerStack { 449 public: 450 // Default constructor, no capture. 451 constexpr MarkerStack() = default; 452 453 // Disallow copy. 454 MarkerStack(const MarkerStack&) = delete; 455 MarkerStack& operator=(const MarkerStack&) = delete; 456 457 // Allow move. 458 MarkerStack(MarkerStack&& aOther) 459 : mCaptureOptions(aOther.mCaptureOptions), 460 mOptionalChunkedBufferStorage( 461 std::move(aOther.mOptionalChunkedBufferStorage)), 462 mChunkedBuffer(aOther.mChunkedBuffer) { 463 AssertInvariants(); 464 aOther.Clear(); 465 } 466 MarkerStack& operator=(MarkerStack&& aOther) { 467 mCaptureOptions = aOther.mCaptureOptions; 468 mOptionalChunkedBufferStorage = 469 std::move(aOther.mOptionalChunkedBufferStorage); 470 mChunkedBuffer = aOther.mChunkedBuffer; 471 AssertInvariants(); 472 aOther.Clear(); 473 return *this; 474 } 475 476 // Take ownership of a backtrace. If null or empty, equivalent to NoStack(). 477 explicit MarkerStack(UniquePtr<ProfileChunkedBuffer>&& aExternalChunkedBuffer) 478 : mOptionalChunkedBufferStorage( 479 (!aExternalChunkedBuffer || aExternalChunkedBuffer->IsEmpty()) 480 ? nullptr 481 : std::move(aExternalChunkedBuffer)), 482 mChunkedBuffer(mOptionalChunkedBufferStorage.get()) { 483 AssertInvariants(); 484 } 485 486 // Use an existing backtrace stored elsewhere, which the user must guarantee 487 // is alive during the add-marker call. If empty, equivalent to NoStack(). 488 explicit MarkerStack(ProfileChunkedBuffer& aExternalChunkedBuffer) 489 : mChunkedBuffer(aExternalChunkedBuffer.IsEmpty() 490 ? nullptr 491 : &aExternalChunkedBuffer) { 492 AssertInvariants(); 493 } 494 495 // Don't capture a stack in this marker. 496 static MarkerStack NoStack() { 497 return MarkerStack(StackCaptureOptions::NoStack); 498 } 499 500 // Capture a stack when adding this marker. 501 static MarkerStack Capture( 502 StackCaptureOptions aCaptureOptions = StackCaptureOptions::Full) { 503 // Actual capture will be handled inside profiler_add_marker. 504 return MarkerStack(aCaptureOptions); 505 } 506 507 // Optionally capture a stack, useful for avoiding long-winded ternaries. 508 static MarkerStack MaybeCapture(bool aDoCapture) { 509 return aDoCapture ? Capture() : NoStack(); 510 } 511 512 // Use an existing backtrace stored elsewhere, which the user must guarantee 513 // is alive during the add-marker call. If empty, equivalent to NoStack(). 514 static MarkerStack UseBacktrace( 515 ProfileChunkedBuffer& aExternalChunkedBuffer) { 516 return MarkerStack(aExternalChunkedBuffer); 517 } 518 519 // Take ownership of a backtrace previously captured with 520 // `profiler_capture_backtrace()`. If null, equivalent to NoStack(). 521 static MarkerStack TakeBacktrace( 522 UniquePtr<ProfileChunkedBuffer>&& aExternalChunkedBuffer) { 523 return MarkerStack(std::move(aExternalChunkedBuffer)); 524 } 525 526 // Construct with the given capture options. 527 static MarkerStack WithCaptureOptions(StackCaptureOptions aCaptureOptions) { 528 return MarkerStack(aCaptureOptions); 529 } 530 531 [[nodiscard]] StackCaptureOptions CaptureOptions() const { 532 return mCaptureOptions; 533 } 534 535 ProfileChunkedBuffer* GetChunkedBuffer() const { return mChunkedBuffer; } 536 537 // Use backtrace after a request. If null, equivalent to NoStack(). 538 void UseRequestedBacktrace(ProfileChunkedBuffer* aExternalChunkedBuffer) { 539 MOZ_RELEASE_ASSERT(mCaptureOptions != StackCaptureOptions::NoStack); 540 mCaptureOptions = StackCaptureOptions::NoStack; 541 if (aExternalChunkedBuffer && !aExternalChunkedBuffer->IsEmpty()) { 542 // We only need to use the provided buffer if it is not empty. 543 mChunkedBuffer = aExternalChunkedBuffer; 544 } 545 AssertInvariants(); 546 } 547 548 void Clear() { 549 mCaptureOptions = StackCaptureOptions::NoStack; 550 mOptionalChunkedBufferStorage.reset(); 551 mChunkedBuffer = nullptr; 552 AssertInvariants(); 553 } 554 555 private: 556 explicit MarkerStack(StackCaptureOptions aCaptureOptions) 557 : mCaptureOptions(aCaptureOptions) { 558 AssertInvariants(); 559 } 560 561 // This should be called after every constructor and non-const function. 562 void AssertInvariants() const { 563 #ifdef DEBUG 564 if (mCaptureOptions != StackCaptureOptions::NoStack) { 565 MOZ_ASSERT(!mOptionalChunkedBufferStorage, 566 "We should not hold a buffer when capture is requested"); 567 MOZ_ASSERT(!mChunkedBuffer, 568 "We should not point at a buffer when capture is requested"); 569 } else { 570 if (mOptionalChunkedBufferStorage) { 571 MOZ_ASSERT(mChunkedBuffer == mOptionalChunkedBufferStorage.get(), 572 "Non-null mOptionalChunkedBufferStorage must be pointed-at " 573 "by mChunkedBuffer"); 574 } 575 if (mChunkedBuffer) { 576 MOZ_ASSERT(!mChunkedBuffer->IsEmpty(), 577 "Non-null mChunkedBuffer must not be empty"); 578 } 579 } 580 #endif // DEBUG 581 } 582 583 StackCaptureOptions mCaptureOptions = StackCaptureOptions::NoStack; 584 585 // Optional storage for the backtrace, in case it was captured before the 586 // add-marker call. 587 UniquePtr<ProfileChunkedBuffer> mOptionalChunkedBufferStorage; 588 589 // If not null, this points to the backtrace. It may point to a backtrace 590 // temporarily stored on the stack, or to mOptionalChunkedBufferStorage. 591 ProfileChunkedBuffer* mChunkedBuffer = nullptr; 592 }; 593 594 // This marker option captures a given inner window id. 595 class MarkerInnerWindowId { 596 public: 597 // Default constructor, it leaves the id unspecified. 598 constexpr MarkerInnerWindowId() = default; 599 600 // Constructor with a specified inner window id. 601 constexpr explicit MarkerInnerWindowId(uint64_t i) : mInnerWindowId(i) {} 602 603 // Constructor with either specified inner window id or Nothing. 604 constexpr explicit MarkerInnerWindowId(const Maybe<uint64_t>& i) 605 : mInnerWindowId(i.valueOr(scNoId)) {} 606 607 // Explicit option with unspecified id. 608 constexpr static MarkerInnerWindowId NoId() { return MarkerInnerWindowId{}; } 609 610 [[nodiscard]] bool IsUnspecified() const { return mInnerWindowId == scNoId; } 611 612 [[nodiscard]] constexpr uint64_t Id() const { return mInnerWindowId; } 613 614 private: 615 static constexpr uint64_t scNoId = 0; 616 uint64_t mInnerWindowId = scNoId; 617 }; 618 619 // This class combines each of the possible marker options above. 620 class MarkerOptions { 621 public: 622 // Constructor from individual options (including none). 623 // Implicit to allow `{}` and one option type as-is. 624 // Options that are not provided here are defaulted. In particular, timing 625 // defaults to `MarkerTiming::InstantNow()` when the marker is recorded. 626 template <typename... Options> 627 MOZ_IMPLICIT MarkerOptions(Options&&... aOptions) { 628 (Set(std::forward<Options>(aOptions)), ...); 629 } 630 631 // Disallow copy. 632 MarkerOptions(const MarkerOptions&) = delete; 633 MarkerOptions& operator=(const MarkerOptions&) = delete; 634 635 // Allow move. 636 MarkerOptions(MarkerOptions&&) = default; 637 MarkerOptions& operator=(MarkerOptions&&) = default; 638 639 // The embedded `MarkerTiming` hasn't been specified yet. 640 [[nodiscard]] bool IsTimingUnspecified() const { 641 return mTiming.IsUnspecified(); 642 } 643 644 // Each option may be added in a chain by e.g.: 645 // `options.Set(MarkerThreadId(123)).Set(MarkerTiming::IntervalEnd())`. 646 // When passed to an add-marker function, it must be an rvalue, either created 647 // on the spot, or `std::move`d from storage, e.g.: 648 // `PROFILER_MARKER_UNTYPED("...", std::move(options).Set(...))`; 649 // 650 // Options can be read by their name (without "Marker"), e.g.: `o.ThreadId()`. 651 // Add "Ref" for a non-const reference, e.g.: `o.ThreadIdRef() = ...;` 652 #define FUNCTIONS_ON_MEMBER(NAME) \ 653 MarkerOptions& Set(Marker##NAME&& a##NAME) & { \ 654 m##NAME = std::move(a##NAME); \ 655 return *this; \ 656 } \ 657 \ 658 MarkerOptions&& Set(Marker##NAME&& a##NAME) && { \ 659 m##NAME = std::move(a##NAME); \ 660 return std::move(*this); \ 661 } \ 662 \ 663 const Marker##NAME& NAME() const { return m##NAME; } \ 664 \ 665 Marker##NAME& NAME##Ref() { return m##NAME; } 666 667 FUNCTIONS_ON_MEMBER(ThreadId); 668 FUNCTIONS_ON_MEMBER(Timing); 669 FUNCTIONS_ON_MEMBER(Stack); 670 FUNCTIONS_ON_MEMBER(InnerWindowId); 671 #undef FUNCTIONS_ON_MEMBER 672 673 private: 674 friend ProfileBufferEntryReader::Deserializer<MarkerOptions>; 675 676 MarkerThreadId mThreadId; 677 MarkerTiming mTiming; 678 MarkerStack mStack; 679 MarkerInnerWindowId mInnerWindowId; 680 }; 681 682 } // namespace mozilla 683 684 namespace mozilla::baseprofiler::markers { 685 686 // Default marker payload types, with no extra information, not even a marker 687 // type and payload. This is intended for label-only markers. 688 struct NoPayload final {}; 689 690 } // namespace mozilla::baseprofiler::markers 691 692 namespace mozilla { 693 694 class JSONWriter; 695 696 // This class collects all the information necessary to stream the JSON schema 697 // that informs the front-end how to display a type of markers. 698 // It will be created and populated in `MarkerTypeDisplay()` functions in each 699 // marker type definition, see Add/Set functions. 700 class MarkerSchema { 701 public: 702 // This is used to describe a C++ type that is expected to be specified to 703 // the marker and used in PayloadField. This type is the expected input type 704 // to the marker data. 705 enum class InputType { 706 Uint64, 707 Uint32, 708 Uint8, 709 Int64, 710 Int32, 711 Int8, 712 Double, 713 Boolean, 714 CString, 715 String, 716 TimeStamp, 717 TimeDuration 718 }; 719 720 template <typename T> 721 static constexpr InputType getDefaultInputTypeForType() { 722 using CleanT = std::remove_cv_t<std::remove_pointer_t<T>>; 723 724 if constexpr (std::is_same_v<CleanT, bool>) { 725 return InputType::Boolean; 726 } else if constexpr (std::is_same_v<CleanT, double>) { 727 return InputType::Double; 728 } else if constexpr (std::is_unsigned_v<CleanT> && sizeof(CleanT) == 4) { 729 return InputType::Uint32; 730 } else if constexpr (std::is_unsigned_v<CleanT> && sizeof(CleanT) == 8) { 731 return InputType::Uint64; 732 } else if constexpr (std::is_unsigned_v<CleanT> && sizeof(CleanT) == 1) { 733 return InputType::Uint8; 734 } else if constexpr (std::is_signed_v<CleanT> && 735 std::is_integral_v<CleanT> && sizeof(CleanT) == 4) { 736 return InputType::Int32; 737 } else if constexpr (std::is_signed_v<CleanT> && 738 std::is_integral_v<CleanT> && sizeof(CleanT) == 8) { 739 return InputType::Int64; 740 } else if constexpr (std::is_signed_v<CleanT> && 741 std::is_integral_v<CleanT> && sizeof(CleanT) == 1) { 742 return InputType::Int8; 743 } else if constexpr (std::is_same_v<CleanT, TimeStamp>) { 744 return InputType::TimeStamp; 745 } else if constexpr (std::is_same_v<CleanT, TimeDuration>) { 746 return InputType::TimeDuration; 747 } else if constexpr (std::is_same_v<CleanT, ProfilerString8View>) { 748 return InputType::CString; 749 } else { 750 static_assert(sizeof(T) == 0, "Unsupported type"); 751 } 752 } 753 754 enum class Location : unsigned { 755 MarkerChart, 756 MarkerTable, 757 // This adds markers to the main marker timeline in the header. 758 TimelineOverview, 759 // In the timeline, this is a section that breaks out markers that are 760 // related to memory. When memory counters are enabled, this is its own 761 // track, otherwise it is displayed with the main thread. 762 TimelineMemory, 763 // This adds markers to the IPC timeline area in the header. 764 TimelineIPC, 765 // This adds markers to the FileIO timeline area in the header. 766 TimelineFileIO, 767 // TODO - This is not supported yet. 768 StackChart 769 }; 770 771 // Used as constructor parameter, to explicitly specify that the location (and 772 // other display options) are handled as a special case in the front-end. 773 // In this case, *no* schema will be output for this type. 774 struct SpecialFrontendLocation {}; 775 776 enum class Format { 777 // ---------------------------------------------------- 778 // String types. 779 780 // Show the URL, and handle PII sanitization 781 Url, 782 // Show the file path, and handle PII sanitization. 783 FilePath, 784 // Show arbitrary string and handle PII sanitization 785 SanitizedString, 786 // Important, do not put URL or file path information here, as it will not 787 // be sanitized. Please be careful with including other types of PII here as 788 // well. 789 // e.g. "Label: Some String" 790 String, 791 792 // Show a string from a UniqueStringArray given an index in the profile. 793 // e.g. 1, given string table ["hello", "world"] will show "world" 794 UniqueString, 795 796 // ---------------------------------------------------- 797 // Numeric types 798 799 // For time data that represents a duration of time. 800 // e.g. "Label: 5s, 5ms, 5μs" 801 Duration, 802 // Data that happened at a specific time, relative to the start of the 803 // profile. e.g. "Label: 15.5s, 20.5ms, 30.5μs" 804 Time, 805 // The following are alternatives to display a time only in a specific unit 806 // of time. 807 Seconds, // "Label: 5s" 808 Milliseconds, // "Label: 5ms" 809 Microseconds, // "Label: 5μs" 810 Nanoseconds, // "Label: 5ns" 811 // e.g. "Label: 5.55mb, 5 bytes, 312.5kb" 812 Bytes, 813 // This should be a value between 0 and 1. 814 // "Label: 50%" 815 Percentage, 816 // The integer should be used for generic representations of numbers. 817 // Do not use it for time information. 818 // "Label: 52, 5,323, 1,234,567" 819 Integer, 820 // The decimal should be used for generic representations of numbers. 821 // Do not use it for time information. 822 // "Label: 52.23, 0.0054, 123,456.78" 823 Decimal, 824 825 // A flow is a u64 identifier that's unique across processes. All of 826 // the markers with same flow id before a terminating flow id will be 827 // considered part of the same "flow" and linked together. 828 Flow, 829 // A terminating flow ends a flow of a particular id and allows that id 830 // to be reused again. It often makes sense for destructors to create 831 // a marker with a field of this type. 832 TerminatingFlow 833 }; 834 835 template <typename T> 836 static constexpr Format getDefaultFormatForType() { 837 using CleanT = std::remove_cv_t<T>; 838 839 if constexpr (std::is_integral_v<CleanT> || std::is_same_v<CleanT, bool>) { 840 return Format::Integer; 841 } else if constexpr (std::is_same_v<CleanT, double>) { 842 return Format::Decimal; 843 } else if constexpr (std::is_same_v<CleanT, TimeStamp>) { 844 return Format::Time; 845 } else if constexpr (std::is_same_v<CleanT, TimeDuration>) { 846 return Format::Duration; 847 } else if constexpr (std::is_same_v<CleanT, ProfilerString8View>) { 848 return Format::SanitizedString; 849 } else { 850 static_assert(sizeof(T) == 0, "Unsupported type"); 851 } 852 } 853 854 // This represents groups of markers which MarkerTypes can expose to indicate 855 // what group they belong to (multiple groups are allowed combined in bitwise 856 // or). This is currently only used for ETW filtering. In the long run this 857 // should be generalized to gecko markers. 858 enum class ETWMarkerGroup : uint64_t { 859 Generic = 1, 860 UserMarkers = 1 << 1, 861 Memory = 1 << 2, 862 Scheduling = 1 << 3, 863 Text = 1 << 4, 864 Tracing = 1 << 5 865 }; 866 867 // Flags which describe additional information for a PayloadField. 868 enum class PayloadFlags : uint32_t { 869 None = 0, 870 Searchable = 1 << 0, 871 Hidden = 1 << 1, 872 }; 873 874 // This is one field of payload to be used for additional marker data. 875 struct PayloadField { 876 // Key identifying the marker. 877 const char* Key; 878 // Input type, this represents the data type specified. 879 InputType InputTy; 880 // Label, additional description. 881 const char* Label = nullptr; 882 // Format as written to the JSON. 883 Format Fmt = Format::String; 884 // Optional PayloadFlags. 885 PayloadFlags Flags = PayloadFlags::None; 886 }; 887 888 enum class GraphType { Line, Bar, FilledLine }; 889 enum class GraphColor { 890 Blue, 891 Green, 892 Grey, 893 Ink, 894 Magenta, 895 Orange, 896 Purple, 897 Red, 898 Teal, 899 Yellow 900 }; 901 902 // Marker schema, with a non-empty list of locations where markers should be 903 // shown. 904 // Tech note: Even though `aLocations` are templated arguments, they are 905 // assigned to an `enum class` object, so they can only be of that enum type. 906 template <typename... Locations> 907 explicit MarkerSchema(Location aLocation, Locations... aLocations) 908 : mLocations{aLocation, aLocations...} {} 909 910 // Alternative constructor for MarkerSchema. 911 explicit MarkerSchema(const mozilla::MarkerSchema::Location* aLocations, 912 size_t aLength) 913 : mLocations(aLocations, aLocations + aLength) {} 914 915 // Marker schema for types that have special frontend handling. 916 // Nothing else should be set in this case. 917 // Implicit to allow quick return from MarkerTypeDisplay functions. 918 MOZ_IMPLICIT MarkerSchema(SpecialFrontendLocation) {} 919 920 // Caller must specify location(s) or SpecialFrontendLocation above. 921 MarkerSchema() = delete; 922 923 // Optional labels in the marker chart, the chart tooltip, and the marker 924 // table. If not provided, the marker "name" will be used. The given string 925 // can contain element keys in braces to include data elements streamed by 926 // `StreamJSONMarkerData()`. E.g.: "This is {text}" 927 928 #define LABEL_SETTER(name) \ 929 MarkerSchema& Set##name(std::string a##name) { \ 930 m##name = std::move(a##name); \ 931 return *this; \ 932 } 933 934 LABEL_SETTER(ChartLabel) 935 LABEL_SETTER(TooltipLabel) 936 LABEL_SETTER(TableLabel) 937 938 #undef LABEL_SETTER 939 940 MarkerSchema& SetAllLabels(std::string aText) { 941 // Here we set the same text in each label. 942 // TODO: Move to a single "label" field once the front-end allows it. 943 SetChartLabel(aText); 944 SetTooltipLabel(aText); 945 SetTableLabel(std::move(aText)); 946 return *this; 947 } 948 949 MarkerSchema& SetIsStackBased() { 950 mIsStackBased = true; 951 return *this; 952 } 953 954 // Each data element that is streamed by `StreamJSONMarkerData()` can be 955 // displayed as indicated by using one of the `Add...` function below. 956 // Each `Add...` will add a line in the full marker description. Parameters: 957 // - `aKey`: Element property name as streamed by `StreamJSONMarkerData()`. 958 // - `aLabel`: Optional prefix. Defaults to the key name. 959 // - `aFormat`: How to format the data element value, see `Format` above. 960 // - `aPayloadFlags`: Optional, indicates additinal flags to serialize inside 961 // the marker schema object. Defaults to `PayloadFlags::None`. 962 963 MarkerSchema& AddKeyFormat(std::string aKey, Format aFormat, 964 PayloadFlags aPayloadFlags = PayloadFlags::None) { 965 mData.emplace_back(mozilla::VariantType<DynamicData>{}, 966 DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat, 967 aPayloadFlags}); 968 return *this; 969 } 970 971 MarkerSchema& AddKeyLabelFormat( 972 std::string aKey, std::string aLabel, Format aFormat, 973 PayloadFlags aPayloadFlags = PayloadFlags::None) { 974 mData.emplace_back( 975 mozilla::VariantType<DynamicData>{}, 976 DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat, 977 aPayloadFlags}); 978 return *this; 979 } 980 981 // The display may also include static rows. 982 983 MarkerSchema& AddStaticLabelValue(std::string aLabel, std::string aValue) { 984 mData.emplace_back(mozilla::VariantType<StaticData>{}, 985 StaticData{std::move(aLabel), std::move(aValue)}); 986 return *this; 987 } 988 989 // Markers can be shown as timeline tracks. 990 991 MarkerSchema& AddChart(std::string aKey, GraphType aType) { 992 mGraphs.emplace_back(GraphData{std::move(aKey), aType, mozilla::Nothing{}}); 993 return *this; 994 } 995 996 MarkerSchema& AddChartColor(std::string aKey, GraphType aType, 997 GraphColor aColor) { 998 mGraphs.emplace_back( 999 GraphData{std::move(aKey), aType, mozilla::Some(aColor)}); 1000 return *this; 1001 } 1002 1003 // Internal streaming function. 1004 MFBT_API void Stream(JSONWriter& aWriter, const Span<const char>& aName) &&; 1005 1006 private: 1007 MFBT_API static Span<const char> LocationToStringSpan(Location aLocation); 1008 MFBT_API static Span<const char> FormatToStringSpan(Format aFormat); 1009 MFBT_API static Span<const char> GraphTypeToStringSpan(GraphType aType); 1010 MFBT_API static Span<const char> GraphColorToStringSpan(GraphColor aColor); 1011 1012 // List of marker display locations. Empty for SpecialFrontendLocation. 1013 std::vector<Location> mLocations; 1014 // Labels for different places. 1015 std::string mChartLabel; 1016 std::string mTooltipLabel; 1017 std::string mTableLabel; 1018 bool mIsStackBased = false; 1019 // Main display, made of zero or more rows of key+label+format or label+value. 1020 private: 1021 struct DynamicData { 1022 std::string mKey; 1023 mozilla::Maybe<std::string> mLabel; 1024 Format mFormat; 1025 PayloadFlags mPayloadFlags; 1026 }; 1027 struct StaticData { 1028 std::string mLabel; 1029 std::string mValue; 1030 }; 1031 using DataRow = mozilla::Variant<DynamicData, StaticData>; 1032 using DataRowVector = std::vector<DataRow>; 1033 1034 DataRowVector mData; 1035 1036 struct GraphData { 1037 std::string mKey; 1038 GraphType mType; 1039 mozilla::Maybe<GraphColor> mColor; 1040 }; 1041 std::vector<GraphData> mGraphs; 1042 }; 1043 1044 namespace detail { 1045 // GCC doesn't allow this to live inside the class. 1046 template <typename PayloadType> 1047 static void StreamPayload(baseprofiler::SpliceableJSONWriter& aWriter, 1048 const Span<const char> aKey, 1049 const PayloadType& aPayload) { 1050 using CleanT = std::remove_cv_t<PayloadType>; 1051 if constexpr (std::is_integral_v<CleanT>) { 1052 aWriter.IntProperty(aKey, aPayload); 1053 } else if constexpr (std::is_same_v<CleanT, double>) { 1054 aWriter.DoubleProperty(aKey, aPayload); 1055 } else { 1056 aWriter.StringProperty(aKey, aPayload); 1057 } 1058 } 1059 1060 template <typename PayloadType> 1061 inline void StreamPayload(baseprofiler::SpliceableJSONWriter& aWriter, 1062 const Span<const char> aKey, 1063 const Maybe<PayloadType>& aPayload) { 1064 if (aPayload.isSome()) { 1065 StreamPayload(aWriter, aKey, *aPayload); 1066 } else { 1067 aWriter.NullProperty(aKey); 1068 } 1069 } 1070 1071 template <> 1072 inline void StreamPayload<bool>(baseprofiler::SpliceableJSONWriter& aWriter, 1073 const Span<const char> aKey, 1074 const bool& aPayload) { 1075 aWriter.BoolProperty(aKey, aPayload); 1076 } 1077 1078 template <> 1079 inline void StreamPayload<Flow>(baseprofiler::SpliceableJSONWriter& aWriter, 1080 const Span<const char> aKey, 1081 const Flow& aPayload) { 1082 aWriter.FlowProperty(aKey, aPayload); 1083 } 1084 1085 } // namespace detail 1086 1087 // This helper class is used by MarkerTypes that want to support the general 1088 // MarkerType object schema. When using this the markers will also transmit 1089 // their payload to the ETW tracer as well as requiring less inline code. 1090 // This is a curiously recurring template, the template argument is the child 1091 // class itself. 1092 template <typename T> 1093 struct BaseMarkerType { 1094 static constexpr const char* Description = nullptr; 1095 1096 static constexpr const char* AllLabels = nullptr; 1097 static constexpr const char* ChartLabel = nullptr; 1098 static constexpr const char* TableLabel = nullptr; 1099 static constexpr const char* TooltipLabel = nullptr; 1100 1101 // Setting this property to true is a promise that the the marker will nest 1102 // properly. i.e. it can't have a partially overlapping time range with any 1103 // other stack based markers on the same thread. 1104 static constexpr bool IsStackBased = false; 1105 1106 // This indicates whether this marker type wants the names passed to the 1107 // individual marker calls stores along with the marker. 1108 static constexpr bool StoreName = false; 1109 1110 static constexpr MarkerSchema::ETWMarkerGroup Group = 1111 MarkerSchema::ETWMarkerGroup::Generic; 1112 1113 static MarkerSchema MarkerTypeDisplay() { 1114 using MS = MarkerSchema; 1115 MS schema{T::Locations, std::size(T::Locations)}; 1116 if (T::AllLabels) { 1117 schema.SetAllLabels(T::AllLabels); 1118 } 1119 if (T::ChartLabel) { 1120 schema.SetChartLabel(T::ChartLabel); 1121 } 1122 if (T::TableLabel) { 1123 schema.SetTableLabel(T::TableLabel); 1124 } 1125 if (T::TooltipLabel) { 1126 schema.SetTooltipLabel(T::TooltipLabel); 1127 } 1128 if (T::IsStackBased) { 1129 schema.SetIsStackBased(); 1130 } 1131 for (const MS::PayloadField field : T::PayloadFields) { 1132 if (field.Label) { 1133 schema.AddKeyLabelFormat(field.Key, field.Label, field.Fmt, 1134 field.Flags); 1135 } else { 1136 schema.AddKeyFormat(field.Key, field.Fmt, field.Flags); 1137 } 1138 } 1139 if (T::Description) { 1140 schema.AddStaticLabelValue("Description", T::Description); 1141 } 1142 return schema; 1143 } 1144 1145 static constexpr Span<const char> MarkerTypeName() { 1146 return MakeStringSpan(T::Name); 1147 } 1148 1149 // This is called by the child class since the child class version of this 1150 // function is used to infer the argument types by the profile buffer and 1151 // allows the child to do any special data conversion it needs to do. 1152 // Optionally the child can opt not to use this at all and write the data 1153 // out itself. 1154 template <typename... PayloadArguments> 1155 static void StreamJSONMarkerDataImpl( 1156 baseprofiler::SpliceableJSONWriter& aWriter, 1157 const PayloadArguments&... aPayloadArguments) { 1158 size_t i = 0; 1159 (detail::StreamPayload(aWriter, MakeStringSpan(T::PayloadFields[i++].Key), 1160 aPayloadArguments), 1161 ...); 1162 } 1163 }; 1164 } // namespace mozilla 1165 1166 #endif // BaseProfilerMarkersPrerequisites_h