Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/tools/profiler/core/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 88 kB image not shown  

Quelle  ProfileBufferEntry.cpp

  Sprache: C
 

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


#include "ProfileBufferEntry.h"

#include "mozilla/ProfilerMarkers.h"
#include "platform.h"
#include "ProfileBuffer.h"
#include "ProfiledThreadData.h"
#include "ProfilerBacktrace.h"
#include "ProfilerRustBindings.h"

#include "js/ProfilingFrameIterator.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Logging.h"
#include "mozilla/JSONStringWriteFuncs.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StackWalk.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "ProfilerCodeAddressService.h"

#include <ostream>
#include <type_traits>

using namespace mozilla;
using namespace mozilla::literals::ProportionValue_literals;

////////////////////////////////////////////////////////////////////////
// BEGIN ProfileBufferEntry

ProfileBufferEntry::ProfileBufferEntry()
    : mKind(Kind::INVALID), mStorage{0, 0, 0, 0, 0, 0, 0, 0} {}

// aString must be a static string.
ProfileBufferEntry::ProfileBufferEntry(Kind aKind, const char* aString)
    : mKind(aKind) {
  MOZ_ASSERT(aKind == Kind::Label);
  memcpy(mStorage, &aString, sizeof(aString));
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, char aChars[kNumChars])
    : mKind(aKind) {
  MOZ_ASSERT(aKind == Kind::DynamicStringFragment);
  memcpy(mStorage, aChars, kNumChars);
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, void* aPtr) : mKind(aKind) {
  memcpy(mStorage, &aPtr, sizeof(aPtr));
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, double aDouble)
    : mKind(aKind) {
  memcpy(mStorage, &aDouble, sizeof(aDouble));
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, int aInt) : mKind(aKind) {
  memcpy(mStorage, &aInt, sizeof(aInt));
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, int64_t aInt64)
    : mKind(aKind) {
  memcpy(mStorage, &aInt64, sizeof(aInt64));
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, uint64_t aUint64)
    : mKind(aKind) {
  memcpy(mStorage, &aUint64, sizeof(aUint64));
}

ProfileBufferEntry::ProfileBufferEntry(Kind aKind, ProfilerThreadId aThreadId)
    : mKind(aKind) {
  static_assert(std::is_trivially_copyable_v<ProfilerThreadId>);
  static_assert(sizeof(aThreadId) <= sizeof(mStorage));
  memcpy(mStorage, &aThreadId, sizeof(aThreadId));
}

