/* -*- 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 "BaseProfilingCategory.h" #include "gtest/MozGtestFriend.h" #include "mozilla/BaseProfileJSONWriter.h" #include "mozilla/HashFunctions.h" #include "mozilla/HashTable.h" #include "mozilla/Maybe.h" #include "mozilla/ProfileBufferEntryKinds.h" #include "mozilla/UniquePtr.h" #include "mozilla/Variant.h" #include "mozilla/Vector.h" #include #include namespace mozilla { namespace baseprofiler { class ProfileBufferEntry { public: using KindUnderlyingType = std::underlying_type_t<::mozilla::ProfileBufferEntryKind>; 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, BaseProfilerThreadId 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; BaseProfilerThreadId 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"); class UniqueStacks { public: struct FrameKey { explicit FrameKey(const char* aLocation) : mData(NormalFrameData{std::string(aLocation), false, 0, Nothing(), Nothing()}) {} FrameKey(std::string&& aLocation, bool aRelevantForJS, uint64_t aInnerWindowID, const Maybe& aLine, const Maybe& aColumn, const Maybe& aCategoryPair) : mData(NormalFrameData{aLocation, aRelevantForJS, aInnerWindowID, aLine, aColumn, aCategoryPair}) {} 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; std::string mLocation; bool mRelevantForJS; uint64_t mInnerWindowID; Maybe mLine; Maybe mColumn; Maybe mCategoryPair; }; Variant mData; }; struct FrameKeyHasher { using Lookup = FrameKey; static HashNumber hash(const FrameKey& aLookup) { HashNumber hash = 0; if (aLookup.mData.is()) { const FrameKey::NormalFrameData& data = aLookup.mData.as(); if (!data.mLocation.empty()) { hash = AddToHash(hash, HashString(data.mLocation.c_str())); } hash = AddToHash(hash, data.mRelevantForJS); hash = mozilla::AddToHash(hash, data.mInnerWindowID); if (data.mLine.isSome()) { hash = AddToHash(hash, *data.mLine); } if (data.mColumn.isSome()) { hash = AddToHash(hash, *data.mColumn); } if (data.mCategoryPair.isSome()) { hash = AddToHash(hash, static_cast(*data.mCategoryPair)); } } 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 { Maybe mPrefixStackIndex; uint32_t mFrameIndex; explicit StackKey(uint32_t aFrame) : mFrameIndex(aFrame), mHash(HashGeneric(aFrame)) {} StackKey(const StackKey& aPrefix, uint32_t aPrefixStackIndex, uint32_t aFrame) : mPrefixStackIndex(Some(aPrefixStackIndex)), mFrameIndex(aFrame), mHash(AddToHash(aPrefix.mHash, aFrame)) {} HashNumber Hash() const { return mHash; } bool operator==(const StackKey& aOther) const { return mPrefixStackIndex == aOther.mPrefixStackIndex && mFrameIndex == aOther.mFrameIndex; } private: HashNumber mHash; }; struct StackKeyHasher { using Lookup = StackKey; static 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(); // Return a StackKey for aFrame as the stack's root frame (no prefix). [[nodiscard]] StackKey BeginStack(const FrameKey& aFrame); // Return a new StackKey that is obtained by appending aFrame to aStack. [[nodiscard]] StackKey AppendFrame(const StackKey& aStack, const FrameKey& aFrame); [[nodiscard]] uint32_t GetOrAddFrameIndex(const FrameKey& aFrame); [[nodiscard]] uint32_t GetOrAddStackIndex(const StackKey& aStack); void SpliceFrameTableElements(SpliceableJSONWriter& aWriter); void SpliceStackTableElements(SpliceableJSONWriter& aWriter); UniqueJSONStrings& UniqueStrings() { MOZ_RELEASE_ASSERT(mUniqueStrings.get()); return *mUniqueStrings; } private: void StreamNonJITFrame(const FrameKey& aFrame); void StreamStack(const StackKey& aStack); UniquePtr mUniqueStrings; SpliceableChunkedJSONWriter mFrameTableWriter; HashMap mFrameToIndexMap; SpliceableChunkedJSONWriter mStackTableWriter; HashMap mStackToIndexMap; }; // // 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 */ // }, // "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 */ // "line": 4, /* number */ // "column": 5, /* number */ // "category": 6, /* index into profile.meta.categories */ // "subcategory": 7 /* index into // profile.meta.categories[category].subcategories */ // }, // "data": // [ // [ 0 ], /* { location: '(root)' } */ // [ 1, 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 */ // ], // } // } // namespace baseprofiler } // namespace mozilla #endif /* ndef ProfileBufferEntry_h */