/* -*- 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 MOZ_PROFILE_ENTRY_H #define MOZ_PROFILE_ENTRY_H #include #include "GeckoProfiler.h" #include "platform.h" #include "ProfileJSONWriter.h" #include "ProfilerBacktrace.h" #include "mozilla/RefPtr.h" #include #include #ifndef SPS_STANDALONE #include "js/ProfilingFrameIterator.h" #include "js/TrackedOptimizationInfo.h" #include "nsHashKeys.h" #include "nsDataHashtable.h" #endif #include "mozilla/Maybe.h" #include "mozilla/Vector.h" #ifndef SPS_STANDALONE #include "gtest/MozGtestFriend.h" #else #define FRIEND_TEST(a, b) // TODO Support standalone gtest #endif #include "mozilla/HashFunctions.h" #include "mozilla/UniquePtr.h" class ThreadProfile; // NB: Packing this structure has been shown to cause SIGBUS issues on ARM. #ifndef __arm__ #pragma pack(push, 1) #endif class ProfileEntry { public: ProfileEntry(); // aTagData must not need release (i.e. be a string from the text segment) ProfileEntry(char aTagName, const char *aTagData); ProfileEntry(char aTagName, void *aTagPtr); ProfileEntry(char aTagName, ProfilerMarker *aTagMarker); ProfileEntry(char aTagName, double aTagDouble); ProfileEntry(char aTagName, uintptr_t aTagOffset); ProfileEntry(char aTagName, Address aTagAddress); ProfileEntry(char aTagName, int aTagLine); ProfileEntry(char aTagName, char aTagChar); bool is_ent_hint(char hintChar); bool is_ent_hint(); bool is_ent(char tagName); void* get_tagPtr(); const ProfilerMarker* getMarker() { MOZ_ASSERT(mTagName == 'm'); return mTagMarker; } char getTagName() const { return mTagName; } private: FRIEND_TEST(ThreadProfile, InsertOneTag); FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer); FRIEND_TEST(ThreadProfile, InsertTagsNoWrap); FRIEND_TEST(ThreadProfile, InsertTagsWrap); FRIEND_TEST(ThreadProfile, MemoryMeasure); friend class ProfileBuffer; union { const char* mTagData; char mTagChars[sizeof(void*)]; void* mTagPtr; ProfilerMarker* mTagMarker; double mTagDouble; Address mTagAddress; uintptr_t mTagOffset; int mTagInt; char mTagChar; }; char mTagName; }; #ifndef __arm__ #pragma pack(pop) #endif class UniqueJSONStrings { public: UniqueJSONStrings() { mStringTableWriter.StartBareList(); } void SpliceStringTableElements(SpliceableJSONWriter& aWriter) { aWriter.TakeAndSplice(mStringTableWriter.WriteFunc()); } void WriteProperty(mozilla::JSONWriter& aWriter, const char* aName, const char* aStr) { aWriter.IntProperty(aName, GetOrAddIndex(aStr)); } void WriteElement(mozilla::JSONWriter& aWriter, const char* aStr) { aWriter.IntElement(GetOrAddIndex(aStr)); } uint32_t GetOrAddIndex(const char* aStr); struct StringKey { explicit StringKey(const char* aStr) : mStr(strdup(aStr)) { mHash = mozilla::HashString(mStr); } StringKey(const StringKey& aOther) : mStr(strdup(aOther.mStr)) { mHash = aOther.mHash; } ~StringKey() { free(mStr); } uint32_t Hash() const; bool operator==(const StringKey& aOther) const { return strcmp(mStr, aOther.mStr) == 0; } bool operator<(const StringKey& aOther) const { return mHash < aOther.mHash; } private: uint32_t mHash; char* mStr; }; private: SpliceableChunkedJSONWriter mStringTableWriter; std::map mStringToIndexMap; }; class UniqueStacks { public: struct FrameKey { #ifdef SPS_STANDALONE std::string mLocation; #else // This cannot be a std::string, as it is not memmove compatible, which // is used by nsHashTable nsCString mLocation; #endif mozilla::Maybe mLine; mozilla::Maybe mCategory; mozilla::Maybe mJITAddress; mozilla::Maybe mJITDepth; explicit FrameKey(const char* aLocation) : mLocation(aLocation) { mHash = Hash(); } FrameKey(const FrameKey& aToCopy) : mLocation(aToCopy.mLocation) , mLine(aToCopy.mLine) , mCategory(aToCopy.mCategory) , mJITAddress(aToCopy.mJITAddress) , mJITDepth(aToCopy.mJITDepth) { mHash = Hash(); } FrameKey(void* aJITAddress, uint32_t aJITDepth) : mJITAddress(mozilla::Some(aJITAddress)) , mJITDepth(mozilla::Some(aJITDepth)) { mHash = Hash(); } uint32_t Hash() const; bool operator==(const FrameKey& aOther) const; bool operator<(const FrameKey& aOther) const { return mHash < aOther.mHash; } private: uint32_t mHash; }; // A FrameKey that holds a scoped reference to a JIT FrameHandle. struct MOZ_STACK_CLASS OnStackFrameKey : public FrameKey { explicit OnStackFrameKey(const char* aLocation) : FrameKey(aLocation) #ifndef SPS_STANDALONE , mJITFrameHandle(nullptr) #endif { } OnStackFrameKey(const OnStackFrameKey& aToCopy) : FrameKey(aToCopy) #ifndef SPS_STANDALONE , mJITFrameHandle(aToCopy.mJITFrameHandle) #endif { } #ifndef SPS_STANDALONE const JS::ForEachProfiledFrameOp::FrameHandle* mJITFrameHandle; OnStackFrameKey(void* aJITAddress, unsigned aJITDepth) : FrameKey(aJITAddress, aJITDepth) , mJITFrameHandle(nullptr) { } OnStackFrameKey(void* aJITAddress, unsigned aJITDepth, const JS::ForEachProfiledFrameOp::FrameHandle& aJITFrameHandle) : FrameKey(aJITAddress, aJITDepth) , mJITFrameHandle(&aJITFrameHandle) { } #endif }; struct StackKey { mozilla::Maybe mPrefixHash; mozilla::Maybe mPrefix; uint32_t mFrame; explicit StackKey(uint32_t aFrame) : mFrame(aFrame) { mHash = Hash(); } uint32_t Hash() const; bool operator==(const StackKey& aOther) const; bool operator<(const StackKey& aOther) const { return mHash < aOther.mHash; } void UpdateHash(uint32_t aPrefixHash, uint32_t aPrefix, uint32_t aFrame) { mPrefixHash = mozilla::Some(aPrefixHash); mPrefix = mozilla::Some(aPrefix); mFrame = aFrame; mHash = Hash(); } private: uint32_t mHash; }; class Stack { public: Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot); void AppendFrame(const OnStackFrameKey& aFrame); uint32_t GetOrAddIndex() const; private: UniqueStacks& mUniqueStacks; StackKey mStack; }; explicit UniqueStacks(JSContext* aContext); Stack BeginStack(const OnStackFrameKey& aRoot); uint32_t LookupJITFrameDepth(void* aAddr); void AddJITFrameDepth(void* aAddr, unsigned depth); void SpliceFrameTableElements(SpliceableJSONWriter& aWriter); void SpliceStackTableElements(SpliceableJSONWriter& aWriter); private: uint32_t GetOrAddFrameIndex(const OnStackFrameKey& aFrame); uint32_t GetOrAddStackIndex(const StackKey& aStack); void StreamFrame(const OnStackFrameKey& aFrame); void StreamStack(const StackKey& aStack); public: UniqueJSONStrings mUniqueStrings; private: JSContext* mContext; // To avoid incurring JitcodeGlobalTable lookup costs for every JIT frame, // we cache the depth of frames keyed by JIT code address. If an address a // maps to a depth d, then frames keyed by a for depths 0 to d are // guaranteed to be in mFrameToIndexMap. std::map mJITFrameDepthMap; uint32_t mFrameCount; SpliceableChunkedJSONWriter mFrameTableWriter; #ifdef SPS_STANDALNOE std::map mFrameToIndexMap; #else nsDataHashtable, uint32_t> mFrameToIndexMap; #endif SpliceableChunkedJSONWriter mStackTableWriter; // This sucks but this is really performance critical, nsDataHashtable is way faster // than map/unordered_map but nsDataHashtable is tied to xpcom so we ifdef // until we can find a better solution. #ifdef SPS_STANDALONE std::map mStackToIndexMap; #else nsDataHashtable, uint32_t> mStackToIndexMap; #endif }; // // ThreadProfile 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 */ // "responsiveness": 2, /* number */ // "rss": 3, /* number */ // "uss": 4, /* number */ // "frameNumber": 5, /* number */ // "power": 6 /* number */ // }, // "data": // [ // [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, responsiveness: 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 */ // "implementation": 1, /* index into stringTable */ // "optimizations": 2, /* arbitrary JSON */ // "line": 3, /* number */ // "category": 4 /* number */ // }, // "data": // [ // [ 0 ], /* { location: '(root)' } */ // [ 1, 2 ] /* { location: 'foo.js', implementation: 'baseline' } */ // ] // }, // // "stringTable": // [ // "(root)", // "foo.js", // "baseline", // "example marker" // ] // } // #endif /* ndef MOZ_PROFILE_ENTRY_H */