const char* ProfileBufferEntry::GetString() const {
  const char* result;
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

void* ProfileBufferEntry::GetPtr() const {
  void* result;
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

double ProfileBufferEntry::GetDouble() const {
  double result;
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

int ProfileBufferEntry::GetInt() const {
  int result;
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

int64_t ProfileBufferEntry::GetInt64() const {
  int64_t result;
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

uint64_t ProfileBufferEntry::GetUint64() const {
  uint64_t result;
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

ProfilerThreadId ProfileBufferEntry::GetThreadId() const {
  ProfilerThreadId result;
  static_assert(std::is_trivially_copyable_v<ProfilerThreadId>);
  memcpy(&result, mStorage, sizeof(result));
  return result;
}

void ProfileBufferEntry::CopyCharsInto(char (&aOutArray)[kNumChars]) const {
  memcpy(aOutArray, mStorage, kNumChars);
}

// END ProfileBufferEntry
////////////////////////////////////////////////////////////////////////

struct TypeInfo {
  Maybe<nsCString> mKeyedBy;
  Maybe<nsCString> mName;
  Maybe<nsCString> mLocation;
  Maybe<unsigned> mLineNumber;
};

// As mentioned in ProfileBufferEntry.h, the JSON format contains many
// arrays whose elements are laid out according to various schemas to help
// de-duplication. This RAII class helps write these arrays by keeping track of
// the last non-null element written and adding the appropriate number of null
// elements when writing new non-null elements. It also automatically opens and
// closes an array element on the given JSON writer.
//
// You grant the AutoArraySchemaWriter exclusive access to the JSONWriter and
// the UniqueJSONStrings objects for the lifetime of AutoArraySchemaWriter. Do
// not access them independently while the AutoArraySchemaWriter is alive.
// If you need to add complex objects, call FreeFormElement(), which will give
// you temporary access to the writer.
//
// Example usage:
//
//     // Define the schema of elements in this type of array: [FOO, BAR, BAZ]
//     enum Schema : uint32_t {
//       FOO = 0,
//       BAR = 1,
//       BAZ = 2
//     };
//
//     AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings);
//     if (shouldWriteFoo) {
//       writer.IntElement(FOO, getFoo());
//     }
//     ... etc ...
//
//     The elements need to be added in-order.
class MOZ_RAII AutoArraySchemaWriter {
 public:
  explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter)
      : mJSONWriter(aWriter), mNextFreeIndex(0) {
    mJSONWriter.StartArrayElement();
  }

  ~AutoArraySchemaWriter() { mJSONWriter.EndArray(); }

  template <typename T>
  void IntElement(uint32_t aIndex, T aValue) {
    static_assert(!std::is_same_v<T, uint64_t>,
                  "Narrowing uint64 -> int64 conversion not allowed");
    FillUpTo(aIndex);
    mJSONWriter.IntElement(static_cast<int64_t>(aValue));
  }

  void DoubleElement(uint32_t aIndex, double aValue) {
    FillUpTo(aIndex);
    mJSONWriter.DoubleElement(aValue);
  }

  void TimeMsElement(uint32_t aIndex, double aTime_ms) {
    FillUpTo(aIndex);
    mJSONWriter.TimeDoubleMsElement(aTime_ms);
  }

  void BoolElement(uint32_t aIndex, bool aValue) {
    FillUpTo(aIndex);
    mJSONWriter.BoolElement(aValue);
  }

 protected:
  SpliceableJSONWriter& Writer() { return mJSONWriter; }

  void FillUpTo(uint32_t aIndex) {
    MOZ_ASSERT(aIndex >= mNextFreeIndex);
    mJSONWriter.NullElements(aIndex - mNextFreeIndex);
    mNextFreeIndex = aIndex + 1;
  }

 private:
  SpliceableJSONWriter& mJSONWriter;
  uint32_t mNextFreeIndex;
};

// Same as AutoArraySchemaWriter, but this can also write strings (output as
// indexes into the table of unique strings).
class MOZ_RAII AutoArraySchemaWithStringsWriter : public AutoArraySchemaWriter {
 public:
  AutoArraySchemaWithStringsWriter(SpliceableJSONWriter& aWriter,
                                   UniqueJSONStrings& aStrings)
      : AutoArraySchemaWriter(aWriter), mStrings(aStrings) {}

  void StringElement(uint32_t aIndex, const Span<const char>& aValue) {
    FillUpTo(aIndex);
    mStrings.WriteElement(Writer(), aValue);
  }

 private:
  UniqueJSONStrings& mStrings;
};

Maybe<UniqueStacks::StackKey> UniqueStacks::BeginStack(const FrameKey& aFrame) {
  if (Maybe<uint32_t> frameIndex = GetOrAddFrameIndex(aFrame); frameIndex) {
    return Some(StackKey(*frameIndex));
  }
  return Nothing{};
}

Vector<JITFrameInfoForBufferRange>&&
JITFrameInfo::MoveRangesWithNewFailureLatch(FailureLatch& aFailureLatch) && {
  aFailureLatch.SetFailureFrom(mLocalFailureLatchSource);
  return std::move(mRanges);
}

UniquePtr<UniqueJSONStrings>&&
JITFrameInfo::MoveUniqueStringsWithNewFailureLatch(
    FailureLatch& aFailureLatch) && {
  if (mUniqueStrings) {
    mUniqueStrings->ChangeFailureLatchAndForwardState(aFailureLatch);
  } else {
    aFailureLatch.SetFailureFrom(mLocalFailureLatchSource);
  }
  return std::move(mUniqueStrings);
}

Maybe<UniqueStacks::StackKey> UniqueStacks::AppendFrame(
    const StackKey& aStack, const FrameKey& aFrame) {
  if (Maybe<uint32_t> stackIndex = GetOrAddStackIndex(aStack); stackIndex) {
    if (Maybe<uint32_t> frameIndex = GetOrAddFrameIndex(aFrame); frameIndex) {
      return Some(StackKey(aStack, *stackIndex, *frameIndex));
    }
  }
  return Nothing{};
}

JITFrameInfoForBufferRange JITFrameInfoForBufferRange::Clone() const {
  JITFrameInfoForBufferRange::JITAddressToJITFramesMap jitAddressToJITFramesMap;
  MOZ_RELEASE_ASSERT(
      jitAddressToJITFramesMap.reserve(mJITAddressToJITFramesMap.count()));
  for (auto iter = mJITAddressToJITFramesMap.iter(); !iter.done();
       iter.next()) {
    const mozilla::Vector<JITFrameKey>& srcKeys = iter.get().value();
    mozilla::Vector<JITFrameKey> destKeys;
    MOZ_RELEASE_ASSERT(destKeys.appendAll(srcKeys));
    jitAddressToJITFramesMap.putNewInfallible(iter.get().key(),
                                              std::move(destKeys));
  }

  JITFrameInfoForBufferRange::JITFrameToFrameJSONMap jitFrameToFrameJSONMap;
  MOZ_RELEASE_ASSERT(
      jitFrameToFrameJSONMap.reserve(mJITFrameToFrameJSONMap.count()));
  for (auto iter = mJITFrameToFrameJSONMap.iter(); !iter.done(); iter.next()) {
    jitFrameToFrameJSONMap.putNewInfallible(iter.get().key(),
                                            iter.get().value());
  }

  return JITFrameInfoForBufferRange{mRangeStart, mRangeEnd,
                                    std::move(jitAddressToJITFramesMap),
                                    std::move(jitFrameToFrameJSONMap)};
}

JITFrameInfo::JITFrameInfo(const JITFrameInfo& aOther,
                           mozilla::ProgressLogger aProgressLogger)
    : mUniqueStrings(MakeUniqueFallible<UniqueJSONStrings>(
          mLocalFailureLatchSource, *aOther.mUniqueStrings,
          aProgressLogger.CreateSubLoggerFromTo(
              0_pc, "Creating JIT frame info unique strings...", 49_pc,
              "Created JIT frame info unique strings"))) {
  if (!mUniqueStrings) {
    mLocalFailureLatchSource.SetFailure(
        "OOM in JITFrameInfo allocating mUniqueStrings");
    return;
  }

  if (mRanges.reserve(aOther.mRanges.length())) {
    for (auto&& [i, progressLogger] :
         aProgressLogger.CreateLoopSubLoggersFromTo(50_pc, 100_pc,
                                                    aOther.mRanges.length(),
                                                    "Copying JIT frame info")) {
      mRanges.infallibleAppend(aOther.mRanges[i].Clone());
    }
  } else {
    mLocalFailureLatchSource.SetFailure("OOM in JITFrameInfo resizing mRanges");
  }
}

bool UniqueStacks::FrameKey::NormalFrameData::operator==(
    const NormalFrameData& aOther) const {
  return mLocation == aOther.mLocation &&
         mRelevantForJS == aOther.mRelevantForJS &&
         mBaselineInterp == aOther.mBaselineInterp &&
         mInnerWindowID == aOther.mInnerWindowID && mLine == aOther.mLine &&
         mColumn == aOther.mColumn && mCategoryPair == aOther.mCategoryPair;
}

bool UniqueStacks::FrameKey::JITFrameData::operator==(
    const JITFrameData& aOther) const {
  return mCanonicalAddress == aOther.mCanonicalAddress &&
         mDepth == aOther.mDepth && mRangeIndex == aOther.mRangeIndex;
}

// Consume aJITFrameInfo by stealing its string table and its JIT frame info
// ranges. The JIT frame info contains JSON which refers to strings from the
// JIT frame info's string table, so our string table needs to have the same
// strings at the same indices.
UniqueStacks::UniqueStacks(
    FailureLatch& aFailureLatch, JITFrameInfo&& aJITFrameInfo,
    ProfilerCodeAddressService* aCodeAddressService /* = nullptr */)
    : mUniqueStrings(std::move(aJITFrameInfo)
                         .MoveUniqueStringsWithNewFailureLatch(aFailureLatch)),
      mCodeAddressService(aCodeAddressService),
      mFrameTableWriter(aFailureLatch),
      mStackTableWriter(aFailureLatch),
      mJITInfoRanges(std::move(aJITFrameInfo)
                         .MoveRangesWithNewFailureLatch(aFailureLatch)) {
  if (!mUniqueStrings) {
    SetFailure("Did not get mUniqueStrings from JITFrameInfo");
    return;
  }

  mFrameTableWriter.StartBareList();
  mStackTableWriter.StartBareList();
}

Maybe<uint32_t> UniqueStacks::GetOrAddStackIndex(const StackKey& aStack) {
  if (Failed()) {
    return Nothing{};
  }

  uint32_t count = mStackToIndexMap.count();
  auto entry = mStackToIndexMap.lookupForAdd(aStack);
  if (entry) {
    MOZ_ASSERT(entry->value() < count);
    return Some(entry->value());
  }

  if (!mStackToIndexMap.add(entry, aStack, count)) {
    SetFailure("OOM in UniqueStacks::GetOrAddStackIndex");
    return Nothing{};
  }
  StreamStack(aStack);
  return Some(count);
}

Maybe<Vector<UniqueStacks::FrameKey>>
UniqueStacks::LookupFramesForJITAddressFromBufferPos(void* aJITAddress,
                                                     uint64_t aBufferPos) {
  JITFrameInfoForBufferRange* rangeIter =
      std::lower_bound(mJITInfoRanges.begin(), mJITInfoRanges.end(), aBufferPos,
                       [](const JITFrameInfoForBufferRange& aRange,
                          uint64_t aPos) { return aRange.mRangeEnd < aPos; });
  MOZ_RELEASE_ASSERT(
      rangeIter != mJITInfoRanges.end() &&
          rangeIter->mRangeStart <= aBufferPos &&
          aBufferPos < rangeIter->mRangeEnd,
      "Buffer position of jit address needs to be in one of the ranges");

  using JITFrameKey = JITFrameInfoForBufferRange::JITFrameKey;

  const JITFrameInfoForBufferRange& jitFrameInfoRange = *rangeIter;
  auto jitFrameKeys =
      jitFrameInfoRange.mJITAddressToJITFramesMap.lookup(aJITAddress);
  if (!jitFrameKeys) {
    return Nothing();
  }

  // Map the array of JITFrameKeys to an array of FrameKeys, and ensure that
  // each of the FrameKeys exists in mFrameToIndexMap.
  Vector<FrameKey> frameKeys;
  MOZ_RELEASE_ASSERT(frameKeys.initCapacity(jitFrameKeys->value().length()));
  for (const JITFrameKey& jitFrameKey : jitFrameKeys->value()) {
    FrameKey frameKey(jitFrameKey.mCanonicalAddress, jitFrameKey.mDepth,
                      rangeIter - mJITInfoRanges.begin());
    uint32_t index = mFrameToIndexMap.count();
    auto entry = mFrameToIndexMap.lookupForAdd(frameKey);
    if (!entry) {
      // We need to add this frame to our frame table. The JSON for this frame
      // already exists in jitFrameInfoRange, we just need to splice it into
      // the frame table and give it an index.
      auto frameJSON =
          jitFrameInfoRange.mJITFrameToFrameJSONMap.lookup(jitFrameKey);
      MOZ_RELEASE_ASSERT(frameJSON, "Should have cached JSON for this frame");
      mFrameTableWriter.Splice(frameJSON->value());
      MOZ_RELEASE_ASSERT(mFrameToIndexMap.add(entry, frameKey, index));
    }
    MOZ_RELEASE_ASSERT(frameKeys.append(std::move(frameKey)));
  }
  return Some(std::move(frameKeys));
}

Maybe<uint32_t> UniqueStacks::GetOrAddFrameIndex(const FrameKey& aFrame) {
  if (Failed()) {
    return Nothing{};
  }

  uint32_t count = mFrameToIndexMap.count();
  auto entry = mFrameToIndexMap.lookupForAdd(aFrame);
  if (entry) {
    MOZ_ASSERT(entry->value() < count);
    return Some(entry->value());
  }

  if (!mFrameToIndexMap.add(entry, aFrame, count)) {
    SetFailure("OOM in UniqueStacks::GetOrAddFrameIndex");
    return Nothing{};
  }
  StreamNonJITFrame(aFrame);
  return Some(count);
}

void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter) {
  mFrameTableWriter.EndBareList();
  aWriter.TakeAndSplice(mFrameTableWriter.TakeChunkedWriteFunc());
}

void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter) {
  mStackTableWriter.EndBareList();
  aWriter.TakeAndSplice(mStackTableWriter.TakeChunkedWriteFunc());
}

[[nodiscard]] nsAutoCString UniqueStacks::FunctionNameOrAddress(void* aPC) {
  nsAutoCString nameOrAddress;

  if (!mCodeAddressService ||
      !mCodeAddressService->GetFunction(aPC, nameOrAddress) ||
      nameOrAddress.IsEmpty()) {
    nameOrAddress.AppendASCII("0x");
    // `AppendInt` only knows `uint32_t` or `uint64_t`, but because these are
    // just aliases for *two* of (`unsigned`, `unsigned long`, and `unsigned
    // long long`), a call with `uintptr_t` could use the third type and
    // therefore would be ambiguous.
    // So we want to force using exactly `uint32_t` or `uint64_t`, whichever
    // matches the size of `uintptr_t`.
    // (The outer cast to `uint` should then be a no-op.)
    using uint = std::conditional_t<sizeof(uintptr_t) <= sizeof(uint32_t),
                                    uint32_t, uint64_t>;
    nameOrAddress.AppendInt(static_cast<uint>(reinterpret_cast<uintptr_t>(aPC)),
                            16);
  }

  return nameOrAddress;
}

void UniqueStacks::StreamStack(const StackKey& aStack) {
  enum Schema : uint32_t { PREFIX = 0, FRAME = 1 };

  AutoArraySchemaWriter writer(mStackTableWriter);
  if (aStack.mPrefixStackIndex.isSome()) {
    writer.IntElement(PREFIX, *aStack.mPrefixStackIndex);
  }
  writer.IntElement(FRAME, aStack.mFrameIndex);
}

void UniqueStacks::StreamNonJITFrame(const FrameKey& aFrame) {
  if (Failed()) {
    return;
  }

  using NormalFrameData = FrameKey::NormalFrameData;

  enum Schema : uint32_t {
    LOCATION = 0,
    RELEVANT_FOR_JS = 1,
    INNER_WINDOW_ID = 2,
    IMPLEMENTATION = 3,
    LINE = 4,
    COLUMN = 5,
    CATEGORY = 6,
    SUBCATEGORY = 7
  };

  AutoArraySchemaWithStringsWriter writer(mFrameTableWriter, *mUniqueStrings);

  const NormalFrameData& data = aFrame.mData.as<NormalFrameData>();
  writer.StringElement(LOCATION, data.mLocation);
  writer.BoolElement(RELEVANT_FOR_JS, data.mRelevantForJS);

  // It's okay to convert uint64_t to double here because DOM always creates IDs
  // that are convertible to double.
  writer.DoubleElement(INNER_WINDOW_ID, data.mInnerWindowID);

  // The C++ interpreter is the default implementation so we only emit element
  // for Baseline Interpreter frames.
  if (data.mBaselineInterp) {
    writer.StringElement(IMPLEMENTATION, MakeStringSpan("blinterp"));
  }

  if (data.mLine.isSome()) {
    writer.IntElement(LINE, *data.mLine);
  }
  if (data.mColumn.isSome()) {
    writer.IntElement(COLUMN, *data.mColumn);
  }
  if (data.mCategoryPair.isSome()) {
    const JS::ProfilingCategoryPairInfo& info =
        JS::GetProfilingCategoryPairInfo(*data.mCategoryPair);
    writer.IntElement(CATEGORY, uint32_t(info.mCategory));
    writer.IntElement(SUBCATEGORY, info.mSubcategoryIndex);
  }
}

static void StreamJITFrame(JSContext* aContext, SpliceableJSONWriter& aWriter,
                           UniqueJSONStrings& aUniqueStrings,
                           const JS::ProfiledFrameHandle& aJITFrame) {
  enum Schema : uint32_t {
    LOCATION = 0,
    RELEVANT_FOR_JS = 1,
    INNER_WINDOW_ID = 2,
    IMPLEMENTATION = 3,
    LINE = 4,
    COLUMN = 5,
    CATEGORY = 6,
    SUBCATEGORY = 7
  };

  AutoArraySchemaWithStringsWriter writer(aWriter, aUniqueStrings);

  writer.StringElement(LOCATION, MakeStringSpan(aJITFrame.label()));
  writer.BoolElement(RELEVANT_FOR_JS, false);

  // It's okay to convert uint64_t to double here because DOM always creates IDs
  // that are convertible to double.
  // Realm ID is the name of innerWindowID inside JS code.
  writer.DoubleElement(INNER_WINDOW_ID, aJITFrame.realmID());

  JS::ProfilingFrameIterator::FrameKind frameKind = aJITFrame.frameKind();
  MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
             frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
  writer.StringElement(IMPLEMENTATION,
                       frameKind == JS::ProfilingFrameIterator::Frame_Ion
                           ? MakeStringSpan("ion")
                           : MakeStringSpan("baseline"));

  const JS::ProfilingCategoryPairInfo& info = JS::GetProfilingCategoryPairInfo(
      frameKind == JS::ProfilingFrameIterator::Frame_Ion
          ? JS::ProfilingCategoryPair::JS_IonMonkey
          : JS::ProfilingCategoryPair::JS_Baseline);
  writer.IntElement(CATEGORY, uint32_t(info.mCategory));
  writer.IntElement(SUBCATEGORY, info.mSubcategoryIndex);
}

static nsCString JSONForJITFrame(JSContext* aContext,
                                 const JS::ProfiledFrameHandle& aJITFrame,
                                 UniqueJSONStrings& aUniqueStrings) {
  nsCString json;
  JSONStringRefWriteFunc jw(json);
  SpliceableJSONWriter writer(jw, aUniqueStrings.SourceFailureLatch());
  StreamJITFrame(aContext, writer, aUniqueStrings, aJITFrame);
  return json;
}

void JITFrameInfo::AddInfoForRange(
    uint64_t aRangeStart, uint64_t aRangeEnd, JSContext* aCx,
    const std::function<void(const std::function<void(void*)>&)>&
        aJITAddressProvider) {
  if (mLocalFailureLatchSource.Failed()) {
    return;
  }

  if (aRangeStart == aRangeEnd) {
    return;
  }

  MOZ_RELEASE_ASSERT(aRangeStart < aRangeEnd);

  if (!mRanges.empty()) {
    const JITFrameInfoForBufferRange& prevRange = mRanges.back();
    MOZ_RELEASE_ASSERT(prevRange.mRangeEnd <= aRangeStart,
                       "Ranges must be non-overlapping and added in-order.");
  }

  using JITFrameKey = JITFrameInfoForBufferRange::JITFrameKey;

  JITFrameInfoForBufferRange::JITAddressToJITFramesMap jitAddressToJITFrameMap;
  JITFrameInfoForBufferRange::JITFrameToFrameJSONMap jitFrameToFrameJSONMap;

  aJITAddressProvider([&](void* aJITAddress) {
    // Make sure that we have cached data for aJITAddress.
    auto addressEntry = jitAddressToJITFrameMap.lookupForAdd(aJITAddress);
    if (!addressEntry) {
      Vector<JITFrameKey> jitFrameKeys;
      for (JS::ProfiledFrameHandle handle :
           JS::GetProfiledFrames(aCx, aJITAddress)) {
        uint32_t depth = jitFrameKeys.length();
        JITFrameKey jitFrameKey{handle.canonicalAddress(), depth};
        auto frameEntry = jitFrameToFrameJSONMap.lookupForAdd(jitFrameKey);
        if (!frameEntry) {
          if (!jitFrameToFrameJSONMap.add(
                  frameEntry, jitFrameKey,
                  JSONForJITFrame(aCx, handle, *mUniqueStrings))) {
            mLocalFailureLatchSource.SetFailure(
                "OOM in JITFrameInfo::AddInfoForRange adding jit->frame map");
            return;
          }
        }
        if (!jitFrameKeys.append(jitFrameKey)) {
          mLocalFailureLatchSource.SetFailure(
              "OOM in JITFrameInfo::AddInfoForRange adding jit frame key");
          return;
        }
      }
      if (!jitAddressToJITFrameMap.add(addressEntry, aJITAddress,
                                       std::move(jitFrameKeys))) {
        mLocalFailureLatchSource.SetFailure(
            "OOM in JITFrameInfo::AddInfoForRange adding addr->jit map");
        return;
      }
    }
  });

  if (!mRanges.append(JITFrameInfoForBufferRange{
          aRangeStart, aRangeEnd, std::move(jitAddressToJITFrameMap),
          std::move(jitFrameToFrameJSONMap)})) {
    mLocalFailureLatchSource.SetFailure(
        "OOM in JITFrameInfo::AddInfoForRange adding range");
    return;
  }
}

struct ProfileSample {
  uint32_t mStack = 0;
  double mTime = 0.0;
  Maybe<double> mResponsiveness;
  RunningTimes mRunningTimes;
};

// Write CPU measurements with "Delta" unit, which is some amount of work that
// happened since the previous sample.
static void WriteDelta(AutoArraySchemaWriter& aSchemaWriter, uint32_t aProperty,
                       uint64_t aDelta) {
  aSchemaWriter.IntElement(aProperty, int64_t(aDelta));
}

static void WriteSample(SpliceableJSONWriter& aWriter,
                        const ProfileSample& aSample) {
  enum Schema : uint32_t {
    STACK = 0,
    TIME = 1,
    EVENT_DELAY = 2
#define RUNNING_TIME_SCHEMA(index, name, unit, jsonProperty) , name
    PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SCHEMA)
#undef RUNNING_TIME_SCHEMA
  };

  AutoArraySchemaWriter writer(aWriter);

  writer.IntElement(STACK, aSample.mStack);

  writer.TimeMsElement(TIME, aSample.mTime);

  if (aSample.mResponsiveness.isSome()) {
    writer.DoubleElement(EVENT_DELAY, *aSample.mResponsiveness);
  }

#define RUNNING_TIME_STREAM(index, name, unit, jsonProperty) \
  aSample.mRunningTimes.GetJson##name##unit().apply(         \
      [&writer](const uint64_t& aValue) {                    \
        Write##unit(writer, name, aValue);                   \
      });

  PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_STREAM)

#undef RUNNING_TIME_STREAM
}

