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
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"
29 class ProfilerCodeAddressService
;
32 class ProfileBufferEntry
{
34 using KindUnderlyingType
=
35 std::underlying_type_t
<::mozilla::ProfileBufferEntryKind
>;
36 using Kind
= mozilla::ProfileBufferEntryKind
;
40 static constexpr size_t kNumChars
= mozilla::ProfileBufferEntryNumChars
;
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
);
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
)
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
)
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
;
77 uint8_t mStorage
[kNumChars
];
79 const char* GetString() const;
81 double GetDouble() 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.
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
;
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
);
127 static bool match(const JITFrameKey
& aKey
, const JITFrameKey
& aLookup
) {
128 return aKey
== aLookup
;
131 static void rekey(JITFrameKey
& aKey
, const JITFrameKey
& 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
146 class JITFrameInfo final
{
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
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
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
) &&;
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
{
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
,
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;
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
;
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
));
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
);
293 static bool match(const FrameKey
& aKey
, const FrameKey
& aLookup
) {
294 return aKey
== aLookup
;
297 static void rekey(FrameKey
& aKey
, const FrameKey
& aNewKey
) {
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
,
311 : mPrefixStackIndex(mozilla::Some(aPrefixStackIndex
)),
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
;
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
) {
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
)
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:
422 // "stack": 0, /* index into stackTable */
423 // "time": 1, /* number */
424 // "eventDelay": 2, /* number */
425 // "ThreadCPUDelta": 3, /* optional number */
429 // [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, eventDelay: 0.0 } */
437 // "name": 0, /* index into stringTable */
438 // "time": 1, /* number */
439 // "data": 2 /* arbitrary JSON */
443 // [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */
451 // "prefix": 0, /* index into stackTable */
452 // "frame": 1 /* index into frameTable */
456 // [ null, 0 ], /* (root) */
457 // [ 0, 1 ] /* (root) > foo.js */
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 */
477 // [ 0 ], /* { location: '(root)' } */
478 // [ 1, null, null, 2 ] /* { location: 'foo.js',
479 // implementation: 'baseline' } */
498 // <0-N threads from above>
500 // "counters": /* includes the memory counter */
504 // "category": "uiop",
505 // "description": "this is qwerty uiop",
509 // "id": 42, /* number (thread id, or object identifier (tab), etc) */
514 // "time": 1, /* number */
515 // "number": 2, /* number (of times the counter was touched) */
516 // "count": 3 /* number (total for the counter) */
521 // 454622 ] /* { time: 0.1, number: 1824, count: 454622 } */
525 // /* more sample-group objects with different id's */
528 // /* more counters */
532 #endif /* ndef ProfileBufferEntry_h */