mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
9225e9b0f1
Differential Revision: https://phabricator.services.mozilla.com/D155658
532 lines
18 KiB
C++
532 lines
18 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef ProfileBufferEntry_h
|
|
#define ProfileBufferEntry_h
|
|
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <utility>
|
|
#include "gtest/MozGtestFriend.h"
|
|
#include "js/ProfilingCategory.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/HashFunctions.h"
|
|
#include "mozilla/HashTable.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/ProfileBufferEntryKinds.h"
|
|
#include "mozilla/ProfileJSONWriter.h"
|
|
#include "mozilla/ProfilerUtils.h"
|
|
#include "mozilla/UniquePtrExtensions.h"
|
|
#include "mozilla/Variant.h"
|
|
#include "mozilla/Vector.h"
|
|
#include "nsString.h"
|
|
|
|
class ProfilerCodeAddressService;
|
|
struct JSContext;
|
|
|
|
class ProfileBufferEntry {
|
|
public:
|
|
using KindUnderlyingType = mozilla::ProfileBufferEntryKindUnderlyingType;
|
|
using Kind = mozilla::ProfileBufferEntryKind;
|
|
|
|
ProfileBufferEntry();
|
|
|
|
static constexpr size_t kNumChars = mozilla::ProfileBufferEntryNumChars;
|
|
|
|
private:
|
|
// aString must be a static string.
|
|
ProfileBufferEntry(Kind aKind, const char* aString);
|
|
ProfileBufferEntry(Kind aKind, char aChars[kNumChars]);
|
|
ProfileBufferEntry(Kind aKind, void* aPtr);
|
|
ProfileBufferEntry(Kind aKind, double aDouble);
|
|
ProfileBufferEntry(Kind aKind, int64_t aInt64);
|
|
ProfileBufferEntry(Kind aKind, uint64_t aUint64);
|
|
ProfileBufferEntry(Kind aKind, int aInt);
|
|
ProfileBufferEntry(Kind aKind, ProfilerThreadId aThreadId);
|
|
|
|
public:
|
|
#define CTOR(KIND, TYPE, SIZE) \
|
|
static ProfileBufferEntry KIND(TYPE aVal) { \
|
|
return ProfileBufferEntry(Kind::KIND, aVal); \
|
|
}
|
|
FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(CTOR)
|
|
#undef CTOR
|
|
|
|
Kind GetKind() const { return mKind; }
|
|
|
|
#define IS_KIND(KIND, TYPE, SIZE) \
|
|
bool Is##KIND() const { return mKind == Kind::KIND; }
|
|
FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(IS_KIND)
|
|
#undef IS_KIND
|
|
|
|
private:
|
|
FRIEND_TEST(ThreadProfile, InsertOneEntry);
|
|
FRIEND_TEST(ThreadProfile, InsertOneEntryWithTinyBuffer);
|
|
FRIEND_TEST(ThreadProfile, InsertEntriesNoWrap);
|
|
FRIEND_TEST(ThreadProfile, InsertEntriesWrap);
|
|
FRIEND_TEST(ThreadProfile, MemoryMeasure);
|
|
friend class ProfileBuffer;
|
|
|
|
Kind mKind;
|
|
uint8_t mStorage[kNumChars];
|
|
|
|
const char* GetString() const;
|
|
void* GetPtr() const;
|
|
double GetDouble() const;
|
|
int GetInt() const;
|
|
int64_t GetInt64() const;
|
|
uint64_t GetUint64() const;
|
|
ProfilerThreadId GetThreadId() const;
|
|
void CopyCharsInto(char (&aOutArray)[kNumChars]) const;
|
|
};
|
|
|
|
// Packed layout: 1 byte for the tag + 8 bytes for the value.
|
|
static_assert(sizeof(ProfileBufferEntry) == 9, "bad ProfileBufferEntry size");
|
|
|
|
// Contains all the information about JIT frames that is needed to stream stack
|
|
// frames for JitReturnAddr entries in the profiler buffer.
|
|
// Every return address (void*) is mapped to one or more JITFrameKeys, and
|
|
// every JITFrameKey is mapped to a JSON string for that frame.
|
|
// mRangeStart and mRangeEnd describe the range in the buffer for which this
|
|
// mapping is valid. Only JitReturnAddr entries within that buffer range can be
|
|
// processed using this JITFrameInfoForBufferRange object.
|
|
struct JITFrameInfoForBufferRange final {
|
|
JITFrameInfoForBufferRange Clone() const;
|
|
|
|
uint64_t mRangeStart;
|
|
uint64_t mRangeEnd; // mRangeEnd marks the first invalid index.
|
|
|
|
struct JITFrameKey {
|
|
bool operator==(const JITFrameKey& aOther) const {
|
|
return mCanonicalAddress == aOther.mCanonicalAddress &&
|
|
mDepth == aOther.mDepth;
|
|
}
|
|
bool operator!=(const JITFrameKey& aOther) const {
|
|
return !(*this == aOther);
|
|
}
|
|
|
|
void* mCanonicalAddress;
|
|
uint32_t mDepth;
|
|
};
|
|
struct JITFrameKeyHasher {
|
|
using Lookup = JITFrameKey;
|
|
|
|
static mozilla::HashNumber hash(const JITFrameKey& aLookup) {
|
|
mozilla::HashNumber hash = 0;
|
|
hash = mozilla::AddToHash(hash, aLookup.mCanonicalAddress);
|
|
hash = mozilla::AddToHash(hash, aLookup.mDepth);
|
|
return hash;
|
|
}
|
|
|
|
static bool match(const JITFrameKey& aKey, const JITFrameKey& aLookup) {
|
|
return aKey == aLookup;
|
|
}
|
|
|
|
static void rekey(JITFrameKey& aKey, const JITFrameKey& aNewKey) {
|
|
aKey = aNewKey;
|
|
}
|
|
};
|
|
|
|
using JITAddressToJITFramesMap =
|
|
mozilla::HashMap<void*, mozilla::Vector<JITFrameKey>>;
|
|
JITAddressToJITFramesMap mJITAddressToJITFramesMap;
|
|
using JITFrameToFrameJSONMap =
|
|
mozilla::HashMap<JITFrameKey, nsCString, JITFrameKeyHasher>;
|
|
JITFrameToFrameJSONMap mJITFrameToFrameJSONMap;
|
|
};
|
|
|
|
// Contains JITFrameInfoForBufferRange objects for multiple profiler buffer
|
|
// ranges.
|
|
class JITFrameInfo final {
|
|
public:
|
|
JITFrameInfo()
|
|
: mUniqueStrings(mozilla::MakeUniqueFallible<UniqueJSONStrings>(
|
|
mLocalFailureLatchSource)) {
|
|
if (!mUniqueStrings) {
|
|
mLocalFailureLatchSource.SetFailure(
|
|
"OOM in JITFrameInfo allocating mUniqueStrings");
|
|
}
|
|
}
|
|
|
|
MOZ_IMPLICIT JITFrameInfo(const JITFrameInfo& aOther,
|
|
mozilla::ProgressLogger aProgressLogger);
|
|
|
|
// Creates a new JITFrameInfoForBufferRange object in mRanges by looking up
|
|
// information about the provided JIT return addresses using aCx.
|
|
// Addresses are provided like this:
|
|
// The caller of AddInfoForRange supplies a function in aJITAddressProvider.
|
|
// This function will be called once, synchronously, with an
|
|
// aJITAddressConsumer argument, which is a function that needs to be called
|
|
// for every address. That function can be called multiple times for the same
|
|
// address.
|
|
void AddInfoForRange(
|
|
uint64_t aRangeStart, uint64_t aRangeEnd, JSContext* aCx,
|
|
const std::function<void(const std::function<void(void*)>&)>&
|
|
aJITAddressProvider);
|
|
|
|
// Returns whether the information stored in this object is still relevant
|
|
// for any entries in the buffer.
|
|
bool HasExpired(uint64_t aCurrentBufferRangeStart) const {
|
|
if (mRanges.empty()) {
|
|
// No information means no relevant information. Allow this object to be
|
|
// discarded.
|
|
return true;
|
|
}
|
|
return mRanges.back().mRangeEnd <= aCurrentBufferRangeStart;
|
|
}
|
|
|
|
mozilla::FailureLatch& LocalFailureLatchSource() {
|
|
return mLocalFailureLatchSource;
|
|
}
|
|
|
|
// The encapsulated data points at the local FailureLatch, so on the way out
|
|
// they must be given a new external FailureLatch to start using instead.
|
|
mozilla::Vector<JITFrameInfoForBufferRange>&& MoveRangesWithNewFailureLatch(
|
|
mozilla::FailureLatch& aFailureLatch) &&;
|
|
mozilla::UniquePtr<UniqueJSONStrings>&& MoveUniqueStringsWithNewFailureLatch(
|
|
mozilla::FailureLatch& aFailureLatch) &&;
|
|
|
|
private:
|
|
// JITFrameInfo's may exist during profiling, so it carries its own fallible
|
|
// FailureLatch. If&when the data below is finally extracted, any error is
|
|
// forwarded to the caller.
|
|
mozilla::FailureLatchSource mLocalFailureLatchSource;
|
|
|
|
// The array of ranges of JIT frame information, sorted by buffer position.
|
|
// Ranges are non-overlapping.
|
|
// The JSON of the cached frames can contain string indexes, which refer
|
|
// to strings in mUniqueStrings.
|
|
mozilla::Vector<JITFrameInfoForBufferRange> mRanges;
|
|
|
|
// The string table which contains strings used in the frame JSON that's
|
|
// cached in mRanges.
|
|
mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
|
|
};
|
|
|
|
class UniqueStacks final : public mozilla::FailureLatch {
|
|
public:
|
|
struct FrameKey {
|
|
explicit FrameKey(const char* aLocation)
|
|
: mData(NormalFrameData{nsCString(aLocation), false, false, 0,
|
|
mozilla::Nothing(), mozilla::Nothing()}) {}
|
|
|
|
FrameKey(nsCString&& aLocation, bool aRelevantForJS, bool aBaselineInterp,
|
|
uint64_t aInnerWindowID, const mozilla::Maybe<unsigned>& aLine,
|
|
const mozilla::Maybe<unsigned>& aColumn,
|
|
const mozilla::Maybe<JS::ProfilingCategoryPair>& aCategoryPair)
|
|
: mData(NormalFrameData{aLocation, aRelevantForJS, aBaselineInterp,
|
|
aInnerWindowID, aLine, aColumn,
|
|
aCategoryPair}) {}
|
|
|
|
FrameKey(void* aJITAddress, uint32_t aJITDepth, uint32_t aRangeIndex)
|
|
: mData(JITFrameData{aJITAddress, aJITDepth, aRangeIndex}) {}
|
|
|
|
FrameKey(const FrameKey& aToCopy) = default;
|
|
|
|
uint32_t Hash() const;
|
|
bool operator==(const FrameKey& aOther) const {
|
|
return mData == aOther.mData;
|
|
}
|
|
|
|
struct NormalFrameData {
|
|
bool operator==(const NormalFrameData& aOther) const;
|
|
|
|
nsCString mLocation;
|
|
bool mRelevantForJS;
|
|
bool mBaselineInterp;
|
|
uint64_t mInnerWindowID;
|
|
mozilla::Maybe<unsigned> mLine;
|
|
mozilla::Maybe<unsigned> mColumn;
|
|
mozilla::Maybe<JS::ProfilingCategoryPair> mCategoryPair;
|
|
};
|
|
struct JITFrameData {
|
|
bool operator==(const JITFrameData& aOther) const;
|
|
|
|
void* mCanonicalAddress;
|
|
uint32_t mDepth;
|
|
uint32_t mRangeIndex;
|
|
};
|
|
mozilla::Variant<NormalFrameData, JITFrameData> mData;
|
|
};
|
|
|
|
struct FrameKeyHasher {
|
|
using Lookup = FrameKey;
|
|
|
|
static mozilla::HashNumber hash(const FrameKey& aLookup) {
|
|
mozilla::HashNumber hash = 0;
|
|
if (aLookup.mData.is<FrameKey::NormalFrameData>()) {
|
|
const FrameKey::NormalFrameData& data =
|
|
aLookup.mData.as<FrameKey::NormalFrameData>();
|
|
if (!data.mLocation.IsEmpty()) {
|
|
hash = mozilla::AddToHash(hash,
|
|
mozilla::HashString(data.mLocation.get()));
|
|
}
|
|
hash = mozilla::AddToHash(hash, data.mRelevantForJS);
|
|
hash = mozilla::AddToHash(hash, data.mBaselineInterp);
|
|
hash = mozilla::AddToHash(hash, data.mInnerWindowID);
|
|
if (data.mLine.isSome()) {
|
|
hash = mozilla::AddToHash(hash, *data.mLine);
|
|
}
|
|
if (data.mColumn.isSome()) {
|
|
hash = mozilla::AddToHash(hash, *data.mColumn);
|
|
}
|
|
if (data.mCategoryPair.isSome()) {
|
|
hash = mozilla::AddToHash(hash,
|
|
static_cast<uint32_t>(*data.mCategoryPair));
|
|
}
|
|
} else {
|
|
const FrameKey::JITFrameData& data =
|
|
aLookup.mData.as<FrameKey::JITFrameData>();
|
|
hash = mozilla::AddToHash(hash, data.mCanonicalAddress);
|
|
hash = mozilla::AddToHash(hash, data.mDepth);
|
|
hash = mozilla::AddToHash(hash, data.mRangeIndex);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static bool match(const FrameKey& aKey, const FrameKey& aLookup) {
|
|
return aKey == aLookup;
|
|
}
|
|
|
|
static void rekey(FrameKey& aKey, const FrameKey& aNewKey) {
|
|
aKey = aNewKey;
|
|
}
|
|
};
|
|
|
|
struct StackKey {
|
|
mozilla::Maybe<uint32_t> mPrefixStackIndex;
|
|
uint32_t mFrameIndex;
|
|
|
|
explicit StackKey(uint32_t aFrame)
|
|
: mFrameIndex(aFrame), mHash(mozilla::HashGeneric(aFrame)) {}
|
|
|
|
StackKey(const StackKey& aPrefix, uint32_t aPrefixStackIndex,
|
|
uint32_t aFrame)
|
|
: mPrefixStackIndex(mozilla::Some(aPrefixStackIndex)),
|
|
mFrameIndex(aFrame),
|
|
mHash(mozilla::AddToHash(aPrefix.mHash, aFrame)) {}
|
|
|
|
mozilla::HashNumber Hash() const { return mHash; }
|
|
|
|
bool operator==(const StackKey& aOther) const {
|
|
return mPrefixStackIndex == aOther.mPrefixStackIndex &&
|
|
mFrameIndex == aOther.mFrameIndex;
|
|
}
|
|
|
|
private:
|
|
mozilla::HashNumber mHash;
|
|
};
|
|
|
|
struct StackKeyHasher {
|
|
using Lookup = StackKey;
|
|
|
|
static mozilla::HashNumber hash(const StackKey& aLookup) {
|
|
return aLookup.Hash();
|
|
}
|
|
|
|
static bool match(const StackKey& aKey, const StackKey& aLookup) {
|
|
return aKey == aLookup;
|
|
}
|
|
|
|
static void rekey(StackKey& aKey, const StackKey& aNewKey) {
|
|
aKey = aNewKey;
|
|
}
|
|
};
|
|
|
|
UniqueStacks(mozilla::FailureLatch& aFailureLatch,
|
|
JITFrameInfo&& aJITFrameInfo,
|
|
ProfilerCodeAddressService* aCodeAddressService = nullptr);
|
|
|
|
// Return a StackKey for aFrame as the stack's root frame (no prefix).
|
|
[[nodiscard]] mozilla::Maybe<StackKey> BeginStack(const FrameKey& aFrame);
|
|
|
|
// Return a new StackKey that is obtained by appending aFrame to aStack.
|
|
[[nodiscard]] mozilla::Maybe<StackKey> AppendFrame(const StackKey& aStack,
|
|
const FrameKey& aFrame);
|
|
|
|
// Look up frame keys for the given JIT address, and ensure that our frame
|
|
// table has entries for the returned frame keys. The JSON for these frames
|
|
// is taken from mJITInfoRanges.
|
|
// aBufferPosition is needed in order to look up the correct JIT frame info
|
|
// object in mJITInfoRanges.
|
|
[[nodiscard]] mozilla::Maybe<mozilla::Vector<UniqueStacks::FrameKey>>
|
|
LookupFramesForJITAddressFromBufferPos(void* aJITAddress,
|
|
uint64_t aBufferPosition);
|
|
|
|
[[nodiscard]] mozilla::Maybe<uint32_t> GetOrAddFrameIndex(
|
|
const FrameKey& aFrame);
|
|
[[nodiscard]] mozilla::Maybe<uint32_t> GetOrAddStackIndex(
|
|
const StackKey& aStack);
|
|
|
|
void SpliceFrameTableElements(SpliceableJSONWriter& aWriter);
|
|
void SpliceStackTableElements(SpliceableJSONWriter& aWriter);
|
|
|
|
[[nodiscard]] UniqueJSONStrings& UniqueStrings() {
|
|
MOZ_RELEASE_ASSERT(mUniqueStrings.get());
|
|
return *mUniqueStrings;
|
|
}
|
|
|
|
// Find the function name at the given PC (if a ProfilerCodeAddressService was
|
|
// provided), otherwise just stringify that PC.
|
|
[[nodiscard]] nsAutoCString FunctionNameOrAddress(void* aPC);
|
|
|
|
FAILURELATCH_IMPL_PROXY(mFrameTableWriter)
|
|
|
|
private:
|
|
void StreamNonJITFrame(const FrameKey& aFrame);
|
|
void StreamStack(const StackKey& aStack);
|
|
|
|
mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
|
|
|
|
ProfilerCodeAddressService* mCodeAddressService = nullptr;
|
|
|
|
SpliceableChunkedJSONWriter mFrameTableWriter;
|
|
mozilla::HashMap<FrameKey, uint32_t, FrameKeyHasher> mFrameToIndexMap;
|
|
|
|
SpliceableChunkedJSONWriter mStackTableWriter;
|
|
mozilla::HashMap<StackKey, uint32_t, StackKeyHasher> mStackToIndexMap;
|
|
|
|
mozilla::Vector<JITFrameInfoForBufferRange> mJITInfoRanges;
|
|
};
|
|
|
|
//
|
|
// Thread profile JSON Format
|
|
// --------------------------
|
|
//
|
|
// The profile contains much duplicate information. The output JSON of the
|
|
// profile attempts to deduplicate strings, frames, and stack prefixes, to cut
|
|
// down on size and to increase JSON streaming speed. Deduplicated values are
|
|
// streamed as indices into their respective tables.
|
|
//
|
|
// Further, arrays of objects with the same set of properties (e.g., samples,
|
|
// frames) are output as arrays according to a schema instead of an object
|
|
// with property names. A property that is not present is represented in the
|
|
// array as null or undefined.
|
|
//
|
|
// The format of the thread profile JSON is shown by the following example
|
|
// with 1 sample and 1 marker:
|
|
//
|
|
// {
|
|
// "name": "Foo",
|
|
// "tid": 42,
|
|
// "samples":
|
|
// {
|
|
// "schema":
|
|
// {
|
|
// "stack": 0, /* index into stackTable */
|
|
// "time": 1, /* number */
|
|
// "eventDelay": 2, /* number */
|
|
// "ThreadCPUDelta": 3, /* optional number */
|
|
// },
|
|
// "data":
|
|
// [
|
|
// [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, eventDelay: 0.0 } */
|
|
// ]
|
|
// },
|
|
//
|
|
// "markers":
|
|
// {
|
|
// "schema":
|
|
// {
|
|
// "name": 0, /* index into stringTable */
|
|
// "time": 1, /* number */
|
|
// "data": 2 /* arbitrary JSON */
|
|
// },
|
|
// "data":
|
|
// [
|
|
// [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */
|
|
// ]
|
|
// },
|
|
//
|
|
// "stackTable":
|
|
// {
|
|
// "schema":
|
|
// {
|
|
// "prefix": 0, /* index into stackTable */
|
|
// "frame": 1 /* index into frameTable */
|
|
// },
|
|
// "data":
|
|
// [
|
|
// [ null, 0 ], /* (root) */
|
|
// [ 0, 1 ] /* (root) > foo.js */
|
|
// ]
|
|
// },
|
|
//
|
|
// "frameTable":
|
|
// {
|
|
// "schema":
|
|
// {
|
|
// "location": 0, /* index into stringTable */
|
|
// "relevantForJS": 1, /* bool */
|
|
// "innerWindowID": 2, /* inner window ID of global JS `window` object */
|
|
// "implementation": 3, /* index into stringTable */
|
|
// "optimizations": 4, /* arbitrary JSON */
|
|
// "line": 5, /* number */
|
|
// "column": 6, /* number */
|
|
// "category": 7, /* index into profile.meta.categories */
|
|
// "subcategory": 8 /* index into
|
|
// profile.meta.categories[category].subcategories */
|
|
// },
|
|
// "data":
|
|
// [
|
|
// [ 0 ], /* { location: '(root)' } */
|
|
// [ 1, null, null, 2 ] /* { location: 'foo.js',
|
|
// implementation: 'baseline' } */
|
|
// ]
|
|
// },
|
|
//
|
|
// "stringTable":
|
|
// [
|
|
// "(root)",
|
|
// "foo.js",
|
|
// "baseline",
|
|
// "example marker"
|
|
// ]
|
|
// }
|
|
//
|
|
// Process:
|
|
// {
|
|
// "name": "Bar",
|
|
// "pid": 24,
|
|
// "threads":
|
|
// [
|
|
// <0-N threads from above>
|
|
// ],
|
|
// "counters": /* includes the memory counter */
|
|
// [
|
|
// {
|
|
// "name": "qwerty",
|
|
// "category": "uiop",
|
|
// "description": "this is qwerty uiop",
|
|
// "sample_groups:
|
|
// [
|
|
// {
|
|
// "id": 42, /* number (thread id, or object identifier (tab), etc) */
|
|
// "samples:
|
|
// {
|
|
// "schema":
|
|
// {
|
|
// "time": 1, /* number */
|
|
// "number": 2, /* number (of times the counter was touched) */
|
|
// "count": 3 /* number (total for the counter) */
|
|
// },
|
|
// "data":
|
|
// [
|
|
// [ 0.1, 1824,
|
|
// 454622 ] /* { time: 0.1, number: 1824, count: 454622 } */
|
|
// ]
|
|
// },
|
|
// },
|
|
// /* more sample-group objects with different id's */
|
|
// ]
|
|
// },
|
|
// /* more counters */
|
|
// ],
|
|
// }
|
|
//
|
|
#endif /* ndef ProfileBufferEntry_h */
|