static void StreamMarkerAfterKind(
    ProfileBufferEntryReader& aER,
    ProcessStreamingContext& aProcessStreamingContext) {
  ThreadStreamingContext* threadData = nullptr;
  mozilla::base_profiler_markers_detail::DeserializeAfterKindAndStream(
      aER,
      [&](ProfilerThreadId aThreadId) -> baseprofiler::SpliceableJSONWriter* {
        threadData =
            aProcessStreamingContext.GetThreadStreamingContext(aThreadId);
        return threadData ? &threadData->mMarkersDataWriter : nullptr;
      },
      [&](ProfileChunkedBuffer& aChunkedBuffer) {
        ProfilerBacktrace backtrace("", &aChunkedBuffer);
        MOZ_ASSERT(threadData,
                   "threadData should have been set before calling here");
        backtrace.StreamJSON(threadData->mMarkersDataWriter,
                             aProcessStreamingContext.ProcessStartTime(),
                             *threadData->mUniqueStacks);
      },
      [&](mozilla::base_profiler_markers_detail::Streaming::DeserializerTag
              aTag) {
        MOZ_ASSERT(threadData,
                   "threadData should have been set before calling here");

        size_t payloadSize = aER.RemainingBytes();

        ProfileBufferEntryReader::DoubleSpanOfConstBytes spans =
            aER.ReadSpans(payloadSize);
        if (MOZ_LIKELY(spans.IsSingleSpan())) {
          // Only a single span, we can just refer to it directly
          // instead of copying it.
          profiler::ffi::gecko_profiler_serialize_marker_for_tag(
              aTag, spans.mFirstOrOnly.Elements(), payloadSize,
              &threadData->mMarkersDataWriter);
        } else {
          // Two spans, we need to concatenate them by copying.
          uint8_t* payloadBuffer = new uint8_t[payloadSize];
          spans.CopyBytesTo(payloadBuffer);
          profiler::ffi::gecko_profiler_serialize_marker_for_tag(
              aTag, payloadBuffer, payloadSize,
              &threadData->mMarkersDataWriter);
          delete[] payloadBuffer;
        }
      });
}

class EntryGetter {
 public:
  explicit EntryGetter(
      ProfileChunkedBuffer::Reader& aReader,
      mozilla::FailureLatch& aFailureLatch,
      mozilla::ProgressLogger aProgressLogger = {},
      uint64_t aInitialReadPos = 0,
      ProcessStreamingContext* aStreamingContextForMarkers = nullptr)
      : mFailureLatch(aFailureLatch),
        mStreamingContextForMarkers(aStreamingContextForMarkers),
        mBlockIt(
            aReader.At(ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
                aInitialReadPos))),
        mBlockItEnd(aReader.end()),
        mRangeStart(mBlockIt.BufferRangeStart().ConvertToProfileBufferIndex()),
        mRangeSize(
            double(mBlockIt.BufferRangeEnd().ConvertToProfileBufferIndex() -
                   mRangeStart)),
        mProgressLogger(std::move(aProgressLogger)) {
    SetLocalProgress(ProgressLogger::NO_LOCATION_UPDATE);
    if (!ReadLegacyOrEnd()) {
      // Find and read the next non-legacy entry.
      Next();
    }
  }

  bool Has() const {
    return (!mFailureLatch.Failed()) && (mBlockIt != mBlockItEnd);
  }

  const ProfileBufferEntry& Get() const {
    MOZ_ASSERT(Has() || mFailureLatch.Failed(),
               "Caller should have checked `Has()` before `Get()`");
    return mEntry;
  }

  void Next() {
    MOZ_ASSERT(Has() || mFailureLatch.Failed(),
               "Caller should have checked `Has()` before `Next()`");
    ++mBlockIt;
    ReadUntilLegacyOrEnd();
  }

  // Hand off the current iterator to the caller, which may be used to read
  // any kind of entries (legacy or modern).
  ProfileChunkedBuffer::BlockIterator Iterator() const { return mBlockIt; }

  // After `Iterator()` was used, we can restart from *after* its updated
  // position.
  void RestartAfter(const ProfileChunkedBuffer::BlockIterator& it) {
    mBlockIt = it;
    if (!Has()) {
      return;
    }
    Next();
  }

  ProfileBufferBlockIndex CurBlockIndex() const {
    return mBlockIt.CurrentBlockIndex();
  }

  uint64_t CurPos() const {
    return CurBlockIndex().ConvertToProfileBufferIndex();
  }

  void SetLocalProgress(const char* aLocation) {
    mProgressLogger.SetLocalProgress(
        ProportionValue{double(CurBlockIndex().ConvertToProfileBufferIndex() -
                               mRangeStart) /
                        mRangeSize},
        aLocation);
  }

 private:
  // Try to read the entry at the current `mBlockIt` position.
  // * If we're at the end of the buffer, just return `true`.
  // * If there is a "legacy" entry (containing a real `ProfileBufferEntry`),
  //   read it into `mEntry`, and return `true` as well.
  // * Otherwise the entry contains a "modern" type that cannot be read into
  // `mEntry`, return `false` (so `EntryGetter` can skip to another entry).
  bool ReadLegacyOrEnd() {
    if (!Has()) {
      return true;
    }
    // Read the entry "kind", which is always at the start of all entries.
    ProfileBufferEntryReader er = *mBlockIt;
    auto type = static_cast<ProfileBufferEntry::Kind>(
        er.ReadObject<ProfileBufferEntry::KindUnderlyingType>());
    MOZ_ASSERT(static_cast<ProfileBufferEntry::KindUnderlyingType>(type) <
               static_cast<ProfileBufferEntry::KindUnderlyingType>(
                   ProfileBufferEntry::Kind::MODERN_LIMIT));
    if (type >= ProfileBufferEntry::Kind::LEGACY_LIMIT) {
      if (type == ProfileBufferEntry::Kind::Marker &&
          mStreamingContextForMarkers) {
        StreamMarkerAfterKind(er, *mStreamingContextForMarkers);
        if (!Has()) {
          return true;
        }
        SetLocalProgress("Processed marker");
      }
      er.SetRemainingBytes(0);
      return false;
    }
    // Here, we have a legacy item, we need to read it from the start.
    // Because the above `ReadObject` moved the reader, we ned to reset it to
    // the start of the entry before reading the whole entry.
    er = *mBlockIt;
    er.ReadBytes(&mEntry, er.RemainingBytes());
    return true;
  }

  void ReadUntilLegacyOrEnd() {
    for (;;) {
      if (ReadLegacyOrEnd()) {
        // Either we're at the end, or we could read a legacy entry -> Done.
        break;
      }
      // Otherwise loop around until we hit a legacy entry or the end.
      ++mBlockIt;
    }
    SetLocalProgress(ProgressLogger::NO_LOCATION_UPDATE);
  }

  mozilla::FailureLatch& mFailureLatch;

  ProcessStreamingContext* const mStreamingContextForMarkers;

  ProfileBufferEntry mEntry;
  ProfileChunkedBuffer::BlockIterator mBlockIt;
  const ProfileChunkedBuffer::BlockIterator mBlockItEnd;

  // Progress logger, and the data needed to compute the current relative
  // position in the buffer.
  const mozilla::ProfileBufferIndex mRangeStart;
  const double mRangeSize;
  mozilla::ProgressLogger mProgressLogger;
};

