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 "BaseProfilingCategory.h"
11 #include "gtest/MozGtestFriend.h"
12 #include "mozilla/BaseProfileJSONWriter.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 #include "mozilla/Vector.h"
22 #include <type_traits>
25 namespace baseprofiler
{
27 class ProfileBufferEntry
{
29 using KindUnderlyingType
=
30 std::underlying_type_t
<::mozilla::ProfileBufferEntryKind
>;
31 using Kind
= ::mozilla::ProfileBufferEntryKind
;
35 static constexpr size_t kNumChars
= ::mozilla::ProfileBufferEntryNumChars
;
38 // aString must be a static string.
39 ProfileBufferEntry(Kind aKind
, const char* aString
);
40 ProfileBufferEntry(Kind aKind
, char aChars
[kNumChars
]);
41 ProfileBufferEntry(Kind aKind
, void* aPtr
);
42 ProfileBufferEntry(Kind aKind
, double aDouble
);
43 ProfileBufferEntry(Kind aKind
, int64_t aInt64
);
44 ProfileBufferEntry(Kind aKind
, uint64_t aUint64
);
45 ProfileBufferEntry(Kind aKind
, int aInt
);
46 ProfileBufferEntry(Kind aKind
, BaseProfilerThreadId aThreadId
);
49 #define CTOR(KIND, TYPE, SIZE) \
50 static ProfileBufferEntry KIND(TYPE aVal) { \
51 return ProfileBufferEntry(Kind::KIND, aVal); \
53 FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(CTOR
)
56 Kind
GetKind() const { return mKind
; }
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
)
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
;
72 uint8_t mStorage
[kNumChars
];
74 const char* GetString() const;
76 double GetDouble() const;
78 int64_t GetInt64() const;
79 uint64_t GetUint64() const;
80 BaseProfilerThreadId
GetThreadId() const;
81 void CopyCharsInto(char (&aOutArray
)[kNumChars
]) const;
84 // Packed layout: 1 byte for the tag + 8 bytes for the value.
85 static_assert(sizeof(ProfileBufferEntry
) == 9, "bad ProfileBufferEntry size");
90 explicit FrameKey(const char* aLocation
)
91 : mData(NormalFrameData
{std::string(aLocation
), false, 0, Nothing(),
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
}) {}
101 FrameKey(const FrameKey
& aToCopy
) = default;
103 uint32_t Hash() const;
104 bool operator==(const FrameKey
& aOther
) const {
105 return mData
== aOther
.mData
;
108 struct NormalFrameData
{
109 bool operator==(const NormalFrameData
& aOther
) const;
111 std::string mLocation
;
113 uint64_t mInnerWindowID
;
114 Maybe
<unsigned> mLine
;
115 Maybe
<unsigned> mColumn
;
116 Maybe
<ProfilingCategoryPair
> mCategoryPair
;
118 Variant
<NormalFrameData
> mData
;
121 struct FrameKeyHasher
{
122 using Lookup
= FrameKey
;
124 static HashNumber
hash(const FrameKey
& aLookup
) {
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()));
132 hash
= AddToHash(hash
, data
.mRelevantForJS
);
133 hash
= mozilla::AddToHash(hash
, data
.mInnerWindowID
);
134 if (data
.mLine
.isSome()) {
135 hash
= AddToHash(hash
, *data
.mLine
);
137 if (data
.mColumn
.isSome()) {
138 hash
= AddToHash(hash
, *data
.mColumn
);
140 if (data
.mCategoryPair
.isSome()) {
141 hash
= AddToHash(hash
, static_cast<uint32_t>(*data
.mCategoryPair
));
147 static bool match(const FrameKey
& aKey
, const FrameKey
& aLookup
) {
148 return aKey
== aLookup
;
151 static void rekey(FrameKey
& aKey
, const FrameKey
& aNewKey
) {
157 Maybe
<uint32_t> mPrefixStackIndex
;
158 uint32_t mFrameIndex
;
160 explicit StackKey(uint32_t aFrame
)
161 : mFrameIndex(aFrame
), mHash(HashGeneric(aFrame
)) {}
163 StackKey(const StackKey
& aPrefix
, uint32_t aPrefixStackIndex
,
165 : mPrefixStackIndex(Some(aPrefixStackIndex
)),
167 mHash(AddToHash(aPrefix
.mHash
, aFrame
)) {}
169 HashNumber
Hash() const { return mHash
; }
171 bool operator==(const StackKey
& aOther
) const {
172 return mPrefixStackIndex
== aOther
.mPrefixStackIndex
&&
173 mFrameIndex
== aOther
.mFrameIndex
;
180 struct StackKeyHasher
{
181 using Lookup
= StackKey
;
183 static HashNumber
hash(const StackKey
& aLookup
) { return aLookup
.Hash(); }
185 static bool match(const StackKey
& aKey
, const StackKey
& aLookup
) {
186 return aKey
== aLookup
;
189 static void rekey(StackKey
& aKey
, const StackKey
& aNewKey
) {
196 // Return a StackKey for aFrame as the stack's root frame (no prefix).
197 [[nodiscard
]] StackKey
BeginStack(const FrameKey
& aFrame
);
199 // Return a new StackKey that is obtained by appending aFrame to aStack.
200 [[nodiscard
]] StackKey
AppendFrame(const StackKey
& aStack
,
201 const FrameKey
& aFrame
);
203 [[nodiscard
]] uint32_t GetOrAddFrameIndex(const FrameKey
& aFrame
);
204 [[nodiscard
]] uint32_t GetOrAddStackIndex(const StackKey
& aStack
);
206 void SpliceFrameTableElements(SpliceableJSONWriter
& aWriter
);
207 void SpliceStackTableElements(SpliceableJSONWriter
& aWriter
);
209 UniqueJSONStrings
& UniqueStrings() {
210 MOZ_RELEASE_ASSERT(mUniqueStrings
.get());
211 return *mUniqueStrings
;
215 void StreamNonJITFrame(const FrameKey
& aFrame
);
216 void StreamStack(const StackKey
& aStack
);
218 UniquePtr
<UniqueJSONStrings
> mUniqueStrings
;
220 SpliceableChunkedJSONWriter mFrameTableWriter
;
221 HashMap
<FrameKey
, uint32_t, FrameKeyHasher
> mFrameToIndexMap
;
223 SpliceableChunkedJSONWriter mStackTableWriter
;
224 HashMap
<StackKey
, uint32_t, StackKeyHasher
> mStackToIndexMap
;
228 // Thread profile JSON Format
229 // --------------------------
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.
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.
241 // The format of the thread profile JSON is shown by the following example
242 // with 1 sample and 1 marker:
251 // "stack": 0, /* index into stackTable */
252 // "time": 1, /* number */
253 // "eventDelay": 2, /* number */
257 // [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, eventDelay: 0.0 } */
265 // "name": 0, /* index into stringTable */
266 // "time": 1, /* number */
267 // "data": 2 /* arbitrary JSON */
271 // [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */
279 // "prefix": 0, /* index into stackTable */
280 // "frame": 1 /* index into frameTable */
284 // [ null, 0 ], /* (root) */
285 // [ 0, 1 ] /* (root) > foo.js */
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 */
305 // [ 0 ], /* { location: '(root)' } */
306 // [ 1, 2 ] /* { location: 'foo.js',
307 // implementation: 'baseline' } */
326 // <0-N threads from above>
328 // "counters": /* includes the memory counter */
332 // "category": "uiop",
333 // "description": "this is qwerty uiop",
337 // "id": 42, /* number (thread id, or object identifier (tab), etc) */
342 // "time": 1, /* number */
343 // "number": 2, /* number (of times the counter was touched) */
344 // "count": 3 /* number (total for the counter) */
349 // 454622 ] /* { time: 0.1, number: 1824, count: 454622 } */
353 // /* more sample-group objects with different id's */
356 // /* more counters */
361 } // namespace baseprofiler
362 } // namespace mozilla
364 #endif /* ndef ProfileBufferEntry_h */