From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../public/BaseAndGeckoProfilerDetail.h | 64 + .../baseprofiler/public/BaseProfileJSONWriter.h | 600 ++++++++ mozglue/baseprofiler/public/BaseProfiler.h | 506 +++++++ mozglue/baseprofiler/public/BaseProfilerCounts.h | 280 ++++ mozglue/baseprofiler/public/BaseProfilerDetail.h | 285 ++++ mozglue/baseprofiler/public/BaseProfilerLabels.h | 178 +++ .../baseprofiler/public/BaseProfilerMarkerTypes.h | 125 ++ mozglue/baseprofiler/public/BaseProfilerMarkers.h | 255 ++++ .../public/BaseProfilerMarkersDetail.h | 741 ++++++++++ .../public/BaseProfilerMarkersPrerequisites.h | 886 +++++++++++ .../baseprofiler/public/BaseProfilerRAIIMacro.h | 15 + .../public/BaseProfilerSharedLibraries.h | 170 +++ mozglue/baseprofiler/public/BaseProfilerState.h | 405 +++++ mozglue/baseprofiler/public/BaseProfilerUtils.h | 227 +++ .../baseprofiler/public/BaseProfilingCategory.h | 68 + mozglue/baseprofiler/public/BaseProfilingStack.h | 517 +++++++ mozglue/baseprofiler/public/BlocksRingBuffer.h | 999 +++++++++++++ mozglue/baseprofiler/public/FailureLatch.h | 217 +++ mozglue/baseprofiler/public/ModuloBuffer.h | 618 ++++++++ mozglue/baseprofiler/public/PowerOfTwo.h | 322 ++++ mozglue/baseprofiler/public/ProfileBufferChunk.h | 547 +++++++ .../public/ProfileBufferChunkManager.h | 134 ++ .../public/ProfileBufferChunkManagerSingle.h | 172 +++ .../ProfileBufferChunkManagerWithLocalLimit.h | 444 ++++++ .../public/ProfileBufferControlledChunkManager.h | 203 +++ .../baseprofiler/public/ProfileBufferEntryKinds.h | 105 ++ .../public/ProfileBufferEntrySerialization.h | 1184 +++++++++++++++ mozglue/baseprofiler/public/ProfileBufferIndex.h | 97 ++ mozglue/baseprofiler/public/ProfileChunkedBuffer.h | 1560 ++++++++++++++++++++ .../public/ProfileChunkedBufferDetail.h | 400 +++++ mozglue/baseprofiler/public/ProgressLogger.h | 500 +++++++ mozglue/baseprofiler/public/ProportionValue.h | 235 +++ mozglue/baseprofiler/public/leb128iterator.h | 207 +++ 33 files changed, 13266 insertions(+) create mode 100644 mozglue/baseprofiler/public/BaseAndGeckoProfilerDetail.h create mode 100644 mozglue/baseprofiler/public/BaseProfileJSONWriter.h create mode 100644 mozglue/baseprofiler/public/BaseProfiler.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerCounts.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerDetail.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerLabels.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerMarkerTypes.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerMarkers.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerMarkersPrerequisites.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerRAIIMacro.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerSharedLibraries.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerState.h create mode 100644 mozglue/baseprofiler/public/BaseProfilerUtils.h create mode 100644 mozglue/baseprofiler/public/BaseProfilingCategory.h create mode 100644 mozglue/baseprofiler/public/BaseProfilingStack.h create mode 100644 mozglue/baseprofiler/public/BlocksRingBuffer.h create mode 100644 mozglue/baseprofiler/public/FailureLatch.h create mode 100644 mozglue/baseprofiler/public/ModuloBuffer.h create mode 100644 mozglue/baseprofiler/public/PowerOfTwo.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferChunk.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferChunkManager.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferChunkManagerSingle.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferChunkManagerWithLocalLimit.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferEntryKinds.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h create mode 100644 mozglue/baseprofiler/public/ProfileBufferIndex.h create mode 100644 mozglue/baseprofiler/public/ProfileChunkedBuffer.h create mode 100644 mozglue/baseprofiler/public/ProfileChunkedBufferDetail.h create mode 100644 mozglue/baseprofiler/public/ProgressLogger.h create mode 100644 mozglue/baseprofiler/public/ProportionValue.h create mode 100644 mozglue/baseprofiler/public/leb128iterator.h (limited to 'mozglue/baseprofiler/public') diff --git a/mozglue/baseprofiler/public/BaseAndGeckoProfilerDetail.h b/mozglue/baseprofiler/public/BaseAndGeckoProfilerDetail.h new file mode 100644 index 0000000000..f45170c83f --- /dev/null +++ b/mozglue/baseprofiler/public/BaseAndGeckoProfilerDetail.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Internal Base and Gecko Profiler utilities. +// It should declare or define things that are used in both profilers, but not +// needed outside of the profilers. +// In particular, it is *not* included in popular headers like BaseProfiler.h +// and GeckoProfiler.h, to avoid rebuilding the world when this is modified. + +#ifndef BaseAndGeckoProfilerDetail_h +#define BaseAndGeckoProfilerDetail_h + +#include "mozilla/BaseProfilerUtils.h" +#include "mozilla/Span.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Types.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class ProfileBufferChunkManagerWithLocalLimit; + +namespace baseprofiler::detail { + +[[nodiscard]] MFBT_API TimeStamp GetProfilingStartTime(); + +[[nodiscard]] MFBT_API UniquePtr +ExtractBaseProfilerChunkManager(); + +// If the current thread is registered, returns its registration time, otherwise +// a null timestamp. +[[nodiscard]] MFBT_API TimeStamp GetThreadRegistrationTime(); + +} // namespace baseprofiler::detail + +namespace profiler::detail { + +// True if the filter is exactly "pid:". +[[nodiscard]] MFBT_API bool FilterHasPid( + const char* aFilter, baseprofiler::BaseProfilerProcessId aPid = + baseprofiler::profiler_current_process_id()); + +// Only true if the filters only contain "pid:..." strings, and *none* of them +// is exactly "pid:". E.g.: +// - [], 123 -> false (no pids) +// - ["main"], 123 -> false (not all pids) +// - ["main", "pid:123"], 123 -> false (not all pids) +// - ["pid:123"], 123 -> false (all pids, including "pid:123") +// - ["pid:123", "pid:456"], 123 -> false (all pids, including "pid:123") +// - ["pid:456"], 123 -> true (all pids, but no "pid:123") +// - ["pid:456", "pid:789"], 123 -> true (all pids, but no "pid:123") +[[nodiscard]] MFBT_API bool FiltersExcludePid( + Span aFilters, + baseprofiler::BaseProfilerProcessId aPid = + baseprofiler::profiler_current_process_id()); + +} // namespace profiler::detail + +} // namespace mozilla + +#endif // BaseAndGeckoProfilerDetail_h diff --git a/mozglue/baseprofiler/public/BaseProfileJSONWriter.h b/mozglue/baseprofiler/public/BaseProfileJSONWriter.h new file mode 100644 index 0000000000..00a2926366 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfileJSONWriter.h @@ -0,0 +1,600 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BASEPROFILEJSONWRITER_H +#define BASEPROFILEJSONWRITER_H + +#include "mozilla/FailureLatch.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/JSONWriter.h" +#include "mozilla/Maybe.h" +#include "mozilla/NotNull.h" +#include "mozilla/ProgressLogger.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtrExtensions.h" + +#include +#include +#include + +namespace mozilla { +namespace baseprofiler { + +class SpliceableJSONWriter; + +// On average, profile JSONs are large enough such that we want to avoid +// reallocating its buffer when expanding. Additionally, the contents of the +// profile are not accessed until the profile is entirely written. For these +// reasons we use a chunked writer that keeps an array of chunks, which is +// concatenated together after writing is finished. +class ChunkedJSONWriteFunc final : public JSONWriteFunc, public FailureLatch { + public: + friend class SpliceableJSONWriter; + + explicit ChunkedJSONWriteFunc(FailureLatch& aFailureLatch) + : mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) { + (void)AllocChunk(kChunkSize); + } + + [[nodiscard]] bool IsEmpty() const { + MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd && mChunkList.length() == 0 && + mChunkLengths.length() == 0); + return !mChunkPtr; + } + + // Length of data written so far, excluding null terminator. + [[nodiscard]] size_t Length() const { + MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); + size_t totalLen = 0; + for (size_t i = 0; i < mChunkLengths.length(); i++) { + MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]); + totalLen += mChunkLengths[i]; + } + return totalLen; + } + + void Write(const Span& aStr) final { + if (Failed()) { + return; + } + + MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd); + MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back()); + MOZ_ASSERT(*mChunkPtr == '\0'); + + // Most strings to be written are small, but subprocess profiles (e.g., + // from the content process in e10s) may be huge. If the string is larger + // than a chunk, allocate its own chunk. + char* newPtr; + if (aStr.size() >= kChunkSize) { + if (!AllocChunk(aStr.size() + 1)) { + return; + } + newPtr = mChunkPtr + aStr.size(); + } else { + newPtr = mChunkPtr + aStr.size(); + if (newPtr >= mChunkEnd) { + if (!AllocChunk(kChunkSize)) { + return; + } + newPtr = mChunkPtr + aStr.size(); + } + } + + memcpy(mChunkPtr, aStr.data(), aStr.size()); + *newPtr = '\0'; + mChunkPtr = newPtr; + mChunkLengths.back() += aStr.size(); + } + + [[nodiscard]] bool CopyDataIntoLazilyAllocatedBuffer( + const std::function& aAllocator) const { + // Request a buffer for the full content plus a null terminator. + if (Failed()) { + return false; + } + + char* ptr = aAllocator(Length() + 1); + + if (!ptr) { + // Failed to allocate memory. + return false; + } + + for (size_t i = 0; i < mChunkList.length(); i++) { + size_t len = mChunkLengths[i]; + memcpy(ptr, mChunkList[i].get(), len); + ptr += len; + } + *ptr = '\0'; + return true; + } + + [[nodiscard]] UniquePtr CopyData() const { + UniquePtr c; + if (!CopyDataIntoLazilyAllocatedBuffer([&](size_t allocationSize) { + c = MakeUnique(allocationSize); + return c.get(); + })) { + // Something went wrong, make sure the returned pointer is null even if + // the allocation happened. + c = nullptr; + } + return c; + } + + void Take(ChunkedJSONWriteFunc&& aOther) { + SetFailureFrom(aOther); + if (Failed()) { + return; + } + + for (size_t i = 0; i < aOther.mChunkList.length(); i++) { + MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i])); + MOZ_ALWAYS_TRUE(mChunkList.append(std::move(aOther.mChunkList[i]))); + } + mChunkPtr = mChunkList.back().get() + mChunkLengths.back(); + mChunkEnd = mChunkPtr; + aOther.Clear(); + } + + FAILURELATCH_IMPL_PROXY(*mFailureLatch) + + // Change the failure latch to be used here, and if the previous latch was + // already in failure state, set that failure in the new latch. + // This allows using this WriteFunc in isolation, before attempting to bring + // it into another operation group with its own FailureLatch. + void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) { + aFailureLatch.SetFailureFrom(*this); + mFailureLatch = WrapNotNullUnchecked(&aFailureLatch); + } + + private: + void Clear() { + mChunkPtr = nullptr; + mChunkEnd = nullptr; + mChunkList.clear(); + mChunkLengths.clear(); + } + + void ClearAndSetFailure(std::string aFailure) { + Clear(); + SetFailure(std::move(aFailure)); + } + + [[nodiscard]] bool ClearAndSetFailureAndFalse(std::string aFailure) { + ClearAndSetFailure(std::move(aFailure)); + return false; + } + + [[nodiscard]] bool AllocChunk(size_t aChunkSize) { + if (Failed()) { + if (mChunkPtr) { + // FailureLatch is in failed state, but chunks have not been cleared yet + // (error must have happened elsewhere). + Clear(); + } + return false; + } + + MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); + UniquePtr newChunk = MakeUniqueFallible(aChunkSize); + if (!newChunk) { + return ClearAndSetFailureAndFalse( + "OOM in ChunkedJSONWriteFunc::AllocChunk allocating new chunk"); + } + mChunkPtr = newChunk.get(); + mChunkEnd = mChunkPtr + aChunkSize; + *mChunkPtr = '\0'; + if (!mChunkLengths.append(0)) { + return ClearAndSetFailureAndFalse( + "OOM in ChunkedJSONWriteFunc::AllocChunk appending length"); + } + if (!mChunkList.append(std::move(newChunk))) { + return ClearAndSetFailureAndFalse( + "OOM in ChunkedJSONWriteFunc::AllocChunk appending new chunk"); + } + return true; + } + + static const size_t kChunkSize = 4096 * 512; + + // Pointer for writing inside the current chunk. + // + // The current chunk is always at the back of mChunkList, i.e., + // mChunkList.back() <= mChunkPtr <= mChunkEnd. + char* mChunkPtr = nullptr; + + // Pointer to the end of the current chunk. + // + // The current chunk is always at the back of mChunkList, i.e., + // mChunkEnd >= mChunkList.back() + mChunkLengths.back(). + char* mChunkEnd = nullptr; + + // List of chunks and their lengths. + // + // For all i, the length of the string in mChunkList[i] is + // mChunkLengths[i]. + Vector> mChunkList; + Vector mChunkLengths; + + NotNull mFailureLatch; +}; + +struct OStreamJSONWriteFunc final : public JSONWriteFunc { + explicit OStreamJSONWriteFunc(std::ostream& aStream) : mStream(aStream) {} + + void Write(const Span& aStr) final { + std::string_view sv(aStr.data(), aStr.size()); + mStream << sv; + } + + std::ostream& mStream; +}; + +class UniqueJSONStrings; + +class SpliceableJSONWriter : public JSONWriter, public FailureLatch { + public: + SpliceableJSONWriter(JSONWriteFunc& aWriter, FailureLatch& aFailureLatch) + : JSONWriter(aWriter, JSONWriter::SingleLineStyle), + mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {} + + SpliceableJSONWriter(UniquePtr aWriter, + FailureLatch& aFailureLatch) + : JSONWriter(std::move(aWriter), JSONWriter::SingleLineStyle), + mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {} + + void StartBareList() { StartCollection(scEmptyString, scEmptyString); } + + void EndBareList() { EndCollection(scEmptyString); } + + // Output a time (int64_t given in nanoseconds) in milliseconds. trim zeroes. + // E.g.: 1'234'567'890 -> "1234.56789" + void TimeI64NsProperty(const Span& aMaybePropertyName, + int64_t aTime_ns) { + if (aTime_ns == 0) { + Scalar(aMaybePropertyName, MakeStringSpan("0")); + return; + } + + static constexpr int64_t million = 1'000'000; + const int64_t absNanos = std::abs(aTime_ns); + const int64_t integerMilliseconds = absNanos / million; + auto remainderNanoseconds = static_cast(absNanos % million); + + // Plenty enough to fit INT64_MIN (-9223372036854775808). + static constexpr size_t DIGITS_MAX = 23; + char buf[DIGITS_MAX + 1]; + int len = + snprintf(buf, DIGITS_MAX, (aTime_ns >= 0) ? "%" PRIu64 : "-%" PRIu64, + integerMilliseconds); + if (remainderNanoseconds != 0) { + buf[len++] = '.'; + // Output up to 6 fractional digits. Exit early if the rest would + // be trailing zeros. + uint32_t powerOfTen = static_cast(million / 10); + for (;;) { + auto digit = remainderNanoseconds / powerOfTen; + buf[len++] = '0' + static_cast(digit); + remainderNanoseconds %= powerOfTen; + if (remainderNanoseconds == 0) { + break; + } + powerOfTen /= 10; + if (powerOfTen == 0) { + break; + } + } + } + + Scalar(aMaybePropertyName, Span(buf, len)); + } + + // Output a (double) time in milliseconds, with at best nanosecond precision. + void TimeDoubleMsProperty(const Span& aMaybePropertyName, + double aTime_ms) { + const double dTime_ns = aTime_ms * 1'000'000.0; + // Make sure it's well within int64_t range. + // 2^63 nanoseconds is almost 300 years; these times are relative to + // firefox startup, this should be enough for most uses. + if (dTime_ns >= 0.0) { + MOZ_RELEASE_ASSERT(dTime_ns < double(INT64_MAX - 1)); + } else { + MOZ_RELEASE_ASSERT(dTime_ns > double(INT64_MIN + 2)); + } + // Round to nearest integer nanosecond. The conversion to integer truncates + // the fractional part, so first we need to push it 0.5 away from zero. + const int64_t iTime_ns = + (dTime_ns >= 0.0) ? int64_t(dTime_ns + 0.5) : int64_t(dTime_ns - 0.5); + TimeI64NsProperty(aMaybePropertyName, iTime_ns); + } + + // Output a (double) time in milliseconds, with at best nanosecond precision. + void TimeDoubleMsElement(double aTime_ms) { + TimeDoubleMsProperty(nullptr, aTime_ms); + } + + // This function must be used to correctly stream timestamps in profiles. + // Null timestamps don't output anything. + void TimeProperty(const Span& aMaybePropertyName, + const TimeStamp& aTime) { + if (!aTime.IsNull()) { + TimeDoubleMsProperty( + aMaybePropertyName, + (aTime - TimeStamp::ProcessCreation()).ToMilliseconds()); + } + } + + void NullElements(uint32_t aCount) { + for (uint32_t i = 0; i < aCount; i++) { + NullElement(); + } + } + + void Splice(const Span& aStr) { + Separator(); + WriteFunc().Write(aStr); + mNeedComma[mDepth] = true; + } + + void Splice(const char* aStr, size_t aLen) { + Separator(); + WriteFunc().Write(Span(aStr, aLen)); + mNeedComma[mDepth] = true; + } + + // Splice the given JSON directly in, without quoting. + void SplicedJSONProperty(const Span& aMaybePropertyName, + const Span& aJsonValue) { + Scalar(aMaybePropertyName, aJsonValue); + } + + void CopyAndSplice(const ChunkedJSONWriteFunc& aFunc) { + Separator(); + for (size_t i = 0; i < aFunc.mChunkList.length(); i++) { + WriteFunc().Write( + Span(aFunc.mChunkList[i].get(), aFunc.mChunkLengths[i])); + } + mNeedComma[mDepth] = true; + } + + // Takes the chunks from aFunc and write them. If move is not possible + // (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its + // storage cleared. + virtual void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) { + Separator(); + for (size_t i = 0; i < aFunc.mChunkList.length(); i++) { + WriteFunc().Write( + Span(aFunc.mChunkList[i].get(), aFunc.mChunkLengths[i])); + } + aFunc.mChunkPtr = nullptr; + aFunc.mChunkEnd = nullptr; + aFunc.mChunkList.clear(); + aFunc.mChunkLengths.clear(); + mNeedComma[mDepth] = true; + } + + // Set (or reset) the pointer to a UniqueJSONStrings. + void SetUniqueStrings(UniqueJSONStrings& aUniqueStrings) { + MOZ_RELEASE_ASSERT(!mUniqueStrings); + mUniqueStrings = &aUniqueStrings; + } + + // Set (or reset) the pointer to a UniqueJSONStrings. + void ResetUniqueStrings() { + MOZ_RELEASE_ASSERT(mUniqueStrings); + mUniqueStrings = nullptr; + } + + // Add `aStr` to the unique-strings list (if not already there), and write its + // index as a named object property. + inline void UniqueStringProperty(const Span& aName, + const Span& aStr); + + // Add `aStr` to the unique-strings list (if not already there), and write its + // index as an array element. + inline void UniqueStringElement(const Span& aStr); + + // THe following functions override JSONWriter functions non-virtually. The + // goal is to try and prevent calls that specify a style, which would be + // ignored anyway because the whole thing is single-lined. It's fine if some + // calls still make it through a `JSONWriter&`, no big deal. + void Start() { JSONWriter::Start(); } + void StartArrayProperty(const Span& aName) { + JSONWriter::StartArrayProperty(aName); + } + template + void StartArrayProperty(const char (&aName)[N]) { + JSONWriter::StartArrayProperty(Span(aName, N)); + } + void StartArrayElement() { JSONWriter::StartArrayElement(); } + void StartObjectProperty(const Span& aName) { + JSONWriter::StartObjectProperty(aName); + } + template + void StartObjectProperty(const char (&aName)[N]) { + JSONWriter::StartObjectProperty(Span(aName, N)); + } + void StartObjectElement() { JSONWriter::StartObjectElement(); } + + FAILURELATCH_IMPL_PROXY(*mFailureLatch) + + protected: + NotNull mFailureLatch; + + private: + UniqueJSONStrings* mUniqueStrings = nullptr; +}; + +class SpliceableChunkedJSONWriter final : public SpliceableJSONWriter { + public: + explicit SpliceableChunkedJSONWriter(FailureLatch& aFailureLatch) + : SpliceableJSONWriter(MakeUnique(aFailureLatch), + aFailureLatch) {} + + // Access the ChunkedJSONWriteFunc as reference-to-const, usually to copy data + // out. + const ChunkedJSONWriteFunc& ChunkedWriteFunc() const { + return ChunkedWriteFuncRef(); + } + + // Access the ChunkedJSONWriteFunc as rvalue-reference, usually to take its + // data out. This writer shouldn't be used anymore after this. + ChunkedJSONWriteFunc&& TakeChunkedWriteFunc() { + ChunkedJSONWriteFunc& ref = ChunkedWriteFuncRef(); +#ifdef DEBUG + mTaken = true; +#endif // + return std::move(ref); + } + + // Adopts the chunks from aFunc without copying. + void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) override { + MOZ_ASSERT(!mTaken); + Separator(); + ChunkedWriteFuncRef().Take(std::move(aFunc)); + mNeedComma[mDepth] = true; + } + + void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) { + mFailureLatch = WrapNotNullUnchecked(&aFailureLatch); + return ChunkedWriteFuncRef().ChangeFailureLatchAndForwardState( + aFailureLatch); + } + + private: + const ChunkedJSONWriteFunc& ChunkedWriteFuncRef() const { + MOZ_ASSERT(!mTaken); + // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the + // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc&. + return static_cast(WriteFunc()); + } + + ChunkedJSONWriteFunc& ChunkedWriteFuncRef() { + MOZ_ASSERT(!mTaken); + // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the + // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc&. + return static_cast(WriteFunc()); + } + +#ifdef DEBUG + bool mTaken = false; +#endif +}; + +class JSONSchemaWriter { + JSONWriter& mWriter; + uint32_t mIndex; + + public: + explicit JSONSchemaWriter(JSONWriter& aWriter) : mWriter(aWriter), mIndex(0) { + aWriter.StartObjectProperty("schema", + SpliceableJSONWriter::SingleLineStyle); + } + + void WriteField(const Span& aName) { + mWriter.IntProperty(aName, mIndex++); + } + + template + void WriteField(const char (&aName)[Np1]) { + WriteField(Span(aName, Np1 - 1)); + } + + ~JSONSchemaWriter() { mWriter.EndObject(); } +}; + +// This class helps create an indexed list of unique strings, and inserts the +// index as a JSON value. The collected list of unique strings can later be +// inserted as a JSON array. +// This can be useful for elements/properties with many repeated strings. +// +// With only JSONWriter w, +// `w.WriteElement("a"); w.WriteElement("b"); w.WriteElement("a");` +// when done inside a JSON array, will generate: +// `["a", "b", "c"]` +// +// With UniqueStrings u, +// `u.WriteElement(w, "a"); u.WriteElement(w, "b"); u.WriteElement(w, "a");` +// when done inside a JSON array, will generate: +// `[0, 1, 0]` +// and later, `u.SpliceStringTableElements(w)` (inside a JSON array), will +// output the corresponding indexed list of unique strings: +// `["a", "b"]` +class UniqueJSONStrings final : public FailureLatch { + public: + // Start an empty list of unique strings. + MFBT_API explicit UniqueJSONStrings(FailureLatch& aFailureLatch); + + // Start with a copy of the strings from another list. + MFBT_API UniqueJSONStrings(FailureLatch& aFailureLatch, + const UniqueJSONStrings& aOther, + ProgressLogger aProgressLogger); + + MFBT_API ~UniqueJSONStrings(); + + // Add `aStr` to the list (if not already there), and write its index as a + // named object property. + void WriteProperty(SpliceableJSONWriter& aWriter, + const Span& aName, + const Span& aStr) { + if (const Maybe maybeIndex = GetOrAddIndex(aStr); maybeIndex) { + aWriter.IntProperty(aName, *maybeIndex); + } else { + aWriter.SetFailureFrom(*this); + } + } + + // Add `aStr` to the list (if not already there), and write its index as an + // array element. + void WriteElement(SpliceableJSONWriter& aWriter, + const Span& aStr) { + if (const Maybe maybeIndex = GetOrAddIndex(aStr); maybeIndex) { + aWriter.IntElement(*maybeIndex); + } else if (!aWriter.Failed()) { + aWriter.SetFailureFrom(*this); + } + } + + // Splice all collected unique strings into an array. This should only be done + // once, and then this UniqueStrings shouldn't be used anymore. + MFBT_API void SpliceStringTableElements(SpliceableJSONWriter& aWriter); + + FAILURELATCH_IMPL_PROXY(mStringTableWriter) + + void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) { + mStringTableWriter.ChangeFailureLatchAndForwardState(aFailureLatch); + } + + private: + MFBT_API void ClearAndSetFailure(std::string aFailure); + + // If `aStr` is already listed, return its index. + // Otherwise add it to the list and return the new index. + MFBT_API Maybe GetOrAddIndex(const Span& aStr); + + SpliceableChunkedJSONWriter mStringTableWriter; + HashMap mStringHashToIndexMap; +}; + +void SpliceableJSONWriter::UniqueStringProperty(const Span& aName, + const Span& aStr) { + MOZ_RELEASE_ASSERT(mUniqueStrings); + mUniqueStrings->WriteProperty(*this, aName, aStr); +} + +// Add `aStr` to the list (if not already there), and write its index as an +// array element. +void SpliceableJSONWriter::UniqueStringElement(const Span& aStr) { + MOZ_RELEASE_ASSERT(mUniqueStrings); + mUniqueStrings->WriteElement(*this, aStr); +} + +} // namespace baseprofiler +} // namespace mozilla + +#endif // BASEPROFILEJSONWRITER_H diff --git a/mozglue/baseprofiler/public/BaseProfiler.h b/mozglue/baseprofiler/public/BaseProfiler.h new file mode 100644 index 0000000000..a085eb9774 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfiler.h @@ -0,0 +1,506 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The Gecko Profiler is an always-on profiler that takes fast and low overhead +// samples of the program execution using only userspace functionality for +// portability. The goal of this module is to provide performance data in a +// generic cross-platform way without requiring custom tools or kernel support. +// +// Samples are collected to form a timeline with optional timeline event +// (markers) used for filtering. The samples include both native stacks and +// platform-independent "label stack" frames. + +#ifndef BaseProfiler_h +#define BaseProfiler_h + +// This file is safe to include unconditionally, and only defines +// empty macros if MOZ_GECKO_PROFILER is not set. + +// These headers are also safe to include unconditionally, with empty macros if +// MOZ_GECKO_PROFILER is not set. +// If your file only uses particular APIs (e.g., only markers), please consider +// including only the needed headers instead of this one, to reduce compilation +// dependencies. +#include "mozilla/BaseProfilerCounts.h" +#include "mozilla/BaseProfilerLabels.h" +#include "mozilla/BaseProfilerMarkers.h" +#include "mozilla/BaseProfilerState.h" + +#ifndef MOZ_GECKO_PROFILER + +# include "mozilla/UniquePtr.h" + +// This file can be #included unconditionally. However, everything within this +// file must be guarded by a #ifdef MOZ_GECKO_PROFILER, *except* for the +// following macros and functions, which encapsulate the most common operations +// and thus avoid the need for many #ifdefs. + +# define AUTO_BASE_PROFILER_INIT \ + ::mozilla::baseprofiler::profiler_init_main_thread_id() + +# define BASE_PROFILER_REGISTER_THREAD(name) +# define BASE_PROFILER_UNREGISTER_THREAD() +# define AUTO_BASE_PROFILER_REGISTER_THREAD(name) + +# define AUTO_BASE_PROFILER_THREAD_SLEEP +# define AUTO_BASE_PROFILER_THREAD_WAKE + +// Function stubs for when MOZ_GECKO_PROFILER is not defined. + +namespace mozilla { + +namespace baseprofiler { +// This won't be used, it's just there to allow the empty definition of +// `profiler_get_backtrace`. +struct ProfilerBacktrace {}; +using UniqueProfilerBacktrace = UniquePtr; + +// Get/Capture-backtrace functions can return nullptr or false, the result +// should be fed to another empty macro or stub anyway. + +static inline UniqueProfilerBacktrace profiler_get_backtrace() { + return nullptr; +} + +static inline bool profiler_capture_backtrace_into( + ProfileChunkedBuffer& aChunkedBuffer, StackCaptureOptions aCaptureOptions) { + return false; +} + +static inline UniquePtr profiler_capture_backtrace() { + return nullptr; +} + +static inline void profiler_init(void* stackTop) {} + +static inline void profiler_shutdown() {} + +} // namespace baseprofiler +} // namespace mozilla + +#else // !MOZ_GECKO_PROFILER + +# include "BaseProfilingStack.h" + +# include "mozilla/Assertions.h" +# include "mozilla/Atomics.h" +# include "mozilla/Attributes.h" +# include "mozilla/BaseProfilerRAIIMacro.h" +# include "mozilla/Maybe.h" +# include "mozilla/PowerOfTwo.h" +# include "mozilla/TimeStamp.h" +# include "mozilla/UniquePtr.h" + +# include +# include +# include + +namespace mozilla { + +class MallocAllocPolicy; +class ProfileChunkedBuffer; +enum class StackCaptureOptions; +template +class Vector; + +namespace baseprofiler { + +class ProfilerBacktrace; +class SpliceableJSONWriter; + +//--------------------------------------------------------------------------- +// Start and stop the profiler +//--------------------------------------------------------------------------- + +static constexpr PowerOfTwo32 BASE_PROFILER_DEFAULT_ENTRIES = +# if !defined(GP_PLAT_arm_android) + MakePowerOfTwo32<8 * 1024 * 1024>(); // 8M entries = 64MB +# else + MakePowerOfTwo32<2 * 1024 * 1024>(); // 2M entries = 16MB +# endif + +// Startup profiling usually need to capture more data, especially on slow +// systems. +// Note: Keep in sync with GeckoThread.maybeStartGeckoProfiler: +// https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java +static constexpr PowerOfTwo32 BASE_PROFILER_DEFAULT_STARTUP_ENTRIES = +# if !defined(GP_PLAT_arm_android) + mozilla::MakePowerOfTwo32<64 * 1024 * 1024>(); // 64M entries = 512MB +# else + mozilla::MakePowerOfTwo32<8 * 1024 * 1024>(); // 8M entries = 64MB +# endif + +// Note: Keep in sync with GeckoThread.maybeStartGeckoProfiler: +// https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java +# define BASE_PROFILER_DEFAULT_INTERVAL 1 /* millisecond */ +# define BASE_PROFILER_MAX_INTERVAL 5000 /* milliseconds */ + +// Initialize the profiler. If MOZ_PROFILER_STARTUP is set the profiler will +// also be started. This call must happen before any other profiler calls +// (except profiler_start(), which will call profiler_init() if it hasn't +// already run). +MFBT_API void profiler_init(void* stackTop); + +# define AUTO_BASE_PROFILER_INIT \ + ::mozilla::baseprofiler::AutoProfilerInit PROFILER_RAII + +// Clean up the profiler module, stopping it if required. This function may +// also save a shutdown profile if requested. No profiler calls should happen +// after this point and all profiling stack labels should have been popped. +MFBT_API void profiler_shutdown(); + +// Start the profiler -- initializing it first if necessary -- with the +// selected options. Stops and restarts the profiler if it is already active. +// After starting the profiler is "active". The samples will be recorded in a +// circular buffer. +// "aCapacity" is the maximum number of 8-byte entries in the profiler's +// circular buffer. +// "aInterval" the sampling interval, measured in millseconds. +// "aFeatures" is the feature set. Features unsupported by this +// platform/configuration are ignored. +// "aFilters" is the list of thread filters. Threads that do not match any +// of the filters are not profiled. A filter matches a thread if +// (a) the thread name contains the filter as a case-insensitive +// substring, or +// (b) the filter is of the form "pid:" where n is the process +// id of the process that the thread is running in. +// "aDuration" is the duration of entries in the profiler's circular buffer. +MFBT_API void profiler_start(PowerOfTwo32 aCapacity, double aInterval, + uint32_t aFeatures, const char** aFilters, + uint32_t aFilterCount, + const Maybe& aDuration = Nothing()); + +// Stop the profiler and discard the profile without saving it. A no-op if the +// profiler is inactive. After stopping the profiler is "inactive". +MFBT_API void profiler_stop(); + +// If the profiler is inactive, start it. If it's already active, restart it if +// the requested settings differ from the current settings. Both the check and +// the state change are performed while the profiler state is locked. +// The only difference to profiler_start is that the current buffer contents are +// not discarded if the profiler is already running with the requested settings. +MFBT_API void profiler_ensure_started( + PowerOfTwo32 aCapacity, double aInterval, uint32_t aFeatures, + const char** aFilters, uint32_t aFilterCount, + const Maybe& aDuration = Nothing()); + +//--------------------------------------------------------------------------- +// Control the profiler +//--------------------------------------------------------------------------- + +// Register/unregister threads with the profiler. Both functions operate the +// same whether the profiler is active or inactive. +# define BASE_PROFILER_REGISTER_THREAD(name) \ + do { \ + char stackTop; \ + ::mozilla::baseprofiler::profiler_register_thread(name, &stackTop); \ + } while (0) +# define BASE_PROFILER_UNREGISTER_THREAD() \ + ::mozilla::baseprofiler::profiler_unregister_thread() +MFBT_API ProfilingStack* profiler_register_thread(const char* name, + void* guessStackTop); +MFBT_API void profiler_unregister_thread(); + +// Registers a DOM Window (the JS global `window`) with the profiler. Each +// Window _roughly_ corresponds to a single document loaded within a +// browsing context. Both the Window Id and Browser Id are recorded to allow +// correlating different Windows loaded within the same tab or frame element. +// +// We register pages for each navigations but we do not register +// history.pushState or history.replaceState since they correspond to the same +// Inner Window ID. When a browsing context is first loaded, the first url +// loaded in it will be about:blank. Because of that, this call keeps the first +// non-about:blank registration of window and discards the previous one. +// +// "aTabID" is the BrowserId of that document belongs to. +// That's used to determine the tab of that page. +// "aInnerWindowID" is the ID of the `window` global object of that +// document. +// "aUrl" is the URL of the page. +// "aEmbedderInnerWindowID" is the inner window id of embedder. It's used to +// determine sub documents of a page. +MFBT_API void profiler_register_page(uint64_t aTabD, uint64_t aInnerWindowID, + const std::string& aUrl, + uint64_t aEmbedderInnerWindowID); + +// Unregister page with the profiler. +// +// Take a Inner Window ID and unregister the page entry that has the same ID. +MFBT_API void profiler_unregister_page(uint64_t aRegisteredInnerWindowID); + +// Remove all registered and unregistered pages in the profiler. +void profiler_clear_all_pages(); + +class BaseProfilerCount; +MFBT_API void profiler_add_sampled_counter(BaseProfilerCount* aCounter); +MFBT_API void profiler_remove_sampled_counter(BaseProfilerCount* aCounter); + +// Register and unregister a thread within a scope. +# define AUTO_BASE_PROFILER_REGISTER_THREAD(name) \ + ::mozilla::baseprofiler::AutoProfilerRegisterThread PROFILER_RAII(name) + +// Pause and resume the profiler. No-ops if the profiler is inactive. While +// paused the profile will not take any samples and will not record any data +// into its buffers. The profiler remains fully initialized in this state. +// This feature will keep JavaScript profiling enabled, thus allowing toggling +// the profiler without invalidating the JIT. +MFBT_API void profiler_pause(); +MFBT_API void profiler_resume(); + +// Only pause and resume the periodic sampling loop, including stack sampling, +// counters, and profiling overheads. +MFBT_API void profiler_pause_sampling(); +MFBT_API void profiler_resume_sampling(); + +// These functions tell the profiler that a thread went to sleep so that we can +// avoid sampling it while it's sleeping. Calling profiler_thread_sleep() +// twice without an intervening profiler_thread_wake() is an error. All three +// functions operate the same whether the profiler is active or inactive. +MFBT_API void profiler_thread_sleep(); +MFBT_API void profiler_thread_wake(); + +// Mark a thread as asleep/awake within a scope. +# define AUTO_BASE_PROFILER_THREAD_SLEEP \ + ::mozilla::baseprofiler::AutoProfilerThreadSleep PROFILER_RAII +# define AUTO_BASE_PROFILER_THREAD_WAKE \ + ::mozilla::baseprofiler::AutoProfilerThreadWake PROFILER_RAII + +//--------------------------------------------------------------------------- +// Get information from the profiler +//--------------------------------------------------------------------------- + +// Get the params used to start the profiler. Returns 0 and an empty vector +// (via outparams) if the profile is inactive. It's possible that the features +// returned may be slightly different to those requested due to required +// adjustments. +MFBT_API void profiler_get_start_params( + int* aEntrySize, Maybe* aDuration, double* aInterval, + uint32_t* aFeatures, Vector* aFilters); + +// The number of milliseconds since the process started. Operates the same +// whether the profiler is active or inactive. +MFBT_API double profiler_time(); + +// An object of this class is passed to profiler_suspend_and_sample_thread(). +// For each stack frame, one of the Collect methods will be called. +class ProfilerStackCollector { + public: + // Some collectors need to worry about possibly overwriting previous + // generations of data. If that's not an issue, this can return Nothing, + // which is the default behaviour. + virtual Maybe SamplePositionInBuffer() { return Nothing(); } + virtual Maybe BufferRangeStart() { return Nothing(); } + + // This method will be called once if the thread being suspended is the main + // thread. Default behaviour is to do nothing. + virtual void SetIsMainThread() {} + + // WARNING: The target thread is suspended when the Collect methods are + // called. Do not try to allocate or acquire any locks, or you could + // deadlock. The target thread will have resumed by the time this function + // returns. + + virtual void CollectNativeLeafAddr(void* aAddr) = 0; + + virtual void CollectProfilingStackFrame( + const ProfilingStackFrame& aFrame) = 0; +}; + +// This method suspends the thread identified by aThreadId, samples its +// profiling stack, JS stack, and (optionally) native stack, passing the +// collected frames into aCollector. aFeatures dictates which compiler features +// are used. |Leaf| is the only relevant one. +// Use `aThreadId`=0 to sample the current thread. +MFBT_API void profiler_suspend_and_sample_thread( + int aThreadId, uint32_t aFeatures, ProfilerStackCollector& aCollector, + bool aSampleNative = true); + +struct ProfilerBacktraceDestructor { + MFBT_API void operator()(ProfilerBacktrace*); +}; + +using UniqueProfilerBacktrace = + UniquePtr; + +// Immediately capture the current thread's call stack, store it in the provided +// buffer (usually to avoid allocations if you can construct the buffer on the +// stack). Returns false if unsuccessful, if the profiler is inactive, or if +// aCaptureOptions is NoStack. +MFBT_API bool profiler_capture_backtrace_into( + ProfileChunkedBuffer& aChunkedBuffer, StackCaptureOptions aCaptureOptions); + +// Immediately capture the current thread's call stack, and return it in a +// ProfileChunkedBuffer (usually for later use in MarkerStack::TakeBacktrace()). +// May be null if unsuccessful, or if the profiler is inactive. +MFBT_API UniquePtr profiler_capture_backtrace(); + +// Immediately capture the current thread's call stack, and return it in a +// ProfilerBacktrace (usually for later use in marker function that take a +// ProfilerBacktrace). May be null if unsuccessful, or if the profiler is +// inactive. +MFBT_API UniqueProfilerBacktrace profiler_get_backtrace(); + +struct ProfilerStats { + unsigned n = 0; + double sum = 0; + double min = std::numeric_limits::max(); + double max = 0; + void Count(double v) { + ++n; + sum += v; + if (v < min) { + min = v; + } + if (v > max) { + max = v; + } + } +}; + +struct ProfilerBufferInfo { + // Index of the oldest entry. + uint64_t mRangeStart; + // Index of the newest entry. + uint64_t mRangeEnd; + // Buffer capacity in number of 8-byte entries. + uint32_t mEntryCount; + // Sampling stats: Interval (us) between successive samplings. + ProfilerStats mIntervalsUs; + // Sampling stats: Total duration (us) of each sampling. (Split detail below.) + ProfilerStats mOverheadsUs; + // Sampling stats: Time (us) to acquire the lock before sampling. + ProfilerStats mLockingsUs; + // Sampling stats: Time (us) to discard expired data. + ProfilerStats mCleaningsUs; + // Sampling stats: Time (us) to collect counter data. + ProfilerStats mCountersUs; + // Sampling stats: Time (us) to sample thread stacks. + ProfilerStats mThreadsUs; +}; + +// Get information about the current buffer status. +// Returns Nothing() if the profiler is inactive. +// +// This information may be useful to a user-interface displaying the current +// status of the profiler, allowing the user to get a sense for how fast the +// buffer is being written to, and how much data is visible. +MFBT_API Maybe profiler_get_buffer_info(); + +} // namespace baseprofiler +} // namespace mozilla + +namespace mozilla { +namespace baseprofiler { + +//--------------------------------------------------------------------------- +// Put profiling data into the profiler (markers) +//--------------------------------------------------------------------------- + +MFBT_API void profiler_add_js_marker(const char* aMarkerName, + const char* aMarkerText); + +//--------------------------------------------------------------------------- +// Output profiles +//--------------------------------------------------------------------------- + +// Set a user-friendly process name, used in JSON stream. +MFBT_API void profiler_set_process_name(const std::string& aProcessName, + const std::string* aETLDplus1); + +// Get the profile encoded as a JSON string. A no-op (returning nullptr) if the +// profiler is inactive. +// If aIsShuttingDown is true, the current time is included as the process +// shutdown time in the JSON's "meta" object. +MFBT_API UniquePtr profiler_get_profile(double aSinceTime = 0, + bool aIsShuttingDown = false, + bool aOnlyThreads = false); + +// Write the profile for this process (excluding subprocesses) into aWriter. +// Returns false if the profiler is inactive. +MFBT_API bool profiler_stream_json_for_this_process( + SpliceableJSONWriter& aWriter, double aSinceTime = 0, + bool aIsShuttingDown = false, bool aOnlyThreads = false); + +// Get the profile and write it into a file. A no-op if the profile is +// inactive. +// Prefixed with "base" to avoid clashing with Gecko Profiler's extern "C" +// profiler_save_profile_to_file when called from debugger. +MFBT_API void baseprofiler_save_profile_to_file(const char* aFilename); + +//--------------------------------------------------------------------------- +// RAII classes +//--------------------------------------------------------------------------- + +class MOZ_RAII AutoProfilerInit { + public: + explicit AutoProfilerInit() { profiler_init(this); } + + ~AutoProfilerInit() { profiler_shutdown(); } + + private: +}; + +// Convenience class to register and unregister a thread with the profiler. +// Needs to be the first object on the stack of the thread. +class MOZ_RAII AutoProfilerRegisterThread final { + public: + explicit AutoProfilerRegisterThread(const char* aName) { + profiler_register_thread(aName, this); + } + + ~AutoProfilerRegisterThread() { profiler_unregister_thread(); } + + private: + AutoProfilerRegisterThread(const AutoProfilerRegisterThread&) = delete; + AutoProfilerRegisterThread& operator=(const AutoProfilerRegisterThread&) = + delete; +}; + +class MOZ_RAII AutoProfilerThreadSleep { + public: + explicit AutoProfilerThreadSleep() { profiler_thread_sleep(); } + + ~AutoProfilerThreadSleep() { profiler_thread_wake(); } + + private: +}; + +// Temporarily wake up the profiling of a thread while servicing events such as +// Asynchronous Procedure Calls (APCs). +class MOZ_RAII AutoProfilerThreadWake { + public: + explicit AutoProfilerThreadWake() + : mIssuedWake(profiler_thread_is_sleeping()) { + if (mIssuedWake) { + profiler_thread_wake(); + } + } + + ~AutoProfilerThreadWake() { + if (mIssuedWake) { + MOZ_ASSERT(!profiler_thread_is_sleeping()); + profiler_thread_sleep(); + } + } + + private: + bool mIssuedWake; +}; + +// Get the MOZ_PROFILER_STARTUP* environment variables that should be +// supplied to a child process that is about to be launched, in order +// to make that child process start with the same profiler settings as +// in the current process. The given function is invoked once for +// each variable to be set. +MFBT_API void GetProfilerEnvVarsForChildProcess( + std::function&& aSetEnv); + +} // namespace baseprofiler +} // namespace mozilla + +#endif // !MOZ_GECKO_PROFILER + +#endif // BaseProfiler_h diff --git a/mozglue/baseprofiler/public/BaseProfilerCounts.h b/mozglue/baseprofiler/public/BaseProfilerCounts.h new file mode 100644 index 0000000000..fbcc713744 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerCounts.h @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilerCounts_h +#define BaseProfilerCounts_h + +#ifndef MOZ_GECKO_PROFILER + +# define BASE_PROFILER_DEFINE_COUNT_TOTAL(label, category, description) +# define BASE_PROFILER_DEFINE_COUNT(label, category, description) +# define BASE_PROFILER_DEFINE_STATIC_COUNT_TOTAL(label, category, description) +# define AUTO_BASE_PROFILER_COUNT_TOTAL(label, count) +# define AUTO_BASE_PROFILER_COUNT(label) +# define AUTO_BASE_PROFILER_STATIC_COUNT(label, count) +# define AUTO_BASE_PROFILER_FORCE_ALLOCATION(label) + +#else + +# include "mozilla/Atomics.h" + +namespace mozilla { +namespace baseprofiler { + +class BaseProfilerCount; +MFBT_API void profiler_add_sampled_counter(BaseProfilerCount* aCounter); +MFBT_API void profiler_remove_sampled_counter(BaseProfilerCount* aCounter); + +typedef Atomic ProfilerAtomicSigned; +typedef Atomic ProfilerAtomicUnsigned; + +// Counter support +// There are two types of counters: +// 1) a simple counter which can be added to or subtracted from. This could +// track the number of objects of a type, the number of calls to something +// (reflow, JIT, etc). +// 2) a combined counter which has the above, plus a number-of-calls counter +// that is incremented by 1 for each call to modify the count. This provides +// an optional source for a 'heatmap' of access. This can be used (for +// example) to track the amount of memory allocated, and provide a heatmap of +// memory operations (allocs/frees). +// +// Counters are sampled by the profiler once per sample-period. At this time, +// all counters are global to the process. In the future, there might be more +// versions with per-thread or other discriminators. +// +// Typical usage: +// There are two ways to use counters: With heap-created counter objects, +// or using macros. Note: the macros use statics, and will be slightly +// faster/smaller, and you need to care about creating them before using +// them. They're similar to the use-pattern for the other AUTO_PROFILER* +// macros, but they do need the PROFILER_DEFINE* to be use to instantiate +// the statics. +// +// PROFILER_DEFINE_COUNT(mything, "JIT", "Some JIT byte count") +// ... +// void foo() { ... AUTO_PROFILER_COUNT(mything, number_of_bytes_used); ... } +// +// or (to also get a heatmap) +// +// PROFILER_DEFINE_COUNT_TOTAL(mything, "JIT", "Some JIT byte count") +// ... +// void foo() { +// ... +// AUTO_PROFILER_COUNT_TOTAL(mything, number_of_bytes_generated); +// ... +// } +// +// To use without statics/macros: +// +// UniquePtr myCounter; +// ... +// myCounter = +// MakeUnique("mything", "JIT", "Some JIT byte count")); +// ... +// void foo() { ... myCounter->Add(number_of_bytes_generated0; ... } + +class BaseProfilerCount { + public: + BaseProfilerCount(const char* aLabel, ProfilerAtomicSigned* aCounter, + ProfilerAtomicUnsigned* aNumber, const char* aCategory, + const char* aDescription) + : mLabel(aLabel), + mCategory(aCategory), + mDescription(aDescription), + mCounter(aCounter), + mNumber(aNumber) { +# define COUNTER_CANARY 0xDEADBEEF +# ifdef DEBUG + mCanary = COUNTER_CANARY; + mPrevNumber = 0; +# endif + // Can't call profiler_* here since this may be non-xul-library + } +# ifdef DEBUG + ~BaseProfilerCount() { mCanary = 0; } +# endif + + void Sample(int64_t& aCounter, uint64_t& aNumber) { + MOZ_ASSERT(mCanary == COUNTER_CANARY); + + aCounter = *mCounter; + aNumber = mNumber ? *mNumber : 0; +# ifdef DEBUG + MOZ_ASSERT(aNumber >= mPrevNumber); + mPrevNumber = aNumber; +# endif + } + + // We don't define ++ and Add() here, since the static defines directly + // increment the atomic counters, and the subclasses implement ++ and + // Add() directly. + + // These typically are static strings (for example if you use the macros + // below) + const char* mLabel; + const char* mCategory; + const char* mDescription; + // We're ok with these being un-ordered in race conditions. These are + // pointers because we want to be able to use statics and increment them + // directly. Otherwise we could just have them inline, and not need the + // constructor args. + // These can be static globals (using the macros below), though they + // don't have to be - their lifetime must be longer than the use of them + // by the profiler (see profiler_add/remove_sampled_counter()). If you're + // using a lot of these, they probably should be allocated at runtime (see + // class ProfilerCountOnly below). + ProfilerAtomicSigned* mCounter; + ProfilerAtomicUnsigned* mNumber; // may be null + +# ifdef DEBUG + uint32_t mCanary; + uint64_t mPrevNumber; // value of number from the last Sample() +# endif +}; + +// Designed to be allocated dynamically, and simply incremented with obj++ +// or obj->Add(n) +class ProfilerCounter final : public BaseProfilerCount { + public: + ProfilerCounter(const char* aLabel, const char* aCategory, + const char* aDescription) + : BaseProfilerCount(aLabel, &mCounter, nullptr, aCategory, aDescription) { + // Assume we're in libxul + profiler_add_sampled_counter(this); + } + + virtual ~ProfilerCounter() { profiler_remove_sampled_counter(this); } + + BaseProfilerCount& operator++() { + Add(1); + return *this; + } + + void Add(int64_t aNumber) { mCounter += aNumber; } + + ProfilerAtomicSigned mCounter; +}; + +// Also keeps a heatmap (number of calls to ++/Add()) +class ProfilerCounterTotal final : public BaseProfilerCount { + public: + ProfilerCounterTotal(const char* aLabel, const char* aCategory, + const char* aDescription) + : BaseProfilerCount(aLabel, &mCounter, &mNumber, aCategory, + aDescription) { + // Assume we're in libxul + profiler_add_sampled_counter(this); + } + + virtual ~ProfilerCounterTotal() { profiler_remove_sampled_counter(this); } + + BaseProfilerCount& operator++() { + Add(1); + return *this; + } + + void Add(int64_t aNumber) { + mCounter += aNumber; + mNumber++; + } + + ProfilerAtomicSigned mCounter; + ProfilerAtomicUnsigned mNumber; +}; + +// Defines a counter that is sampled on each profiler tick, with a running +// count (signed), and number-of-instances. Note that because these are two +// independent Atomics, there is a possiblity that count will not include +// the last call, but number of uses will. I think this is not worth +// worrying about +# define BASE_PROFILER_DEFINE_COUNT_TOTAL(label, category, description) \ + ProfilerAtomicSigned profiler_count_##label(0); \ + ProfilerAtomicUnsigned profiler_number_##label(0); \ + const char profiler_category_##label[] = category; \ + const char profiler_description_##label[] = description; \ + UniquePtr<::mozilla::baseprofiler::BaseProfilerCount> AutoCount_##label; + +// This counts, but doesn't keep track of the number of calls to +// AUTO_PROFILER_COUNT() +# define BASE_PROFILER_DEFINE_COUNT(label, category, description) \ + ProfilerAtomicSigned profiler_count_##label(0); \ + const char profiler_category_##label[] = category; \ + const char profiler_description_##label[] = description; \ + UniquePtr<::mozilla::baseprofiler::BaseProfilerCount> AutoCount_##label; + +// This will create a static initializer if used, but avoids a possible +// allocation. +# define BASE_PROFILER_DEFINE_STATIC_COUNT_TOTAL(label, category, \ + description) \ + ProfilerAtomicSigned profiler_count_##label(0); \ + ProfilerAtomicUnsigned profiler_number_##label(0); \ + ::mozilla::baseprofiler::BaseProfilerCount AutoCount_##label( \ + #label, &profiler_count_##label, &profiler_number_##label, category, \ + description); + +// If we didn't care about static initializers, we could avoid the need for +// a ptr to the BaseProfilerCount object. + +// XXX It would be better to do this without the if() and without the +// theoretical race to set the UniquePtr (i.e. possible leak). +# define AUTO_BASE_PROFILER_COUNT_TOTAL(label, count) \ + do { \ + profiler_number_##label++; /* do this first*/ \ + profiler_count_##label += count; \ + if (!AutoCount_##label) { \ + /* Ignore that we could call this twice in theory, and that we leak \ + * them \ + */ \ + AutoCount_##label.reset(new BaseProfilerCount( \ + #label, &profiler_count_##label, &profiler_number_##label, \ + profiler_category_##label, profiler_description_##label)); \ + ::mozilla::baseprofiler::profiler_add_sampled_counter( \ + AutoCount_##label.get()); \ + } \ + } while (0) + +# define AUTO_BASE_PROFILER_COUNT(label, count) \ + do { \ + profiler_count_##label += count; /* do this first*/ \ + if (!AutoCount_##label) { \ + /* Ignore that we could call this twice in theory, and that we leak \ + * them \ + */ \ + AutoCount_##label.reset(new BaseProfilerCount( \ + #label, nullptr, &profiler_number_##label, \ + profiler_category_##label, profiler_description_##label)); \ + ::mozilla::baseprofiler::profiler_add_sampled_counter( \ + AutoCount_##label.get()); \ + } \ + } while (0) + +# define AUTO_BASE_PROFILER_STATIC_COUNT(label, count) \ + do { \ + profiler_number_##label++; /* do this first*/ \ + profiler_count_##label += count; \ + } while (0) + +// if we need to force the allocation +# define AUTO_BASE_PROFILER_FORCE_ALLOCATION(label) \ + do { \ + if (!AutoCount_##label) { \ + /* Ignore that we could call this twice in theory, and that we leak \ + * them \ + */ \ + AutoCount_##label.reset( \ + new ::mozilla::baseprofiler::BaseProfilerCount( \ + #label, &profiler_count_##label, &profiler_number_##label, \ + profiler_category_##label, profiler_description_##label)); \ + } \ + } while (0) + +} // namespace baseprofiler +} // namespace mozilla + +#endif // !MOZ_GECKO_PROFILER + +#endif // BaseProfilerCounts_h diff --git a/mozglue/baseprofiler/public/BaseProfilerDetail.h b/mozglue/baseprofiler/public/BaseProfilerDetail.h new file mode 100644 index 0000000000..6ecf6e117b --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerDetail.h @@ -0,0 +1,285 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Internal Base Profiler utilities. + +#ifndef BaseProfilerDetail_h +#define BaseProfilerDetail_h + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/PlatformMutex.h" +#include "mozilla/PlatformRWLock.h" +#include "mozilla/BaseProfilerUtils.h" + +namespace mozilla { +namespace baseprofiler { + +namespace detail { + +// Thin shell around mozglue PlatformMutex, for Base Profiler internal use. +class MOZ_CAPABILITY("mutex") BaseProfilerMutex + : private ::mozilla::detail::MutexImpl { + public: + BaseProfilerMutex() : ::mozilla::detail::MutexImpl() {} + explicit BaseProfilerMutex(const char* aName) + : ::mozilla::detail::MutexImpl(), mName(aName) {} + + BaseProfilerMutex(const BaseProfilerMutex&) = delete; + BaseProfilerMutex& operator=(const BaseProfilerMutex&) = delete; + BaseProfilerMutex(BaseProfilerMutex&&) = delete; + BaseProfilerMutex& operator=(BaseProfilerMutex&&) = delete; + +#ifdef DEBUG + ~BaseProfilerMutex() { + MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(), + "BaseProfilerMutex should have been unlocked when destroyed"); + } +#endif // DEBUG + + [[nodiscard]] bool IsLockedOnCurrentThread() const { + return BaseProfilerThreadId::FromNumber(mOwningThreadId) == + baseprofiler::profiler_current_thread_id(); + } + + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(IsLockedOnCurrentThread()); + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { + const BaseProfilerThreadId tid = baseprofiler::profiler_current_thread_id(); + MOZ_ASSERT(tid.IsSpecified()); + MOZ_ASSERT(!IsLockedOnCurrentThread(), "Recursive locking"); + ::mozilla::detail::MutexImpl::lock(); + MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(), + "Not unlocked properly"); + mOwningThreadId = tid.ToNumber(); + } + + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) { + const BaseProfilerThreadId tid = baseprofiler::profiler_current_thread_id(); + MOZ_ASSERT(tid.IsSpecified()); + MOZ_ASSERT(!IsLockedOnCurrentThread(), "Recursive locking"); + if (!::mozilla::detail::MutexImpl::tryLock()) { + // Failed to lock, nothing more to do. + return false; + } + MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(), + "Not unlocked properly"); + mOwningThreadId = tid.ToNumber(); + return true; + } + + void Unlock() MOZ_CAPABILITY_RELEASE() { + MOZ_ASSERT(IsLockedOnCurrentThread(), "Unlocking when not locked here"); + // We're still holding the mutex here, so it's safe to just reset + // `mOwningThreadId`. + mOwningThreadId = BaseProfilerThreadId{}.ToNumber(); + ::mozilla::detail::MutexImpl::unlock(); + } + + const char* GetName() const { return mName; } + + private: + // Thread currently owning the lock, or 0. + // Atomic because it may be read at any time independent of the mutex. + // Relaxed because threads only need to know if they own it already, so: + // - If it's their id, only *they* wrote that value with a locked mutex. + // - If it's different from their thread id it doesn't matter what other + // number it is (0 or another id) and that it can change again at any time. + Atomic + mOwningThreadId; + + const char* mName = nullptr; +}; + +// RAII class to lock a mutex. +class MOZ_RAII BaseProfilerAutoLock { + public: + explicit BaseProfilerAutoLock(BaseProfilerMutex& aMutex) : mMutex(aMutex) { + mMutex.Lock(); + } + + BaseProfilerAutoLock(const BaseProfilerAutoLock&) = delete; + BaseProfilerAutoLock& operator=(const BaseProfilerAutoLock&) = delete; + BaseProfilerAutoLock(BaseProfilerAutoLock&&) = delete; + BaseProfilerAutoLock& operator=(BaseProfilerAutoLock&&) = delete; + + ~BaseProfilerAutoLock() { mMutex.Unlock(); } + + private: + BaseProfilerMutex& mMutex; +}; + +// Thin shell around mozglue PlatformMutex, for Base Profiler internal use. +// Actual mutex may be disabled at construction time. +class BaseProfilerMaybeMutex : private ::mozilla::detail::MutexImpl { + public: + explicit BaseProfilerMaybeMutex(bool aActivate) { + if (aActivate) { + mMaybeMutex.emplace(); + } + } + + BaseProfilerMaybeMutex(const BaseProfilerMaybeMutex&) = delete; + BaseProfilerMaybeMutex& operator=(const BaseProfilerMaybeMutex&) = delete; + BaseProfilerMaybeMutex(BaseProfilerMaybeMutex&&) = delete; + BaseProfilerMaybeMutex& operator=(BaseProfilerMaybeMutex&&) = delete; + + ~BaseProfilerMaybeMutex() = default; + + bool IsActivated() const { return mMaybeMutex.isSome(); } + + [[nodiscard]] bool IsActivatedAndLockedOnCurrentThread() const { + if (!IsActivated()) { + // Not activated, so we can never be locked. + return false; + } + return mMaybeMutex->IsLockedOnCurrentThread(); + } + + void AssertCurrentThreadOwns() const { +#ifdef DEBUG + if (IsActivated()) { + mMaybeMutex->AssertCurrentThreadOwns(); + } +#endif // DEBUG + } + + MOZ_PUSH_IGNORE_THREAD_SAFETY + void Lock() { + if (IsActivated()) { + mMaybeMutex->Lock(); + } + } + + void Unlock() { + if (IsActivated()) { + mMaybeMutex->Unlock(); + } + } + MOZ_POP_THREAD_SAFETY + + private: + Maybe mMaybeMutex; +}; + +// RAII class to lock a mutex. +class MOZ_RAII BaseProfilerMaybeAutoLock { + public: + explicit BaseProfilerMaybeAutoLock(BaseProfilerMaybeMutex& aMaybeMutex) + : mMaybeMutex(aMaybeMutex) { + mMaybeMutex.Lock(); + } + + BaseProfilerMaybeAutoLock(const BaseProfilerMaybeAutoLock&) = delete; + BaseProfilerMaybeAutoLock& operator=(const BaseProfilerMaybeAutoLock&) = + delete; + BaseProfilerMaybeAutoLock(BaseProfilerMaybeAutoLock&&) = delete; + BaseProfilerMaybeAutoLock& operator=(BaseProfilerMaybeAutoLock&&) = delete; + + ~BaseProfilerMaybeAutoLock() { mMaybeMutex.Unlock(); } + + private: + BaseProfilerMaybeMutex& mMaybeMutex; +}; + +class BaseProfilerSharedMutex : public ::mozilla::detail::RWLockImpl { + public: +#ifdef DEBUG + ~BaseProfilerSharedMutex() { + MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(), + "BaseProfilerMutex should have been unlocked when destroyed"); + } +#endif // DEBUG + + [[nodiscard]] bool IsLockedExclusiveOnCurrentThread() const { + return BaseProfilerThreadId::FromNumber(mOwningThreadId) == + baseprofiler::profiler_current_thread_id(); + } + + void LockExclusive() { + const BaseProfilerThreadId tid = baseprofiler::profiler_current_thread_id(); + MOZ_ASSERT(tid.IsSpecified()); + MOZ_ASSERT(!IsLockedExclusiveOnCurrentThread(), "Recursive locking"); + ::mozilla::detail::RWLockImpl::writeLock(); + MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(), + "Not unlocked properly"); + mOwningThreadId = tid.ToNumber(); + } + + void UnlockExclusive() { + MOZ_ASSERT(IsLockedExclusiveOnCurrentThread(), + "Unlocking when not locked here"); + // We're still holding the mutex here, so it's safe to just reset + // `mOwningThreadId`. + mOwningThreadId = BaseProfilerThreadId{}.ToNumber(); + writeUnlock(); + } + + void LockShared() { readLock(); } + + void UnlockShared() { readUnlock(); } + + private: + // Thread currently owning the exclusive lock, or 0. + // Atomic because it may be read at any time independent of the mutex. + // Relaxed because threads only need to know if they own it already, so: + // - If it's their id, only *they* wrote that value with a locked mutex. + // - If it's different from their thread id it doesn't matter what other + // number it is (0 or another id) and that it can change again at any time. + Atomic + mOwningThreadId; +}; + +// RAII class to lock a shared mutex exclusively. +class MOZ_RAII BaseProfilerAutoLockExclusive { + public: + explicit BaseProfilerAutoLockExclusive(BaseProfilerSharedMutex& aSharedMutex) + : mSharedMutex(aSharedMutex) { + mSharedMutex.LockExclusive(); + } + + BaseProfilerAutoLockExclusive(const BaseProfilerAutoLockExclusive&) = delete; + BaseProfilerAutoLockExclusive& operator=( + const BaseProfilerAutoLockExclusive&) = delete; + BaseProfilerAutoLockExclusive(BaseProfilerAutoLockExclusive&&) = delete; + BaseProfilerAutoLockExclusive& operator=(BaseProfilerAutoLockExclusive&&) = + delete; + + ~BaseProfilerAutoLockExclusive() { mSharedMutex.UnlockExclusive(); } + + private: + BaseProfilerSharedMutex& mSharedMutex; +}; + +// RAII class to lock a shared mutex non-exclusively, other +// BaseProfilerAutoLockShared's may happen in other threads. +class MOZ_RAII BaseProfilerAutoLockShared { + public: + explicit BaseProfilerAutoLockShared(BaseProfilerSharedMutex& aSharedMutex) + : mSharedMutex(aSharedMutex) { + mSharedMutex.LockShared(); + } + + BaseProfilerAutoLockShared(const BaseProfilerAutoLockShared&) = delete; + BaseProfilerAutoLockShared& operator=(const BaseProfilerAutoLockShared&) = + delete; + BaseProfilerAutoLockShared(BaseProfilerAutoLockShared&&) = delete; + BaseProfilerAutoLockShared& operator=(BaseProfilerAutoLockShared&&) = delete; + + ~BaseProfilerAutoLockShared() { mSharedMutex.UnlockShared(); } + + private: + BaseProfilerSharedMutex& mSharedMutex; +}; + +} // namespace detail +} // namespace baseprofiler +} // namespace mozilla + +#endif // BaseProfilerDetail_h diff --git a/mozglue/baseprofiler/public/BaseProfilerLabels.h b/mozglue/baseprofiler/public/BaseProfilerLabels.h new file mode 100644 index 0000000000..8da596e3ab --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerLabels.h @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This header contains all definitions related to Base Profiler labels (outside +// of XUL). +// It is safe to include unconditionally, and only defines empty macros if +// MOZ_GECKO_PROFILER is not set. + +#ifndef BaseProfilerLabels_h +#define BaseProfilerLabels_h + +#ifndef MOZ_GECKO_PROFILER + +# define AUTO_BASE_PROFILER_LABEL(label, categoryPair) +# define AUTO_BASE_PROFILER_LABEL_CATEGORY_PAIR(categoryPair) +# define AUTO_BASE_PROFILER_LABEL_DYNAMIC_CSTR(label, categoryPair, cStr) +# define AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING(label, categoryPair, str) +# define AUTO_BASE_PROFILER_LABEL_FAST(label, categoryPair, ctx) +# define AUTO_BASE_PROFILER_LABEL_DYNAMIC_FAST(label, dynamicString, \ + categoryPair, ctx, flags) + +#else // !MOZ_GECKO_PROFILER + +# include "BaseProfilingStack.h" + +# include "mozilla/Attributes.h" +# include "mozilla/Maybe.h" +# include "mozilla/BaseProfilerRAIIMacro.h" +# include "mozilla/BaseProfilerState.h" +# include "mozilla/ThreadLocal.h" + +# include +# include + +namespace mozilla::baseprofiler { + +// Insert an RAII object in this scope to enter a label stack frame. Any +// samples collected in this scope will contain this label in their stack. +// The label argument must be a static C string. It is usually of the +// form "ClassName::FunctionName". (Ideally we'd use the compiler to provide +// that for us, but __func__ gives us the function name without the class +// name.) If the label applies to only part of a function, you can qualify it +// like this: "ClassName::FunctionName:PartName". +// +// Use AUTO_BASE_PROFILER_LABEL_DYNAMIC_* if you want to add additional / +// dynamic information to the label stack frame. +# define AUTO_BASE_PROFILER_LABEL(label, categoryPair) \ + ::mozilla::baseprofiler::AutoProfilerLabel PROFILER_RAII( \ + label, nullptr, \ + ::mozilla::baseprofiler::ProfilingCategoryPair::categoryPair) + +// Similar to AUTO_BASE_PROFILER_LABEL, but with only one argument: the category +// pair. The label string is taken from the category pair. This is convenient +// for labels like +// AUTO_BASE_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_LayerBuilding) which would +// otherwise just repeat the string. +# define AUTO_BASE_PROFILER_LABEL_CATEGORY_PAIR(categoryPair) \ + ::mozilla::baseprofiler::AutoProfilerLabel PROFILER_RAII( \ + "", nullptr, \ + ::mozilla::baseprofiler::ProfilingCategoryPair::categoryPair, \ + uint32_t(::mozilla::baseprofiler::ProfilingStackFrame::Flags:: \ + LABEL_DETERMINED_BY_CATEGORY_PAIR)) + +// Similar to AUTO_BASE_PROFILER_LABEL, but with an additional string. The +// inserted RAII object stores the cStr pointer in a field; it does not copy the +// string. +// +// WARNING: This means that the string you pass to this macro needs to live at +// least until the end of the current scope. Be careful using this macro with +// ns[C]String; the other AUTO_BASE_PROFILER_LABEL_DYNAMIC_* macros below are +// preferred because they avoid this problem. +// +// If the profiler samples the current thread and walks the label stack while +// this RAII object is on the stack, it will copy the supplied string into the +// profile buffer. So there's one string copy operation, and it happens at +// sample time. +// +// Compare this to the plain AUTO_BASE_PROFILER_LABEL macro, which only accepts +// literal strings: When the label stack frames generated by +// AUTO_BASE_PROFILER_LABEL are sampled, no string copy needs to be made because +// the profile buffer can just store the raw pointers to the literal strings. +// Consequently, AUTO_BASE_PROFILER_LABEL frames take up considerably less space +// in the profile buffer than AUTO_BASE_PROFILER_LABEL_DYNAMIC_* frames. +# define AUTO_BASE_PROFILER_LABEL_DYNAMIC_CSTR(label, categoryPair, cStr) \ + ::mozilla::baseprofiler::AutoProfilerLabel PROFILER_RAII( \ + label, cStr, \ + ::mozilla::baseprofiler::ProfilingCategoryPair::categoryPair) + +// Similar to AUTO_BASE_PROFILER_LABEL_DYNAMIC_CSTR, but takes an std::string. +// +// Note: The use of the Maybe<>s ensures the scopes for the dynamic string and +// the AutoProfilerLabel are appropriate, while also not incurring the runtime +// cost of the string assignment unless the profiler is active. Therefore, +// unlike AUTO_BASE_PROFILER_LABEL and AUTO_BASE_PROFILER_LABEL_DYNAMIC_CSTR, +// this macro doesn't push/pop a label when the profiler is inactive. +# define AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING(label, categoryPair, str) \ + Maybe autoStr; \ + Maybe<::mozilla::baseprofiler::AutoProfilerLabel> raiiObjectString; \ + if (::mozilla::baseprofiler::profiler_is_active()) { \ + autoStr.emplace(str); \ + raiiObjectString.emplace( \ + label, autoStr->c_str(), \ + ::mozilla::baseprofiler::ProfilingCategoryPair::categoryPair); \ + } + +// Similar to AUTO_BASE_PROFILER_LABEL, but accepting a JSContext* parameter, +// and a no-op if the profiler is disabled. Used to annotate functions for which +// overhead in the range of nanoseconds is noticeable. It avoids overhead from +// the TLS lookup because it can get the ProfilingStack from the JS context, and +// avoids almost all overhead in the case where the profiler is disabled. +# define AUTO_BASE_PROFILER_LABEL_FAST(label, categoryPair, ctx) \ + ::mozilla::baseprofiler::AutoProfilerLabel PROFILER_RAII( \ + ctx, label, nullptr, \ + ::mozilla::baseprofiler::ProfilingCategoryPair::categoryPair) + +// Similar to AUTO_BASE_PROFILER_LABEL_FAST, but also takes an extra string and +// an additional set of flags. The flags parameter should carry values from the +// ProfilingStackFrame::Flags enum. +# define AUTO_BASE_PROFILER_LABEL_DYNAMIC_FAST(label, dynamicString, \ + categoryPair, ctx, flags) \ + ::mozilla::baseprofiler::AutoProfilerLabel PROFILER_RAII( \ + ctx, label, dynamicString, \ + ::mozilla::baseprofiler::ProfilingCategoryPair::categoryPair, flags) + +// This class creates a non-owning ProfilingStack reference. Objects of this +// class are stack-allocated, and so exist within a thread, and are thus bounded +// by the lifetime of the thread, which ensures that the references held can't +// be used after the ProfilingStack is destroyed. +class MOZ_RAII AutoProfilerLabel { + public: + // This is the AUTO_BASE_PROFILER_LABEL and AUTO_BASE_PROFILER_LABEL_DYNAMIC + // variant. + AutoProfilerLabel(const char* aLabel, const char* aDynamicString, + ProfilingCategoryPair aCategoryPair, uint32_t aFlags = 0) { + // Get the ProfilingStack from TLS. + Push(GetProfilingStack(), aLabel, aDynamicString, aCategoryPair, aFlags); + } + + void Push(ProfilingStack* aProfilingStack, const char* aLabel, + const char* aDynamicString, ProfilingCategoryPair aCategoryPair, + uint32_t aFlags = 0) { + // This function runs both on and off the main thread. + + mProfilingStack = aProfilingStack; + if (mProfilingStack) { + mProfilingStack->pushLabelFrame(aLabel, aDynamicString, this, + aCategoryPair, aFlags); + } + } + + ~AutoProfilerLabel() { + // This function runs both on and off the main thread. + + if (mProfilingStack) { + mProfilingStack->pop(); + } + } + + MFBT_API static ProfilingStack* GetProfilingStack(); + + private: + // We save a ProfilingStack pointer in the ctor so we don't have to redo the + // TLS lookup in the dtor. + ProfilingStack* mProfilingStack; + + public: + // See the comment on the definition in platform.cpp for details about this. + static MOZ_THREAD_LOCAL(ProfilingStack*) sProfilingStack; +}; + +} // namespace mozilla::baseprofiler + +#endif // !MOZ_GECKO_PROFILER + +#endif // BaseProfilerLabels_h diff --git a/mozglue/baseprofiler/public/BaseProfilerMarkerTypes.h b/mozglue/baseprofiler/public/BaseProfilerMarkerTypes.h new file mode 100644 index 0000000000..404e15c5f6 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerMarkerTypes.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilerMarkerTypes_h +#define BaseProfilerMarkerTypes_h + +// This header contains common marker type definitions. +// +// It #include's "mozilla/BaseProfilerMarkers.h", see that file for how to +// define other marker types, and how to add markers to the profiler buffers. +// +// If you don't need to use these common types, #include +// "mozilla/BaseProfilerMarkers.h" instead. +// +// Types in this files can be defined without relying on xpcom. +// Others are defined in "ProfilerMarkerTypes.h". + +// !!! /!\ WORK IN PROGRESS /!\ !!! +// This file contains draft marker definitions, but most are not used yet. +// Further work is needed to complete these definitions, and use them to convert +// existing PROFILER_ADD_MARKER calls. See meta bug 1661394. + +#include "mozilla/BaseProfilerMarkers.h" + +namespace mozilla::baseprofiler::markers { + +struct MediaSampleMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("MediaSample"); + } + static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter, + int64_t aSampleStartTimeUs, + int64_t aSampleEndTimeUs, + int64_t aQueueLength) { + aWriter.IntProperty("sampleStartTimeUs", aSampleStartTimeUs); + aWriter.IntProperty("sampleEndTimeUs", aSampleEndTimeUs); + aWriter.IntProperty("queueLength", aQueueLength); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("sampleStartTimeUs", "Sample start time", + MS::Format::Microseconds); + schema.AddKeyLabelFormat("sampleEndTimeUs", "Sample end time", + MS::Format::Microseconds); + schema.AddKeyLabelFormat("queueLength", "Queue length", + MS::Format::Integer); + return schema; + } +}; + +struct VideoFallingBehindMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("VideoFallingBehind"); + } + static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter, + int64_t aVideoFrameStartTimeUs, + int64_t aMediaCurrentTimeUs) { + aWriter.IntProperty("videoFrameStartTimeUs", aVideoFrameStartTimeUs); + aWriter.IntProperty("mediaCurrentTimeUs", aMediaCurrentTimeUs); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("videoFrameStartTimeUs", "Video frame start time", + MS::Format::Microseconds); + schema.AddKeyLabelFormat("mediaCurrentTimeUs", "Media current time", + MS::Format::Microseconds); + return schema; + } +}; + +struct ContentBuildMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("CONTENT_FULL_PAINT_TIME"); + } + static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter) {} + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + return schema; + } +}; + +struct MediaEngineMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("MediaEngine"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint64_t aMediaEngineId) { + aWriter.IntProperty("id", aMediaEngineId); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("id", "Id", MS::Format::Integer); + return schema; + } +}; + +struct MediaEngineTextMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("MediaEngineText"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint64_t aMediaEngineId, + const ProfilerString8View& aText) { + aWriter.IntProperty("id", aMediaEngineId); + aWriter.StringProperty("text", aText); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("id", "Id", MS::Format::Integer); + schema.AddKeyLabelFormat("text", "Details", MS::Format::String); + return schema; + } +}; + +} // namespace mozilla::baseprofiler::markers + +#endif // BaseProfilerMarkerTypes_h diff --git a/mozglue/baseprofiler/public/BaseProfilerMarkers.h b/mozglue/baseprofiler/public/BaseProfilerMarkers.h new file mode 100644 index 0000000000..d706cefd4a --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerMarkers.h @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Markers are useful to delimit something important happening such as the first +// paint. Unlike labels, which are only recorded in the profile buffer if a +// sample is collected while the label is on the label stack, markers will +// always be recorded in the profile buffer. +// +// This header contains basic definitions necessary to create marker types, and +// to add markers to the profiler buffers. +// +// If basic marker types are needed, #include +// "mozilla/BaseProfilerMarkerTypes.h" instead. +// +// But if you want to create your own marker type locally, you can #include this +// header only; look at mozilla/BaseProfilerMarkerTypes.h for examples of how to +// define types, and mozilla/BaseProfilerMarkerPrerequisites.h for some +// supporting types. +// +// To then record markers: +// - Use `baseprofiler::AddMarker(...)` from mozglue or other libraries that +// are outside of xul, especially if they may happen outside of xpcom's +// lifetime (typically startup, shutdown, or tests). +// - Otherwise #include "ProfilerMarkers.h" instead, and use +// `profiler_add_marker(...)`. +// See these functions for more details. + +#ifndef BaseProfilerMarkers_h +#define BaseProfilerMarkers_h + +#include "mozilla/BaseProfilerMarkersDetail.h" +#include "mozilla/BaseProfilerLabels.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" + +#include +#include +#include + +namespace mozilla::baseprofiler { + +#ifdef MOZ_GECKO_PROFILER +// Forward-declaration. TODO: Move to more common header, see bug 1681416. +MFBT_API bool profiler_capture_backtrace_into( + ProfileChunkedBuffer& aChunkedBuffer, StackCaptureOptions aCaptureOptions); + +// Add a marker to a given buffer. `AddMarker()` and related macros should be +// used in most cases, see below for more information about them and the +// parameters; This function may be useful when markers need to be recorded in a +// local buffer outside of the main profiler buffer. +template +ProfileBufferBlockIndex AddMarkerToBuffer( + ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName, + const MarkerCategory& aCategory, MarkerOptions&& aOptions, + MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) { + Unused << aMarkerType; // Only the empty object type is useful. + AUTO_BASE_PROFILER_LABEL("baseprofiler::AddMarkerToBuffer", PROFILER); + return base_profiler_markers_detail::AddMarkerToBuffer( + aBuffer, aName, aCategory, std::move(aOptions), + // Do not capture a stack if the NoMarkerStacks feature is set. + profiler_active_without_feature(ProfilerFeature::NoMarkerStacks) + ? ::mozilla::baseprofiler::profiler_capture_backtrace_into + : nullptr, + aPayloadArguments...); +} + +// Add a marker (without payload) to a given buffer. +inline ProfileBufferBlockIndex AddMarkerToBuffer( + ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName, + const MarkerCategory& aCategory, MarkerOptions&& aOptions = {}) { + return AddMarkerToBuffer(aBuffer, aName, aCategory, std::move(aOptions), + markers::NoPayload{}); +} +#endif // MOZ_GECKO_PROFILER + +// Add a marker to the Base Profiler buffer. +// - aName: Main name of this marker. +// - aCategory: Category for this marker. +// - aOptions: Optional settings (such as timing, inner window id, +// backtrace...), see `MarkerOptions` for details. +// - aMarkerType: Empty object that specifies the type of marker. +// - aPayloadArguments: Arguments expected by this marker type's +// ` StreamJSONMarkerData` function. +template +ProfileBufferBlockIndex AddMarker( + const ProfilerString8View& aName, const MarkerCategory& aCategory, + MarkerOptions&& aOptions, MarkerType aMarkerType, + const PayloadArguments&... aPayloadArguments) { +#ifndef MOZ_GECKO_PROFILER + return {}; +#else + // Record base markers whenever the core buffer is in session. + // TODO: When profiler_thread_is_being_profiled becomes available from + // mozglue, use it instead. + ProfileChunkedBuffer& coreBuffer = + ::mozilla::baseprofiler::profiler_get_core_buffer(); + if (!coreBuffer.IsInSession()) { + return {}; + } + return ::mozilla::baseprofiler::AddMarkerToBuffer( + coreBuffer, aName, aCategory, std::move(aOptions), aMarkerType, + aPayloadArguments...); +#endif +} + +// Add a marker (without payload) to the Base Profiler buffer. +inline ProfileBufferBlockIndex AddMarker(const ProfilerString8View& aName, + const MarkerCategory& aCategory, + MarkerOptions&& aOptions = {}) { + return AddMarker(aName, aCategory, std::move(aOptions), markers::NoPayload{}); +} + +} // namespace mozilla::baseprofiler + +// Same as `AddMarker()` (without payload). This macro is safe to use even if +// MOZ_GECKO_PROFILER is not #defined. +#define BASE_PROFILER_MARKER_UNTYPED(markerName, categoryName, ...) \ + do { \ + AUTO_PROFILER_STATS(BASE_PROFILER_MARKER_UNTYPED); \ + ::mozilla::baseprofiler::AddMarker( \ + markerName, ::mozilla::baseprofiler::category::categoryName, \ + ##__VA_ARGS__); \ + } while (false) + +// Same as `AddMarker()` (with payload). This macro is safe to use even if +// MOZ_GECKO_PROFILER is not #defined. +#define BASE_PROFILER_MARKER(markerName, categoryName, options, MarkerType, \ + ...) \ + do { \ + AUTO_PROFILER_STATS(BASE_PROFILER_MARKER_with_##MarkerType); \ + ::mozilla::baseprofiler::AddMarker( \ + markerName, ::mozilla::baseprofiler::category::categoryName, options, \ + ::mozilla::baseprofiler::markers::MarkerType{}, ##__VA_ARGS__); \ + } while (false) + +namespace mozilla::baseprofiler::markers { +// Most common marker type. Others are in BaseProfilerMarkerTypes.h. +struct TextMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("Text"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aText) { + aWriter.StringProperty("name", aText); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.SetChartLabel("{marker.data.name}"); + schema.SetTableLabel("{marker.name} - {marker.data.name}"); + schema.AddKeyLabelFormatSearchable("name", "Details", MS::Format::String, + MS::Searchable::Searchable); + return schema; + } +}; + +// Keep this struct in sync with the `gecko_profiler::marker::Tracing` Rust +// counterpart. +struct Tracing { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("tracing"); + } + static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter, + const ProfilerString8View& aCategory) { + if (aCategory.Length() != 0) { + aWriter.StringProperty("category", aCategory); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, + MS::Location::TimelineOverview}; + schema.AddKeyLabelFormatSearchable("category", "Type", MS::Format::String, + MS::Searchable::Searchable); + return schema; + } +}; +} // namespace mozilla::baseprofiler::markers + +// Add a text marker. This macro is safe to use even if MOZ_GECKO_PROFILER is +// not #defined. +#define BASE_PROFILER_MARKER_TEXT(markerName, categoryName, options, text) \ + do { \ + AUTO_PROFILER_STATS(BASE_PROFILER_MARKER_TEXT); \ + ::mozilla::baseprofiler::AddMarker( \ + markerName, ::mozilla::baseprofiler::category::categoryName, options, \ + ::mozilla::baseprofiler::markers::TextMarker{}, text); \ + } while (false) + +namespace mozilla::baseprofiler { + +// RAII object that adds a BASE_PROFILER_MARKER_TEXT when destroyed; the +// marker's timing will be the interval from construction (unless an instant or +// start time is already specified in the provided options) until destruction. +class MOZ_RAII AutoProfilerTextMarker { + public: + AutoProfilerTextMarker(const char* aMarkerName, + const MarkerCategory& aCategory, + MarkerOptions&& aOptions, const std::string& aText) + : mMarkerName(aMarkerName), + mCategory(aCategory), + mOptions(std::move(aOptions)), + mText(aText) { + MOZ_ASSERT(mOptions.Timing().EndTime().IsNull(), + "AutoProfilerTextMarker options shouldn't have an end time"); + if (profiler_is_active_and_unpaused() && + mOptions.Timing().StartTime().IsNull()) { + mOptions.Set(MarkerTiming::InstantNow()); + } + } + + ~AutoProfilerTextMarker() { + if (profiler_is_active_and_unpaused()) { + mOptions.TimingRef().SetIntervalEnd(); + AUTO_PROFILER_STATS(AUTO_BASE_PROFILER_MARKER_TEXT); + AddMarker(ProfilerString8View::WrapNullTerminatedString(mMarkerName), + mCategory, std::move(mOptions), markers::TextMarker{}, mText); + } + } + + protected: + const char* mMarkerName; + MarkerCategory mCategory; + MarkerOptions mOptions; + std::string mText; +}; + +#ifdef MOZ_GECKO_PROFILER +extern template MFBT_API ProfileBufferBlockIndex +AddMarker(const ProfilerString8View&, const MarkerCategory&, MarkerOptions&&, + markers::TextMarker, const std::string&); + +extern template MFBT_API ProfileBufferBlockIndex +AddMarkerToBuffer(ProfileChunkedBuffer&, const ProfilerString8View&, + const MarkerCategory&, MarkerOptions&&, markers::NoPayload); + +extern template MFBT_API ProfileBufferBlockIndex AddMarkerToBuffer( + ProfileChunkedBuffer&, const ProfilerString8View&, const MarkerCategory&, + MarkerOptions&&, markers::TextMarker, const std::string&); +#endif // MOZ_GECKO_PROFILER + +} // namespace mozilla::baseprofiler + +// Creates an AutoProfilerTextMarker RAII object. This macro is safe to use +// even if MOZ_GECKO_PROFILER is not #defined. +#define AUTO_BASE_PROFILER_MARKER_TEXT(markerName, categoryName, options, \ + text) \ + ::mozilla::baseprofiler::AutoProfilerTextMarker PROFILER_RAII( \ + markerName, ::mozilla::baseprofiler::category::categoryName, options, \ + text) + +#endif // BaseProfilerMarkers_h diff --git a/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h b/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h new file mode 100644 index 0000000000..1102499100 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h @@ -0,0 +1,741 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilerMarkersDetail_h +#define BaseProfilerMarkersDetail_h + +#ifndef BaseProfilerMarkers_h +# error "This header should only be #included by BaseProfilerMarkers.h" +#endif + +#include "mozilla/BaseProfilerMarkersPrerequisites.h" + +// ~~ HERE BE DRAGONS ~~ +// +// Everything below is internal implementation detail, you shouldn't need to +// look at it unless working on the profiler code. + +#include "mozilla/BaseProfileJSONWriter.h" +#include "mozilla/ProfileBufferEntryKinds.h" + +#include +#include +#include + +namespace mozilla::baseprofiler { +// Implemented in platform.cpp +MFBT_API ProfileChunkedBuffer& profiler_get_core_buffer(); +} // namespace mozilla::baseprofiler + +namespace mozilla::base_profiler_markers_detail { + +struct Streaming { + // A `MarkerDataDeserializer` is a free function that can read a serialized + // payload from an `EntryReader` and streams it as JSON object properties. + using MarkerDataDeserializer = void (*)(ProfileBufferEntryReader&, + baseprofiler::SpliceableJSONWriter&); + + // A `MarkerTypeNameFunction` is a free function that returns the name of the + // marker type. + using MarkerTypeNameFunction = Span (*)(); + + // A `MarkerSchemaFunction` is a free function that returns a + // `MarkerSchema`, which contains all the information needed to stream + // the display schema associated with a marker type. + using MarkerSchemaFunction = MarkerSchema (*)(); + + struct MarkerTypeFunctions { + MarkerDataDeserializer mMarkerDataDeserializer = nullptr; + MarkerTypeNameFunction mMarkerTypeNameFunction = nullptr; + MarkerSchemaFunction mMarkerSchemaFunction = nullptr; + }; + + // A `DeserializerTag` will be added before the payload, to help select the + // correct deserializer when reading back the payload. + using DeserializerTag = uint8_t; + + // Store a deserializer (and other marker-type-specific functions) and get its + // `DeserializerTag`. + // This is intended to be only used once per deserializer when a new marker + // type is used for the first time, so it should be called to initialize a + // `static const` tag that will be re-used by all markers of the corresponding + // payload type -- see use below. + MFBT_API static DeserializerTag TagForMarkerTypeFunctions( + MarkerDataDeserializer aDeserializer, + MarkerTypeNameFunction aMarkerTypeNameFunction, + MarkerSchemaFunction aMarkerSchemaFunction); + + // Get the `MarkerDataDeserializer` for a given `DeserializerTag`. + MFBT_API static MarkerDataDeserializer DeserializerForTag( + DeserializerTag aTag); + + // Retrieve all MarkerTypeFunctions's. + // While this object lives, no other operations can happen on this list. + class LockedMarkerTypeFunctionsList { + public: + MFBT_API LockedMarkerTypeFunctionsList(); + MFBT_API ~LockedMarkerTypeFunctionsList(); + + LockedMarkerTypeFunctionsList(const LockedMarkerTypeFunctionsList&) = + delete; + LockedMarkerTypeFunctionsList& operator=( + const LockedMarkerTypeFunctionsList&) = delete; + + auto begin() const { return mMarkerTypeFunctionsSpan.begin(); } + auto end() const { return mMarkerTypeFunctionsSpan.end(); } + + private: + Span mMarkerTypeFunctionsSpan; + }; +}; + +// This helper will examine a marker type's `StreamJSONMarkerData` function, see +// specialization below. +template +struct StreamFunctionTypeHelper; + +// Helper specialization that takes the expected +// `StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter&, ...)` function and +// provide information about the `...` parameters. +template +struct StreamFunctionTypeHelper { + constexpr static size_t scArity = sizeof...(As); + using TupleType = + std::tuple>...>; + + // Serialization function that takes the exact same parameter types + // (const-ref'd) as `StreamJSONMarkerData`. This has to be inside the helper + // because only here can we access the raw parameter pack `As...`. + // And because we're using the same argument types through + // references-to-const, permitted implicit conversions can happen. + static ProfileBufferBlockIndex Serialize( + ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName, + const MarkerCategory& aCategory, MarkerOptions&& aOptions, + Streaming::DeserializerTag aDeserializerTag, const As&... aAs) { + // Note that options are first after the entry kind, because they contain + // the thread id, which is handled first to filter markers by threads. + return aBuffer.PutObjects(ProfileBufferEntryKind::Marker, aOptions, aName, + aCategory, aDeserializerTag, + MarkerPayloadType::Cpp, aAs...); + } +}; + +// Helper for a marker type. +// A marker type is defined in a `struct` with some expected static member +// functions. See example in BaseProfilerMarkers.h. +template +struct MarkerTypeSerialization { + // Definitions to access the expected + // `StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter&, ...)` function + // and its parameters. + using StreamFunctionType = + StreamFunctionTypeHelper; + constexpr static size_t scStreamFunctionParameterCount = + StreamFunctionType::scArity; + using StreamFunctionUserParametersTuple = + typename StreamFunctionType::TupleType; + template + using StreamFunctionParameter = + std::tuple_element_t; + + template + static ProfileBufferBlockIndex Serialize(ProfileChunkedBuffer& aBuffer, + const ProfilerString8View& aName, + const MarkerCategory& aCategory, + MarkerOptions&& aOptions, + const Ts&... aTs) { + static_assert(!std::is_same_v, + "NoPayload should have been handled in the caller."); + // Register marker type functions, and get the tag for this deserializer. + // Note that the tag is stored in a function-static object, and this + // function is static in a templated struct, so there should only be one tag + // per MarkerType. + // Making the tag class-static may have been more efficient (to avoid a + // thread-safe init check at every call), but random global static + // initialization order would make it more complex to coordinate with + // `Streaming::TagForMarkerTypeFunctions()`, and also would add a (small) + // cost for everybody, even the majority of users not using the profiler. + static const Streaming::DeserializerTag tag = + Streaming::TagForMarkerTypeFunctions(Deserialize, + MarkerType::MarkerTypeName, + MarkerType::MarkerTypeDisplay); + return StreamFunctionType::Serialize(aBuffer, aName, aCategory, + std::move(aOptions), tag, aTs...); + } + + private: + // This templated function will recursively deserialize each argument expected + // by `MarkerType::StreamJSONMarkerData()` on the stack, and call it at the + // end. E.g., for `StreamJSONMarkerData(int, char)`: + // - DeserializeArguments<0>(aER, aWriter) reads an int and calls: + // - DeserializeArguments<1>(aER, aWriter, const int&) reads a char and calls: + // - MarkerType::StreamJSONMarkerData(aWriter, const int&, const char&). + // Prototyping on godbolt showed that clang and gcc can flatten these + // recursive calls into one function with successive reads followed by the one + // stream call; tested up to 40 arguments: https://godbolt.org/z/5KeeM4 + template + static void DeserializeArguments(ProfileBufferEntryReader& aEntryReader, + baseprofiler::SpliceableJSONWriter& aWriter, + const Args&... aArgs) { + static_assert(sizeof...(Args) == i, + "We should have collected `i` arguments so far"); + if constexpr (i < scStreamFunctionParameterCount) { + // Deserialize the i-th argument on this stack. + auto argument = aEntryReader.ReadObject>(); + // Add our local argument to the next recursive call. + DeserializeArguments(aEntryReader, aWriter, aArgs..., argument); + } else { + // We've read all the arguments, finally call the `StreamJSONMarkerData` + // function, which should write the appropriate JSON elements for this + // marker type. Note that the MarkerType-specific "type" element is + // already written. + MarkerType::StreamJSONMarkerData(aWriter, aArgs...); + } + } + + public: + static void Deserialize(ProfileBufferEntryReader& aEntryReader, + baseprofiler::SpliceableJSONWriter& aWriter) { + aWriter.StringProperty("type", MarkerType::MarkerTypeName()); + DeserializeArguments(aEntryReader, aWriter); + } +}; + +template <> +struct MarkerTypeSerialization<::mozilla::baseprofiler::markers::NoPayload> { + // Nothing! NoPayload has special handling avoiding payload work. +}; + +template +static ProfileBufferBlockIndex AddMarkerWithOptionalStackToBuffer( + ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName, + const MarkerCategory& aCategory, MarkerOptions&& aOptions, + const Ts&... aTs) { + if constexpr (std::is_same_v) { + static_assert(sizeof...(Ts) == 0, + "NoPayload does not accept any payload arguments."); + // Special case for NoPayload where there is a stack or inner window id: + // Because these options would be stored in the payload 'data' object, but + // there is no such object for NoPayload, we convert the marker to another + // type (without user fields in the 'data' object), so that the stack and/or + // inner window id are not lost. + // TODO: Remove this when bug 1646714 lands. + if (aOptions.Stack().GetChunkedBuffer() || + !aOptions.InnerWindowId().IsUnspecified()) { + struct NoPayloadUserData { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("NoPayloadUserData"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) { + // No user payload. + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + using MS = mozilla::MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + // No user data to display. + return schema; + } + }; + return MarkerTypeSerialization::Serialize( + aBuffer, aName, aCategory, std::move(aOptions)); + } + + // Note that options are first after the entry kind, because they contain + // the thread id, which is handled first to filter markers by threads. + return aBuffer.PutObjects( + ProfileBufferEntryKind::Marker, std::move(aOptions), aName, aCategory, + base_profiler_markers_detail::Streaming::DeserializerTag(0)); + } else { + return MarkerTypeSerialization::Serialize( + aBuffer, aName, aCategory, std::move(aOptions), aTs...); + } +} + +// Pointer to a function that can capture a backtrace into the provided +// `ProfileChunkedBuffer`, and returns true when successful. +using OptionalBacktraceCaptureFunction = bool (*)(ProfileChunkedBuffer&, + StackCaptureOptions); + +// Use a pre-allocated and cleared chunked buffer in the main thread's +// `AddMarkerToBuffer()`. +// Null if not the main thread, or if profilers are not active. +MFBT_API ProfileChunkedBuffer* GetClearedBufferForMainThreadAddMarker(); +// Called by the profiler(s) when starting/stopping. Safe to nest. +MFBT_API void EnsureBufferForMainThreadAddMarker(); +MFBT_API void ReleaseBufferForMainThreadAddMarker(); + +// Add a marker with the given name, options, and arguments to the given buffer. +// Because this may be called from either Base or Gecko Profiler functions, the +// appropriate backtrace-capturing function must also be provided. +template +ProfileBufferBlockIndex AddMarkerToBuffer( + ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName, + const MarkerCategory& aCategory, MarkerOptions&& aOptions, + OptionalBacktraceCaptureFunction aOptionalBacktraceCaptureFunction, + const Ts&... aTs) { + if (aOptions.ThreadId().IsUnspecified()) { + // If yet unspecified, set thread to this thread where the marker is added. + aOptions.Set(MarkerThreadId::CurrentThread()); + } + + if (aOptions.IsTimingUnspecified()) { + // If yet unspecified, set timing to this instant of adding the marker. + aOptions.Set(MarkerTiming::InstantNow()); + } + + StackCaptureOptions captureOptions = aOptions.Stack().CaptureOptions(); + if (captureOptions != StackCaptureOptions::NoStack && + // Backtrace capture function will be nullptr if the profiler + // NoMarkerStacks feature is set. + aOptionalBacktraceCaptureFunction != nullptr) { + // A capture was requested, let's attempt to do it here&now. This avoids a + // lot of allocations that would be necessary if capturing a backtrace + // separately. + // TODO reduce internal profiler stack levels, see bug 1659872. + auto CaptureStackAndAddMarker = [&](ProfileChunkedBuffer& aChunkedBuffer) { + aOptions.StackRef().UseRequestedBacktrace( + aOptionalBacktraceCaptureFunction(aChunkedBuffer, captureOptions) + ? &aChunkedBuffer + : nullptr); + // This call must be made from here, while chunkedBuffer is in scope. + return AddMarkerWithOptionalStackToBuffer( + aBuffer, aName, aCategory, std::move(aOptions), aTs...); + }; + + if (ProfileChunkedBuffer* buffer = GetClearedBufferForMainThreadAddMarker(); + buffer) { + // Use a pre-allocated buffer for the main thread (because it's the most + // used thread, and most sensitive to overhead), so it's only allocated + // once. It could be null if this is not the main thread, or no profilers + // are currently active. + return CaptureStackAndAddMarker(*buffer); + } + // TODO use a local on-stack byte buffer to remove last allocation. + ProfileBufferChunkManagerSingle chunkManager( + ProfileBufferChunkManager::scExpectedMaximumStackSize); + ProfileChunkedBuffer chunkedBuffer( + ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager); + return CaptureStackAndAddMarker(chunkedBuffer); + } + + return AddMarkerWithOptionalStackToBuffer( + aBuffer, aName, aCategory, std::move(aOptions), aTs...); +} + +// Assuming aEntryReader points right after the entry type (being Marker), this +// reads the remainder of the marker and outputs it. +// - GetWriterForThreadCallback, called first, after the thread id is read: +// (ThreadId) -> SpliceableJSONWriter* or null +// If null, nothing will be output, but aEntryReader will still be read fully. +// - StackCallback, only called if GetWriterForThreadCallback didn't return +// null, and if the marker contains a stack: +// (ProfileChunkedBuffer&) -> void +// - RustMarkerCallback, only called if GetWriterForThreadCallback didn't return +// null, and if the marker contains a Rust payload: +// (DeserializerTag) -> void +template +void DeserializeAfterKindAndStream( + ProfileBufferEntryReader& aEntryReader, + GetWriterForThreadCallback&& aGetWriterForThreadCallback, + StackCallback&& aStackCallback, RustMarkerCallback&& aRustMarkerCallback) { + // Each entry is made up of the following: + // ProfileBufferEntry::Kind::Marker, <- already read by caller + // options, <- next location in entries + // name, + // payload + const MarkerOptions options = aEntryReader.ReadObject(); + + baseprofiler::SpliceableJSONWriter* writer = + std::forward(aGetWriterForThreadCallback)( + options.ThreadId().ThreadId()); + if (!writer) { + // No writer associated with this thread id, drop it. + aEntryReader.SetRemainingBytes(0); + return; + } + + // Write the information to JSON with the following schema: + // [name, startTime, endTime, phase, category, data] + writer->StartArrayElement(); + { + writer->UniqueStringElement(aEntryReader.ReadObject()); + + const double startTime = options.Timing().GetStartTime(); + writer->TimeDoubleMsElement(startTime); + + const double endTime = options.Timing().GetEndTime(); + writer->TimeDoubleMsElement(endTime); + + writer->IntElement(static_cast(options.Timing().MarkerPhase())); + + MarkerCategory category = aEntryReader.ReadObject(); + writer->IntElement(static_cast(category.GetCategory())); + + if (const auto tag = + aEntryReader.ReadObject(); + tag != 0) { + writer->StartObjectElement(); + { + // Stream "common props". + + // TODO: Move this to top-level tuple, when frontend supports it. + if (!options.InnerWindowId().IsUnspecified()) { + // Here, we are converting uint64_t to double. Both Browsing Context + // and Inner Window IDs are created using + // `nsContentUtils::GenerateProcessSpecificId`, which is specifically + // designed to only use 53 of the 64 bits to be lossless when passed + // into and out of JS as a double. + writer->DoubleProperty( + "innerWindowID", + static_cast(options.InnerWindowId().Id())); + } + + // TODO: Move this to top-level tuple, when frontend supports it. + if (ProfileChunkedBuffer* chunkedBuffer = + options.Stack().GetChunkedBuffer(); + chunkedBuffer) { + writer->StartObjectProperty("stack"); + { std::forward(aStackCallback)(*chunkedBuffer); } + writer->EndObject(); + } + + auto payloadType = static_cast( + aEntryReader.ReadObject< + std::underlying_type_t>()); + + // Stream the payload, including the type. + switch (payloadType) { + case mozilla::MarkerPayloadType::Cpp: { + mozilla::base_profiler_markers_detail::Streaming:: + MarkerDataDeserializer deserializer = + mozilla::base_profiler_markers_detail::Streaming:: + DeserializerForTag(tag); + MOZ_RELEASE_ASSERT(deserializer); + deserializer(aEntryReader, *writer); + MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u); + break; + } + case mozilla::MarkerPayloadType::Rust: + std::forward(aRustMarkerCallback)(tag); + MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown payload type."); + break; + } + } + writer->EndObject(); + } + } + writer->EndArray(); + MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u); +} + +} // namespace mozilla::base_profiler_markers_detail + +namespace mozilla { + +// ---------------------------------------------------------------------------- +// Serializer, Deserializer: ProfilerStringView + +// The serialization starts with a ULEB128 number that encodes both whether the +// ProfilerStringView is literal (Least Significant Bit = 0) or not (LSB = 1), +// plus the string length (excluding null terminator) in bytes, shifted left by +// 1 bit. Following that number: +// - If literal, the string pointer value. +// - If non-literal, the contents as bytes (excluding null terminator if any). +template +struct ProfileBufferEntryWriter::Serializer> { + static Length Bytes(const ProfilerStringView& aString) { + MOZ_RELEASE_ASSERT( + aString.Length() < std::numeric_limits::max() / 2, + "Double the string length doesn't fit in Length type"); + const Length stringLength = static_cast(aString.Length()); + if (aString.IsLiteral()) { + // Literal -> Length shifted left and LSB=0, then pointer. + return ULEB128Size(stringLength << 1 | 0u) + + static_cast(sizeof(const CHAR*)); + } + // Non-literal -> Length shifted left and LSB=1, then string size in bytes. + return ULEB128Size((stringLength << 1) | 1u) + stringLength * sizeof(CHAR); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const ProfilerStringView& aString) { + MOZ_RELEASE_ASSERT( + aString.Length() < std::numeric_limits::max() / 2, + "Double the string length doesn't fit in Length type"); + const Span span = aString; + if (aString.IsLiteral()) { + // Literal -> Length shifted left and LSB=0, then pointer. + aEW.WriteULEB128(span.Length() << 1 | 0u); + aEW.WriteObject(WrapProfileBufferRawPointer(span.Elements())); + return; + } + // Non-literal -> Length shifted left and LSB=1, then string size in bytes. + aEW.WriteULEB128(span.Length() << 1 | 1u); + aEW.WriteBytes(span.Elements(), span.LengthBytes()); + } +}; + +template +struct ProfileBufferEntryReader::Deserializer> { + static void ReadInto(ProfileBufferEntryReader& aER, + ProfilerStringView& aString) { + aString = Read(aER); + } + + static ProfilerStringView Read(ProfileBufferEntryReader& aER) { + const Length lengthAndIsLiteral = aER.ReadULEB128(); + const Length stringLength = lengthAndIsLiteral >> 1; + if ((lengthAndIsLiteral & 1u) == 0u) { + // LSB==0 -> Literal string, read the string pointer. + return ProfilerStringView( + aER.ReadObject(), stringLength, + ProfilerStringView::Ownership::Literal); + } + // LSB==1 -> Not a literal string. + ProfileBufferEntryReader::DoubleSpanOfConstBytes spans = + aER.ReadSpans(stringLength * sizeof(CHAR)); + if (MOZ_LIKELY(spans.IsSingleSpan()) && + reinterpret_cast(spans.mFirstOrOnly.Elements()) % + alignof(CHAR) == + 0u) { + // Only a single span, correctly aligned for the CHAR type, we can just + // refer to it directly, assuming that this ProfilerStringView will not + // outlive the chunk. + return ProfilerStringView( + reinterpret_cast(spans.mFirstOrOnly.Elements()), + stringLength, ProfilerStringView::Ownership::Reference); + } else { + // Two spans, we need to concatenate them; or one span, but misaligned. + // Allocate a buffer to store the string (plus terminal, for safety), and + // give it to the ProfilerStringView; Note that this is a secret use of + // ProfilerStringView, which is intended to only be used between + // deserialization and JSON streaming. + CHAR* buffer = new CHAR[stringLength + 1]; + spans.CopyBytesTo(buffer); + buffer[stringLength] = CHAR(0); + return ProfilerStringView( + buffer, stringLength, + ProfilerStringView::Ownership::OwnedThroughStringView); + } + } +}; + +// Serializer, Deserializer: MarkerCategory + +// The serialization contains both category numbers encoded as ULEB128. +template <> +struct ProfileBufferEntryWriter::Serializer { + static Length Bytes(const MarkerCategory& aCategory) { + return ULEB128Size(static_cast(aCategory.CategoryPair())); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const MarkerCategory& aCategory) { + aEW.WriteULEB128(static_cast(aCategory.CategoryPair())); + } +}; + +template <> +struct ProfileBufferEntryReader::Deserializer { + static void ReadInto(ProfileBufferEntryReader& aER, + MarkerCategory& aCategory) { + aCategory = Read(aER); + } + + static MarkerCategory Read(ProfileBufferEntryReader& aER) { + return MarkerCategory(static_cast( + aER.ReadULEB128())); + } +}; + +// ---------------------------------------------------------------------------- +// Serializer, Deserializer: MarkerTiming + +// The serialization starts with the marker phase, followed by one or two +// timestamps as needed. +template <> +struct ProfileBufferEntryWriter::Serializer { + static Length Bytes(const MarkerTiming& aTiming) { + MOZ_ASSERT(!aTiming.IsUnspecified()); + const auto phase = aTiming.MarkerPhase(); + switch (phase) { + case MarkerTiming::Phase::Instant: + return SumBytes(phase, aTiming.StartTime()); + case MarkerTiming::Phase::Interval: + return SumBytes(phase, aTiming.StartTime(), aTiming.EndTime()); + case MarkerTiming::Phase::IntervalStart: + return SumBytes(phase, aTiming.StartTime()); + case MarkerTiming::Phase::IntervalEnd: + return SumBytes(phase, aTiming.EndTime()); + default: + MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant || + phase == MarkerTiming::Phase::Interval || + phase == MarkerTiming::Phase::IntervalStart || + phase == MarkerTiming::Phase::IntervalEnd); + return 0; // Only to avoid build errors. + } + } + + static void Write(ProfileBufferEntryWriter& aEW, + const MarkerTiming& aTiming) { + MOZ_ASSERT(!aTiming.IsUnspecified()); + const auto phase = aTiming.MarkerPhase(); + switch (phase) { + case MarkerTiming::Phase::Instant: + aEW.WriteObjects(phase, aTiming.StartTime()); + return; + case MarkerTiming::Phase::Interval: + aEW.WriteObjects(phase, aTiming.StartTime(), aTiming.EndTime()); + return; + case MarkerTiming::Phase::IntervalStart: + aEW.WriteObjects(phase, aTiming.StartTime()); + return; + case MarkerTiming::Phase::IntervalEnd: + aEW.WriteObjects(phase, aTiming.EndTime()); + return; + default: + MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant || + phase == MarkerTiming::Phase::Interval || + phase == MarkerTiming::Phase::IntervalStart || + phase == MarkerTiming::Phase::IntervalEnd); + return; + } + } +}; + +template <> +struct ProfileBufferEntryReader::Deserializer { + static void ReadInto(ProfileBufferEntryReader& aER, MarkerTiming& aTiming) { + aTiming.mPhase = aER.ReadObject(); + switch (aTiming.mPhase) { + case MarkerTiming::Phase::Instant: + aTiming.mStartTime = aER.ReadObject(); + aTiming.mEndTime = TimeStamp{}; + break; + case MarkerTiming::Phase::Interval: + aTiming.mStartTime = aER.ReadObject(); + aTiming.mEndTime = aER.ReadObject(); + break; + case MarkerTiming::Phase::IntervalStart: + aTiming.mStartTime = aER.ReadObject(); + aTiming.mEndTime = TimeStamp{}; + break; + case MarkerTiming::Phase::IntervalEnd: + aTiming.mStartTime = TimeStamp{}; + aTiming.mEndTime = aER.ReadObject(); + break; + default: + MOZ_RELEASE_ASSERT(aTiming.mPhase == MarkerTiming::Phase::Instant || + aTiming.mPhase == MarkerTiming::Phase::Interval || + aTiming.mPhase == + MarkerTiming::Phase::IntervalStart || + aTiming.mPhase == MarkerTiming::Phase::IntervalEnd); + break; + } + } + + static MarkerTiming Read(ProfileBufferEntryReader& aER) { + TimeStamp start; + TimeStamp end; + auto phase = aER.ReadObject(); + switch (phase) { + case MarkerTiming::Phase::Instant: + start = aER.ReadObject(); + break; + case MarkerTiming::Phase::Interval: + start = aER.ReadObject(); + end = aER.ReadObject(); + break; + case MarkerTiming::Phase::IntervalStart: + start = aER.ReadObject(); + break; + case MarkerTiming::Phase::IntervalEnd: + end = aER.ReadObject(); + break; + default: + MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant || + phase == MarkerTiming::Phase::Interval || + phase == MarkerTiming::Phase::IntervalStart || + phase == MarkerTiming::Phase::IntervalEnd); + break; + } + return MarkerTiming(start, end, phase); + } +}; + +// ---------------------------------------------------------------------------- +// Serializer, Deserializer: MarkerStack + +// The serialization only contains the `ProfileChunkedBuffer` from the +// backtrace; if there is no backtrace or if it's empty, this will implicitly +// store a nullptr (see +// `ProfileBufferEntryWriter::Serializer`). +template <> +struct ProfileBufferEntryWriter::Serializer { + static Length Bytes(const MarkerStack& aStack) { + return SumBytes(aStack.GetChunkedBuffer()); + } + + static void Write(ProfileBufferEntryWriter& aEW, const MarkerStack& aStack) { + aEW.WriteObject(aStack.GetChunkedBuffer()); + } +}; + +template <> +struct ProfileBufferEntryReader::Deserializer { + static void ReadInto(ProfileBufferEntryReader& aER, MarkerStack& aStack) { + aStack = Read(aER); + } + + static MarkerStack Read(ProfileBufferEntryReader& aER) { + return MarkerStack(aER.ReadObject>()); + } +}; + +// ---------------------------------------------------------------------------- +// Serializer, Deserializer: MarkerOptions + +// The serialization contains all members (either trivially-copyable, or they +// provide their specialization above). +template <> +struct ProfileBufferEntryWriter::Serializer { + static Length Bytes(const MarkerOptions& aOptions) { + return SumBytes(aOptions.ThreadId(), aOptions.Timing(), aOptions.Stack(), + aOptions.InnerWindowId()); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const MarkerOptions& aOptions) { + aEW.WriteObjects(aOptions.ThreadId(), aOptions.Timing(), aOptions.Stack(), + aOptions.InnerWindowId()); + } +}; + +template <> +struct ProfileBufferEntryReader::Deserializer { + static void ReadInto(ProfileBufferEntryReader& aER, MarkerOptions& aOptions) { + aER.ReadIntoObjects(aOptions.mThreadId, aOptions.mTiming, aOptions.mStack, + aOptions.mInnerWindowId); + } + + static MarkerOptions Read(ProfileBufferEntryReader& aER) { + MarkerOptions options; + ReadInto(aER, options); + return options; + } +}; + +} // namespace mozilla + +#endif // BaseProfilerMarkersDetail_h diff --git a/mozglue/baseprofiler/public/BaseProfilerMarkersPrerequisites.h b/mozglue/baseprofiler/public/BaseProfilerMarkersPrerequisites.h new file mode 100644 index 0000000000..15fd729d0c --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerMarkersPrerequisites.h @@ -0,0 +1,886 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This header contains basic definitions required to create marker types, and +// to add markers to the profiler buffers. +// +// In most cases, #include "mozilla/BaseProfilerMarkers.h" instead, or +// #include "mozilla/BaseProfilerMarkerTypes.h" for common marker types. + +#ifndef BaseProfilerMarkersPrerequisites_h +#define BaseProfilerMarkersPrerequisites_h + +namespace mozilla { + +enum class StackCaptureOptions { + NoStack, // No stack captured. + Full, // Capture a full stack, including label frames, JS frames and + // native frames. + NonNative, // Capture a stack without native frames for reduced overhead. +}; + +} + +#include "BaseProfilingCategory.h" +#include "mozilla/Maybe.h" +#include "mozilla/ProfileChunkedBuffer.h" +#include "mozilla/BaseProfilerState.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" + +#include +#include +#include +#include +#include +#include + +namespace mozilla { + +// Return a NotNull pointing at the literal empty string `""`. +template +constexpr const CHAR* LiteralEmptyStringPointer() { + static_assert(std::is_same_v || std::is_same_v, + "Only char and char16_t are supported in Firefox"); + if constexpr (std::is_same_v) { + return ""; + } + if constexpr (std::is_same_v) { + return u""; + } +} + +// Return a string_view pointing at the literal empty string. +template +constexpr std::basic_string_view LiteralEmptyStringView() { + static_assert(std::is_same_v || std::is_same_v, + "Only char and char16_t are supported in Firefox"); + // Use `operator""sv()` from . + using namespace std::literals::string_view_literals; + if constexpr (std::is_same_v) { + return ""sv; + } + if constexpr (std::is_same_v) { + return u""sv; + } +} + +// General string view, optimized for short on-stack life before serialization, +// and between deserialization and JSON-streaming. +template +class MOZ_STACK_CLASS ProfilerStringView { + public: + // Default constructor points at "" (literal empty string). + constexpr ProfilerStringView() = default; + + // Don't allow copy. + ProfilerStringView(const ProfilerStringView&) = delete; + ProfilerStringView& operator=(const ProfilerStringView&) = delete; + + // Allow move. For consistency the moved-from string is always reset to "". + constexpr ProfilerStringView(ProfilerStringView&& aOther) + : mStringView(std::move(aOther.mStringView)), + mOwnership(aOther.mOwnership) { + if (mOwnership == Ownership::OwnedThroughStringView) { + // We now own the buffer, make the other point at the literal "". + aOther.mStringView = LiteralEmptyStringView(); + aOther.mOwnership = Ownership::Literal; + } + } + constexpr ProfilerStringView& operator=(ProfilerStringView&& aOther) { + mStringView = std::move(aOther.mStringView); + mOwnership = aOther.mOwnership; + if (mOwnership == Ownership::OwnedThroughStringView) { + // We now own the buffer, make the other point at the literal "". + aOther.mStringView = LiteralEmptyStringView(); + aOther.mOwnership = Ownership::Literal; + } + return *this; + } + + ~ProfilerStringView() { + if (MOZ_UNLIKELY(mOwnership == Ownership::OwnedThroughStringView)) { + // We own the buffer pointed at by mStringView, destroy it. + // This is only used between deserialization and streaming. + delete mStringView.data(); + } + } + + // Implicit construction from nullptr, points at "" (literal empty string). + constexpr MOZ_IMPLICIT ProfilerStringView(decltype(nullptr)) {} + + // Implicit constructor from a literal string. + template + constexpr MOZ_IMPLICIT ProfilerStringView(const CHAR (&aLiteralString)[Np1]) + : ProfilerStringView(aLiteralString, Np1 - 1, Ownership::Literal) {} + + // Constructor from a non-literal string. + constexpr ProfilerStringView(const CHAR* aString, size_t aLength) + : ProfilerStringView(aString, aLength, Ownership::Reference) {} + + // Implicit constructor from a string_view. + constexpr MOZ_IMPLICIT ProfilerStringView( + const std::basic_string_view& aStringView) + : ProfilerStringView(aStringView.data(), aStringView.length(), + Ownership::Reference) {} + + // Implicit constructor from an expiring string_view. We assume that the + // pointed-at string will outlive this ProfilerStringView. + constexpr MOZ_IMPLICIT ProfilerStringView( + std::basic_string_view&& aStringView) + : ProfilerStringView(aStringView.data(), aStringView.length(), + Ownership::Reference) {} + + // Implicit constructor from std::string. + constexpr MOZ_IMPLICIT ProfilerStringView( + const std::basic_string& aString) + : ProfilerStringView(aString.data(), aString.length(), + Ownership::Reference) {} + + // Construction from a raw pointer to a null-terminated string. + // This is a named class-static function to make it more obvious where work is + // being done (to determine the string length), and encourage users to instead + // provide a length, if already known. + // TODO: Find callers and convert them to constructor instead if possible. + static constexpr ProfilerStringView WrapNullTerminatedString( + const CHAR* aString) { + return ProfilerStringView( + aString, aString ? std::char_traits::length(aString) : 0, + Ownership::Reference); + } + + // Implicit constructor for an object with member functions `Data()` + // `Length()`, and `IsLiteral()`, common in xpcom strings. + template < + typename String, + typename DataReturnType = decltype(std::declval().Data()), + typename LengthReturnType = + decltype(std::declval().Length()), + typename IsLiteralReturnType = + decltype(std::declval().IsLiteral()), + typename = + std::enable_if_t && + std::is_integral_v && + std::is_same_v>> + constexpr MOZ_IMPLICIT ProfilerStringView(const String& aString) + : ProfilerStringView( + static_cast(aString.Data()), aString.Length(), + aString.IsLiteral() ? Ownership::Literal : Ownership::Reference) {} + + [[nodiscard]] constexpr const std::basic_string_view& StringView() + const { + return mStringView; + } + + [[nodiscard]] constexpr size_t Length() const { return mStringView.length(); } + + [[nodiscard]] constexpr bool IsLiteral() const { + return mOwnership == Ownership::Literal; + } + [[nodiscard]] constexpr bool IsReference() const { + return mOwnership == Ownership::Reference; + } + // No `IsOwned...()` because it's a secret, only used internally! + + [[nodiscard]] Span AsSpan() const { + return Span(mStringView.data(), mStringView.length()); + } + [[nodiscard]] operator Span() const { return AsSpan(); } + + private: + enum class Ownership { Literal, Reference, OwnedThroughStringView }; + + // Allow deserializer to store anything here. + friend ProfileBufferEntryReader::Deserializer; + + constexpr ProfilerStringView(const CHAR* aString, size_t aLength, + Ownership aOwnership) + : mStringView(aString ? std::basic_string_view(aString, aLength) + : LiteralEmptyStringView()), + mOwnership(aString ? aOwnership : Ownership::Literal) {} + + // String view to an outside string (literal or reference). + // We may actually own the pointed-at buffer, but it is only used internally + // between deserialization and JSON streaming. + std::basic_string_view mStringView = LiteralEmptyStringView(); + + Ownership mOwnership = Ownership::Literal; +}; + +using ProfilerString8View = ProfilerStringView; +using ProfilerString16View = ProfilerStringView; + +// This compulsory marker parameter contains the required category information. +class MarkerCategory { + public: + // Constructor from category pair (includes both super- and sub-categories). + constexpr explicit MarkerCategory( + baseprofiler::ProfilingCategoryPair aCategoryPair) + : mCategoryPair(aCategoryPair) {} + + // Returns the stored category pair. + constexpr baseprofiler::ProfilingCategoryPair CategoryPair() const { + return mCategoryPair; + } + + // Returns the super-category from the stored category pair. + baseprofiler::ProfilingCategory GetCategory() const { + return GetProfilingCategoryPairInfo(mCategoryPair).mCategory; + } + + private: + baseprofiler::ProfilingCategoryPair mCategoryPair = + baseprofiler::ProfilingCategoryPair::OTHER; +}; + +namespace baseprofiler::category { + +// Each category pair name constructs a MarkerCategory. +// E.g.: mozilla::baseprofiler::category::OTHER_Profiling +// Profiler macros will take the category name alone without namespace. +// E.g.: `PROFILER_MARKER_UNTYPED("name", OTHER_Profiling)` +#define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) +#define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) \ + static constexpr MarkerCategory name{ProfilingCategoryPair::name}; +#define CATEGORY_ENUM_END_CATEGORY +MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY, + CATEGORY_ENUM_SUBCATEGORY, + CATEGORY_ENUM_END_CATEGORY) +#undef CATEGORY_ENUM_BEGIN_CATEGORY +#undef CATEGORY_ENUM_SUBCATEGORY +#undef CATEGORY_ENUM_END_CATEGORY + +// Import `MarkerCategory` into this namespace. This will allow using this type +// dynamically in macros that prepend `::mozilla::baseprofiler::category::` to +// the given category, e.g.: +// `PROFILER_MARKER_UNTYPED("name", MarkerCategory(...))` +using MarkerCategory = ::mozilla::MarkerCategory; + +} // namespace baseprofiler::category + +// The classes below are all embedded in a `MarkerOptions` object. +class MarkerOptions; + +// This marker option captures a given thread id. +// If left unspecified (by default construction) during the add-marker call, the +// current thread id will be used then. +class MarkerThreadId { + public: + // Default constructor, keeps the thread id unspecified. + constexpr MarkerThreadId() = default; + + // Constructor from a given thread id. + constexpr explicit MarkerThreadId( + baseprofiler::BaseProfilerThreadId aThreadId) + : mThreadId(aThreadId) {} + + // Use the current thread's id. + static MarkerThreadId CurrentThread() { + return MarkerThreadId(baseprofiler::profiler_current_thread_id()); + } + + // Use the main thread's id. This can be useful to record a marker from a + // possibly-unregistered thread, and display it in the main thread track. + static MarkerThreadId MainThread() { + return MarkerThreadId(baseprofiler::profiler_main_thread_id()); + } + + [[nodiscard]] constexpr baseprofiler::BaseProfilerThreadId ThreadId() const { + return mThreadId; + } + + [[nodiscard]] constexpr bool IsUnspecified() const { + return !mThreadId.IsSpecified(); + } + + private: + baseprofiler::BaseProfilerThreadId mThreadId; +}; + +// This marker option contains marker timing information. +// This class encapsulates the logic for correctly storing a marker based on its +// Use the static methods to create the MarkerTiming. This is a transient object +// that is being used to enforce the constraints of the combinations of the +// data. +class MarkerTiming { + public: + // The following static methods are used to create the MarkerTiming based on + // the type that it is. + + static MarkerTiming InstantAt(const TimeStamp& aTime) { + MOZ_ASSERT(!aTime.IsNull(), "Time is null for an instant marker."); + return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::Instant}; + } + + static MarkerTiming InstantNow() { return InstantAt(TimeStamp::Now()); } + + static MarkerTiming Interval(const TimeStamp& aStartTime, + const TimeStamp& aEndTime) { + MOZ_ASSERT(!aStartTime.IsNull(), + "Start time is null for an interval marker."); + MOZ_ASSERT(!aEndTime.IsNull(), "End time is null for an interval marker."); + return MarkerTiming{aStartTime, aEndTime, MarkerTiming::Phase::Interval}; + } + + static MarkerTiming IntervalUntilNowFrom(const TimeStamp& aStartTime) { + return Interval(aStartTime, TimeStamp::Now()); + } + + static MarkerTiming IntervalStart(const TimeStamp& aTime = TimeStamp::Now()) { + MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval start marker."); + return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::IntervalStart}; + } + + static MarkerTiming IntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) { + MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker."); + return MarkerTiming{TimeStamp{}, aTime, MarkerTiming::Phase::IntervalEnd}; + } + + // Set the interval end in this timing. + // If there was already a start time, this makes it a full interval. + void SetIntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) { + MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker."); + mEndTime = aTime; + mPhase = mStartTime.IsNull() ? Phase::IntervalEnd : Phase::Interval; + } + + [[nodiscard]] const TimeStamp& StartTime() const { return mStartTime; } + [[nodiscard]] const TimeStamp& EndTime() const { return mEndTime; } + + enum class Phase : uint8_t { + Instant = 0, + Interval = 1, + IntervalStart = 2, + IntervalEnd = 3, + }; + + [[nodiscard]] Phase MarkerPhase() const { + MOZ_ASSERT(!IsUnspecified()); + return mPhase; + } + + // The following getter methods are used to put the value into the buffer for + // storage. + [[nodiscard]] double GetStartTime() const { + MOZ_ASSERT(!IsUnspecified()); + // If mStartTime is null (e.g., for IntervalEnd), this will output 0.0 as + // expected. + return MarkerTiming::timeStampToDouble(mStartTime); + } + + [[nodiscard]] double GetEndTime() const { + MOZ_ASSERT(!IsUnspecified()); + // If mEndTime is null (e.g., for Instant or IntervalStart), this will + // output 0.0 as expected. + return MarkerTiming::timeStampToDouble(mEndTime); + } + + [[nodiscard]] uint8_t GetPhase() const { + MOZ_ASSERT(!IsUnspecified()); + return static_cast(mPhase); + } + + // This is a constructor for Rust FFI bindings. It must not be used outside of + // this! Please see the other static constructors above. + static void UnsafeConstruct(MarkerTiming* aMarkerTiming, + const TimeStamp& aStartTime, + const TimeStamp& aEndTime, Phase aPhase) { + new (aMarkerTiming) MarkerTiming{aStartTime, aEndTime, aPhase}; + } + + private: + friend ProfileBufferEntryWriter::Serializer; + friend ProfileBufferEntryReader::Deserializer; + friend MarkerOptions; + + // Default timing leaves it internally "unspecified", serialization getters + // and add-marker functions will default to `InstantNow()`. + constexpr MarkerTiming() = default; + + // This should only be used by internal profiler code. + [[nodiscard]] bool IsUnspecified() const { + return mStartTime.IsNull() && mEndTime.IsNull(); + } + + // Full constructor, used by static factory functions. + constexpr MarkerTiming(const TimeStamp& aStartTime, const TimeStamp& aEndTime, + Phase aPhase) + : mStartTime(aStartTime), mEndTime(aEndTime), mPhase(aPhase) {} + + static double timeStampToDouble(const TimeStamp& time) { + if (time.IsNull()) { + // The Phase lets us know not to use this value. + return 0; + } + return (time - TimeStamp::ProcessCreation()).ToMilliseconds(); + } + + TimeStamp mStartTime; + TimeStamp mEndTime; + Phase mPhase = Phase::Instant; +}; + +// This marker option allows three cases: +// - By default, no stacks are captured. +// - The caller can request a stack capture, and the add-marker code will take +// care of it in the most efficient way. +// - The caller can still provide an existing backtrace, for cases where a +// marker reports something that happened elsewhere. +class MarkerStack { + public: + // Default constructor, no capture. + constexpr MarkerStack() = default; + + // Disallow copy. + MarkerStack(const MarkerStack&) = delete; + MarkerStack& operator=(const MarkerStack&) = delete; + + // Allow move. + MarkerStack(MarkerStack&& aOther) + : mCaptureOptions(aOther.mCaptureOptions), + mOptionalChunkedBufferStorage( + std::move(aOther.mOptionalChunkedBufferStorage)), + mChunkedBuffer(aOther.mChunkedBuffer) { + AssertInvariants(); + aOther.Clear(); + } + MarkerStack& operator=(MarkerStack&& aOther) { + mCaptureOptions = aOther.mCaptureOptions; + mOptionalChunkedBufferStorage = + std::move(aOther.mOptionalChunkedBufferStorage); + mChunkedBuffer = aOther.mChunkedBuffer; + AssertInvariants(); + aOther.Clear(); + return *this; + } + + // Take ownership of a backtrace. If null or empty, equivalent to NoStack(). + explicit MarkerStack(UniquePtr&& aExternalChunkedBuffer) + : mOptionalChunkedBufferStorage( + (!aExternalChunkedBuffer || aExternalChunkedBuffer->IsEmpty()) + ? nullptr + : std::move(aExternalChunkedBuffer)), + mChunkedBuffer(mOptionalChunkedBufferStorage.get()) { + AssertInvariants(); + } + + // Use an existing backtrace stored elsewhere, which the user must guarantee + // is alive during the add-marker call. If empty, equivalent to NoStack(). + explicit MarkerStack(ProfileChunkedBuffer& aExternalChunkedBuffer) + : mChunkedBuffer(aExternalChunkedBuffer.IsEmpty() + ? nullptr + : &aExternalChunkedBuffer) { + AssertInvariants(); + } + + // Don't capture a stack in this marker. + static MarkerStack NoStack() { + return MarkerStack(StackCaptureOptions::NoStack); + } + + // Capture a stack when adding this marker. + static MarkerStack Capture( + StackCaptureOptions aCaptureOptions = StackCaptureOptions::Full) { + // Actual capture will be handled inside profiler_add_marker. + return MarkerStack(aCaptureOptions); + } + + // Optionally capture a stack, useful for avoiding long-winded ternaries. + static MarkerStack MaybeCapture(bool aDoCapture) { + return aDoCapture ? Capture() : NoStack(); + } + + // Use an existing backtrace stored elsewhere, which the user must guarantee + // is alive during the add-marker call. If empty, equivalent to NoStack(). + static MarkerStack UseBacktrace( + ProfileChunkedBuffer& aExternalChunkedBuffer) { + return MarkerStack(aExternalChunkedBuffer); + } + + // Take ownership of a backtrace previously captured with + // `profiler_capture_backtrace()`. If null, equivalent to NoStack(). + static MarkerStack TakeBacktrace( + UniquePtr&& aExternalChunkedBuffer) { + return MarkerStack(std::move(aExternalChunkedBuffer)); + } + + // Construct with the given capture options. + static MarkerStack WithCaptureOptions(StackCaptureOptions aCaptureOptions) { + return MarkerStack(aCaptureOptions); + } + + [[nodiscard]] StackCaptureOptions CaptureOptions() const { + return mCaptureOptions; + } + + ProfileChunkedBuffer* GetChunkedBuffer() const { return mChunkedBuffer; } + + // Use backtrace after a request. If null, equivalent to NoStack(). + void UseRequestedBacktrace(ProfileChunkedBuffer* aExternalChunkedBuffer) { + MOZ_RELEASE_ASSERT(mCaptureOptions != StackCaptureOptions::NoStack); + mCaptureOptions = StackCaptureOptions::NoStack; + if (aExternalChunkedBuffer && !aExternalChunkedBuffer->IsEmpty()) { + // We only need to use the provided buffer if it is not empty. + mChunkedBuffer = aExternalChunkedBuffer; + } + AssertInvariants(); + } + + void Clear() { + mCaptureOptions = StackCaptureOptions::NoStack; + mOptionalChunkedBufferStorage.reset(); + mChunkedBuffer = nullptr; + AssertInvariants(); + } + + private: + explicit MarkerStack(StackCaptureOptions aCaptureOptions) + : mCaptureOptions(aCaptureOptions) { + AssertInvariants(); + } + + // This should be called after every constructor and non-const function. + void AssertInvariants() const { +#ifdef DEBUG + if (mCaptureOptions != StackCaptureOptions::NoStack) { + MOZ_ASSERT(!mOptionalChunkedBufferStorage, + "We should not hold a buffer when capture is requested"); + MOZ_ASSERT(!mChunkedBuffer, + "We should not point at a buffer when capture is requested"); + } else { + if (mOptionalChunkedBufferStorage) { + MOZ_ASSERT(mChunkedBuffer == mOptionalChunkedBufferStorage.get(), + "Non-null mOptionalChunkedBufferStorage must be pointed-at " + "by mChunkedBuffer"); + } + if (mChunkedBuffer) { + MOZ_ASSERT(!mChunkedBuffer->IsEmpty(), + "Non-null mChunkedBuffer must not be empty"); + } + } +#endif // DEBUG + } + + StackCaptureOptions mCaptureOptions = StackCaptureOptions::NoStack; + + // Optional storage for the backtrace, in case it was captured before the + // add-marker call. + UniquePtr mOptionalChunkedBufferStorage; + + // If not null, this points to the backtrace. It may point to a backtrace + // temporarily stored on the stack, or to mOptionalChunkedBufferStorage. + ProfileChunkedBuffer* mChunkedBuffer = nullptr; +}; + +// This marker option captures a given inner window id. +class MarkerInnerWindowId { + public: + // Default constructor, it leaves the id unspecified. + constexpr MarkerInnerWindowId() = default; + + // Constructor with a specified inner window id. + constexpr explicit MarkerInnerWindowId(uint64_t i) : mInnerWindowId(i) {} + + // Constructor with either specified inner window id or Nothing. + constexpr explicit MarkerInnerWindowId(const Maybe& i) + : mInnerWindowId(i.valueOr(scNoId)) {} + + // Explicit option with unspecified id. + constexpr static MarkerInnerWindowId NoId() { return MarkerInnerWindowId{}; } + + [[nodiscard]] bool IsUnspecified() const { return mInnerWindowId == scNoId; } + + [[nodiscard]] constexpr uint64_t Id() const { return mInnerWindowId; } + + private: + static constexpr uint64_t scNoId = 0; + uint64_t mInnerWindowId = scNoId; +}; + +// This class combines each of the possible marker options above. +class MarkerOptions { + public: + // Constructor from individual options (including none). + // Implicit to allow `{}` and one option type as-is. + // Options that are not provided here are defaulted. In particular, timing + // defaults to `MarkerTiming::InstantNow()` when the marker is recorded. + template + MOZ_IMPLICIT MarkerOptions(Options&&... aOptions) { + (Set(std::forward(aOptions)), ...); + } + + // Disallow copy. + MarkerOptions(const MarkerOptions&) = delete; + MarkerOptions& operator=(const MarkerOptions&) = delete; + + // Allow move. + MarkerOptions(MarkerOptions&&) = default; + MarkerOptions& operator=(MarkerOptions&&) = default; + + // The embedded `MarkerTiming` hasn't been specified yet. + [[nodiscard]] bool IsTimingUnspecified() const { + return mTiming.IsUnspecified(); + } + + // Each option may be added in a chain by e.g.: + // `options.Set(MarkerThreadId(123)).Set(MarkerTiming::IntervalEnd())`. + // When passed to an add-marker function, it must be an rvalue, either created + // on the spot, or `std::move`d from storage, e.g.: + // `PROFILER_MARKER_UNTYPED("...", std::move(options).Set(...))`; + // + // Options can be read by their name (without "Marker"), e.g.: `o.ThreadId()`. + // Add "Ref" for a non-const reference, e.g.: `o.ThreadIdRef() = ...;` +#define FUNCTIONS_ON_MEMBER(NAME) \ + MarkerOptions& Set(Marker##NAME&& a##NAME)& { \ + m##NAME = std::move(a##NAME); \ + return *this; \ + } \ + \ + MarkerOptions&& Set(Marker##NAME&& a##NAME)&& { \ + m##NAME = std::move(a##NAME); \ + return std::move(*this); \ + } \ + \ + const Marker##NAME& NAME() const { return m##NAME; } \ + \ + Marker##NAME& NAME##Ref() { return m##NAME; } + + FUNCTIONS_ON_MEMBER(ThreadId); + FUNCTIONS_ON_MEMBER(Timing); + FUNCTIONS_ON_MEMBER(Stack); + FUNCTIONS_ON_MEMBER(InnerWindowId); +#undef FUNCTIONS_ON_MEMBER + + private: + friend ProfileBufferEntryReader::Deserializer; + + MarkerThreadId mThreadId; + MarkerTiming mTiming; + MarkerStack mStack; + MarkerInnerWindowId mInnerWindowId; +}; + +} // namespace mozilla + +namespace mozilla::baseprofiler::markers { + +// Default marker payload types, with no extra information, not even a marker +// type and payload. This is intended for label-only markers. +struct NoPayload final {}; + +} // namespace mozilla::baseprofiler::markers + +namespace mozilla { + +class JSONWriter; + +// This class collects all the information necessary to stream the JSON schema +// that informs the front-end how to display a type of markers. +// It will be created and populated in `MarkerTypeDisplay()` functions in each +// marker type definition, see Add/Set functions. +class MarkerSchema { + public: + enum class Location : unsigned { + MarkerChart, + MarkerTable, + // This adds markers to the main marker timeline in the header. + TimelineOverview, + // In the timeline, this is a section that breaks out markers that are + // related to memory. When memory counters are enabled, this is its own + // track, otherwise it is displayed with the main thread. + TimelineMemory, + // This adds markers to the IPC timeline area in the header. + TimelineIPC, + // This adds markers to the FileIO timeline area in the header. + TimelineFileIO, + // TODO - This is not supported yet. + StackChart + }; + + // Used as constructor parameter, to explicitly specify that the location (and + // other display options) are handled as a special case in the front-end. + // In this case, *no* schema will be output for this type. + struct SpecialFrontendLocation {}; + + enum class Format { + // ---------------------------------------------------- + // String types. + + // Show the URL, and handle PII sanitization + Url, + // Show the file path, and handle PII sanitization. + FilePath, + // Important, do not put URL or file path information here, as it will not + // be sanitized. Please be careful with including other types of PII here as + // well. + // e.g. "Label: Some String" + String, + + // ---------------------------------------------------- + // Numeric types + + // For time data that represents a duration of time. + // e.g. "Label: 5s, 5ms, 5μs" + Duration, + // Data that happened at a specific time, relative to the start of the + // profile. e.g. "Label: 15.5s, 20.5ms, 30.5μs" + Time, + // The following are alternatives to display a time only in a specific unit + // of time. + Seconds, // "Label: 5s" + Milliseconds, // "Label: 5ms" + Microseconds, // "Label: 5μs" + Nanoseconds, // "Label: 5ns" + // e.g. "Label: 5.55mb, 5 bytes, 312.5kb" + Bytes, + // This should be a value between 0 and 1. + // "Label: 50%" + Percentage, + // The integer should be used for generic representations of numbers. + // Do not use it for time information. + // "Label: 52, 5,323, 1,234,567" + Integer, + // The decimal should be used for generic representations of numbers. + // Do not use it for time information. + // "Label: 52.23, 0.0054, 123,456.78" + Decimal + }; + + enum class Searchable { NotSearchable, Searchable }; + + // Marker schema, with a non-empty list of locations where markers should be + // shown. + // Tech note: Even though `aLocations` are templated arguments, they are + // assigned to an `enum class` object, so they can only be of that enum type. + template + explicit MarkerSchema(Location aLocation, Locations... aLocations) + : mLocations{aLocation, aLocations...} {} + + // Alternative constructor for MarkerSchema. + explicit MarkerSchema(const mozilla::MarkerSchema::Location* aLocations, + size_t aLength) + : mLocations(aLocations, aLocations + aLength) {} + + // Marker schema for types that have special frontend handling. + // Nothing else should be set in this case. + // Implicit to allow quick return from MarkerTypeDisplay functions. + MOZ_IMPLICIT MarkerSchema(SpecialFrontendLocation) {} + + // Caller must specify location(s) or SpecialFrontendLocation above. + MarkerSchema() = delete; + + // Optional labels in the marker chart, the chart tooltip, and the marker + // table. If not provided, the marker "name" will be used. The given string + // can contain element keys in braces to include data elements streamed by + // `StreamJSONMarkerData()`. E.g.: "This is {text}" + +#define LABEL_SETTER(name) \ + MarkerSchema& Set##name(std::string a##name) { \ + m##name = std::move(a##name); \ + return *this; \ + } + + LABEL_SETTER(ChartLabel) + LABEL_SETTER(TooltipLabel) + LABEL_SETTER(TableLabel) + +#undef LABEL_SETTER + + MarkerSchema& SetAllLabels(std::string aText) { + // Here we set the same text in each label. + // TODO: Move to a single "label" field once the front-end allows it. + SetChartLabel(aText); + SetTooltipLabel(aText); + SetTableLabel(std::move(aText)); + return *this; + } + + // Each data element that is streamed by `StreamJSONMarkerData()` can be + // displayed as indicated by using one of the `Add...` function below. + // Each `Add...` will add a line in the full marker description. Parameters: + // - `aKey`: Element property name as streamed by `StreamJSONMarkerData()`. + // - `aLabel`: Optional prefix. Defaults to the key name. + // - `aFormat`: How to format the data element value, see `Format` above. + // - `aSearchable`: Optional, indicates if the value is used in searches, + // defaults to false. + + MarkerSchema& AddKeyFormat(std::string aKey, Format aFormat) { + mData.emplace_back(mozilla::VariantType{}, + DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat, + mozilla::Nothing{}}); + return *this; + } + + MarkerSchema& AddKeyLabelFormat(std::string aKey, std::string aLabel, + Format aFormat) { + mData.emplace_back( + mozilla::VariantType{}, + DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat, + mozilla::Nothing{}}); + return *this; + } + + MarkerSchema& AddKeyFormatSearchable(std::string aKey, Format aFormat, + Searchable aSearchable) { + mData.emplace_back(mozilla::VariantType{}, + DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat, + mozilla::Some(aSearchable)}); + return *this; + } + + MarkerSchema& AddKeyLabelFormatSearchable(std::string aKey, + std::string aLabel, Format aFormat, + Searchable aSearchable) { + mData.emplace_back( + mozilla::VariantType{}, + DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat, + mozilla::Some(aSearchable)}); + return *this; + } + + // The display may also include static rows. + + MarkerSchema& AddStaticLabelValue(std::string aLabel, std::string aValue) { + mData.emplace_back(mozilla::VariantType{}, + StaticData{std::move(aLabel), std::move(aValue)}); + return *this; + } + + // Internal streaming function. + MFBT_API void Stream(JSONWriter& aWriter, const Span& aName) &&; + + private: + MFBT_API static Span LocationToStringSpan(Location aLocation); + MFBT_API static Span FormatToStringSpan(Format aFormat); + + // List of marker display locations. Empty for SpecialFrontendLocation. + std::vector mLocations; + // Labels for different places. + std::string mChartLabel; + std::string mTooltipLabel; + std::string mTableLabel; + // Main display, made of zero or more rows of key+label+format or label+value. + private: + struct DynamicData { + std::string mKey; + mozilla::Maybe mLabel; + Format mFormat; + mozilla::Maybe mSearchable; + }; + struct StaticData { + std::string mLabel; + std::string mValue; + }; + using DataRow = mozilla::Variant; + using DataRowVector = std::vector; + + DataRowVector mData; +}; + +} // namespace mozilla + +#endif // BaseProfilerMarkersPrerequisites_h diff --git a/mozglue/baseprofiler/public/BaseProfilerRAIIMacro.h b/mozglue/baseprofiler/public/BaseProfilerRAIIMacro.h new file mode 100644 index 0000000000..b89f7e1f77 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerRAIIMacro.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilerRAIIMacro_h +#define BaseProfilerRAIIMacro_h + +// Macros used by the AUTO_PROFILER_* macros to generate unique variable names. +#define PROFILER_RAII_PASTE(id, line) id##line +#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) +#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) + +#endif // BaseProfilerRAIIMacro_h diff --git a/mozglue/baseprofiler/public/BaseProfilerSharedLibraries.h b/mozglue/baseprofiler/public/BaseProfilerSharedLibraries.h new file mode 100644 index 0000000000..705ca55f4b --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerSharedLibraries.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BASE_PROFILER_SHARED_LIBRARIES_H_ +#define BASE_PROFILER_SHARED_LIBRARIES_H_ + +#include "BaseProfiler.h" + +#ifndef MOZ_GECKO_PROFILER +# error Do not #include this header when MOZ_GECKO_PROFILER is not #defined. +#endif + +#include +#include +#include +#include +#include + +class SharedLibrary { + public: + SharedLibrary(uintptr_t aStart, uintptr_t aEnd, uintptr_t aOffset, + const std::string& aBreakpadId, const std::string& aCodeId, + const std::string& aModuleName, const std::string& aModulePath, + const std::string& aDebugName, const std::string& aDebugPath, + const std::string& aVersion, const char* aArch) + : mStart(aStart), + mEnd(aEnd), + mOffset(aOffset), + mBreakpadId(aBreakpadId), + mCodeId(aCodeId), + mModuleName(aModuleName), + mModulePath(aModulePath), + mDebugName(aDebugName), + mDebugPath(aDebugPath), + mVersion(aVersion), + mArch(aArch) {} + + SharedLibrary(const SharedLibrary& aEntry) + : mStart(aEntry.mStart), + mEnd(aEntry.mEnd), + mOffset(aEntry.mOffset), + mBreakpadId(aEntry.mBreakpadId), + mCodeId(aEntry.mCodeId), + mModuleName(aEntry.mModuleName), + mModulePath(aEntry.mModulePath), + mDebugName(aEntry.mDebugName), + mDebugPath(aEntry.mDebugPath), + mVersion(aEntry.mVersion), + mArch(aEntry.mArch) {} + + SharedLibrary& operator=(const SharedLibrary& aEntry) { + // Gracefully handle self assignment + if (this == &aEntry) return *this; + + mStart = aEntry.mStart; + mEnd = aEntry.mEnd; + mOffset = aEntry.mOffset; + mBreakpadId = aEntry.mBreakpadId; + mCodeId = aEntry.mCodeId; + mModuleName = aEntry.mModuleName; + mModulePath = aEntry.mModulePath; + mDebugName = aEntry.mDebugName; + mDebugPath = aEntry.mDebugPath; + mVersion = aEntry.mVersion; + mArch = aEntry.mArch; + return *this; + } + + bool operator==(const SharedLibrary& other) const { + return (mStart == other.mStart) && (mEnd == other.mEnd) && + (mOffset == other.mOffset) && (mModuleName == other.mModuleName) && + (mModulePath == other.mModulePath) && + (mDebugName == other.mDebugName) && + (mDebugPath == other.mDebugPath) && + (mBreakpadId == other.mBreakpadId) && (mCodeId == other.mCodeId) && + (mVersion == other.mVersion) && (mArch == other.mArch); + } + + uintptr_t GetStart() const { return mStart; } + uintptr_t GetEnd() const { return mEnd; } + uintptr_t GetOffset() const { return mOffset; } + const std::string& GetBreakpadId() const { return mBreakpadId; } + const std::string& GetCodeId() const { return mCodeId; } + const std::string& GetModuleName() const { return mModuleName; } + const std::string& GetModulePath() const { return mModulePath; } + const std::string& GetDebugName() const { return mDebugName; } + const std::string& GetDebugPath() const { return mDebugPath; } + const std::string& GetVersion() const { return mVersion; } + const std::string& GetArch() const { return mArch; } + + private: + SharedLibrary() : mStart{0}, mEnd{0}, mOffset{0} {} + + uintptr_t mStart; + uintptr_t mEnd; + uintptr_t mOffset; + std::string mBreakpadId; + // A string carrying an identifier for a binary. + // + // All platforms have different formats: + // - Windows: The code ID for a Windows PE file. + // It's the PE timestamp and PE image size. + // - macOS: The code ID for a macOS / iOS binary (mach-O). + // It's the mach-O UUID without dashes and without the trailing 0 for the + // breakpad ID. + // - Linux/Android: The code ID for a Linux ELF file. + // It's the complete build ID, as hex string. + std::string mCodeId; + std::string mModuleName; + std::string mModulePath; + std::string mDebugName; + std::string mDebugPath; + std::string mVersion; + std::string mArch; +}; + +static bool CompareAddresses(const SharedLibrary& first, + const SharedLibrary& second) { + return first.GetStart() < second.GetStart(); +} + +class SharedLibraryInfo { + public: + static SharedLibraryInfo GetInfoForSelf(); +#ifdef XP_WIN + static SharedLibraryInfo GetInfoFromPath(const wchar_t* aPath); +#endif + + static void Initialize(); + + SharedLibraryInfo() {} + + void AddSharedLibrary(SharedLibrary entry) { mEntries.push_back(entry); } + + const SharedLibrary& GetEntry(size_t i) const { return mEntries[i]; } + + SharedLibrary& GetMutableEntry(size_t i) { return mEntries[i]; } + + // Removes items in the range [first, last) + // i.e. element at the "last" index is not removed + void RemoveEntries(size_t first, size_t last) { + mEntries.erase(mEntries.begin() + first, mEntries.begin() + last); + } + + bool Contains(const SharedLibrary& searchItem) const { + return (mEntries.end() != + std::find(mEntries.begin(), mEntries.end(), searchItem)); + } + + size_t GetSize() const { return mEntries.size(); } + + void SortByAddress() { + std::sort(mEntries.begin(), mEntries.end(), CompareAddresses); + } + + void Clear() { mEntries.clear(); } + + private: +#ifdef XP_WIN + void AddSharedLibraryFromModuleInfo(const wchar_t* aModulePath, + mozilla::Maybe aModule); +#endif + + std::vector mEntries; +}; + +#endif // BASE_PROFILER_SHARED_LIBRARIES_H_ diff --git a/mozglue/baseprofiler/public/BaseProfilerState.h b/mozglue/baseprofiler/public/BaseProfilerState.h new file mode 100644 index 0000000000..15be2743a4 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerState.h @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The Gecko Profiler is an always-on profiler that takes fast and low overhead +// samples of the program execution using only userspace functionality for +// portability. The goal of this module is to provide performance data in a +// generic cross-platform way without requiring custom tools or kernel support. +// +// Samples are collected to form a timeline with optional timeline event +// (markers) used for filtering. The samples include both native stacks and +// platform-independent "label stack" frames. + +#ifndef BaseProfilerState_h +#define BaseProfilerState_h + +// This header contains most functions that give information about the Base +// Profiler: Whether it is active or not, paused, the selected features, and +// some generic process and thread information. +// It is safe to include unconditionally, but uses of structs and functions must +// be guarded by `#ifdef MOZ_GECKO_PROFILER`. + +#include "mozilla/BaseProfilerUtils.h" + +#ifndef MOZ_GECKO_PROFILER + +# define AUTO_PROFILER_STATS(name) + +namespace mozilla::baseprofiler { + +[[nodiscard]] inline bool profiler_is_active() { return false; } +[[nodiscard]] inline bool profiler_is_active_and_unpaused() { return false; } + +} // namespace mozilla::baseprofiler + +#else // !MOZ_GECKO_PROFILER + +# include "mozilla/Atomics.h" +# include "mozilla/Maybe.h" + +# include +# include + +// Uncomment the following line to display profiler runtime statistics at +// shutdown. +// # define PROFILER_RUNTIME_STATS + +# ifdef PROFILER_RUNTIME_STATS +# include "mozilla/TimeStamp.h" +# endif + +namespace mozilla::baseprofiler { + +# ifdef PROFILER_RUNTIME_STATS +// This class gathers durations and displays some basic stats when destroyed. +// It is intended to be used as a static variable (see `AUTO_PROFILER_STATS` +// below), to display stats at the end of the program. +class StaticBaseProfilerStats { + public: + explicit StaticBaseProfilerStats(const char* aName) : mName(aName) {} + + ~StaticBaseProfilerStats() { + // Using unsigned long long for computations and printfs. + using ULL = unsigned long long; + ULL n = static_cast(mNumberDurations); + if (n != 0) { + ULL sumNs = static_cast(mSumDurationsNs); + printf( + "[%d] Profiler stats `%s`: %llu ns / %llu = %llu ns, max %llu ns\n", + int(profiler_current_process_id().ToNumber()), mName, sumNs, n, + sumNs / n, static_cast(mLongestDurationNs)); + } else { + printf("[%d] Profiler stats `%s`: (nothing)\n", + int(profiler_current_process_id().ToNumber()), mName); + } + } + + void AddDurationFrom(TimeStamp aStart) { + DurationNs duration = static_cast( + (TimeStamp::Now() - aStart).ToMicroseconds() * 1000 + 0.5); + mSumDurationsNs += duration; + ++mNumberDurations; + // Update mLongestDurationNs if this one is longer. + for (;;) { + DurationNs longest = mLongestDurationNs; + if (MOZ_LIKELY(longest >= duration)) { + // This duration is not the longest, nothing to do. + break; + } + if (MOZ_LIKELY(mLongestDurationNs.compareExchange(longest, duration))) { + // Successfully updated `mLongestDurationNs` with the new value. + break; + } + // Otherwise someone else just updated `mLongestDurationNs`, we need to + // try again by looping. + } + } + + private: + using DurationNs = uint64_t; + using Count = uint32_t; + + Atomic mSumDurationsNs{0}; + Atomic mLongestDurationNs{0}; + Atomic mNumberDurations{0}; + const char* mName; +}; + +// RAII object that measure its scoped lifetime duration and reports it to a +// `StaticBaseProfilerStats`. +class MOZ_RAII AutoProfilerStats { + public: + explicit AutoProfilerStats(StaticBaseProfilerStats& aStats) + : mStats(aStats), mStart(TimeStamp::Now()) {} + + ~AutoProfilerStats() { mStats.AddDurationFrom(mStart); } + + private: + StaticBaseProfilerStats& mStats; + TimeStamp mStart; +}; + +// Macro that should be used to collect basic statistics from measurements of +// block durations, from where this macro is, until the end of its enclosing +// scope. The name is used in the static variable name and when displaying stats +// at the end of the program; Another location could use the same name but their +// stats will not be combined, so use different name if these locations should +// be distinguished. +# define AUTO_PROFILER_STATS(name) \ + static ::mozilla::baseprofiler::StaticBaseProfilerStats sStat##name( \ + #name); \ + ::mozilla::baseprofiler::AutoProfilerStats autoStat##name(sStat##name); + +# else // PROFILER_RUNTIME_STATS + +# define AUTO_PROFILER_STATS(name) + +# endif // PROFILER_RUNTIME_STATS else + +//--------------------------------------------------------------------------- +// Profiler features +//--------------------------------------------------------------------------- + +# if defined(__APPLE__) && defined(__aarch64__) +# define POWER_HELP "Sample per process power use" +# elif defined(__APPLE__) && defined(__x86_64__) +# define POWER_HELP \ + "Record the power used by the entire system with each sample." +# elif defined(__linux__) && defined(__x86_64__) +# define POWER_HELP \ + "Record the power used by the entire system with each sample. " \ + "Only available with Intel CPUs and requires setting " \ + "the sysctl kernel.perf_event_paranoid to 0." +# elif defined(_MSC_VER) +# define POWER_HELP \ + "Record the value of every energy meter available on the system with " \ + "each sample. Only available on Windows 11 with Intel CPUs." +# else +# define POWER_HELP "Not supported on this platform." +# endif + +// Higher-order macro containing all the feature info in one place. Define +// |MACRO| appropriately to extract the relevant parts. Note that the number +// values are used internally only and so can be changed without consequence. +// Any changes to this list should also be applied to the feature list in +// toolkit/components/extensions/schemas/geckoProfiler.json. +// *** Synchronize with lists in ProfilerState.h and geckoProfiler.json *** +# define BASE_PROFILER_FOR_EACH_FEATURE(MACRO) \ + MACRO(0, "java", Java, "Profile Java code, Android only") \ + \ + MACRO(1, "js", JS, \ + "Get the JS engine to expose the JS stack to the profiler") \ + \ + MACRO(2, "mainthreadio", MainThreadIO, "Add main thread file I/O") \ + \ + MACRO(3, "fileio", FileIO, \ + "Add file I/O from all profiled threads, implies mainthreadio") \ + \ + MACRO(4, "fileioall", FileIOAll, \ + "Add file I/O from all threads, implies fileio") \ + \ + MACRO(5, "nomarkerstacks", NoMarkerStacks, \ + "Markers do not capture stacks, to reduce overhead") \ + \ + MACRO(6, "screenshots", Screenshots, \ + "Take a snapshot of the window on every composition") \ + \ + MACRO(7, "seqstyle", SequentialStyle, \ + "Disable parallel traversal in styling") \ + \ + MACRO(8, "stackwalk", StackWalk, \ + "Walk the C++ stack, not available on all platforms") \ + \ + MACRO(9, "jsallocations", JSAllocations, \ + "Have the JavaScript engine track allocations") \ + \ + MACRO(10, "nostacksampling", NoStackSampling, \ + "Disable all stack sampling: Cancels \"js\", \"stackwalk\" and " \ + "labels") \ + \ + MACRO(11, "nativeallocations", NativeAllocations, \ + "Collect the stacks from a smaller subset of all native " \ + "allocations, biasing towards collecting larger allocations") \ + \ + MACRO(12, "ipcmessages", IPCMessages, \ + "Have the IPC layer track cross-process messages") \ + \ + MACRO(13, "audiocallbacktracing", AudioCallbackTracing, \ + "Audio callback tracing") \ + \ + MACRO(14, "cpu", CPUUtilization, "CPU utilization") \ + \ + MACRO(15, "notimerresolutionchange", NoTimerResolutionChange, \ + "Do not adjust the timer resolution for fast sampling, so that " \ + "other Firefox timers do not get affected") \ + \ + MACRO(16, "cpuallthreads", CPUAllThreads, \ + "Sample the CPU utilization of all registered threads") \ + \ + MACRO(17, "samplingallthreads", SamplingAllThreads, \ + "Sample the stacks of all registered threads") \ + \ + MACRO(18, "markersallthreads", MarkersAllThreads, \ + "Record markers from all registered threads") \ + \ + MACRO(19, "unregisteredthreads", UnregisteredThreads, \ + "Discover and profile unregistered threads -- beware: expensive!") \ + \ + MACRO(20, "processcpu", ProcessCPU, \ + "Sample the CPU utilization of each process") \ + \ + MACRO(21, "power", Power, POWER_HELP) +// *** Synchronize with lists in ProfilerState.h and geckoProfiler.json *** + +struct ProfilerFeature { +# define DECLARE(n_, str_, Name_, desc_) \ + static constexpr uint32_t Name_ = (1u << n_); \ + [[nodiscard]] static constexpr bool Has##Name_(uint32_t aFeatures) { \ + return aFeatures & Name_; \ + } \ + static constexpr void Set##Name_(uint32_t& aFeatures) { \ + aFeatures |= Name_; \ + } \ + static constexpr void Clear##Name_(uint32_t& aFeatures) { \ + aFeatures &= ~Name_; \ + } + + // Define a bitfield constant, a getter, and two setters for each feature. + BASE_PROFILER_FOR_EACH_FEATURE(DECLARE) + +# undef DECLARE +}; + +namespace detail { + +// RacyFeatures is only defined in this header file so that its methods can +// be inlined into profiler_is_active(). Please do not use anything from the +// detail namespace outside the profiler. + +// Within the profiler's code, the preferred way to check profiler activeness +// and features is via ActivePS(). However, that requires locking gPSMutex. +// There are some hot operations where absolute precision isn't required, so we +// duplicate the activeness/feature state in a lock-free manner in this class. +class RacyFeatures { + public: + MFBT_API static void SetActive(uint32_t aFeatures); + + MFBT_API static void SetInactive(); + + MFBT_API static void SetPaused(); + + MFBT_API static void SetUnpaused(); + + MFBT_API static void SetSamplingPaused(); + + MFBT_API static void SetSamplingUnpaused(); + + [[nodiscard]] MFBT_API static mozilla::Maybe FeaturesIfActive() { + if (uint32_t af = sActiveAndFeatures; af & Active) { + // Active, remove the Active&Paused bits to get all features. + return Some(af & ~(Active | Paused | SamplingPaused)); + } + return Nothing(); + } + + [[nodiscard]] MFBT_API static bool IsActive(); + + [[nodiscard]] MFBT_API static bool IsActiveWithFeature(uint32_t aFeature); + + [[nodiscard]] MFBT_API static bool IsActiveWithoutFeature(uint32_t aFeature); + + // True if profiler is active, and not fully paused. + // Note that periodic sampling *could* be paused! + [[nodiscard]] MFBT_API static bool IsActiveAndUnpaused(); + + // True if profiler is active, and sampling is not paused (though generic + // `SetPaused()` or specific `SetSamplingPaused()`). + [[nodiscard]] MFBT_API static bool IsActiveAndSamplingUnpaused(); + + private: + static constexpr uint32_t Active = 1u << 31; + static constexpr uint32_t Paused = 1u << 30; + static constexpr uint32_t SamplingPaused = 1u << 29; + +// Ensure Active/Paused don't overlap with any of the feature bits. +# define NO_OVERLAP(n_, str_, Name_, desc_) \ + static_assert(ProfilerFeature::Name_ != SamplingPaused, \ + "bad feature value"); + + BASE_PROFILER_FOR_EACH_FEATURE(NO_OVERLAP); + +# undef NO_OVERLAP + + // We combine the active bit with the feature bits so they can be read or + // written in a single atomic operation. + // TODO: Could this be MFBT_DATA for better inlining optimization? + static Atomic sActiveAndFeatures; +}; + +MFBT_API bool IsThreadBeingProfiled(); + +} // namespace detail + +//--------------------------------------------------------------------------- +// Get information from the profiler +//--------------------------------------------------------------------------- + +// Is the profiler active? Note: the return value of this function can become +// immediately out-of-date. E.g. the profile might be active but then +// profiler_stop() is called immediately afterward. One common and reasonable +// pattern of usage is the following: +// +// if (profiler_is_active()) { +// ExpensiveData expensiveData = CreateExpensiveData(); +// PROFILER_OPERATION(expensiveData); +// } +// +// where PROFILER_OPERATION is a no-op if the profiler is inactive. In this +// case the profiler_is_active() check is just an optimization -- it prevents +// us calling CreateExpensiveData() unnecessarily in most cases, but the +// expensive data will end up being created but not used if another thread +// stops the profiler between the CreateExpensiveData() and PROFILER_OPERATION +// calls. +[[nodiscard]] inline bool profiler_is_active() { + return baseprofiler::detail::RacyFeatures::IsActive(); +} + +// Same as profiler_is_active(), but also checks if the profiler is not paused. +[[nodiscard]] inline bool profiler_is_active_and_unpaused() { + return baseprofiler::detail::RacyFeatures::IsActiveAndUnpaused(); +} + +// Is the profiler active and unpaused, and is the current thread being +// profiled? (Same caveats and recommented usage as profiler_is_active().) +[[nodiscard]] inline bool profiler_thread_is_being_profiled() { + return baseprofiler::detail::RacyFeatures::IsActiveAndUnpaused() && + baseprofiler::detail::IsThreadBeingProfiled(); +} + +// Is the profiler active and paused? Returns false if the profiler is inactive. +[[nodiscard]] MFBT_API bool profiler_is_paused(); + +// Is the profiler active and sampling is paused? Returns false if the profiler +// is inactive. +[[nodiscard]] MFBT_API bool profiler_is_sampling_paused(); + +// Is the current thread sleeping? +[[nodiscard]] MFBT_API bool profiler_thread_is_sleeping(); + +// Get all the features supported by the profiler that are accepted by +// profiler_start(). The result is the same whether the profiler is active or +// not. +[[nodiscard]] MFBT_API uint32_t profiler_get_available_features(); + +// Returns the full feature set if the profiler is active. +// Note: the return value can become immediately out-of-date, much like the +// return value of profiler_is_active(). +[[nodiscard]] inline mozilla::Maybe profiler_features_if_active() { + return baseprofiler::detail::RacyFeatures::FeaturesIfActive(); +} + +// Check if a profiler feature (specified via the ProfilerFeature type) is +// active. Returns false if the profiler is inactive. Note: the return value +// can become immediately out-of-date, much like the return value of +// profiler_is_active(). +[[nodiscard]] MFBT_API bool profiler_feature_active(uint32_t aFeature); + +// Check if the profiler is active without a feature (specified via the +// ProfilerFeature type). Note: the return value can become immediately +// out-of-date, much like the return value of profiler_is_active(). +[[nodiscard]] MFBT_API bool profiler_active_without_feature(uint32_t aFeature); + +// Returns true if any of the profiler mutexes are currently locked *on the +// current thread*. This may be used by re-entrant code that may call profiler +// functions while the same of a different profiler mutex is locked, which could +// deadlock. +[[nodiscard]] bool profiler_is_locked_on_current_thread(); + +} // namespace mozilla::baseprofiler + +#endif // !MOZ_GECKO_PROFILER + +#endif // BaseProfilerState_h diff --git a/mozglue/baseprofiler/public/BaseProfilerUtils.h b/mozglue/baseprofiler/public/BaseProfilerUtils.h new file mode 100644 index 0000000000..ab02e03b95 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilerUtils.h @@ -0,0 +1,227 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilerUtils_h +#define BaseProfilerUtils_h + +// This header contains most process- and thread-related functions. +// It is safe to include unconditionally. + +// --------------------------------------------- WASI process & thread ids +#if defined(__wasi__) + +namespace mozilla::baseprofiler::detail { +using ProcessIdType = unsigned; +using ThreadIdType = unsigned; +} // namespace mozilla::baseprofiler::detail + +// --------------------------------------------- Windows process & thread ids +#elif defined(XP_WIN) + +namespace mozilla::baseprofiler::detail { +using ProcessIdType = int; +using ThreadIdType = unsigned long; +} // namespace mozilla::baseprofiler::detail + +// --------------------------------------------- Non-Windows process id +#else +// All non-Windows platforms are assumed to be POSIX, which has getpid(). + +# include +namespace mozilla::baseprofiler::detail { +using ProcessIdType = decltype(getpid()); +} // namespace mozilla::baseprofiler::detail + +// --------------------------------------------- Non-Windows thread id +// ------------------------------------------------------- macOS +# if defined(XP_MACOSX) + +namespace mozilla::baseprofiler::detail { +using ThreadIdType = uint64_t; +} // namespace mozilla::baseprofiler::detail + +// ------------------------------------------------------- Android +// Test Android before Linux, because Linux includes Android. +# elif defined(__ANDROID__) || defined(ANDROID) + +# include +namespace mozilla::baseprofiler::detail { +using ThreadIdType = decltype(gettid()); +} // namespace mozilla::baseprofiler::detail + +// ------------------------------------------------------- Linux +# elif defined(XP_LINUX) + +namespace mozilla::baseprofiler::detail { +using ThreadIdType = long; +} // namespace mozilla::baseprofiler::detail + +// ------------------------------------------------------- FreeBSD +# elif defined(XP_FREEBSD) + +namespace mozilla::baseprofiler::detail { +using ThreadIdType = long; +} // namespace mozilla::baseprofiler::detail + +// ------------------------------------------------------- Others +# else + +# include + +namespace mozilla::baseprofiler::detail { +using ThreadIdType = std::thread::id; +} // namespace mozilla::baseprofiler::detail + +# endif +#endif // End of non-XP_WIN. + +#include +#include +#include + +namespace mozilla::baseprofiler { + +// Trivially-copyable class containing a process id. It may be left unspecified. +class BaseProfilerProcessId { + public: + using NativeType = detail::ProcessIdType; + + using NumberType = + std::conditional_t<(sizeof(NativeType) <= 4), uint32_t, uint64_t>; + static_assert(sizeof(NativeType) <= sizeof(NumberType)); + + // Unspecified process id. + constexpr BaseProfilerProcessId() = default; + + [[nodiscard]] constexpr bool IsSpecified() const { + return mProcessId != scUnspecified; + } + + // Construct from a native type. + [[nodiscard]] static BaseProfilerProcessId FromNativeId( + const NativeType& aNativeProcessId) { + BaseProfilerProcessId id; + // Convert trivially-copyable native id by copying its bits. + static_assert(std::is_trivially_copyable_v); + memcpy(&id.mProcessId, &aNativeProcessId, sizeof(NativeType)); + return id; + } + + // Get the process id as a number, which may be unspecified. + // This should only be used for serialization or logging. + [[nodiscard]] constexpr NumberType ToNumber() const { return mProcessId; } + + // BaseProfilerProcessId from given number (which may be unspecified). + constexpr static BaseProfilerProcessId FromNumber( + const NumberType& aProcessId) { + BaseProfilerProcessId id; + id.mProcessId = aProcessId; + return id; + } + + [[nodiscard]] constexpr bool operator==( + const BaseProfilerProcessId& aOther) const { + return mProcessId == aOther.mProcessId; + } + [[nodiscard]] constexpr bool operator!=( + const BaseProfilerProcessId& aOther) const { + return mProcessId != aOther.mProcessId; + } + + private: + static constexpr NumberType scUnspecified = 0; + NumberType mProcessId = scUnspecified; +}; + +// Check traits. These should satisfy usage in std::atomic. +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_copy_constructible_v); +static_assert(std::is_move_constructible_v); +static_assert(std::is_copy_assignable_v); +static_assert(std::is_move_assignable_v); + +// Trivially-copyable class containing a thread id. It may be left unspecified. +class BaseProfilerThreadId { + public: + using NativeType = detail::ThreadIdType; + + using NumberType = + std::conditional_t<(sizeof(NativeType) <= 4), uint32_t, uint64_t>; + static_assert(sizeof(NativeType) <= sizeof(NumberType)); + + // Unspecified thread id. + constexpr BaseProfilerThreadId() = default; + + [[nodiscard]] constexpr bool IsSpecified() const { + return mThreadId != scUnspecified; + } + + // Construct from a native type. + [[nodiscard]] static BaseProfilerThreadId FromNativeId( + const NativeType& aNativeThreadId) { + BaseProfilerThreadId id; + // Convert trivially-copyable native id by copying its bits. + static_assert(std::is_trivially_copyable_v); + memcpy(&id.mThreadId, &aNativeThreadId, sizeof(NativeType)); + return id; + } + + // Get the thread id as a number, which may be unspecified. + // This should only be used for serialization or logging. + [[nodiscard]] constexpr NumberType ToNumber() const { return mThreadId; } + + // BaseProfilerThreadId from given number (which may be unspecified). + constexpr static BaseProfilerThreadId FromNumber( + const NumberType& aThreadId) { + BaseProfilerThreadId id; + id.mThreadId = aThreadId; + return id; + } + + [[nodiscard]] constexpr bool operator==( + const BaseProfilerThreadId& aOther) const { + return mThreadId == aOther.mThreadId; + } + [[nodiscard]] constexpr bool operator!=( + const BaseProfilerThreadId& aOther) const { + return mThreadId != aOther.mThreadId; + } + + private: + static constexpr NumberType scUnspecified = 0; + NumberType mThreadId = scUnspecified; +}; + +// Check traits. These should satisfy usage in std::atomic. +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_copy_constructible_v); +static_assert(std::is_move_constructible_v); +static_assert(std::is_copy_assignable_v); +static_assert(std::is_move_assignable_v); + +} // namespace mozilla::baseprofiler + +#include "mozilla/Types.h" + +namespace mozilla::baseprofiler { + +// Get the current process's ID. +[[nodiscard]] MFBT_API BaseProfilerProcessId profiler_current_process_id(); + +// Get the current thread's ID. +[[nodiscard]] MFBT_API BaseProfilerThreadId profiler_current_thread_id(); + +// Must be called at least once from the main thread, before any other main- +// thread id function. +MFBT_API void profiler_init_main_thread_id(); + +[[nodiscard]] MFBT_API BaseProfilerThreadId profiler_main_thread_id(); + +[[nodiscard]] MFBT_API bool profiler_is_main_thread(); + +} // namespace mozilla::baseprofiler + +#endif // BaseProfilerUtils_h diff --git a/mozglue/baseprofiler/public/BaseProfilingCategory.h b/mozglue/baseprofiler/public/BaseProfilingCategory.h new file mode 100644 index 0000000000..2e80950966 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilingCategory.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilingCategory_h +#define BaseProfilingCategory_h + +#include "mozilla/Types.h" + +#include + +#include "ProfilingCategoryList.h" + +namespace mozilla { +namespace baseprofiler { + +// clang-format off + +// An enum that lists all possible category pairs in one list. +// This is the enum that is used in profiler stack labels. Having one list that +// includes subcategories from all categories in one list allows assigning the +// category pair to a stack label with just one number. +#define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) +#define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) name, +#define CATEGORY_ENUM_END_CATEGORY +enum class ProfilingCategoryPair : uint32_t { + MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY, + CATEGORY_ENUM_SUBCATEGORY, + CATEGORY_ENUM_END_CATEGORY) + COUNT, + LAST = COUNT - 1, +}; +#undef CATEGORY_ENUM_BEGIN_CATEGORY +#undef CATEGORY_ENUM_SUBCATEGORY +#undef CATEGORY_ENUM_END_CATEGORY + +// An enum that lists just the categories without their subcategories. +#define SUPERCATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) name, +#define SUPERCATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) +#define SUPERCATEGORY_ENUM_END_CATEGORY +enum class ProfilingCategory : uint32_t { + MOZ_PROFILING_CATEGORY_LIST(SUPERCATEGORY_ENUM_BEGIN_CATEGORY, + SUPERCATEGORY_ENUM_SUBCATEGORY, + SUPERCATEGORY_ENUM_END_CATEGORY) + COUNT, + LAST = COUNT - 1, +}; +#undef SUPERCATEGORY_ENUM_BEGIN_CATEGORY +#undef SUPERCATEGORY_ENUM_SUBCATEGORY +#undef SUPERCATEGORY_ENUM_END_CATEGORY + +// clang-format on + +struct ProfilingCategoryPairInfo { + ProfilingCategory mCategory; + uint32_t mSubcategoryIndex; + const char* mLabel; +}; + +MFBT_API const ProfilingCategoryPairInfo& GetProfilingCategoryPairInfo( + ProfilingCategoryPair aCategoryPair); + +} // namespace baseprofiler +} // namespace mozilla + +#endif /* BaseProfilingCategory_h */ diff --git a/mozglue/baseprofiler/public/BaseProfilingStack.h b/mozglue/baseprofiler/public/BaseProfilingStack.h new file mode 100644 index 0000000000..2e3781cf17 --- /dev/null +++ b/mozglue/baseprofiler/public/BaseProfilingStack.h @@ -0,0 +1,517 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseProfilingStack_h +#define BaseProfilingStack_h + +#ifndef MOZ_GECKO_PROFILER +# error Do not #include this header when MOZ_GECKO_PROFILER is not #defined. +#endif + +#include "BaseProfilingCategory.h" + +#include "mozilla/Atomics.h" + +#include + +// This file defines the classes ProfilingStack and ProfilingStackFrame. +// The ProfilingStack manages an array of ProfilingStackFrames. +// It keeps track of the "label stack" and the JS interpreter stack. +// The two stack types are interleaved. +// +// Usage: +// +// ProfilingStack* profilingStack = ...; +// +// // For label frames: +// profilingStack->pushLabelFrame(...); +// // Execute some code. When finished, pop the frame: +// profilingStack->pop(); +// +// // For JS stack frames: +// profilingStack->pushJSFrame(...); +// // Execute some code. When finished, pop the frame: +// profilingStack->pop(); +// +// +// Concurrency considerations +// +// A thread's profiling stack (and the frames inside it) is only modified by +// that thread. However, the profiling stack can be *read* by a different +// thread, the sampler thread: Whenever the profiler wants to sample a given +// thread A, the following happens: +// (1) Thread A is suspended. +// (2) The sampler thread (thread S) reads the ProfilingStack of thread A, +// including all ProfilingStackFrames that are currently in that stack +// (profilingStack->frames[0..profilingStack->stackSize()]). +// (3) Thread A is resumed. +// +// Thread suspension is achieved using platform-specific APIs; refer to each +// platform's Sampler::SuspendAndSampleAndResumeThread implementation in +// platform-*.cpp for details. +// +// When the thread is suspended, the values in profilingStack->stackPointer and +// in the stack frame range +// profilingStack->frames[0..profilingStack->stackPointer] need to be in a +// consistent state, so that thread S does not read partially- constructed stack +// frames. More specifically, we have two requirements: +// (1) When adding a new frame at the top of the stack, its ProfilingStackFrame +// data needs to be put in place *before* the stackPointer is incremented, +// and the compiler + CPU need to know that this order matters. +// (2) When popping an frame from the stack and then preparing the +// ProfilingStackFrame data for the next frame that is about to be pushed, +// the decrement of the stackPointer in pop() needs to happen *before* the +// ProfilingStackFrame for the new frame is being popuplated, and the +// compiler + CPU need to know that this order matters. +// +// We can express the relevance of these orderings in multiple ways. +// Option A is to make stackPointer an atomic with SequentiallyConsistent +// memory ordering. This would ensure that no writes in thread A would be +// reordered across any writes to stackPointer, which satisfies requirements +// (1) and (2) at the same time. Option A is the simplest. +// Option B is to use ReleaseAcquire memory ordering both for writes to +// stackPointer *and* for writes to ProfilingStackFrame fields. Release-stores +// ensure that all writes that happened *before this write in program order* are +// not reordered to happen after this write. ReleaseAcquire ordering places no +// requirements on the ordering of writes that happen *after* this write in +// program order. +// Using release-stores for writes to stackPointer expresses requirement (1), +// and using release-stores for writes to the ProfilingStackFrame fields +// expresses requirement (2). +// +// Option B is more complicated than option A, but has much better performance +// on x86/64: In a microbenchmark run on a Macbook Pro from 2017, switching +// from option A to option B reduced the overhead of pushing+popping a +// ProfilingStackFrame by 10 nanoseconds. +// On x86/64, release-stores require no explicit hardware barriers or lock +// instructions. +// On ARM/64, option B may be slower than option A, because the compiler will +// generate hardware barriers for every single release-store instead of just +// for the writes to stackPointer. However, the actual performance impact of +// this has not yet been measured on ARM, so we're currently using option B +// everywhere. This is something that we may want to change in the future once +// we've done measurements. + +namespace mozilla { +namespace baseprofiler { + +// A call stack can be specified to the JS engine such that all JS entry/exits +// to functions push/pop a stack frame to/from the specified stack. +// +// For more detailed information, see vm/GeckoProfiler.h. +// +class ProfilingStackFrame { + // A ProfilingStackFrame represents either a label frame or a JS frame. + + // WARNING WARNING WARNING + // + // All the fields below are Atomic<...,ReleaseAcquire>. This is needed so + // that writes to these fields are release-writes, which ensures that + // earlier writes in this thread don't get reordered after the writes to + // these fields. In particular, the decrement of the stack pointer in + // ProfilingStack::pop() is a write that *must* happen before the values in + // this ProfilingStackFrame are changed. Otherwise, the sampler thread might + // see an inconsistent state where the stack pointer still points to a + // ProfilingStackFrame which has already been popped off the stack and whose + // fields have now been partially repopulated with new values. + // See the "Concurrency considerations" paragraph at the top of this file + // for more details. + + // Descriptive label for this stack frame. Must be a static string! Can be + // an empty string, but not a null pointer. + Atomic label_; + + // An additional descriptive string of this frame which is combined with + // |label_| in profiler output. Need not be (and usually isn't) static. Can + // be null. + Atomic dynamicString_; + + // Stack pointer for non-JS stack frames, the script pointer otherwise. + Atomic spOrScript; + + // ID of the JS Realm for JS stack frames. + // Must not be used on non-JS frames; it'll contain either the default 0, + // or a leftover value from a previous JS stack frame that was using this + // ProfilingStackFrame object. + mozilla::Atomic realmID_; + + // The bytecode offset for JS stack frames. + // Must not be used on non-JS frames; it'll contain either the default 0, + // or a leftover value from a previous JS stack frame that was using this + // ProfilingStackFrame object. + Atomic pcOffsetIfJS_; + + // Bits 0...8 hold the Flags. Bits 9...31 hold the category pair. + Atomic flagsAndCategoryPair_; + + public: + ProfilingStackFrame() = default; + ProfilingStackFrame& operator=(const ProfilingStackFrame& other) { + label_ = other.label(); + dynamicString_ = other.dynamicString(); + void* spScript = other.spOrScript; + spOrScript = spScript; + int32_t offsetIfJS = other.pcOffsetIfJS_; + pcOffsetIfJS_ = offsetIfJS; + int64_t realmID = other.realmID_; + realmID_ = realmID; + uint32_t flagsAndCategory = other.flagsAndCategoryPair_; + flagsAndCategoryPair_ = flagsAndCategory; + return *this; + } + + // Reserve up to 16 bits for flags, and 16 for category pair. + enum class Flags : uint32_t { + // The first three flags describe the kind of the frame and are + // mutually exclusive. (We still give them individual bits for + // simplicity.) + + // A regular label frame. These usually come from AutoProfilerLabel. + IS_LABEL_FRAME = 1 << 0, + + // A special frame indicating the start of a run of JS profiling stack + // frames. IS_SP_MARKER_FRAME frames are ignored, except for the sp + // field. These frames are needed to get correct ordering between JS + // and LABEL frames because JS frames don't carry sp information. + // SP is short for "stack pointer". + IS_SP_MARKER_FRAME = 1 << 1, + + // A JS frame. + IS_JS_FRAME = 1 << 2, + + // An interpreter JS frame that has OSR-ed into baseline. IS_JS_FRAME + // frames can have this flag set and unset during their lifetime. + // JS_OSR frames are ignored. + JS_OSR = 1 << 3, + + // The next three are mutually exclusive. + // By default, for profiling stack frames that have both a label and a + // dynamic string, the two strings are combined into one string of the + // form "