// The following grammar shows legal sequences of profile buffer entries.
// The sequences beginning with a ThreadId entry are known as "samples".
//
// (
//   ( /* Samples */
//     ThreadId
//     TimeBeforeCompactStack
//     RunningTimes?
//     UnresponsivenessDurationMs?
//     CompactStack
//         /* internally including:
//           ( NativeLeafAddr
//           | Label FrameFlags? DynamicStringFragment*
//             LineNumber? CategoryPair?
//           | JitReturnAddr
//           )+
//         */
//   )
//   | ( /* Reference to a previous identical sample */
//       ThreadId
//       TimeBeforeSameSample
//       RunningTimes?
//       SameSample
//     )
//   | Marker
//   | ( /* Counters */
//       CounterId
//       Time
//       (
//         CounterKey
//         Count
//         Number?
//       )*
//     )
//   | CollectionStart
//   | CollectionEnd
//   | Pause
//   | Resume
//   | ( ProfilerOverheadTime /* Sampling start timestamp */
//       ProfilerOverheadDuration /* Lock acquisition */
//       ProfilerOverheadDuration /* Expired markers cleaning */
//       ProfilerOverheadDuration /* Counters */
//       ProfilerOverheadDuration /* Threads */
//     )
// )*
//
// The most complicated part is the stack entry sequence that begins with
// Label. Here are some examples.
//
// - ProfilingStack frames without a dynamic string:
//
//     Label("js::RunScript")
//     CategoryPair(JS::ProfilingCategoryPair::JS)
//
//     Label("XREMain::XRE_main")
//     LineNumber(4660)
//     CategoryPair(JS::ProfilingCategoryPair::OTHER)
//
//     Label("ElementRestyler::ComputeStyleChangeFor")
//     LineNumber(3003)
//     CategoryPair(JS::ProfilingCategoryPair::CSS)
//
// - ProfilingStack frames with a dynamic string:
//
//     Label("nsObserverService::NotifyObservers")
//     FrameFlags(uint64_t(ProfilingStackFrame::Flags::IS_LABEL_FRAME))
//     DynamicStringFragment("domwindo")
//     DynamicStringFragment("wopened")
//     LineNumber(291)
//     CategoryPair(JS::ProfilingCategoryPair::OTHER)
//
//     Label("")
//     FrameFlags(uint64_t(ProfilingStackFrame::Flags::IS_JS_FRAME))
//     DynamicStringFragment("closeWin")
//     DynamicStringFragment("dow (chr")
//     DynamicStringFragment("ome://gl")
//     DynamicStringFragment("obal/con")
//     DynamicStringFragment("tent/glo")
//     DynamicStringFragment("balOverl")
//     DynamicStringFragment("ay.js:5)")
//     DynamicStringFragment("")          # this string holds the closing '\0'
//     LineNumber(25)
//     CategoryPair(JS::ProfilingCategoryPair::JS)
//
//     Label("")
//     FrameFlags(uint64_t(ProfilingStackFrame::Flags::IS_JS_FRAME))
//     DynamicStringFragment("bound (s")
//     DynamicStringFragment("elf-host")
//     DynamicStringFragment("ed:914)")
//     LineNumber(945)
//     CategoryPair(JS::ProfilingCategoryPair::JS)
//
// - A profiling stack frame with an overly long dynamic string:
//
//     Label("")
//     FrameFlags(uint64_t(ProfilingStackFrame::Flags::IS_LABEL_FRAME))
//     DynamicStringFragment("(too lon")
//     DynamicStringFragment("g)")
//     LineNumber(100)
//     CategoryPair(JS::ProfilingCategoryPair::NETWORK)
//
// - A wasm JIT frame:
//
//     Label("")
//     FrameFlags(uint64_t(0))
//     DynamicStringFragment("wasm-fun")
//     DynamicStringFragment("ction[87")
//     DynamicStringFragment("36] (blo")
//     DynamicStringFragment("b:http:/")
//     DynamicStringFragment("/webasse")
//     DynamicStringFragment("mbly.org")
//     DynamicStringFragment("/3dc5759")
//     DynamicStringFragment("4-ce58-4")
//     DynamicStringFragment("626-975b")
//     DynamicStringFragment("-08ad116")
//     DynamicStringFragment("30bc1:38")
//     DynamicStringFragment("29856)")
//
// - A JS frame in a synchronous sample:
//
//     Label("")
//     FrameFlags(uint64_t(ProfilingStackFrame::Flags::IS_LABEL_FRAME))
//     DynamicStringFragment("u (https")
//     DynamicStringFragment("://perf-")
//     DynamicStringFragment("html.io/")
//     DynamicStringFragment("ac0da204")
//     DynamicStringFragment("aaa44d75")
//     DynamicStringFragment("a800.bun")
//     DynamicStringFragment("dle.js:2")
//     DynamicStringFragment("5)")

// Because this is a format entirely internal to the Profiler, any parsing
// error indicates a bug in the ProfileBuffer writing or the parser itself,
// or possibly flaky hardware.
#define ERROR_AND_CONTINUE(msg)                            \
  {                                                        \
    fprintf(stderr, "ProfileBuffer parse error: %s", msg); \
    MOZ_ASSERT(false, msg);                                \
    continue;                                              \
  }

struct StreamingParametersForThread {
  SpliceableJSONWriter& mWriter;
  UniqueStacks& mUniqueStacks;
  ThreadStreamingContext::PreviousStackState& mPreviousStackState;
  uint32_t& mPreviousStack;

  StreamingParametersForThread(
      SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks,
      ThreadStreamingContext::PreviousStackState& aPreviousStackState,
      uint32_t& aPreviousStack)
      : mWriter(aWriter),
        mUniqueStacks(aUniqueStacks),
        mPreviousStackState(aPreviousStackState),
        mPreviousStack(aPreviousStack) {}
};

#ifdef MOZ_EXECUTION_TRACING

template <typename GetStreamingParametersForThreadCallback>
void ProfileBuffer::MaybeStreamExecutionTraceToJSON(
    GetStreamingParametersForThreadCallback&&
        aGetStreamingParametersForThreadCallback,
    double aSinceTime) const {
  JS::ExecutionTrace trace;
  if (!JS_TracerSnapshotTrace(trace)) {
    return;
  }

  for (const JS::ExecutionTrace::TracedJSContext& context : trace.contexts) {
    Maybe<StreamingParametersForThread> streamingParameters =
        std::forward<GetStreamingParametersForThreadCallback>(
            aGetStreamingParametersForThreadCallback)(context.id);

    // Ignore samples that are for the wrong thread.
    if (!streamingParameters) {
      continue;
    }

    SpliceableJSONWriter& writer = streamingParameters->mWriter;
    UniqueStacks& uniqueStacks = streamingParameters->mUniqueStacks;

    mozilla::Vector<UniqueStacks::StackKey> frameStack;

    Maybe<UniqueStacks::StackKey> maybeStack =
        uniqueStacks.BeginStack(UniqueStacks::FrameKey("(root)"));
    if (!maybeStack) {
      writer.SetFailure("BeginStack failure");
      continue;
    }

    UniqueStacks::StackKey stack = *maybeStack;
    if (!frameStack.append(stack)) {
      writer.SetFailure("frameStack append failure");
      continue;
    }

    for (const JS::ExecutionTrace::TracedEvent& event : context.events) {
      if (event.time < aSinceTime) {
        continue;
      }

      if (event.kind == JS::ExecutionTrace::EventKind::Error) {
        writer.SetFailure("Error during tracing (likely OOM)");
        continue;
      }

      if (event.kind == JS::ExecutionTrace::EventKind::FunctionEnter) {
        HashMap<uint32_t, size_t>::Ptr functionName =
            context.atoms.lookup(event.functionEvent.functionNameId);
        // This is uncommon, but if one of our ring buffers wraps around, we
        // can end up with missing function name entries
        const char* functionNameStr = "<expired>";
        if (functionName) {
          functionNameStr = &trace.stringBuffer[functionName->value()];
        }
        HashMap<uint32_t, size_t>::Ptr scriptUrl =
            context.scriptUrls.lookup(event.functionEvent.scriptId);
        // See the comment above functionNameStr
        const char* scriptUrlStr = "<expired>";
        if (scriptUrl) {
          scriptUrlStr = &trace.stringBuffer[scriptUrl->value()];
        }
        nsAutoCStringN<1024> name(functionNameStr);
        name.AppendPrintf(" (%s:%u:%u)", scriptUrlStr,
                          event.functionEvent.lineNumber,
                          event.functionEvent.column);
        JS::ProfilingCategoryPair categoryPair;
        switch (event.functionEvent.implementation) {
          case JS::ExecutionTrace::ImplementationType::Interpreter:
            categoryPair = JS::ProfilingCategoryPair::JS;
            break;
          case JS::ExecutionTrace::ImplementationType::Baseline:
            categoryPair = JS::ProfilingCategoryPair::JS_Baseline;
            break;
          case JS::ExecutionTrace::ImplementationType::Ion:
            categoryPair = JS::ProfilingCategoryPair::JS_IonMonkey;
            break;
          case JS::ExecutionTrace::ImplementationType::Wasm:
            categoryPair = JS::ProfilingCategoryPair::JS_WasmOther;
            break;
        }

        UniqueStacks::FrameKey newFrame(nsCString(name.get()), truefalse,
                                        event.functionEvent.realmID, Nothing{},
                                        Nothing{}, Some(categoryPair));
        maybeStack = uniqueStacks.AppendFrame(stack, newFrame);
        if (!maybeStack) {
          writer.SetFailure("AppendFrame failure");
          continue;
        }
        stack = *maybeStack;
        if (!frameStack.append(stack)) {
          writer.SetFailure("frameStack append failure");
          continue;
        }

      } else if (event.kind == JS::ExecutionTrace::EventKind::LabelEnter) {
        UniqueStacks::FrameKey newFrame(
            nsCString(&trace.stringBuffer[event.labelEvent.label]), truefalse,
            0, Nothing{}, Nothing{}, Some(JS::ProfilingCategoryPair::DOM));
        maybeStack = uniqueStacks.AppendFrame(stack, newFrame);
        if (!maybeStack) {
          writer.SetFailure("AppendFrame failure");
          continue;
        }
        stack = *maybeStack;
        if (!frameStack.append(stack)) {
          writer.SetFailure("frameStack append failure");
          continue;
        }

      } else {
        MOZ_ASSERT(event.kind == JS::ExecutionTrace::EventKind::LabelLeave ||
                   event.kind == JS::ExecutionTrace::EventKind::FunctionLeave);
        if (frameStack.length() > 0) {
          frameStack.popBack();
        }
        if (frameStack.length() > 0) {
          stack = frameStack[frameStack.length() - 1];
        } else {
          maybeStack =
              uniqueStacks.BeginStack(UniqueStacks::FrameKey("(root)"));
          if (!maybeStack) {
            writer.SetFailure("BeginStack failure");
            continue;
          }

          stack = *maybeStack;
          if (!frameStack.append(stack)) {
            writer.SetFailure("frameStack append failure");
            continue;
          }
        }
      }

      const Maybe<uint32_t> stackIndex = uniqueStacks.GetOrAddStackIndex(stack);
      if (!stackIndex) {
        writer.SetFailure("Can't add unique string for stack");
        continue;
      }

      WriteSample(writer, ProfileSample{*stackIndex, event.time, Nothing{},
                                        RunningTimes{}});
    }
  }
}
#endif

