ProfileBufferEntry.h (10877B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef ProfileBufferEntry_h 8 #define ProfileBufferEntry_h 9 10 #include "gtest/MozGtestFriend.h" 11 #include "mozilla/BaseProfileJSONWriter.h" 12 #include "mozilla/BaseProfilingCategory.h" 13 #include "mozilla/HashFunctions.h" 14 #include "mozilla/HashTable.h" 15 #include "mozilla/Maybe.h" 16 #include "mozilla/ProfileBufferEntryKinds.h" 17 #include "mozilla/UniquePtr.h" 18 #include "mozilla/Variant.h" 19 20 #include <string> 21 #include <type_traits> 22 23 namespace mozilla { 24 namespace baseprofiler { 25 26 class ProfileBufferEntry { 27 public: 28 using KindUnderlyingType = 29 std::underlying_type_t<::mozilla::ProfileBufferEntryKind>; 30 using Kind = ::mozilla::ProfileBufferEntryKind; 31 32 ProfileBufferEntry(); 33 34 static constexpr size_t kNumChars = ::mozilla::ProfileBufferEntryNumChars; 35 36 private: 37 // aString must be a static string. 38 ProfileBufferEntry(Kind aKind, const char* aString); 39 ProfileBufferEntry(Kind aKind, char aChars[kNumChars]); 40 ProfileBufferEntry(Kind aKind, void* aPtr); 41 ProfileBufferEntry(Kind aKind, double aDouble); 42 ProfileBufferEntry(Kind aKind, int64_t aInt64); 43 ProfileBufferEntry(Kind aKind, uint64_t aUint64); 44 ProfileBufferEntry(Kind aKind, uint32_t aUint32); 45 ProfileBufferEntry(Kind aKind, int aInt); 46 ProfileBufferEntry(Kind aKind, BaseProfilerThreadId aThreadId); 47 48 public: 49 #define CTOR(KIND, TYPE, SIZE) \ 50 static ProfileBufferEntry KIND(TYPE aVal) { \ 51 return ProfileBufferEntry(Kind::KIND, aVal); \ 52 } 53 FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(CTOR) 54 #undef CTOR 55 56 Kind GetKind() const { return mKind; } 57 58 #define IS_KIND(KIND, TYPE, SIZE) \ 59 bool Is##KIND() const { return mKind == Kind::KIND; } 60 FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(IS_KIND) 61 #undef IS_KIND 62 63 private: 64 FRIEND_TEST(ThreadProfile, InsertOneEntry); 65 FRIEND_TEST(ThreadProfile, InsertOneEntryWithTinyBuffer); 66 FRIEND_TEST(ThreadProfile, InsertEntriesNoWrap); 67 FRIEND_TEST(ThreadProfile, InsertEntriesWrap); 68 FRIEND_TEST(ThreadProfile, MemoryMeasure); 69 friend class ProfileBuffer; 70 71 Kind mKind; 72 uint8_t mStorage[kNumChars]; 73 74 const char* GetString() const; 75 void* GetPtr() const; 76 double GetDouble() const; 77 int GetInt() const; 78 int64_t GetInt64() const; 79 uint64_t GetUint64() const; 80 BaseProfilerThreadId GetThreadId() const; 81 void CopyCharsInto(char (&aOutArray)[kNumChars]) const; 82 }; 83 84 // Packed layout: 1 byte for the tag + 8 bytes for the value. 85 static_assert(sizeof(ProfileBufferEntry) == 9, "bad ProfileBufferEntry size"); 86 87 class UniqueStacks { 88 public: 89 struct FrameKey { 90 explicit FrameKey(const char* aLocation) 91 : mData(NormalFrameData{std::string(aLocation), false, 0, Nothing(), 92 Nothing()}) {} 93 94 FrameKey(std::string&& aLocation, bool aRelevantForJS, 95 uint64_t aInnerWindowID, const Maybe<unsigned>& aLine, 96 const Maybe<unsigned>& aColumn, 97 const Maybe<ProfilingCategoryPair>& aCategoryPair) 98 : mData(NormalFrameData{aLocation, aRelevantForJS, aInnerWindowID, 99 aLine, aColumn, aCategoryPair}) {} 100 101 FrameKey(const FrameKey& aToCopy) = default; 102 103 uint32_t Hash() const; 104 bool operator==(const FrameKey& aOther) const { 105 return mData == aOther.mData; 106 } 107 108 struct NormalFrameData { 109 bool operator==(const NormalFrameData& aOther) const; 110 111 std::string mLocation; 112 bool mRelevantForJS; 113 uint64_t mInnerWindowID; 114 Maybe<unsigned> mLine; 115 Maybe<unsigned> mColumn; 116 Maybe<ProfilingCategoryPair> mCategoryPair; 117 }; 118 Variant<NormalFrameData> mData; 119 }; 120 121 struct FrameKeyHasher { 122 using Lookup = FrameKey; 123 124 static HashNumber hash(const FrameKey& aLookup) { 125 HashNumber hash = 0; 126 if (aLookup.mData.is<FrameKey::NormalFrameData>()) { 127 const FrameKey::NormalFrameData& data = 128 aLookup.mData.as<FrameKey::NormalFrameData>(); 129 if (!data.mLocation.empty()) { 130 hash = AddToHash(hash, HashString(data.mLocation.c_str())); 131 } 132 hash = AddToHash(hash, data.mRelevantForJS); 133 hash = mozilla::AddToHash(hash, data.mInnerWindowID); 134 if (data.mLine.isSome()) { 135 hash = AddToHash(hash, *data.mLine); 136 } 137 if (data.mColumn.isSome()) { 138 hash = AddToHash(hash, *data.mColumn); 139 } 140 if (data.mCategoryPair.isSome()) { 141 hash = AddToHash(hash, static_cast<uint32_t>(*data.mCategoryPair)); 142 } 143 } 144 return hash; 145 } 146 147 static bool match(const FrameKey& aKey, const FrameKey& aLookup) { 148 return aKey == aLookup; 149 } 150 151 static void rekey(FrameKey& aKey, const FrameKey& aNewKey) { 152 aKey = aNewKey; 153 } 154 }; 155 156 struct StackKey { 157 Maybe<uint32_t> mPrefixStackIndex; 158 uint32_t mFrameIndex; 159 160 explicit StackKey(uint32_t aFrame) 161 : mFrameIndex(aFrame), mHash(HashGeneric(aFrame)) {} 162 163 StackKey(const StackKey& aPrefix, uint32_t aPrefixStackIndex, 164 uint32_t aFrame) 165 : mPrefixStackIndex(Some(aPrefixStackIndex)), 166 mFrameIndex(aFrame), 167 mHash(AddToHash(aPrefix.mHash, aFrame)) {} 168 169 HashNumber Hash() const { return mHash; } 170 171 bool operator==(const StackKey& aOther) const { 172 return mPrefixStackIndex == aOther.mPrefixStackIndex && 173 mFrameIndex == aOther.mFrameIndex; 174 } 175 176 private: 177 HashNumber mHash; 178 }; 179 180 struct StackKeyHasher { 181 using Lookup = StackKey; 182 183 static HashNumber hash(const StackKey& aLookup) { return aLookup.Hash(); } 184 185 static bool match(const StackKey& aKey, const StackKey& aLookup) { 186 return aKey == aLookup; 187 } 188 189 static void rekey(StackKey& aKey, const StackKey& aNewKey) { 190 aKey = aNewKey; 191 } 192 }; 193 194 UniqueStacks(); 195 196 // Return a StackKey for aFrame as the stack's root frame (no prefix). 197 [[nodiscard]] StackKey BeginStack(const FrameKey& aFrame); 198 199 // Return a new StackKey that is obtained by appending aFrame to aStack. 200 [[nodiscard]] StackKey AppendFrame(const StackKey& aStack, 201 const FrameKey& aFrame); 202 203 [[nodiscard]] uint32_t GetOrAddFrameIndex(const FrameKey& aFrame); 204 [[nodiscard]] uint32_t GetOrAddStackIndex(const StackKey& aStack); 205 206 void SpliceFrameTableElements(SpliceableJSONWriter& aWriter); 207 void SpliceStackTableElements(SpliceableJSONWriter& aWriter); 208 209 UniqueJSONStrings& UniqueStrings() { 210 MOZ_RELEASE_ASSERT(mUniqueStrings.get()); 211 return *mUniqueStrings; 212 } 213 214 private: 215 void StreamNonJITFrame(const FrameKey& aFrame); 216 void StreamStack(const StackKey& aStack); 217 218 UniquePtr<UniqueJSONStrings> mUniqueStrings; 219 220 SpliceableChunkedJSONWriter mFrameTableWriter; 221 HashMap<FrameKey, uint32_t, FrameKeyHasher> mFrameToIndexMap; 222 223 SpliceableChunkedJSONWriter mStackTableWriter; 224 HashMap<StackKey, uint32_t, StackKeyHasher> mStackToIndexMap; 225 }; 226 227 // 228 // Thread profile JSON Format 229 // -------------------------- 230 // 231 // The profile contains much duplicate information. The output JSON of the 232 // profile attempts to deduplicate strings, frames, and stack prefixes, to cut 233 // down on size and to increase JSON streaming speed. Deduplicated values are 234 // streamed as indices into their respective tables. 235 // 236 // Further, arrays of objects with the same set of properties (e.g., samples, 237 // frames) are output as arrays according to a schema instead of an object 238 // with property names. A property that is not present is represented in the 239 // array as null or undefined. 240 // 241 // The format of the thread profile JSON is shown by the following example 242 // with 1 sample and 1 marker: 243 // 244 // { 245 // "name": "Foo", 246 // "tid": 42, 247 // "samples": 248 // { 249 // "schema": 250 // { 251 // "stack": 0, /* index into stackTable */ 252 // "time": 1, /* number */ 253 // "eventDelay": 2, /* number */ 254 // }, 255 // "data": 256 // [ 257 // [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, eventDelay: 0.0 } */ 258 // ] 259 // }, 260 // 261 // "markers": 262 // { 263 // "schema": 264 // { 265 // "name": 0, /* index into stringTable */ 266 // "time": 1, /* number */ 267 // "data": 2 /* arbitrary JSON */ 268 // }, 269 // "data": 270 // [ 271 // [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */ 272 // ] 273 // }, 274 // 275 // "stackTable": 276 // { 277 // "schema": 278 // { 279 // "prefix": 0, /* index into stackTable */ 280 // "frame": 1 /* index into frameTable */ 281 // }, 282 // "data": 283 // [ 284 // [ null, 0 ], /* (root) */ 285 // [ 0, 1 ] /* (root) > foo.js */ 286 // ] 287 // }, 288 // 289 // "frameTable": 290 // { 291 // "schema": 292 // { 293 // "location": 0, /* index into stringTable */ 294 // "relevantForJS": 1, /* bool */ 295 // "innerWindowID": 2, /* inner window ID of global JS `window` object */ 296 // "implementation": 3, /* index into stringTable */ 297 // "line": 4, /* number */ 298 // "column": 5, /* number */ 299 // "category": 6, /* index into profile.meta.categories */ 300 // "subcategory": 7 /* index into 301 // profile.meta.categories[category].subcategories */ 302 // }, 303 // "data": 304 // [ 305 // [ 0 ], /* { location: '(root)' } */ 306 // [ 1, 2 ] /* { location: 'foo.js', 307 // implementation: 'baseline' } */ 308 // ] 309 // }, 310 // 311 // "stringTable": 312 // [ 313 // "(root)", 314 // "foo.js", 315 // "baseline", 316 // "example marker" 317 // ] 318 // } 319 // 320 // Process: 321 // { 322 // "name": "Bar", 323 // "pid": 24, 324 // "threads": 325 // [ 326 // <0-N threads from above> 327 // ], 328 // "counters": /* includes the memory counter */ 329 // [ 330 // { 331 // "name": "qwerty", 332 // "category": "uiop", 333 // "description": "this is qwerty uiop", 334 // "sample_groups: 335 // [ 336 // { 337 // "id": 42, /* number (thread id, or object identifier (tab), etc) */ 338 // "samples: 339 // { 340 // "schema": 341 // { 342 // "time": 1, /* number */ 343 // "number": 2, /* number (of times the counter was touched) */ 344 // "count": 3 /* number (total for the counter) */ 345 // }, 346 // "data": 347 // [ 348 // [ 0.1, 1824, 349 // 454622 ] /* { time: 0.1, number: 1824, count: 454622 } */ 350 // ] 351 // }, 352 // }, 353 // /* more sample-group objects with different id's */ 354 // ] 355 // }, 356 // /* more counters */ 357 // ], 358 // } 359 // 360 361 } // namespace baseprofiler 362 } // namespace mozilla 363 364 #endif /* ndef ProfileBufferEntry_h */