Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / tools / profiler / core / ProfileBufferEntry.h
blobbfee4923a376dc128c9d95fd5c86434682d67b37
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/. */
7 #ifndef ProfileBufferEntry_h
8 #define ProfileBufferEntry_h
10 #include <cstdint>
11 #include <cstdlib>
12 #include <functional>
13 #include <utility>
14 #include <type_traits>
15 #include "gtest/MozGtestFriend.h"
16 #include "js/ProfilingCategory.h"
17 #include "mozilla/Attributes.h"
18 #include "mozilla/HashFunctions.h"
19 #include "mozilla/HashTable.h"
20 #include "mozilla/Maybe.h"
21 #include "mozilla/ProfileBufferEntryKinds.h"
22 #include "mozilla/ProfileJSONWriter.h"
23 #include "mozilla/ProfilerUtils.h"
24 #include "mozilla/UniquePtrExtensions.h"
25 #include "mozilla/Variant.h"
26 #include "mozilla/Vector.h"
27 #include "nsString.h"
29 class ProfilerCodeAddressService;
30 struct JSContext;
32 class ProfileBufferEntry {
33 public:
34 using KindUnderlyingType =
35 std::underlying_type_t<::mozilla::ProfileBufferEntryKind>;
36 using Kind = mozilla::ProfileBufferEntryKind;
38 ProfileBufferEntry();
40 static constexpr size_t kNumChars = mozilla::ProfileBufferEntryNumChars;
42 private:
43 // aString must be a static string.
44 ProfileBufferEntry(Kind aKind, const char* aString);
45 ProfileBufferEntry(Kind aKind, char aChars[kNumChars]);
46 ProfileBufferEntry(Kind aKind, void* aPtr);
47 ProfileBufferEntry(Kind aKind, double aDouble);
48 ProfileBufferEntry(Kind aKind, int64_t aInt64);
49 ProfileBufferEntry(Kind aKind, uint64_t aUint64);
50 ProfileBufferEntry(Kind aKind, int aInt);
51 ProfileBufferEntry(Kind aKind, ProfilerThreadId aThreadId);
53 public:
54 #define CTOR(KIND, TYPE, SIZE) \
55 static ProfileBufferEntry KIND(TYPE aVal) { \
56 return ProfileBufferEntry(Kind::KIND, aVal); \
58 FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(CTOR)
59 #undef CTOR
61 Kind GetKind() const { return mKind; }
63 #define IS_KIND(KIND, TYPE, SIZE) \
64 bool Is##KIND() const { return mKind == Kind::KIND; }
65 FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(IS_KIND)
66 #undef IS_KIND
68 private:
69 FRIEND_TEST(ThreadProfile, InsertOneEntry);
70 FRIEND_TEST(ThreadProfile, InsertOneEntryWithTinyBuffer);
71 FRIEND_TEST(ThreadProfile, InsertEntriesNoWrap);
72 FRIEND_TEST(ThreadProfile, InsertEntriesWrap);
73 FRIEND_TEST(ThreadProfile, MemoryMeasure);
74 friend class ProfileBuffer;
76 Kind mKind;
77 uint8_t mStorage[kNumChars];
79 const char* GetString() const;
80 void* GetPtr() const;
81 double GetDouble() const;
82 int GetInt() const;
83 int64_t GetInt64() const;
84 uint64_t GetUint64() const;
85 ProfilerThreadId GetThreadId() const;
86 void CopyCharsInto(char (&aOutArray)[kNumChars]) const;
89 // Packed layout: 1 byte for the tag + 8 bytes for the value.
90 static_assert(sizeof(ProfileBufferEntry) == 9, "bad ProfileBufferEntry size");
92 // Contains all the information about JIT frames that is needed to stream stack
93 // frames for JitReturnAddr entries in the profiler buffer.
94 // Every return address (void*) is mapped to one or more JITFrameKeys, and
95 // every JITFrameKey is mapped to a JSON string for that frame.
96 // mRangeStart and mRangeEnd describe the range in the buffer for which this
97 // mapping is valid. Only JitReturnAddr entries within that buffer range can be
98 // processed using this JITFrameInfoForBufferRange object.
99 struct JITFrameInfoForBufferRange final {
100 JITFrameInfoForBufferRange Clone() const;
102 uint64_t mRangeStart;
103 uint64_t mRangeEnd; // mRangeEnd marks the first invalid index.
105 struct JITFrameKey {
106 bool operator==(const JITFrameKey& aOther) const {
107 return mCanonicalAddress == aOther.mCanonicalAddress &&
108 mDepth == aOther.mDepth;
110 bool operator!=(const JITFrameKey& aOther) const {
111 return !(*this == aOther);
114 void* mCanonicalAddress;
115 uint32_t mDepth;
117 struct JITFrameKeyHasher {
118 using Lookup = JITFrameKey;
120 static mozilla::HashNumber hash(const JITFrameKey& aLookup) {
121 mozilla::HashNumber hash = 0;
122 hash = mozilla::AddToHash(hash, aLookup.mCanonicalAddress);
123 hash = mozilla::AddToHash(hash, aLookup.mDepth);
124 return hash;
127 static bool match(const JITFrameKey& aKey, const JITFrameKey& aLookup) {
128 return aKey == aLookup;
131 static void rekey(JITFrameKey& aKey, const JITFrameKey& aNewKey) {
132 aKey = aNewKey;
136 using JITAddressToJITFramesMap =
137 mozilla::HashMap<void*, mozilla::Vector<JITFrameKey>>;
138 JITAddressToJITFramesMap mJITAddressToJITFramesMap;
139 using JITFrameToFrameJSONMap =
140 mozilla::HashMap<JITFrameKey, nsCString, JITFrameKeyHasher>;
141 JITFrameToFrameJSONMap mJITFrameToFrameJSONMap;
144 // Contains JITFrameInfoForBufferRange objects for multiple profiler buffer
145 // ranges.
146 class JITFrameInfo final {
147 public:
148 JITFrameInfo()
149 : mUniqueStrings(mozilla::MakeUniqueFallible<UniqueJSONStrings>(
150 mLocalFailureLatchSource)) {
151 if (!mUniqueStrings) {
152 mLocalFailureLatchSource.SetFailure(
153 "OOM in JITFrameInfo allocating mUniqueStrings");
157 MOZ_IMPLICIT JITFrameInfo(const JITFrameInfo& aOther,
158 mozilla::ProgressLogger aProgressLogger);
160 // Creates a new JITFrameInfoForBufferRange object in mRanges by looking up
161 // information about the provided JIT return addresses using aCx.
162 // Addresses are provided like this:
163 // The caller of AddInfoForRange supplies a function in aJITAddressProvider.
164 // This function will be called once, synchronously, with an
165 // aJITAddressConsumer argument, which is a function that needs to be called
166 // for every address. That function can be called multiple times for the same
167 // address.
168 void AddInfoForRange(
169 uint64_t aRangeStart, uint64_t aRangeEnd, JSContext* aCx,
170 const std::function<void(const std::function<void(void*)>&)>&
171 aJITAddressProvider);
173 // Returns whether the information stored in this object is still relevant
174 // for any entries in the buffer.
175 bool HasExpired(uint64_t aCurrentBufferRangeStart) const {
176 if (mRanges.empty()) {
177 // No information means no relevant information. Allow this object to be
178 // discarded.
179 return true;
181 return mRanges.back().mRangeEnd <= aCurrentBufferRangeStart;
184 mozilla::FailureLatch& LocalFailureLatchSource() {
185 return mLocalFailureLatchSource;
188 // The encapsulated data points at the local FailureLatch, so on the way out
189 // they must be given a new external FailureLatch to start using instead.
190 mozilla::Vector<JITFrameInfoForBufferRange>&& MoveRangesWithNewFailureLatch(
191 mozilla::FailureLatch& aFailureLatch) &&;
192 mozilla::UniquePtr<UniqueJSONStrings>&& MoveUniqueStringsWithNewFailureLatch(
193 mozilla::FailureLatch& aFailureLatch) &&;
195 private:
196 // JITFrameInfo's may exist during profiling, so it carries its own fallible
197 // FailureLatch. If&when the data below is finally extracted, any error is
198 // forwarded to the caller.
199 mozilla::FailureLatchSource mLocalFailureLatchSource;
201 // The array of ranges of JIT frame information, sorted by buffer position.
202 // Ranges are non-overlapping.
203 // The JSON of the cached frames can contain string indexes, which refer
204 // to strings in mUniqueStrings.
205 mozilla::Vector<JITFrameInfoForBufferRange> mRanges;
207 // The string table which contains strings used in the frame JSON that's
208 // cached in mRanges.
209 mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
212 class UniqueStacks final : public mozilla::FailureLatch {
213 public:
214 struct FrameKey {
215 explicit FrameKey(const char* aLocation)
216 : mData(NormalFrameData{nsCString(aLocation), false, false, 0,
217 mozilla::Nothing(), mozilla::Nothing()}) {}
219 FrameKey(nsCString&& aLocation, bool aRelevantForJS, bool aBaselineInterp,
220 uint64_t aInnerWindowID, const mozilla::Maybe<unsigned>& aLine,
221 const mozilla::Maybe<unsigned>& aColumn,
222 const mozilla::Maybe<JS::ProfilingCategoryPair>& aCategoryPair)
223 : mData(NormalFrameData{aLocation, aRelevantForJS, aBaselineInterp,
224 aInnerWindowID, aLine, aColumn,
225 aCategoryPair}) {}
227 FrameKey(void* aJITAddress, uint32_t aJITDepth, uint32_t aRangeIndex)
228 : mData(JITFrameData{aJITAddress, aJITDepth, aRangeIndex}) {}
230 FrameKey(const FrameKey& aToCopy) = default;
232 uint32_t Hash() const;
233 bool operator==(const FrameKey& aOther) const {
234 return mData == aOther.mData;
237 struct NormalFrameData {
238 bool operator==(const NormalFrameData& aOther) const;
240 nsCString mLocation;
241 bool mRelevantForJS;
242 bool mBaselineInterp;
243 uint64_t mInnerWindowID;
244 mozilla::Maybe<unsigned> mLine;
245 mozilla::Maybe<unsigned> mColumn;
246 mozilla::Maybe<JS::ProfilingCategoryPair> mCategoryPair;
248 struct JITFrameData {
249 bool operator==(const JITFrameData& aOther) const;
251 void* mCanonicalAddress;
252 uint32_t mDepth;
253 uint32_t mRangeIndex;
255 mozilla::Variant<NormalFrameData, JITFrameData> mData;
258 struct FrameKeyHasher {
259 using Lookup = FrameKey;
261 static mozilla::HashNumber hash(const FrameKey& aLookup) {
262 mozilla::HashNumber hash = 0;
263 if (aLookup.mData.is<FrameKey::NormalFrameData>()) {
264 const FrameKey::NormalFrameData& data =
265 aLookup.mData.as<FrameKey::NormalFrameData>();
266 if (!data.mLocation.IsEmpty()) {
267 hash = mozilla::AddToHash(hash,
268 mozilla::HashString(data.mLocation.get()));
270 hash = mozilla::AddToHash(hash, data.mRelevantForJS);
271 hash = mozilla::AddToHash(hash, data.mBaselineInterp);
272 hash = mozilla::AddToHash(hash, data.mInnerWindowID);
273 if (data.mLine.isSome()) {
274 hash = mozilla::AddToHash(hash, *data.mLine);
276 if (data.mColumn.isSome()) {
277 hash = mozilla::AddToHash(hash, *data.mColumn);
279 if (data.mCategoryPair.isSome()) {
280 hash = mozilla::AddToHash(hash,
281 static_cast<uint32_t>(*data.mCategoryPair));
283 } else {
284 const FrameKey::JITFrameData& data =
285 aLookup.mData.as<FrameKey::JITFrameData>();
286 hash = mozilla::AddToHash(hash, data.mCanonicalAddress);
287 hash = mozilla::AddToHash(hash, data.mDepth);
288 hash = mozilla::AddToHash(hash, data.mRangeIndex);
290 return hash;
293 static bool match(const FrameKey& aKey, const FrameKey& aLookup) {
294 return aKey == aLookup;
297 static void rekey(FrameKey& aKey, const FrameKey& aNewKey) {
298 aKey = aNewKey;
302 struct StackKey {
303 mozilla::Maybe<uint32_t> mPrefixStackIndex;
304 uint32_t mFrameIndex;
306 explicit StackKey(uint32_t aFrame)
307 : mFrameIndex(aFrame), mHash(mozilla::HashGeneric(aFrame)) {}
309 StackKey(const StackKey& aPrefix, uint32_t aPrefixStackIndex,
310 uint32_t aFrame)
311 : mPrefixStackIndex(mozilla::Some(aPrefixStackIndex)),
312 mFrameIndex(aFrame),
313 mHash(mozilla::AddToHash(aPrefix.mHash, aFrame)) {}
315 mozilla::HashNumber Hash() const { return mHash; }
317 bool operator==(const StackKey& aOther) const {
318 return mPrefixStackIndex == aOther.mPrefixStackIndex &&
319 mFrameIndex == aOther.mFrameIndex;
322 private:
323 mozilla::HashNumber mHash;
326 struct StackKeyHasher {
327 using Lookup = StackKey;
329 static mozilla::HashNumber hash(const StackKey& aLookup) {
330 return aLookup.Hash();
333 static bool match(const StackKey& aKey, const StackKey& aLookup) {
334 return aKey == aLookup;
337 static void rekey(StackKey& aKey, const StackKey& aNewKey) {
338 aKey = aNewKey;
342 UniqueStacks(mozilla::FailureLatch& aFailureLatch,
343 JITFrameInfo&& aJITFrameInfo,
344 ProfilerCodeAddressService* aCodeAddressService = nullptr);
346 // Return a StackKey for aFrame as the stack's root frame (no prefix).
347 [[nodiscard]] mozilla::Maybe<StackKey> BeginStack(const FrameKey& aFrame);
349 // Return a new StackKey that is obtained by appending aFrame to aStack.
350 [[nodiscard]] mozilla::Maybe<StackKey> AppendFrame(const StackKey& aStack,
351 const FrameKey& aFrame);
353 // Look up frame keys for the given JIT address, and ensure that our frame
354 // table has entries for the returned frame keys. The JSON for these frames
355 // is taken from mJITInfoRanges.
356 // aBufferPosition is needed in order to look up the correct JIT frame info
357 // object in mJITInfoRanges.
358 [[nodiscard]] mozilla::Maybe<mozilla::Vector<UniqueStacks::FrameKey>>
359 LookupFramesForJITAddressFromBufferPos(void* aJITAddress,
360 uint64_t aBufferPosition);
362 [[nodiscard]] mozilla::Maybe<uint32_t> GetOrAddFrameIndex(
363 const FrameKey& aFrame);
364 [[nodiscard]] mozilla::Maybe<uint32_t> GetOrAddStackIndex(
365 const StackKey& aStack);
367 void SpliceFrameTableElements(SpliceableJSONWriter& aWriter);
368 void SpliceStackTableElements(SpliceableJSONWriter& aWriter);
370 [[nodiscard]] UniqueJSONStrings& UniqueStrings() {
371 MOZ_RELEASE_ASSERT(mUniqueStrings.get());
372 return *mUniqueStrings;
375 // Find the function name at the given PC (if a ProfilerCodeAddressService was
376 // provided), otherwise just stringify that PC.
377 [[nodiscard]] nsAutoCString FunctionNameOrAddress(void* aPC);
379 FAILURELATCH_IMPL_PROXY(mFrameTableWriter)
381 private:
382 void StreamNonJITFrame(const FrameKey& aFrame);
383 void StreamStack(const StackKey& aStack);
385 mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
387 ProfilerCodeAddressService* mCodeAddressService = nullptr;
389 SpliceableChunkedJSONWriter mFrameTableWriter;
390 mozilla::HashMap<FrameKey, uint32_t, FrameKeyHasher> mFrameToIndexMap;
392 SpliceableChunkedJSONWriter mStackTableWriter;
393 mozilla::HashMap<StackKey, uint32_t, StackKeyHasher> mStackToIndexMap;
395 mozilla::Vector<JITFrameInfoForBufferRange> mJITInfoRanges;
399 // Thread profile JSON Format
400 // --------------------------
402 // The profile contains much duplicate information. The output JSON of the
403 // profile attempts to deduplicate strings, frames, and stack prefixes, to cut
404 // down on size and to increase JSON streaming speed. Deduplicated values are
405 // streamed as indices into their respective tables.
407 // Further, arrays of objects with the same set of properties (e.g., samples,
408 // frames) are output as arrays according to a schema instead of an object
409 // with property names. A property that is not present is represented in the
410 // array as null or undefined.
412 // The format of the thread profile JSON is shown by the following example
413 // with 1 sample and 1 marker:
415 // {
416 // "name": "Foo",
417 // "tid": 42,
418 // "samples":
419 // {
420 // "schema":
421 // {
422 // "stack": 0, /* index into stackTable */
423 // "time": 1, /* number */
424 // "eventDelay": 2, /* number */
425 // "ThreadCPUDelta": 3, /* optional number */
426 // },
427 // "data":
428 // [
429 // [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, eventDelay: 0.0 } */
430 // ]
431 // },
433 // "markers":
434 // {
435 // "schema":
436 // {
437 // "name": 0, /* index into stringTable */
438 // "time": 1, /* number */
439 // "data": 2 /* arbitrary JSON */
440 // },
441 // "data":
442 // [
443 // [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */
444 // ]
445 // },
447 // "stackTable":
448 // {
449 // "schema":
450 // {
451 // "prefix": 0, /* index into stackTable */
452 // "frame": 1 /* index into frameTable */
453 // },
454 // "data":
455 // [
456 // [ null, 0 ], /* (root) */
457 // [ 0, 1 ] /* (root) > foo.js */
458 // ]
459 // },
461 // "frameTable":
462 // {
463 // "schema":
464 // {
465 // "location": 0, /* index into stringTable */
466 // "relevantForJS": 1, /* bool */
467 // "innerWindowID": 2, /* inner window ID of global JS `window` object */
468 // "implementation": 3, /* index into stringTable */
469 // "line": 4, /* number */
470 // "column": 5, /* number */
471 // "category": 6, /* index into profile.meta.categories */
472 // "subcategory": 7 /* index into
473 // profile.meta.categories[category].subcategories */
474 // },
475 // "data":
476 // [
477 // [ 0 ], /* { location: '(root)' } */
478 // [ 1, null, null, 2 ] /* { location: 'foo.js',
479 // implementation: 'baseline' } */
480 // ]
481 // },
483 // "stringTable":
484 // [
485 // "(root)",
486 // "foo.js",
487 // "baseline",
488 // "example marker"
489 // ]
490 // }
492 // Process:
493 // {
494 // "name": "Bar",
495 // "pid": 24,
496 // "threads":
497 // [
498 // <0-N threads from above>
499 // ],
500 // "counters": /* includes the memory counter */
501 // [
502 // {
503 // "name": "qwerty",
504 // "category": "uiop",
505 // "description": "this is qwerty uiop",
506 // "sample_groups:
507 // [
508 // {
509 // "id": 42, /* number (thread id, or object identifier (tab), etc) */
510 // "samples:
511 // {
512 // "schema":
513 // {
514 // "time": 1, /* number */
515 // "number": 2, /* number (of times the counter was touched) */
516 // "count": 3 /* number (total for the counter) */
517 // },
518 // "data":
519 // [
520 // [ 0.1, 1824,
521 // 454622 ] /* { time: 0.1, number: 1824, count: 454622 } */
522 // ]
523 // },
524 // },
525 // /* more sample-group objects with different id's */
526 // ]
527 // },
528 // /* more counters */
529 // ],
530 // }
532 #endif /* ndef ProfileBufferEntry_h */