// GetStreamingParametersForThreadCallback:
//   (ProfilerThreadId) -> Maybe<StreamingParametersForThread>
template <typename GetStreamingParametersForThreadCallback>
ProfilerThreadId ProfileBuffer::DoStreamSamplesAndMarkersToJSON(
    mozilla::FailureLatch& aFailureLatch,
    GetStreamingParametersForThreadCallback&&
        aGetStreamingParametersForThreadCallback,
    double aSinceTime, ProcessStreamingContext* aStreamingContextForMarkers,
    mozilla::ProgressLogger aProgressLogger) const {
  UniquePtr<char[]> dynStrBuf = MakeUnique<char[]>(kMaxFrameKeyLength);

  return mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
    MOZ_ASSERT(aReader,
               "ProfileChunkedBuffer cannot be out-of-session when sampler is "
               "running");

    ProfilerThreadId processedThreadId;

    EntryGetter e(*aReader, aFailureLatch, std::move(aProgressLogger),
                  /* aInitialReadPos */ 0, aStreamingContextForMarkers);

    for (;;) {
      // This block skips entries until we find the start of the next sample.
      // This is useful in three situations.
      //
      // - The circular buffer overwrites old entries, so when we start parsing
      //   we might be in the middle of a sample, and we must skip forward to
      //   the start of the next sample.
      //
      // - We skip samples that don't have an appropriate ThreadId or Time.
      //
      // - We skip range Pause, Resume, CollectionStart, Marker, Counter
      //   and CollectionEnd entries between samples.
      while (e.Has()) {
        if (e.Get().IsThreadId()) {
          break;
        }
        e.Next();
      }

      if (!e.Has()) {
        break;
      }

      // Due to the skip_to_next_sample block above, if we have an entry here it
      // must be a ThreadId entry.
      MOZ_ASSERT(e.Get().IsThreadId());

      ProfilerThreadId threadId = e.Get().GetThreadId();
      e.Next();

      Maybe<StreamingParametersForThread> streamingParameters =
          std::forward<GetStreamingParametersForThreadCallback>(
              aGetStreamingParametersForThreadCallback)(threadId);

      // Ignore samples that are for the wrong thread.
      if (!streamingParameters) {
        continue;
      }

      SpliceableJSONWriter& writer = streamingParameters->mWriter;
      UniqueStacks& uniqueStacks = streamingParameters->mUniqueStacks;
      ThreadStreamingContext::PreviousStackState& previousStackState =
          streamingParameters->mPreviousStackState;
      uint32_t& previousStack = streamingParameters->mPreviousStack;

      auto ReadStack = [&](EntryGetter& e, double time, uint64_t entryPosition,
                           const Maybe<double>& unresponsiveDuration,
                           const RunningTimes& runningTimes) {
        if (writer.Failed()) {
          return;
        }

        Maybe<UniqueStacks::StackKey> maybeStack =
            uniqueStacks.BeginStack(UniqueStacks::FrameKey("(root)"));
        if (!maybeStack) {
          writer.SetFailure("BeginStack failure");
          return;
        }

        UniqueStacks::StackKey stack = *maybeStack;

        int numFrames = 0;
        while (e.Has()) {
          if (e.Get().IsNativeLeafAddr()) {
            numFrames++;

            void* pc = e.Get().GetPtr();
            e.Next();

            nsAutoCString functionNameOrAddress =
                uniqueStacks.FunctionNameOrAddress(pc);

            maybeStack = uniqueStacks.AppendFrame(
                stack, UniqueStacks::FrameKey(functionNameOrAddress.get()));
            if (!maybeStack) {
              writer.SetFailure("AppendFrame failure");
              return;
            }
            stack = *maybeStack;

          } else if (e.Get().IsLabel()) {
            numFrames++;

            const char* label = e.Get().GetString();
            e.Next();

            using FrameFlags = js::ProfilingStackFrame::Flags;
            uint32_t frameFlags = 0;
            if (e.Has() && e.Get().IsFrameFlags()) {
              frameFlags = uint32_t(e.Get().GetUint64());
              e.Next();
            }

            bool relevantForJS =
                frameFlags & uint32_t(FrameFlags::RELEVANT_FOR_JS);

            bool isBaselineInterp =
                frameFlags & uint32_t(FrameFlags::IS_BLINTERP_FRAME);

            // Copy potential dynamic string fragments into dynStrBuf, so that
            // dynStrBuf will then contain the entire dynamic string.
            size_t i = 0;
            dynStrBuf[0] = '\0';
            while (e.Has()) {
              if (e.Get().IsDynamicStringFragment()) {
                char chars[ProfileBufferEntry::kNumChars];
                e.Get().CopyCharsInto(chars);
                for (char c : chars) {
                  if (i < kMaxFrameKeyLength) {
                    dynStrBuf[i] = c;
                    i++;
                  }
                }
                e.Next();
              } else {
                break;
              }
            }
            dynStrBuf[kMaxFrameKeyLength - 1] = '\0';
            bool hasDynamicString = (i != 0);

            nsAutoCStringN<1024> frameLabel;
            if (label[0] != '\0' && hasDynamicString) {
              if (frameFlags & uint32_t(FrameFlags::STRING_TEMPLATE_METHOD)) {
                frameLabel.AppendPrintf("%s.%s", label, dynStrBuf.get());
              } else if (frameFlags &
                         uint32_t(FrameFlags::STRING_TEMPLATE_GETTER)) {
                frameLabel.AppendPrintf("get %s.%s", label, dynStrBuf.get());
              } else if (frameFlags &
                         uint32_t(FrameFlags::STRING_TEMPLATE_SETTER)) {
                frameLabel.AppendPrintf("set %s.%s", label, dynStrBuf.get());
              } else {
                frameLabel.AppendPrintf("%s %s", label, dynStrBuf.get());
              }
            } else if (hasDynamicString) {
              frameLabel.Append(dynStrBuf.get());
            } else {
              frameLabel.Append(label);
            }

            uint64_t innerWindowID = 0;
            if (e.Has() && e.Get().IsInnerWindowID()) {
              innerWindowID = uint64_t(e.Get().GetUint64());
              e.Next();
            }

            Maybe<unsigned> line;
            if (e.Has() && e.Get().IsLineNumber()) {
              line = Some(unsigned(e.Get().GetInt()));
              e.Next();
            }

            Maybe<unsigned> column;
            if (e.Has() && e.Get().IsColumnNumber()) {
              column = Some(unsigned(e.Get().GetInt()));
              e.Next();
            }

            Maybe<JS::ProfilingCategoryPair> categoryPair;
            if (e.Has() && e.Get().IsCategoryPair()) {
              categoryPair =
                  Some(JS::ProfilingCategoryPair(uint32_t(e.Get().GetInt())));
              e.Next();
            }

            maybeStack = uniqueStacks.AppendFrame(
                stack,
                UniqueStacks::FrameKey(std::move(frameLabel), relevantForJS,
                                       isBaselineInterp, innerWindowID, line,
                                       column, categoryPair));
            if (!maybeStack) {
              writer.SetFailure("AppendFrame failure");
              return;
            }
            stack = *maybeStack;

          } else if (e.Get().IsJitReturnAddr()) {
            numFrames++;

            // A JIT frame may expand to multiple frames due to inlining.
            void* pc = e.Get().GetPtr();
            const Maybe<Vector<UniqueStacks::FrameKey>>& frameKeys =
                uniqueStacks.LookupFramesForJITAddressFromBufferPos(
                    pc, entryPosition ? entryPosition : e.CurPos());
            MOZ_RELEASE_ASSERT(
                frameKeys,
                "Attempting to stream samples for a buffer range "
                "for which we don't have JITFrameInfo?");
            for (const UniqueStacks::FrameKey& frameKey : *frameKeys) {
              maybeStack = uniqueStacks.AppendFrame(stack, frameKey);
              if (!maybeStack) {
                writer.SetFailure("AppendFrame failure");
                return;
              }
              stack = *maybeStack;
            }

            e.Next();

          } else {
            break;
          }
        }

        // Even if this stack is considered empty, it contains the root frame,
        // which needs to be in the JSON output because following "same samples"
        // may refer to it when reusing this sample.mStack.
        const Maybe<uint32_t> stackIndex =
            uniqueStacks.GetOrAddStackIndex(stack);
        if (!stackIndex) {
          writer.SetFailure("Can't add unique string for stack");
          return;
        }

        // And store that possibly-empty stack in case it's followed by "same
        // sample" entries.
        previousStack = *stackIndex;
        previousStackState = (numFrames == 0)
                                 ? ThreadStreamingContext::eStackWasEmpty
                                 : ThreadStreamingContext::eStackWasNotEmpty;

        // Even if too old or empty, we did process a sample for this thread id.
        processedThreadId = threadId;

        // Discard samples that are too old.
        if (time < aSinceTime) {
          return;
        }

        if (numFrames == 0 && runningTimes.IsEmpty()) {
          // It is possible to have empty stacks if native stackwalking is
          // disabled. Skip samples with empty stacks, unless we have useful
          // running times.
          return;
        }

        WriteSample(writer, ProfileSample{*stackIndex, time,
                                          unresponsiveDuration, runningTimes});
      };  // End of `ReadStack(EntryGetter&)` lambda.

      if (e.Has() && e.Get().IsTime()) {
        double time = e.Get().GetDouble();
        e.Next();
        // Note: Even if this sample is too old (before aSinceTime), we still
        // need to read it, so that its frames are in the tables, in case there
        // is a same-sample following it that would be after aSinceTime, which
        // would need these frames to be present.

        ReadStack(e, time, 0, Nothing{}, RunningTimes{});

        e.SetLocalProgress("Processed sample");
      } else if (e.Has() && e.Get().IsTimeBeforeCompactStack()) {
        double time = e.Get().GetDouble();
        // Note: Even if this sample is too old (before aSinceTime), we still
        // need to read it, so that its frames are in the tables, in case there
        // is a same-sample following it that would be after aSinceTime, which
        // would need these frames to be present.

        RunningTimes runningTimes;
        Maybe<double> unresponsiveDuration;

        ProfileChunkedBuffer::BlockIterator it = e.Iterator();
        for (;;) {
          ++it;
          if (it.IsAtEnd()) {
            break;
          }
          ProfileBufferEntryReader er = *it;
          ProfileBufferEntry::Kind kind =
              er.ReadObject<ProfileBufferEntry::Kind>();

          // There may be running times before the CompactStack.
          if (kind == ProfileBufferEntry::Kind::RunningTimes) {
            er.ReadIntoObject(runningTimes);
            continue;
          }

          // There may be an UnresponsiveDurationMs before the CompactStack.
          if (kind == ProfileBufferEntry::Kind::UnresponsiveDurationMs) {
            unresponsiveDuration = Some(er.ReadObject<double>());
            continue;
          }

          if (kind == ProfileBufferEntry::Kind::CompactStack) {
            ProfileChunkedBuffer tempBuffer(
                ProfileChunkedBuffer::ThreadSafety::WithoutMutex,
                WorkerChunkManager());
            er.ReadIntoObject(tempBuffer);
            tempBuffer.Read([&](ProfileChunkedBuffer::Reader* aReader) {
              MOZ_ASSERT(aReader,
                         "Local ProfileChunkedBuffer cannot be out-of-session");
              // This is a compact stack, it should only contain one sample.
              EntryGetter stackEntryGetter(*aReader, aFailureLatch);
              ReadStack(stackEntryGetter, time,
                        it.CurrentBlockIndex().ConvertToProfileBufferIndex(),
                        unresponsiveDuration, runningTimes);
            });
            WorkerChunkManager().Reset(tempBuffer.GetAllChunks());
            break;
          }

          if (kind == ProfileBufferEntry::Kind::Marker &&
              aStreamingContextForMarkers) {
            StreamMarkerAfterKind(er, *aStreamingContextForMarkers);
            continue;
          }

          MOZ_ASSERT(kind >= ProfileBufferEntry::Kind::LEGACY_LIMIT,
                     "There should be no legacy entries between "
                     "TimeBeforeCompactStack and CompactStack");
          er.SetRemainingBytes(0);
        }

        e.RestartAfter(it);

        e.SetLocalProgress("Processed compact sample");
      } else if (e.Has() && e.Get().IsTimeBeforeSameSample()) {
        if (previousStackState == ThreadStreamingContext::eNoStackYet) {
          // We don't have any full sample yet, we cannot duplicate a "previous"
          // one. This should only happen at most once per thread, for the very
          // first sample.
          continue;
        }

        ProfileSample sample;

        // Keep the same `mStack` as previously output.
        // Note that it may be empty, this is checked below before writing it.
        sample.mStack = previousStack;

        sample.mTime = e.Get().GetDouble();

        // Ignore samples that are too old.
        if (sample.mTime < aSinceTime) {
          e.Next();
          continue;
        }

        sample.mResponsiveness = Nothing{};

        sample.mRunningTimes.Clear();

        ProfileChunkedBuffer::BlockIterator it = e.Iterator();
        for (;;) {
          ++it;
          if (it.IsAtEnd()) {
            break;
          }
          ProfileBufferEntryReader er = *it;
          ProfileBufferEntry::Kind kind =
              er.ReadObject<ProfileBufferEntry::Kind>();

          // There may be running times before the SameSample.
          if (kind == ProfileBufferEntry::Kind::RunningTimes) {
            er.ReadIntoObject(sample.mRunningTimes);
            continue;
          }

          if (kind == ProfileBufferEntry::Kind::SameSample) {
            if (previousStackState == ThreadStreamingContext::eStackWasEmpty &&
                sample.mRunningTimes.IsEmpty()) {
              // Skip samples with empty stacks, unless we have useful running
              // times.
              break;
            }
            WriteSample(writer, sample);
            break;
          }

          if (kind == ProfileBufferEntry::Kind::Marker &&
              aStreamingContextForMarkers) {
            StreamMarkerAfterKind(er, *aStreamingContextForMarkers);
            continue;
          }

          MOZ_ASSERT(kind >= ProfileBufferEntry::Kind::LEGACY_LIMIT,
                     "There should be no legacy entries between "
                     "TimeBeforeSameSample and SameSample");
          er.SetRemainingBytes(0);
        }

        e.RestartAfter(it);

        e.SetLocalProgress("Processed repeated sample");
      } else {
        ERROR_AND_CONTINUE("expected a Time entry");
      }
    }

    return processedThreadId;
  });
}

ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
    SpliceableJSONWriter& aWriter, ProfilerThreadId aThreadId,
    double aSinceTime, UniqueStacks& aUniqueStacks,
    mozilla::ProgressLogger aProgressLogger) const {
  ThreadStreamingContext::PreviousStackState previousStackState =
      ThreadStreamingContext::eNoStackYet;
  uint32_t stack = 0u;
#ifdef DEBUG
  int processedCount = 0;
#endif  // DEBUG

  return DoStreamSamplesAndMarkersToJSON(
      aWriter.SourceFailureLatch(),
      [&](ProfilerThreadId aReadThreadId) {
        Maybe<StreamingParametersForThread> streamingParameters;
#ifdef DEBUG
        ++processedCount;
        MOZ_ASSERT(
            aThreadId.IsSpecified() ||
                (processedCount == 1 && aReadThreadId.IsSpecified()),
            "Unspecified aThreadId should only be used with 1-sample buffer");
#endif  // DEBUG
        if (!aThreadId.IsSpecified() || aThreadId == aReadThreadId) {
          streamingParameters.emplace(aWriter, aUniqueStacks,
                                      previousStackState, stack);
        }
        return streamingParameters;
      },
      aSinceTime, /* aStreamingContextForMarkers */ nullptr,
      std::move(aProgressLogger));
}

