gecko-dev/mozglue/baseprofiler/public/BaseProfileJSONWriter.h
Gerald Squelart 50995cc271 Bug 1673513 - Use ProgressLogger in Gecko Profiler - r=florian
Add `ProgressLogger` parameter to most JSON-generating functions.
Each function can update the given `ProgressLogger` between 0% and 100%, and create sub-loggers when calling functions.

The main goal of this instrumentation is to notice when any progress is made by child processes (when the parent process is gathering profiles), so it needs to go deep enough so that it is not stuck on a progress value for "too long" -- During development, that meant progress was always happening when observed every 10ms; In later patches, the overall timeout for no-progress-made will be at least 1 second.

Differential Revision: https://phabricator.services.mozilla.com/D135479
2022-01-31 02:22:27 +00:00

458 lines
15 KiB
C++

/* -*- 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/HashFunctions.h"
#include "mozilla/HashTable.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/ProgressLogger.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include <functional>
#include <ostream>
#include <string_view>
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:
friend class SpliceableJSONWriter;
ChunkedJSONWriteFunc() : mChunkPtr{nullptr}, mChunkEnd{nullptr} {
AllocChunk(kChunkSize);
}
bool IsEmpty() const {
MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd && mChunkList.length() == 0 &&
mChunkLengths.length() == 0);
return !mChunkPtr;
}
void Write(const Span<const char>& aStr) override {
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) {
AllocChunk(aStr.size() + 1);
newPtr = mChunkPtr + aStr.size();
} else {
newPtr = mChunkPtr + aStr.size();
if (newPtr >= mChunkEnd) {
AllocChunk(kChunkSize);
newPtr = mChunkPtr + aStr.size();
}
}
memcpy(mChunkPtr, aStr.data(), aStr.size());
*newPtr = '\0';
mChunkPtr = newPtr;
mChunkLengths.back() += aStr.size();
}
void CopyDataIntoLazilyAllocatedBuffer(
const std::function<char*(size_t)>& aAllocator) const {
// Request a buffer for the full content plus a null terminator.
MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
size_t totalLen = 1;
for (size_t i = 0; i < mChunkLengths.length(); i++) {
MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]);
totalLen += mChunkLengths[i];
}
char* ptr = aAllocator(totalLen);
if (!ptr) {
// Failed to allocate memory.
return;
}
for (size_t i = 0; i < mChunkList.length(); i++) {
size_t len = mChunkLengths[i];
memcpy(ptr, mChunkList[i].get(), len);
ptr += len;
}
*ptr = '\0';
}
UniquePtr<char[]> CopyData() const {
UniquePtr<char[]> c;
CopyDataIntoLazilyAllocatedBuffer([&](size_t allocationSize) {
c = MakeUnique<char[]>(allocationSize);
return c.get();
});
return c;
}
void Take(ChunkedJSONWriteFunc&& aOther) {
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.mChunkPtr = nullptr;
aOther.mChunkEnd = nullptr;
aOther.mChunkList.clear();
aOther.mChunkLengths.clear();
}
private:
void AllocChunk(size_t aChunkSize) {
MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
UniquePtr<char[]> newChunk = MakeUnique<char[]>(aChunkSize);
mChunkPtr = newChunk.get();
mChunkEnd = mChunkPtr + aChunkSize;
*mChunkPtr = '\0';
MOZ_ALWAYS_TRUE(mChunkLengths.append(0));
MOZ_ALWAYS_TRUE(mChunkList.append(std::move(newChunk)));
}
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;
// 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;
// List of chunks and their lengths.
//
// For all i, the length of the string in mChunkList[i] is
// mChunkLengths[i].
Vector<UniquePtr<char[]>> mChunkList;
Vector<size_t> mChunkLengths;
};
struct OStreamJSONWriteFunc final : public JSONWriteFunc {
explicit OStreamJSONWriteFunc(std::ostream& aStream) : mStream(aStream) {}
void Write(const Span<const char>& aStr) override {
std::string_view sv(aStr.data(), aStr.size());
mStream << sv;
}
std::ostream& mStream;
};
class UniqueJSONStrings;
class SpliceableJSONWriter : public JSONWriter {
public:
explicit SpliceableJSONWriter(UniquePtr<JSONWriteFunc> aWriter)
: JSONWriter(std::move(aWriter)) {}
void StartBareList(CollectionStyle aStyle = MultiLineStyle) {
StartCollection(scEmptyString, scEmptyString, aStyle);
}
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<const char>& 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<uint32_t>(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<uint32_t>(million / 10);
for (;;) {
auto digit = remainderNanoseconds / powerOfTen;
buf[len++] = '0' + static_cast<char>(digit);
remainderNanoseconds %= powerOfTen;
if (remainderNanoseconds == 0) {
break;
}
powerOfTen /= 10;
if (powerOfTen == 0) {
break;
}
}
}
Scalar(aMaybePropertyName, Span<const char>(buf, len));
}
// Output a (double) time in milliseconds, with at best nanosecond precision.
void TimeDoubleMsProperty(const Span<const char>& 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<const char>& 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<const char>& aStr) {
Separator();
WriteFunc()->Write(aStr);
mNeedComma[mDepth] = true;
}
void Splice(const char* aStr, size_t aLen) {
Separator();
WriteFunc()->Write(Span<const char>(aStr, aLen));
mNeedComma[mDepth] = true;
}
// Splice the given JSON directly in, without quoting.
void SplicedJSONProperty(const Span<const char>& aMaybePropertyName,
const Span<const char>& aJsonValue) {
Scalar(aMaybePropertyName, aJsonValue);
}
void CopyAndSplice(const ChunkedJSONWriteFunc& aFunc) {
Separator();
for (size_t i = 0; i < aFunc.mChunkList.length(); i++) {
WriteFunc()->Write(
Span<const char>(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<const char>(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<const char>& aName,
const Span<const char>& 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<const char>& aStr);
private:
UniqueJSONStrings* mUniqueStrings = nullptr;
};
class SpliceableChunkedJSONWriter final : public SpliceableJSONWriter {
public:
explicit SpliceableChunkedJSONWriter()
: SpliceableJSONWriter(MakeUnique<ChunkedJSONWriteFunc>()) {}
// Access the ChunkedJSONWriteFunc as reference-to-const, usually to copy data
// out.
const ChunkedJSONWriteFunc& ChunkedWriteFunc() 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<const ChunkedJSONWriteFunc*>(WriteFunc());
}
// Access the ChunkedJSONWriteFunc as rvalue-reference, usually to take its
// data out. This writer shouldn't be used anymore after this.
ChunkedJSONWriteFunc&& TakeChunkedWriteFunc() {
#ifdef DEBUG
MOZ_ASSERT(!mTaken);
mTaken = true;
#endif //
// The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
// only constructor above, so it's safe to cast to ChunkedJSONWriteFunc*.
return std::move(*static_cast<ChunkedJSONWriteFunc*>(WriteFunc()));
}
// Adopts the chunks from aFunc without copying.
void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) override {
MOZ_ASSERT(!mTaken);
Separator();
// The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
// only constructor above, so it's safe to cast to ChunkedJSONWriteFunc*.
static_cast<ChunkedJSONWriteFunc*>(WriteFunc())->Take(std::move(aFunc));
mNeedComma[mDepth] = true;
}
#ifdef DEBUG
private:
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<const char>& aName) {
mWriter.IntProperty(aName, mIndex++);
}
template <size_t Np1>
void WriteField(const char (&aName)[Np1]) {
WriteField(Span<const char>(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 {
public:
// Start an empty list of unique strings.
MFBT_API explicit UniqueJSONStrings(
JSONWriter::CollectionStyle aStyle = JSONWriter::MultiLineStyle);
// Start with a copy of the strings from another list.
MFBT_API explicit UniqueJSONStrings(
const UniqueJSONStrings& aOther, ProgressLogger aProgressLogger,
JSONWriter::CollectionStyle aStyle = JSONWriter::MultiLineStyle);
MFBT_API ~UniqueJSONStrings();
// Add `aStr` to the list (if not already there), and write its index as a
// named object property.
void WriteProperty(JSONWriter& aWriter, const Span<const char>& aName,
const Span<const char>& aStr) {
aWriter.IntProperty(aName, GetOrAddIndex(aStr));
}
// Add `aStr` to the list (if not already there), and write its index as an
// array element.
void WriteElement(JSONWriter& aWriter, const Span<const char>& aStr) {
aWriter.IntElement(GetOrAddIndex(aStr));
}
// 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);
private:
// If `aStr` is already listed, return its index.
// Otherwise add it to the list and return the new index.
MFBT_API uint32_t GetOrAddIndex(const Span<const char>& aStr);
SpliceableChunkedJSONWriter mStringTableWriter;
HashMap<HashNumber, uint32_t> mStringHashToIndexMap;
};
void SpliceableJSONWriter::UniqueStringProperty(const Span<const char>& aName,
const Span<const char>& 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<const char>& aStr) {
MOZ_RELEASE_ASSERT(mUniqueStrings);
mUniqueStrings->WriteElement(*this, aStr);
}
} // namespace baseprofiler
} // namespace mozilla
#endif // BASEPROFILEJSONWRITER_H