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 "ProfileJSONWriter.h"
11 
12 #include "gtest/MozGtestFriend.h"
13 #include "js/ProfilingCategory.h"
14 #include "js/ProfilingFrameIterator.h"
15 #include "mozilla/HashFunctions.h"
16 #include "mozilla/HashTable.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/UniquePtr.h"
19 #include "mozilla/Variant.h"
20 #include "mozilla/Vector.h"
21 #include "nsString.h"
22 
23 class ProfilerCodeAddressService;
24 
25 // NOTE!  If you add entries, you need to verify if they need to be added to the
26 // switch statement in DuplicateLastSample!
27 // This will evaluate the MACRO with (KIND, TYPE, SIZE)
28 #define FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(MACRO)                    \
29   MACRO(CategoryPair, int, sizeof(int))                              \
30   MACRO(CollectionStart, double, sizeof(double))                     \
31   MACRO(CollectionEnd, double, sizeof(double))                       \
32   MACRO(Label, const char*, sizeof(const char*))                     \
33   MACRO(FrameFlags, uint64_t, sizeof(uint64_t))                      \
34   MACRO(DynamicStringFragment, char*, ProfileBufferEntry::kNumChars) \
35   MACRO(JitReturnAddr, void*, sizeof(void*))                         \
36   MACRO(InnerWindowID, uint64_t, sizeof(uint64_t))                   \
37   MACRO(LineNumber, int, sizeof(int))                                \
38   MACRO(ColumnNumber, int, sizeof(int))                              \
39   MACRO(NativeLeafAddr, void*, sizeof(void*))                        \
40   MACRO(Pause, double, sizeof(double))                               \
41   MACRO(Resume, double, sizeof(double))                              \
42   MACRO(ThreadId, int, sizeof(int))                                  \
43   MACRO(Time, double, sizeof(double))                                \
44   MACRO(TimeBeforeCompactStack, double, sizeof(double))              \
45   MACRO(CounterId, void*, sizeof(void*))                             \
46   MACRO(CounterKey, uint64_t, sizeof(uint64_t))                      \
47   MACRO(Number, uint64_t, sizeof(uint64_t))                          \
48   MACRO(Count, int64_t, sizeof(int64_t))                             \
49   MACRO(ProfilerOverheadTime, double, sizeof(double))                \
50   MACRO(ProfilerOverheadDuration, double, sizeof(double))
51 
52 class ProfileBufferEntry {
53  public:
54   // The `Kind` is a single byte identifying the type of data that is actually
55   // stored in a `ProfileBufferEntry`, as per the list in
56   // `FOR_EACH_PROFILE_BUFFER_ENTRY_KIND`.
57   //
58   // This byte is also used to identify entries in ProfileChunkedBuffer blocks,
59   // for both "legacy" entries that do contain a `ProfileBufferEntry`, and for
60   // new types of entries that may carry more data of different types.
61   // TODO: Eventually each type of "legacy" entry should be replaced with newer,
62   // more efficient kinds of entries (e.g., stack frames could be stored in one
63   // bigger entry, instead of multiple `ProfileBufferEntry`s); then we could
64   // discard `ProfileBufferEntry` and move this enum to a more appropriate spot.
65   using KindUnderlyingType = uint8_t;
66   enum class Kind : KindUnderlyingType {
67     INVALID = 0,
68 #define KIND(KIND, TYPE, SIZE) KIND,
69     FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(KIND)
70 #undef KIND
71 
72     // Any value under `LEGACY_LIMIT` represents a `ProfileBufferEntry`.
73     LEGACY_LIMIT,
74 
75     // Any value starting here does *not* represent a `ProfileBufferEntry` and
76     // requires separate decoding and handling.
77 
78     // Marker data, including payload.
79     MarkerData = LEGACY_LIMIT,
80 
81     // Optional between TimeBeforeCompactStack and CompactStack.
82     UnresponsiveDurationMs,
83 
84     // Collection of legacy stack entries, must follow a ThreadId and
85     // TimeBeforeCompactStack (which are not included in the CompactStack;
86     // TimeBeforeCompactStack is equivalent to Time, but indicates that a
87     // CompactStack follows shortly afterwards).
88     CompactStack,
89 
90     MODERN_LIMIT
91   };
92 
93   ProfileBufferEntry();
94 
95   // This is equal to sizeof(double), which is the largest non-char variant in
96   // |u|.
97   static const size_t kNumChars = 8;
98 
99  private:
100   // aString must be a static string.
101   ProfileBufferEntry(Kind aKind, const char* aString);
102   ProfileBufferEntry(Kind aKind, char aChars[kNumChars]);
103   ProfileBufferEntry(Kind aKind, void* aPtr);
104   ProfileBufferEntry(Kind aKind, double aDouble);
105   ProfileBufferEntry(Kind aKind, int64_t aInt64);
106   ProfileBufferEntry(Kind aKind, uint64_t aUint64);
107   ProfileBufferEntry(Kind aKind, int aInt);
108 
109  public:
110 #define CTOR(KIND, TYPE, SIZE)                   \
111   static ProfileBufferEntry KIND(TYPE aVal) {    \
112     return ProfileBufferEntry(Kind::KIND, aVal); \
113   }
FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(CTOR)114   FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(CTOR)
115 #undef CTOR
116 
117   Kind GetKind() const { return mKind; }
118 
119 #define IS_KIND(KIND, TYPE, SIZE) \
120   bool Is##KIND() const { return mKind == Kind::KIND; }
121   FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(IS_KIND)
122 #undef IS_KIND
123 
124  private:
125   FRIEND_TEST(ThreadProfile, InsertOneEntry);
126   FRIEND_TEST(ThreadProfile, InsertOneEntryWithTinyBuffer);
127   FRIEND_TEST(ThreadProfile, InsertEntriesNoWrap);
128   FRIEND_TEST(ThreadProfile, InsertEntriesWrap);
129   FRIEND_TEST(ThreadProfile, MemoryMeasure);
130   friend class ProfileBuffer;
131 
132   Kind mKind;
133   uint8_t mStorage[kNumChars];
134 
135   const char* GetString() const;
136   void* GetPtr() const;
137   double GetDouble() const;
138   int GetInt() const;
139   int64_t GetInt64() const;
140   uint64_t GetUint64() const;
141   void CopyCharsInto(char (&aOutArray)[kNumChars]) const;
142 };
143 
144 // Packed layout: 1 byte for the tag + 8 bytes for the value.
145 static_assert(sizeof(ProfileBufferEntry) == 9, "bad ProfileBufferEntry size");
146 
147 class UniqueJSONStrings {
148  public:
149   UniqueJSONStrings();
150   explicit UniqueJSONStrings(const UniqueJSONStrings& aOther);
151 
SpliceStringTableElements(SpliceableJSONWriter & aWriter)152   void SpliceStringTableElements(SpliceableJSONWriter& aWriter) {
153     aWriter.TakeAndSplice(mStringTableWriter.WriteFunc());
154   }
155 
WriteProperty(mozilla::JSONWriter & aWriter,const char * aName,const char * aStr)156   void WriteProperty(mozilla::JSONWriter& aWriter, const char* aName,
157                      const char* aStr) {
158     aWriter.IntProperty(aName, GetOrAddIndex(aStr));
159   }
160 
WriteElement(mozilla::JSONWriter & aWriter,const char * aStr)161   void WriteElement(mozilla::JSONWriter& aWriter, const char* aStr) {
162     aWriter.IntElement(GetOrAddIndex(aStr));
163   }
164 
165   uint32_t GetOrAddIndex(const char* aStr);
166 
167  private:
168   SpliceableChunkedJSONWriter mStringTableWriter;
169   mozilla::HashMap<mozilla::HashNumber, uint32_t> mStringHashToIndexMap;
170 };
171 
172 // Contains all the information about JIT frames that is needed to stream stack
173 // frames for JitReturnAddr entries in the profiler buffer.
174 // Every return address (void*) is mapped to one or more JITFrameKeys, and
175 // every JITFrameKey is mapped to a JSON string for that frame.
176 // mRangeStart and mRangeEnd describe the range in the buffer for which this
177 // mapping is valid. Only JitReturnAddr entries within that buffer range can be
178 // processed using this JITFrameInfoForBufferRange object.
179 struct JITFrameInfoForBufferRange final {
180   JITFrameInfoForBufferRange Clone() const;
181 
182   uint64_t mRangeStart;
183   uint64_t mRangeEnd;  // mRangeEnd marks the first invalid index.
184 
185   struct JITFrameKey {
186     bool operator==(const JITFrameKey& aOther) const {
187       return mCanonicalAddress == aOther.mCanonicalAddress &&
188              mDepth == aOther.mDepth;
189     }
190     bool operator!=(const JITFrameKey& aOther) const {
191       return !(*this == aOther);
192     }
193 
194     void* mCanonicalAddress;
195     uint32_t mDepth;
196   };
197   struct JITFrameKeyHasher {
198     using Lookup = JITFrameKey;
199 
hashfinal::JITFrameKeyHasher200     static mozilla::HashNumber hash(const JITFrameKey& aLookup) {
201       mozilla::HashNumber hash = 0;
202       hash = mozilla::AddToHash(hash, aLookup.mCanonicalAddress);
203       hash = mozilla::AddToHash(hash, aLookup.mDepth);
204       return hash;
205     }
206 
matchfinal::JITFrameKeyHasher207     static bool match(const JITFrameKey& aKey, const JITFrameKey& aLookup) {
208       return aKey == aLookup;
209     }
210 
rekeyfinal::JITFrameKeyHasher211     static void rekey(JITFrameKey& aKey, const JITFrameKey& aNewKey) {
212       aKey = aNewKey;
213     }
214   };
215 
216   using JITAddressToJITFramesMap =
217       mozilla::HashMap<void*, mozilla::Vector<JITFrameKey>>;
218   JITAddressToJITFramesMap mJITAddressToJITFramesMap;
219   using JITFrameToFrameJSONMap =
220       mozilla::HashMap<JITFrameKey, nsCString, JITFrameKeyHasher>;
221   JITFrameToFrameJSONMap mJITFrameToFrameJSONMap;
222 };
223 
224 // Contains JITFrameInfoForBufferRange objects for multiple profiler buffer
225 // ranges.
226 struct JITFrameInfo final {
JITFrameInfofinal227   JITFrameInfo() : mUniqueStrings(mozilla::MakeUnique<UniqueJSONStrings>()) {}
228 
229   MOZ_IMPLICIT JITFrameInfo(const JITFrameInfo& aOther);
230 
231   // Creates a new JITFrameInfoForBufferRange object in mRanges by looking up
232   // information about the provided JIT return addresses using aCx.
233   // Addresses are provided like this:
234   // The caller of AddInfoForRange supplies a function in aJITAddressProvider.
235   // This function will be called once, synchronously, with an
236   // aJITAddressConsumer argument, which is a function that needs to be called
237   // for every address. That function can be called multiple times for the same
238   // address.
239   void AddInfoForRange(
240       uint64_t aRangeStart, uint64_t aRangeEnd, JSContext* aCx,
241       const std::function<void(const std::function<void(void*)>&)>&
242           aJITAddressProvider);
243 
244   // Returns whether the information stored in this object is still relevant
245   // for any entries in the buffer.
HasExpiredfinal246   bool HasExpired(uint64_t aCurrentBufferRangeStart) const {
247     if (mRanges.empty()) {
248       // No information means no relevant information. Allow this object to be
249       // discarded.
250       return true;
251     }
252     return mRanges.back().mRangeEnd <= aCurrentBufferRangeStart;
253   }
254 
255   // The array of ranges of JIT frame information, sorted by buffer position.
256   // Ranges are non-overlapping.
257   // The JSON of the cached frames can contain string indexes, which refer
258   // to strings in mUniqueStrings.
259   mozilla::Vector<JITFrameInfoForBufferRange> mRanges;
260 
261   // The string table which contains strings used in the frame JSON that's
262   // cached in mRanges.
263   mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
264 };
265 
266 class UniqueStacks {
267  public:
268   struct FrameKey {
FrameKeyFrameKey269     explicit FrameKey(const char* aLocation)
270         : mData(NormalFrameData{nsCString(aLocation), false, 0,
271                                 mozilla::Nothing(), mozilla::Nothing()}) {}
272 
FrameKeyFrameKey273     FrameKey(nsCString&& aLocation, bool aRelevantForJS,
274              uint64_t aInnerWindowID, const mozilla::Maybe<unsigned>& aLine,
275              const mozilla::Maybe<unsigned>& aColumn,
276              const mozilla::Maybe<JS::ProfilingCategoryPair>& aCategoryPair)
277         : mData(NormalFrameData{aLocation, aRelevantForJS, aInnerWindowID,
278                                 aLine, aColumn, aCategoryPair}) {}
279 
FrameKeyFrameKey280     FrameKey(void* aJITAddress, uint32_t aJITDepth, uint32_t aRangeIndex)
281         : mData(JITFrameData{aJITAddress, aJITDepth, aRangeIndex}) {}
282 
283     FrameKey(const FrameKey& aToCopy) = default;
284 
285     uint32_t Hash() const;
286     bool operator==(const FrameKey& aOther) const {
287       return mData == aOther.mData;
288     }
289 
290     struct NormalFrameData {
291       bool operator==(const NormalFrameData& aOther) const;
292 
293       nsCString mLocation;
294       bool mRelevantForJS;
295       uint64_t mInnerWindowID;
296       mozilla::Maybe<unsigned> mLine;
297       mozilla::Maybe<unsigned> mColumn;
298       mozilla::Maybe<JS::ProfilingCategoryPair> mCategoryPair;
299     };
300     struct JITFrameData {
301       bool operator==(const JITFrameData& aOther) const;
302 
303       void* mCanonicalAddress;
304       uint32_t mDepth;
305       uint32_t mRangeIndex;
306     };
307     mozilla::Variant<NormalFrameData, JITFrameData> mData;
308   };
309 
310   struct FrameKeyHasher {
311     using Lookup = FrameKey;
312 
hashFrameKeyHasher313     static mozilla::HashNumber hash(const FrameKey& aLookup) {
314       mozilla::HashNumber hash = 0;
315       if (aLookup.mData.is<FrameKey::NormalFrameData>()) {
316         const FrameKey::NormalFrameData& data =
317             aLookup.mData.as<FrameKey::NormalFrameData>();
318         if (!data.mLocation.IsEmpty()) {
319           hash = mozilla::AddToHash(hash,
320                                     mozilla::HashString(data.mLocation.get()));
321         }
322         hash = mozilla::AddToHash(hash, data.mRelevantForJS);
323         hash = mozilla::AddToHash(hash, data.mInnerWindowID);
324         if (data.mLine.isSome()) {
325           hash = mozilla::AddToHash(hash, *data.mLine);
326         }
327         if (data.mColumn.isSome()) {
328           hash = mozilla::AddToHash(hash, *data.mColumn);
329         }
330         if (data.mCategoryPair.isSome()) {
331           hash = mozilla::AddToHash(hash,
332                                     static_cast<uint32_t>(*data.mCategoryPair));
333         }
334       } else {
335         const FrameKey::JITFrameData& data =
336             aLookup.mData.as<FrameKey::JITFrameData>();
337         hash = mozilla::AddToHash(hash, data.mCanonicalAddress);
338         hash = mozilla::AddToHash(hash, data.mDepth);
339         hash = mozilla::AddToHash(hash, data.mRangeIndex);
340       }
341       return hash;
342     }
343 
matchFrameKeyHasher344     static bool match(const FrameKey& aKey, const FrameKey& aLookup) {
345       return aKey == aLookup;
346     }
347 
rekeyFrameKeyHasher348     static void rekey(FrameKey& aKey, const FrameKey& aNewKey) {
349       aKey = aNewKey;
350     }
351   };
352 
353   struct StackKey {
354     mozilla::Maybe<uint32_t> mPrefixStackIndex;
355     uint32_t mFrameIndex;
356 
StackKeyStackKey357     explicit StackKey(uint32_t aFrame)
358         : mFrameIndex(aFrame), mHash(mozilla::HashGeneric(aFrame)) {}
359 
StackKeyStackKey360     StackKey(const StackKey& aPrefix, uint32_t aPrefixStackIndex,
361              uint32_t aFrame)
362         : mPrefixStackIndex(mozilla::Some(aPrefixStackIndex)),
363           mFrameIndex(aFrame),
364           mHash(mozilla::AddToHash(aPrefix.mHash, aFrame)) {}
365 
HashStackKey366     mozilla::HashNumber Hash() const { return mHash; }
367 
368     bool operator==(const StackKey& aOther) const {
369       return mPrefixStackIndex == aOther.mPrefixStackIndex &&
370              mFrameIndex == aOther.mFrameIndex;
371     }
372 
373    private:
374     mozilla::HashNumber mHash;
375   };
376 
377   struct StackKeyHasher {
378     using Lookup = StackKey;
379 
hashStackKeyHasher380     static mozilla::HashNumber hash(const StackKey& aLookup) {
381       return aLookup.Hash();
382     }
383 
matchStackKeyHasher384     static bool match(const StackKey& aKey, const StackKey& aLookup) {
385       return aKey == aLookup;
386     }
387 
rekeyStackKeyHasher388     static void rekey(StackKey& aKey, const StackKey& aNewKey) {
389       aKey = aNewKey;
390     }
391   };
392 
393   explicit UniqueStacks(JITFrameInfo&& aJITFrameInfo);
394 
395   // Return a StackKey for aFrame as the stack's root frame (no prefix).
396   [[nodiscard]] StackKey BeginStack(const FrameKey& aFrame);
397 
398   // Return a new StackKey that is obtained by appending aFrame to aStack.
399   [[nodiscard]] StackKey AppendFrame(const StackKey& aStack,
400                                      const FrameKey& aFrame);
401 
402   // Look up frame keys for the given JIT address, and ensure that our frame
403   // table has entries for the returned frame keys. The JSON for these frames
404   // is taken from mJITInfoRanges.
405   // aBufferPosition is needed in order to look up the correct JIT frame info
406   // object in mJITInfoRanges.
407   [[nodiscard]] mozilla::Maybe<mozilla::Vector<UniqueStacks::FrameKey>>
408   LookupFramesForJITAddressFromBufferPos(void* aJITAddress,
409                                          uint64_t aBufferPosition);
410 
411   [[nodiscard]] uint32_t GetOrAddFrameIndex(const FrameKey& aFrame);
412   [[nodiscard]] uint32_t GetOrAddStackIndex(const StackKey& aStack);
413 
414   void SpliceFrameTableElements(SpliceableJSONWriter& aWriter);
415   void SpliceStackTableElements(SpliceableJSONWriter& aWriter);
416 
417  private:
418   void StreamNonJITFrame(const FrameKey& aFrame);
419   void StreamStack(const StackKey& aStack);
420 
421  public:
422   mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
423 
424   ProfilerCodeAddressService* mCodeAddressService = nullptr;
425 
426  private:
427   SpliceableChunkedJSONWriter mFrameTableWriter;
428   mozilla::HashMap<FrameKey, uint32_t, FrameKeyHasher> mFrameToIndexMap;
429 
430   SpliceableChunkedJSONWriter mStackTableWriter;
431   mozilla::HashMap<StackKey, uint32_t, StackKeyHasher> mStackToIndexMap;
432 
433   mozilla::Vector<JITFrameInfoForBufferRange> mJITInfoRanges;
434 };
435 
436 //
437 // Thread profile JSON Format
438 // --------------------------
439 //
440 // The profile contains much duplicate information. The output JSON of the
441 // profile attempts to deduplicate strings, frames, and stack prefixes, to cut
442 // down on size and to increase JSON streaming speed. Deduplicated values are
443 // streamed as indices into their respective tables.
444 //
445 // Further, arrays of objects with the same set of properties (e.g., samples,
446 // frames) are output as arrays according to a schema instead of an object
447 // with property names. A property that is not present is represented in the
448 // array as null or undefined.
449 //
450 // The format of the thread profile JSON is shown by the following example
451 // with 1 sample and 1 marker:
452 //
453 // {
454 //   "name": "Foo",
455 //   "tid": 42,
456 //   "samples":
457 //   {
458 //     "schema":
459 //     {
460 //       "stack": 0,          /* index into stackTable */
461 //       "time": 1,           /* number */
462 //       "eventDelay": 2,     /* number */
463 //     },
464 //     "data":
465 //     [
466 //       [ 1, 0.0, 0.0 ]      /* { stack: 1, time: 0.0, eventDelay: 0.0 } */
467 //     ]
468 //   },
469 //
470 //   "markers":
471 //   {
472 //     "schema":
473 //     {
474 //       "name": 0,           /* index into stringTable */
475 //       "time": 1,           /* number */
476 //       "data": 2            /* arbitrary JSON */
477 //     },
478 //     "data":
479 //     [
480 //       [ 3, 0.1 ]           /* { name: 'example marker', time: 0.1 } */
481 //     ]
482 //   },
483 //
484 //   "stackTable":
485 //   {
486 //     "schema":
487 //     {
488 //       "prefix": 0,         /* index into stackTable */
489 //       "frame": 1           /* index into frameTable */
490 //     },
491 //     "data":
492 //     [
493 //       [ null, 0 ],         /* (root) */
494 //       [ 0,    1 ]          /* (root) > foo.js */
495 //     ]
496 //   },
497 //
498 //   "frameTable":
499 //   {
500 //     "schema":
501 //     {
502 //       "location": 0,       /* index into stringTable */
503 //       "relevantForJS": 1,  /* bool */
504 //       "innerWindowID": 2,  /* inner window ID of global JS `window` object */
505 //       "implementation": 3, /* index into stringTable */
506 //       "optimizations": 4,  /* arbitrary JSON */
507 //       "line": 5,           /* number */
508 //       "column": 6,         /* number */
509 //       "category": 7,       /* index into profile.meta.categories */
510 //       "subcategory": 8     /* index into
511 //       profile.meta.categories[category].subcategories */
512 //     },
513 //     "data":
514 //     [
515 //       [ 0 ],               /* { location: '(root)' } */
516 //       [ 1, 2 ]             /* { location: 'foo.js',
517 //                                 implementation: 'baseline' } */
518 //     ]
519 //   },
520 //
521 //   "stringTable":
522 //   [
523 //     "(root)",
524 //     "foo.js",
525 //     "baseline",
526 //     "example marker"
527 //   ]
528 // }
529 //
530 // Process:
531 // {
532 //   "name": "Bar",
533 //   "pid": 24,
534 //   "threads":
535 //   [
536 //     <0-N threads from above>
537 //   ],
538 //   "counters": /* includes the memory counter */
539 //   [
540 //     {
541 //       "name": "qwerty",
542 //       "category": "uiop",
543 //       "description": "this is qwerty uiop",
544 //       "sample_groups:
545 //       [
546 //         {
547 //           "id": 42, /* number (thread id, or object identifier (tab), etc) */
548 //           "samples:
549 //           {
550 //             "schema":
551 //             {
552 //               "time": 1,   /* number */
553 //               "number": 2, /* number (of times the counter was touched) */
554 //               "count": 3   /* number (total for the counter) */
555 //             },
556 //             "data":
557 //             [
558 //               [ 0.1, 1824,
559 //                 454622 ]   /* { time: 0.1, number: 1824, count: 454622 } */
560 //             ]
561 //           },
562 //         },
563 //         /* more sample-group objects with different id's */
564 //       ]
565 //     },
566 //     /* more counters */
567 //   ],
568 // }
569 //
570 #endif /* ndef ProfileBufferEntry_h */
571