void ProfileBuffer::StreamSamplesAndMarkersToJSON(
    ProcessStreamingContext& aProcessStreamingContext,
    mozilla::ProgressLogger aProgressLogger) const {
  auto getStreamingParamsCallback = [&](ProfilerThreadId aReadThreadId) {
    Maybe<StreamingParametersForThread> streamingParameters;
    ThreadStreamingContext* threadData =
        aProcessStreamingContext.GetThreadStreamingContext(aReadThreadId);
    if (threadData) {
      streamingParameters.emplace(
          threadData->mSamplesDataWriter, *threadData->mUniqueStacks,
          threadData->mPreviousStackState, threadData->mPreviousStack);
    }
    return streamingParameters;
  };

#ifdef MOZ_EXECUTION_TRACING
  MaybeStreamExecutionTraceToJSON(getStreamingParamsCallback,
                                  aProcessStreamingContext.GetSinceTime());
#endif

  (void)DoStreamSamplesAndMarkersToJSON(
      aProcessStreamingContext.SourceFailureLatch(), getStreamingParamsCallback,
      aProcessStreamingContext.GetSinceTime(), &aProcessStreamingContext,
      std::move(aProgressLogger));
}

void ProfileBuffer::AddJITInfoForRange(
    uint64_t aRangeStart, ProfilerThreadId aThreadId, JSContext* aContext,
    JITFrameInfo& aJITFrameInfo,
    mozilla::ProgressLogger aProgressLogger) const {
  // We can only process JitReturnAddr entries if we have a JSContext.
  MOZ_RELEASE_ASSERT(aContext);

  aRangeStart = std::max(aRangeStart, BufferRangeStart());
  aJITFrameInfo.AddInfoForRange(
      aRangeStart, BufferRangeEnd(), aContext,
      [&](const std::function<void(void*)>& aJITAddressConsumer) {
        // Find all JitReturnAddr entries in the given range for the given
        // thread, and call aJITAddressConsumer with those addresses.

        mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
          MOZ_ASSERT(aReader,
                     "ProfileChunkedBuffer cannot be out-of-session when "
                     "sampler is running");

          EntryGetter e(*aReader, aJITFrameInfo.LocalFailureLatchSource(),
                        std::move(aProgressLogger), aRangeStart);

          while (true) {
            // Advance to the next ThreadId entry.
            while (e.Has() && !e.Get().IsThreadId()) {
              e.Next();
            }
            if (!e.Has()) {
              break;
            }

            MOZ_ASSERT(e.Get().IsThreadId());
            ProfilerThreadId threadId = e.Get().GetThreadId();
            e.Next();

            // Ignore samples that are for a different thread.
            if (threadId != aThreadId) {
              continue;
            }

            if (e.Has() && e.Get().IsTime()) {
              // Legacy stack.
              e.Next();
              while (e.Has() && !e.Get().IsThreadId()) {
                if (e.Get().IsJitReturnAddr()) {
                  aJITAddressConsumer(e.Get().GetPtr());
                }
                e.Next();
              }
            } else if (e.Has() && e.Get().IsTimeBeforeCompactStack()) {
              // Compact stack.
              ProfileChunkedBuffer::BlockIterator it = e.Iterator();
              for (;;) {
                ++it;
                if (it.IsAtEnd()) {
                  break;
                }
                ProfileBufferEntryReader er = *it;
                ProfileBufferEntry::Kind kind =
                    er.ReadObject<ProfileBufferEntry::Kind>();
                if (kind == ProfileBufferEntry::Kind::CompactStack) {
                  ProfileChunkedBuffer tempBuffer(
                      ProfileChunkedBuffer::ThreadSafety::WithoutMutex,
                      WorkerChunkManager());
                  er.ReadIntoObject(tempBuffer);
                  tempBuffer.Read([&](ProfileChunkedBuffer::Reader* aReader) {
                    MOZ_ASSERT(
                        aReader,
                        "Local ProfileChunkedBuffer cannot be out-of-session");
                    EntryGetter stackEntryGetter(
                        *aReader, aJITFrameInfo.LocalFailureLatchSource());
                    while (stackEntryGetter.Has()) {
                      if (stackEntryGetter.Get().IsJitReturnAddr()) {
                        aJITAddressConsumer(stackEntryGetter.Get().GetPtr());
                      }
                      stackEntryGetter.Next();
                    }
                  });
                  WorkerChunkManager().Reset(tempBuffer.GetAllChunks());
                  break;
                }

                MOZ_ASSERT(kind >= ProfileBufferEntry::Kind::LEGACY_LIMIT,
                           "There should be no legacy entries between "
                           "TimeBeforeCompactStack and CompactStack");
                er.SetRemainingBytes(0);
              }

              e.Next();
            } else if (e.Has() && e.Get().IsTimeBeforeSameSample()) {
              // Sample index, nothing to do.

            } else {
              ERROR_AND_CONTINUE("expected a Time entry");
            }
          }
        });
      });
}

void ProfileBuffer::StreamMarkersToJSON(
    SpliceableJSONWriter& aWriter, ProfilerThreadId aThreadId,
    const TimeStamp& aProcessStartTime, double aSinceTime,
    UniqueStacks& aUniqueStacks,
    mozilla::ProgressLogger aProgressLogger) const {
  mEntries.ReadEach([&](ProfileBufferEntryReader& aER) {
    auto type = static_cast<ProfileBufferEntry::Kind>(
        aER.ReadObject<ProfileBufferEntry::KindUnderlyingType>());
    MOZ_ASSERT(static_cast<ProfileBufferEntry::KindUnderlyingType>(type) <
               static_cast<ProfileBufferEntry::KindUnderlyingType>(
                   ProfileBufferEntry::Kind::MODERN_LIMIT));
    if (type == ProfileBufferEntry::Kind::Marker) {
      mozilla::base_profiler_markers_detail::DeserializeAfterKindAndStream(
          aER,
          [&](const ProfilerThreadId& aMarkerThreadId) {
            return (!aThreadId.IsSpecified() || aMarkerThreadId == aThreadId)
                       ? &aWriter
                       : nullptr;
          },
          [&](ProfileChunkedBuffer& aChunkedBuffer) {
            ProfilerBacktrace backtrace("", &aChunkedBuffer);
            backtrace.StreamJSON(aWriter, aProcessStartTime, aUniqueStacks);
          },
          [&](mozilla::base_profiler_markers_detail::Streaming::DeserializerTag
                  aTag) {
            size_t payloadSize = aER.RemainingBytes();

            ProfileBufferEntryReader::DoubleSpanOfConstBytes spans =
                aER.ReadSpans(payloadSize);
            if (MOZ_LIKELY(spans.IsSingleSpan())) {
              // Only a single span, we can just refer to it directly
              // instead of copying it.
              profiler::ffi::gecko_profiler_serialize_marker_for_tag(
                  aTag, spans.mFirstOrOnly.Elements(), payloadSize, &aWriter);
            } else {
              // Two spans, we need to concatenate them by copying.
              uint8_t* payloadBuffer = new uint8_t[payloadSize];
              spans.CopyBytesTo(payloadBuffer);
              profiler::ffi::gecko_profiler_serialize_marker_for_tag(
                  aTag, payloadBuffer, payloadSize, &aWriter);
              delete[] payloadBuffer;
            }
          });
    } else {
      // The entry was not a marker, we need to skip to the end.
      aER.SetRemainingBytes(0);
    }
  });
}

void ProfileBuffer::StreamProfilerOverheadToJSON(
    SpliceableJSONWriter& aWriter, const TimeStamp& aProcessStartTime,
    double aSinceTime, mozilla::ProgressLogger aProgressLogger) const {
  const char* recordOverheads = getenv("MOZ_PROFILER_RECORD_OVERHEADS");
  if (!recordOverheads || recordOverheads[0] == '\0') {
    // Overheads were not recorded, return early.
    return;
  }

  mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
    MOZ_ASSERT(aReader,
               "ProfileChunkedBuffer cannot be out-of-session when sampler is "
               "running");

    EntryGetter e(*aReader, aWriter.SourceFailureLatch(),
                  std::move(aProgressLogger));

    enum Schema : uint32_t {
      TIME = 0,
      LOCKING = 1,
      MARKER_CLEANING = 2,
      COUNTERS = 3,
      THREADS = 4
    };

    aWriter.StartObjectProperty("profilerOverhead");
    aWriter.StartObjectProperty("samples");
    // Stream all sampling overhead data. We skip other entries, because we
    // process them in StreamSamplesToJSON()/etc.
    {
      JSONSchemaWriter schema(aWriter);
      schema.WriteField("time");
      schema.WriteField("locking");
      schema.WriteField("expiredMarkerCleaning");
      schema.WriteField("counters");
      schema.WriteField("threads");
    }

    aWriter.StartArrayProperty("data");
    double firstTime = 0.0;
    double lastTime = 0.0;
    ProfilerStats intervals, overheads, lockings, cleanings, counters, threads;
    while (e.Has()) {
      // valid sequence: ProfilerOverheadTime, ProfilerOverheadDuration * 4
      if (e.Get().IsProfilerOverheadTime()) {
        double time = e.Get().GetDouble();
        if (time >= aSinceTime) {
          e.Next();
          if (!e.Has() || !e.Get().IsProfilerOverheadDuration()) {
            ERROR_AND_CONTINUE(
                "expected a ProfilerOverheadDuration entry after "
                "ProfilerOverheadTime");
          }
          double locking = e.Get().GetDouble();
          e.Next();
          if (!e.Has() || !e.Get().IsProfilerOverheadDuration()) {
            ERROR_AND_CONTINUE(
                "expected a ProfilerOverheadDuration entry after "
                "ProfilerOverheadTime,ProfilerOverheadDuration");
          }
          double cleaning = e.Get().GetDouble();
          e.Next();
          if (!e.Has() || !e.Get().IsProfilerOverheadDuration()) {
            ERROR_AND_CONTINUE(
                "expected a ProfilerOverheadDuration entry after "
                "ProfilerOverheadTime,ProfilerOverheadDuration*2");
          }
          double counter = e.Get().GetDouble();
          e.Next();
          if (!e.Has() || !e.Get().IsProfilerOverheadDuration()) {
            ERROR_AND_CONTINUE(
                "expected a ProfilerOverheadDuration entry after "
                "ProfilerOverheadTime,ProfilerOverheadDuration*3");
          }
          double thread = e.Get().GetDouble();

          if (firstTime == 0.0) {
            firstTime = time;
          } else {
            // Note that we'll have 1 fewer interval than other numbers (because
            // we need both ends of an interval to know its duration). The final
            // difference should be insignificant over the expected many
            // thousands of iterations.
            intervals.Count(time - lastTime);
          }
          lastTime = time;
          overheads.Count(locking + cleaning + counter + thread);
          lockings.Count(locking);
          cleanings.Count(cleaning);
          counters.Count(counter);
          threads.Count(thread);

          AutoArraySchemaWriter writer(aWriter);
          writer.TimeMsElement(TIME, time);
          writer.DoubleElement(LOCKING, locking);
          writer.DoubleElement(MARKER_CLEANING, cleaning);
          writer.DoubleElement(COUNTERS, counter);
          writer.DoubleElement(THREADS, thread);
        }
      }
      e.Next();
    }
    aWriter.EndArray();   // data
    aWriter.EndObject();  // samples

    // Only output statistics if there is at least one full interval (and
    // therefore at least two samplings.)
    if (intervals.n > 0) {
      aWriter.StartObjectProperty("statistics");
      aWriter.DoubleProperty("profiledDuration", lastTime - firstTime);
      aWriter.IntProperty("samplingCount", overheads.n);
      aWriter.DoubleProperty("overheadDurations", overheads.sum);
      aWriter.DoubleProperty("overheadPercentage",
                             overheads.sum / (lastTime - firstTime));
#define PROFILER_STATS(name, var)                           \
  aWriter.DoubleProperty("mean" name, (var).sum / (var).n); \
  aWriter.DoubleProperty("min" name, (var).min);            \
  aWriter.DoubleProperty("max" name, (var).max);
      PROFILER_STATS("Interval", intervals);
      PROFILER_STATS("Overhead", overheads);
      PROFILER_STATS("Lockings", lockings);
      PROFILER_STATS("Cleaning", cleanings);
      PROFILER_STATS("Counter", counters);
      PROFILER_STATS("Thread", threads);
#undef PROFILER_STATS
      aWriter.EndObject();  // statistics
    }
    aWriter.EndObject();  // profilerOverhead
  });
}

struct CounterSample {
  double mTime;
  uint64_t mNumber;
  int64_t mCount;
};

using CounterSamples = Vector<CounterSample>;

static LazyLogModule sFuzzyfoxLog("Fuzzyfox");

// HashMap lookup, if not found, a default value is inserted.
// Returns reference to (existing or new) value inside the HashMap.
template <typename HashM, typename Key>
static auto& LookupOrAdd(HashM& aMap, Key&& aKey) {
  auto addPtr = aMap.lookupForAdd(aKey);
  if (!addPtr) {
    MOZ_RELEASE_ASSERT(aMap.add(addPtr, std::forward<Key>(aKey),
                                typename HashM::Entry::ValueType{}));
    MOZ_ASSERT(!!addPtr);
  }
  return addPtr->value();
}

void ProfileBuffer::StreamCountersToJSON(
    SpliceableJSONWriter& aWriter, const TimeStamp& aProcessStartTime,
    double aSinceTime, mozilla::ProgressLogger aProgressLogger) const {
  // Because this is a format entirely internal to the Profiler, any parsing
  // error indicates a bug in the ProfileBuffer writing or the parser itself,
  // or possibly flaky hardware.

  mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
    MOZ_ASSERT(aReader,
               "ProfileChunkedBuffer cannot be out-of-session when sampler is "
               "running");

    EntryGetter e(*aReader, aWriter.SourceFailureLatch(),
                  std::move(aProgressLogger));

    enum Schema : uint32_t { TIME = 0, COUNT = 1, NUMBER = 2 };

    // Stream all counters. We skip other entries, because we process them in
    // StreamSamplesToJSON()/etc.
    //
    // Valid sequence in the buffer:
    // CounterID
    // Time
    // ( Count Number? )*
    //
    // And the JSON (example):
    // "counters": {
    //  "name": "malloc",
    //  "category": "Memory",
    //  "description": "Amount of allocated memory",
    //  "samples": {
    //   "schema": {"time": 0, "count": 1, "number": 2},
    //   "data": [
    //    [
    //     16117.033968000002,
    //     2446216,
    //     6801320
    //    ],
    //    [
    //     16118.037638,
    //     2446216,
    //     6801320
    //    ],
    //   ],
    //  },
    // }

    // Build the map of counters and populate it
    HashMap<void*, CounterSamples> counters;

    while (e.Has()) {
      // skip all non-Counters, including if we start in the middle of a counter
      if (e.Get().IsCounterId()) {
        void* id = e.Get().GetPtr();
        CounterSamples& data = LookupOrAdd(counters, id);
        e.Next();
        if (!e.Has() || !e.Get().IsTime()) {
          ERROR_AND_CONTINUE("expected a Time entry");
        }
        double time = e.Get().GetDouble();
        e.Next();
        if (time >= aSinceTime) {
          if (!e.Has() || !e.Get().IsCount()) {
            ERROR_AND_CONTINUE("expected a Count entry");
          }
          int64_t count = e.Get().GetUint64();
          e.Next();
          uint64_t number;
          if (!e.Has() || !e.Get().IsNumber()) {
            number = 0;
          } else {
            number = e.Get().GetInt64();
            e.Next();
          }
          CounterSample sample = {time, number, count};
          MOZ_RELEASE_ASSERT(data.append(sample));
        } else {
          // skip counter sample - only need to skip the initial counter
          // id, then let the loop at the top skip the rest
        }
      } else {
        e.Next();
      }
    }
    // we have a map of counter entries; dump them to JSON
    if (counters.count() == 0) {
      return;
    }

    aWriter.StartArrayProperty("counters");
    for (auto iter = counters.iter(); !iter.done(); iter.next()) {
      CounterSamples& samples = iter.get().value();
      size_t size = samples.length();
      if (size == 0) {
        continue;
      }
      const BaseProfilerCount* base_counter =
          static_cast<const BaseProfilerCount*>(iter.get().key());

      aWriter.Start();
      aWriter.StringProperty("name", MakeStringSpan(base_counter->mLabel));
      aWriter.StringProperty("category",
                             MakeStringSpan(base_counter->mCategory));
      aWriter.StringProperty("description",
                             MakeStringSpan(base_counter->mDescription));

      bool hasNumber = false;
      for (size_t i = 0; i < size; i++) {
        if (samples[i].mNumber != 0) {
          hasNumber = true;
          break;
        }
      }
      aWriter.StartObjectProperty("samples");
      {
        JSONSchemaWriter schema(aWriter);
        schema.WriteField("time");
        schema.WriteField("count");
        if (hasNumber) {
          schema.WriteField("number");
        }
      }

      aWriter.StartArrayProperty("data");
      double previousSkippedTime = 0.0;
      uint64_t previousNumber = 0;
      int64_t previousCount = 0;
      for (size_t i = 0; i < size; i++) {
        // Encode as deltas, and only encode if different than the previous
        // or next sample; Always write the first and last samples.
        if (i == 0 || i == size - 1 || samples[i].mNumber != previousNumber ||
            samples[i].mCount != previousCount ||
            // Ensure we ouput the first 0 before skipping samples.
            (i >= 2 && (samples[i - 2].mNumber != previousNumber ||
                        samples[i - 2].mCount != previousCount))) {
          if (i != 0 && samples[i].mTime >= samples[i - 1].mTime) {
            MOZ_LOG(sFuzzyfoxLog, mozilla::LogLevel::Error,
                    ("Fuzzyfox Profiler Assertion: %f >= %f", samples[i].mTime,
                     samples[i - 1].mTime));
          }
          MOZ_ASSERT(i == 0 || samples[i].mTime >= samples[i - 1].mTime);
          MOZ_ASSERT(samples[i].mNumber >= previousNumber);
          MOZ_ASSERT(samples[i].mNumber - previousNumber <=
                     uint64_t(std::numeric_limits<int64_t>::max()));

          int64_t numberDelta =
              static_cast<int64_t>(samples[i].mNumber - previousNumber);
          int64_t countDelta = samples[i].mCount - previousCount;

          if (previousSkippedTime != 0.0 &&
              (numberDelta != 0 || countDelta != 0)) {
            // Write the last skipped sample, unless the new one is all
            // zeroes (that'd be redundant) This is useful to know when a
            // certain value was last sampled, so that the front-end graph
            // will be more correct.
            AutoArraySchemaWriter writer(aWriter);
            writer.TimeMsElement(TIME, previousSkippedTime);
            // The deltas are effectively zeroes, since no change happened
            // between the last actually-written sample and the last skipped
            // one.
            writer.IntElement(COUNT, 0);
            if (hasNumber) {
              writer.IntElement(NUMBER, 0);
            }
          }

          AutoArraySchemaWriter writer(aWriter);
          writer.TimeMsElement(TIME, samples[i].mTime);
          writer.IntElement(COUNT, countDelta);
          if (hasNumber) {
            writer.IntElement(NUMBER, numberDelta);
          }

          previousSkippedTime = 0.0;
          previousNumber = samples[i].mNumber;
          previousCount = samples[i].mCount;
        } else {
          previousSkippedTime = samples[i].mTime;
        }
      }
      aWriter.EndArray();   // data
      aWriter.EndObject();  // samples
      aWriter.End();        // for each counter
    }
    aWriter.EndArray();  // counters
  });
}

#undef ERROR_AND_CONTINUE

static void AddPausedRange(SpliceableJSONWriter& aWriter, const char* aReason,
                           const Maybe<double>& aStartTime,
                           const Maybe<double>& aEndTime) {
  aWriter.Start();
  if (aStartTime) {
    aWriter.TimeDoubleMsProperty("startTime", *aStartTime);
  } else {
    aWriter.NullProperty("startTime");
  }
  if (aEndTime) {
    aWriter.TimeDoubleMsProperty("endTime", *aEndTime);
  } else {
    aWriter.NullProperty("endTime");
  }
  aWriter.StringProperty("reason", MakeStringSpan(aReason));
  aWriter.End();
}

void ProfileBuffer::StreamPausedRangesToJSON(
    SpliceableJSONWriter& aWriter, double aSinceTime,
    mozilla::ProgressLogger aProgressLogger) const {
  mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
    MOZ_ASSERT(aReader,
               "ProfileChunkedBuffer cannot be out-of-session when sampler is "
               "running");

    EntryGetter e(*aReader, aWriter.SourceFailureLatch(),
                  aProgressLogger.CreateSubLoggerFromTo(
                      1_pc, "Streaming pauses...", 99_pc, "Streamed pauses"));

    Maybe<double> currentPauseStartTime;
    Maybe<double> currentCollectionStartTime;

    while (e.Has()) {
      if (e.Get().IsPause()) {
        currentPauseStartTime = Some(e.Get().GetDouble());
      } else if (e.Get().IsResume()) {
        AddPausedRange(aWriter, "profiler-paused", currentPauseStartTime,
                       Some(e.Get().GetDouble()));
        currentPauseStartTime = Nothing();
      } else if (e.Get().IsCollectionStart()) {
        currentCollectionStartTime = Some(e.Get().GetDouble());
      } else if (e.Get().IsCollectionEnd()) {
        AddPausedRange(aWriter, "collecting", currentCollectionStartTime,
                       Some(e.Get().GetDouble()));
        currentCollectionStartTime = Nothing();
      }
      e.Next();
    }

    if (currentPauseStartTime) {
      AddPausedRange(aWriter, "profiler-paused", currentPauseStartTime,
                     Nothing());
    }
    if (currentCollectionStartTime) {
      AddPausedRange(aWriter, "collecting", currentCollectionStartTime,
                     Nothing());
    }
  });
}

bool ProfileBuffer::DuplicateLastSample(ProfilerThreadId aThreadId,
                                        double aSampleTimeMs,
                                        Maybe<uint64_t>& aLastSample,
                                        const RunningTimes& aRunningTimes) {
  if (!aLastSample) {
    return false;
  }

  if (mEntries.IsIndexInCurrentChunk(ProfileBufferIndex{*aLastSample})) {
    // The last (fully-written) sample is in this chunk, we can refer to it.

    // Note that between now and when we write the SameSample below, another
    // chunk could have been started, so the SameSample will in fact refer to a
    // block in a previous chunk. This is okay, because:
    // - When serializing to JSON, if that chunk is still there, we'll still be
    //   able to find that old stack, so nothing will be lost.
    // - If unfortunately that chunk has been destroyed, we will lose this
    //   sample. But this will only happen to the first sample (per thread) in
    //   in the whole JSON output, because the next time we're here to duplicate
    //   the same sample again, IsIndexInCurrentChunk will say `false` and we
    //   will fall back to the normal copy or even re-sample. Losing the first
    //   sample out of many in a whole recording is acceptable.
    //
    // |---| = chunk, S = Sample, D = Duplicate, s = same sample
    // |---S-s-s--| |s-D--s--s-| |s-D--s---s|
    // Later, the first chunk is destroyed/recycled:
    //              |s-D--s--s-| |s-D--s---s| |-...
    // Output:       ^ ^  ^       ^
    //               `-|--|-------|--- Same but no previous -> lost.
    //                 `--|-------|--- Full duplicate sample.
    //                    `-------|--- Same with previous -> okay.
    //                            `--- Same but now we have a previous -> okay!

    AUTO_PROFILER_STATS(DuplicateLastSample_SameSample);

    // Add the thread id first. We don't update `aLastSample` because we are not
    // writing a full sample.
    (void)AddThreadIdEntry(aThreadId);

    // Copy the new time, to be followed by a SameSample.
    AddEntry(ProfileBufferEntry::TimeBeforeSameSample(aSampleTimeMs));

    // Add running times if they have data.
    if (!aRunningTimes.IsEmpty()) {
      mEntries.PutObjects(ProfileBufferEntry::Kind::RunningTimes,
                          aRunningTimes);
    }

    // Finish with a SameSample entry.
    mEntries.PutObjects(ProfileBufferEntry::Kind::SameSample);

    return true;
  }

  AUTO_PROFILER_STATS(DuplicateLastSample_copy);

  ProfileChunkedBuffer tempBuffer(
      ProfileChunkedBuffer::ThreadSafety::WithoutMutex, WorkerChunkManager());

  auto retrieveWorkerChunk = MakeScopeExit(
      [&]() { WorkerChunkManager().Reset(tempBuffer.GetAllChunks()); });

  const bool ok = mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
    MOZ_ASSERT(aReader,
               "ProfileChunkedBuffer cannot be out-of-session when sampler is "
               "running");

    // DuplicateLastSample is only called during profiling, so we don't need a
    // progress logger (only useful when capturing the final profile).
    EntryGetter e(*aReader, mozilla::FailureLatchInfallibleSource::Singleton(),
                  ProgressLogger{}, *aLastSample);

    if (e.CurPos() != *aLastSample) {
      // The last sample is no longer within the buffer range, so we cannot
      // use it. Reset the stored buffer position to Nothing().
      aLastSample.reset();
      return false;
    }

    MOZ_RELEASE_ASSERT(e.Has() && e.Get().IsThreadId() &&
                       e.Get().GetThreadId() == aThreadId);

    e.Next();

    // Go through the whole entry and duplicate it, until we find the next
    // one.
    while (e.Has()) {
      switch (e.Get().GetKind()) {
        case ProfileBufferEntry::Kind::Pause:
        case ProfileBufferEntry::Kind::Resume:
        case ProfileBufferEntry::Kind::PauseSampling:
        case ProfileBufferEntry::Kind::ResumeSampling:
        case ProfileBufferEntry::Kind::CollectionStart:
        case ProfileBufferEntry::Kind::CollectionEnd:
        case ProfileBufferEntry::Kind::ThreadId:
        case ProfileBufferEntry::Kind::TimeBeforeSameSample:
          // We're done.
          return true;
        case ProfileBufferEntry::Kind::Time:
          // Copy with new time
          AddEntry(tempBuffer, ProfileBufferEntry::Time(aSampleTimeMs));
          break;
        case ProfileBufferEntry::Kind::TimeBeforeCompactStack: {
          // Copy with new time, followed by a compact stack.
          AddEntry(tempBuffer,
                   ProfileBufferEntry::TimeBeforeCompactStack(aSampleTimeMs));

          // Add running times if they have data.
          if (!aRunningTimes.IsEmpty()) {
            tempBuffer.PutObjects(ProfileBufferEntry::Kind::RunningTimes,
                                  aRunningTimes);
          }

          // The `CompactStack` *must* be present afterwards, but may not
          // immediately follow `TimeBeforeCompactStack` (e.g., some markers
          // could be written in-between), so we need to look for it in the
          // following entries.
          ProfileChunkedBuffer::BlockIterator it = e.Iterator();
          for (;;) {
            ++it;
            if (it.IsAtEnd()) {
              break;
            }
            ProfileBufferEntryReader er = *it;
            auto kind = static_cast<ProfileBufferEntry::Kind>(
                er.ReadObject<ProfileBufferEntry::KindUnderlyingType>());
            MOZ_ASSERT(
                static_cast<ProfileBufferEntry::KindUnderlyingType>(kind) <
                static_cast<ProfileBufferEntry::KindUnderlyingType>(
                    ProfileBufferEntry::Kind::MODERN_LIMIT));
            if (kind == ProfileBufferEntry::Kind::CompactStack) {
              // Found our CompactStack, just make a copy of the whole entry.
              er = *it;
              auto bytes = er.RemainingBytes();
              MOZ_ASSERT(bytes <
                         ProfileBufferChunkManager::scExpectedMaximumStackSize);
              tempBuffer.Put(bytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) {
                MOZ_ASSERT(aEW.isSome(), "tempBuffer cannot be out-of-session");
                aEW->WriteFromReader(er, bytes);
              });
              // CompactStack marks the end, we're done.
              break;
            }

            MOZ_ASSERT(kind >= ProfileBufferEntry::Kind::LEGACY_LIMIT,
                       "There should be no legacy entries between "
                       "TimeBeforeCompactStack and CompactStack");
            er.SetRemainingBytes(0);
            // Here, we have encountered a non-legacy entry that was not the
            // CompactStack we're looking for; just continue the search...
          }
          // We're done.
          return true;
        }
        case ProfileBufferEntry::Kind::Number:
        case ProfileBufferEntry::Kind::Count:
          // Don't copy anything not part of a thread's stack sample
          break;
        case ProfileBufferEntry::Kind::CounterId:
          // CounterId is normally followed by Time - if so, we'd like
          // to skip it.  If we duplicate Time, it won't hurt anything, just
          // waste buffer space (and this can happen if the CounterId has
          // fallen off the end of the buffer, but Time (and Number/Count)
          // are still in the buffer).
          e.Next();
          if (e.Has() && e.Get().GetKind() != ProfileBufferEntry::Kind::Time) {
            // this would only happen if there was an invalid sequence
            // in the buffer.  Don't skip it.
            continue;
          }
          // we've skipped Time
          break;
        case ProfileBufferEntry::Kind::ProfilerOverheadTime:
          // ProfilerOverheadTime is normally followed by
          // ProfilerOverheadDuration*4 - if so, we'd like to skip it. Don't
          // duplicate, as we are in the middle of a sampling and will soon
          // capture its own overhead.
          e.Next();
          // A missing Time would only happen if there was an invalid
          // sequence in the buffer. Don't skip unexpected entry.
          if (e.Has() &&
              e.Get().GetKind() !=
                  ProfileBufferEntry::Kind::ProfilerOverheadDuration) {
            continue;
          }
          e.Next();
          if (e.Has() &&
              e.Get().GetKind() !=
                  ProfileBufferEntry::Kind::ProfilerOverheadDuration) {
            continue;
          }
          e.Next();
          if (e.Has() &&
              e.Get().GetKind() !=
                  ProfileBufferEntry::Kind::ProfilerOverheadDuration) {
            continue;
          }
          e.Next();
          if (e.Has() &&
              e.Get().GetKind() !=
                  ProfileBufferEntry::Kind::ProfilerOverheadDuration) {
            continue;
          }
          // we've skipped ProfilerOverheadTime and
          // ProfilerOverheadDuration*4.
          break;
        default: {
          // Copy anything else we don't know about.
          AddEntry(tempBuffer, e.Get());
          break;
        }
      }
      e.Next();
    }
    return true;
  });

  if (!ok) {
    return false;
  }

  // If the buffer was big enough, there won't be any cleared blocks.
  if (tempBuffer.GetState().mClearedBlockCount != 0) {
    // No need to try to read stack again as it won't fit. Reset the stored
    // buffer position to Nothing().
    aLastSample.reset();
    return false;
  }

  aLastSample = Some(AddThreadIdEntry(aThreadId));

  mEntries.AppendContents(tempBuffer);

  return true;
}

void ProfileBuffer::DiscardSamplesBeforeTime(double aTime) {
  // This function does nothing!
  // The duration limit will be removed from Firefox, see bug 1632365.
  Unused << aTime;
}

// END ProfileBuffer
////////////////////////////////////////////////////////////////////////

Messung V0.5 in Prozent
C=83 H=92 G=87

¤ Dauer der Verarbeitung: 0.37 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.