Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef mozilla_BackgroundHangTelemetry_h
7 : #define mozilla_BackgroundHangTelemetry_h
8 :
9 : #include "mozilla/Array.h"
10 : #include "mozilla/Assertions.h"
11 : #include "mozilla/HangAnnotations.h"
12 : #include "mozilla/Move.h"
13 : #include "mozilla/Mutex.h"
14 : #include "mozilla/PodOperations.h"
15 : #include "mozilla/Vector.h"
16 : #include "mozilla/CombinedStacks.h"
17 :
18 : #include "nsString.h"
19 : #include "prinrval.h"
20 : #include "jsapi.h"
21 :
22 : namespace mozilla {
23 : namespace Telemetry {
24 :
25 : // This variable controls the maximum number of native hang stacks which may be
26 : // attached to a ping. This is due to how large native stacks can be. We want to
27 : // reduce the chance of a ping being discarded due to it exceeding the maximum
28 : // ping size.
29 : static const uint32_t kMaximumNativeHangStacks = 300;
30 :
31 : static const size_t kTimeHistogramBuckets = 8 * sizeof(PRIntervalTime);
32 :
33 : /* TimeHistogram is an efficient histogram that puts time durations into
34 : exponential (base 2) buckets; times are accepted in PRIntervalTime and
35 : stored in milliseconds. */
36 : class TimeHistogram : public mozilla::Array<uint32_t, kTimeHistogramBuckets>
37 : {
38 : public:
39 0 : TimeHistogram()
40 0 : {
41 0 : mozilla::PodArrayZero(*this);
42 0 : }
43 : // Get minimum (inclusive) range of bucket in milliseconds
44 0 : uint32_t GetBucketMin(size_t aBucket) const {
45 0 : MOZ_ASSERT(aBucket < ArrayLength(*this));
46 0 : return (1u << aBucket) & ~1u; // Bucket 0 starts at 0, not 1
47 : }
48 : // Get maximum (inclusive) range of bucket in milliseconds
49 0 : uint32_t GetBucketMax(size_t aBucket) const {
50 0 : MOZ_ASSERT(aBucket < ArrayLength(*this));
51 0 : return (1u << (aBucket + 1u)) - 1u;
52 : }
53 : void Add(PRIntervalTime aTime);
54 : };
55 :
56 : /* A native stack is a simple list of pointers, so rather than building a
57 : wrapper type, we typdef the type here. */
58 : typedef std::vector<uintptr_t> NativeHangStack;
59 :
60 : /* HangStack stores an array of const char pointers,
61 : with optional internal storage for strings. */
62 0 : class HangStack
63 : {
64 : public:
65 : static const size_t sMaxInlineStorage = 8;
66 :
67 : // The maximum depth for the native stack frames that we might collect.
68 : // XXX: Consider moving this to a different object?
69 : static const size_t sMaxNativeFrames = 150;
70 :
71 : private:
72 : typedef mozilla::Vector<const char*, sMaxInlineStorage> Impl;
73 : Impl mImpl;
74 :
75 : // Stack entries can either be a static const char*
76 : // or a pointer to within this buffer.
77 : mozilla::Vector<char, 0> mBuffer;
78 :
79 : public:
80 0 : HangStack() {}
81 :
82 0 : HangStack(HangStack&& aOther)
83 0 : : mImpl(mozilla::Move(aOther.mImpl))
84 0 : , mBuffer(mozilla::Move(aOther.mBuffer))
85 : {
86 0 : }
87 :
88 : HangStack& operator=(HangStack&& aOther) {
89 : mImpl = mozilla::Move(aOther.mImpl);
90 : mBuffer = mozilla::Move(aOther.mBuffer);
91 : return *this;
92 : }
93 :
94 0 : bool operator==(const HangStack& aOther) const {
95 0 : for (size_t i = 0; i < length(); i++) {
96 0 : if (!IsSameAsEntry(operator[](i), aOther[i])) {
97 0 : return false;
98 : }
99 : }
100 0 : return true;
101 : }
102 :
103 : bool operator!=(const HangStack& aOther) const {
104 : return !operator==(aOther);
105 : }
106 :
107 0 : const char*& operator[](size_t aIndex) {
108 0 : return mImpl[aIndex];
109 : }
110 :
111 0 : const char* const& operator[](size_t aIndex) const {
112 0 : return mImpl[aIndex];
113 : }
114 :
115 : size_t capacity() const { return mImpl.capacity(); }
116 0 : size_t length() const { return mImpl.length(); }
117 : bool empty() const { return mImpl.empty(); }
118 0 : bool canAppendWithoutRealloc(size_t aNeeded) const {
119 0 : return mImpl.canAppendWithoutRealloc(aNeeded);
120 : }
121 0 : void infallibleAppend(const char* aEntry) { mImpl.infallibleAppend(aEntry); }
122 0 : bool reserve(size_t aRequest) { return mImpl.reserve(aRequest); }
123 0 : const char** begin() { return mImpl.begin(); }
124 0 : const char* const* begin() const { return mImpl.begin(); }
125 0 : const char** end() { return mImpl.end(); }
126 0 : const char* const* end() const { return mImpl.end(); }
127 : const char*& back() { return mImpl.back(); }
128 0 : void erase(const char** aEntry) { mImpl.erase(aEntry); }
129 0 : void erase(const char** aBegin, const char** aEnd) {
130 0 : mImpl.erase(aBegin, aEnd);
131 0 : }
132 :
133 0 : void clear() {
134 0 : mImpl.clear();
135 0 : mBuffer.clear();
136 0 : }
137 :
138 0 : bool IsInBuffer(const char* aEntry) const {
139 0 : return aEntry >= mBuffer.begin() && aEntry < mBuffer.end();
140 : }
141 :
142 0 : bool IsSameAsEntry(const char* aEntry, const char* aOther) const {
143 : // If the entry came from the buffer, we need to compare its content;
144 : // otherwise we only need to compare its pointer.
145 0 : return IsInBuffer(aEntry) ? !strcmp(aEntry, aOther) : (aEntry == aOther);
146 : }
147 :
148 : size_t AvailableBufferSize() const {
149 : return mBuffer.capacity() - mBuffer.length();
150 : }
151 :
152 : bool EnsureBufferCapacity(size_t aCapacity) {
153 : // aCapacity is the minimal capacity and Vector may make the actual
154 : // capacity larger, in which case we want to use up all the space.
155 : return mBuffer.reserve(aCapacity) &&
156 : mBuffer.reserve(mBuffer.capacity());
157 : }
158 :
159 : const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength);
160 : const char* AppendViaBuffer(const char* aText, size_t aLength);
161 : };
162 :
163 : /* A hang histogram consists of a stack associated with the
164 : hang, along with a time histogram of the hang times. */
165 0 : class HangHistogram : public TimeHistogram
166 : {
167 : public:
168 : // Value used for mNativeStackIndex to represent the absence of a cached
169 : // native stack.
170 : static const uint32_t NO_NATIVE_STACK_INDEX = UINT32_MAX;
171 :
172 : private:
173 : static uint32_t GetHash(const HangStack& aStack);
174 :
175 : HangStack mStack;
176 : // Cached index of the native stack in the mCombinedStacks list in the owning
177 : // ThreadHangStats object. A default value of NO_NATIVE_STACK_INDEX means that
178 : // the ThreadHangStats object which owns this HangHistogram doesn't have a
179 : // cached CombinedStacks with this HangHistogram in it.
180 : uint32_t mNativeStackIndex;
181 : // Use a hash to speed comparisons
182 : const uint32_t mHash;
183 : // Annotations attributed to this stack
184 : HangMonitor::HangAnnotationsVector mAnnotations;
185 : // The name of the runnable on the current thread.
186 : nsCString mRunnableName;
187 :
188 : public:
189 0 : explicit HangHistogram(HangStack&& aStack, const nsACString& aRunnableName)
190 0 : : mStack(mozilla::Move(aStack))
191 : , mNativeStackIndex(NO_NATIVE_STACK_INDEX)
192 0 : , mHash(GetHash(mStack))
193 0 : , mRunnableName(aRunnableName)
194 : {
195 0 : }
196 :
197 0 : HangHistogram(HangHistogram&& aOther)
198 0 : : TimeHistogram(mozilla::Move(aOther))
199 0 : , mStack(mozilla::Move(aOther.mStack))
200 0 : , mNativeStackIndex(mozilla::Move(aOther.mNativeStackIndex))
201 0 : , mHash(mozilla::Move(aOther.mHash))
202 0 : , mAnnotations(mozilla::Move(aOther.mAnnotations))
203 0 : , mRunnableName(aOther.mRunnableName)
204 : {
205 0 : }
206 : bool operator==(const HangHistogram& aOther) const;
207 : bool operator!=(const HangHistogram& aOther) const
208 : {
209 : return !operator==(aOther);
210 : }
211 0 : const HangStack& GetStack() const {
212 0 : return mStack;
213 : }
214 0 : uint32_t GetNativeStackIndex() const {
215 0 : return mNativeStackIndex;
216 : }
217 0 : void SetNativeStackIndex(uint32_t aIndex) {
218 0 : MOZ_ASSERT(aIndex != NO_NATIVE_STACK_INDEX);
219 0 : mNativeStackIndex = aIndex;
220 0 : }
221 0 : const char* GetRunnableName() const {
222 0 : return mRunnableName.get();
223 : }
224 0 : const HangMonitor::HangAnnotationsVector& GetAnnotations() const {
225 0 : return mAnnotations;
226 : }
227 0 : void Add(PRIntervalTime aTime, HangMonitor::HangAnnotationsPtr aAnnotations) {
228 0 : TimeHistogram::Add(aTime);
229 0 : if (aAnnotations) {
230 0 : if (!mAnnotations.append(Move(aAnnotations))) {
231 0 : MOZ_CRASH();
232 : }
233 : }
234 0 : }
235 : };
236 :
237 : /* Thread hang stats consist of
238 : - thread name
239 : - time histogram of all task run times
240 : - hang histograms of individual hangs
241 : - annotations for each hang
242 : - combined native stacks for all hangs
243 : */
244 0 : class ThreadHangStats
245 : {
246 : private:
247 : nsCString mName;
248 :
249 : public:
250 : TimeHistogram mActivity;
251 : mozilla::Vector<HangHistogram, 4> mHangs;
252 : uint32_t mNativeStackCnt;
253 : CombinedStacks mCombinedStacks;
254 :
255 0 : explicit ThreadHangStats(const char* aName)
256 0 : : mName(aName)
257 : , mNativeStackCnt(0)
258 0 : , mCombinedStacks(Telemetry::kMaximumNativeHangStacks)
259 : {
260 0 : }
261 0 : ThreadHangStats(ThreadHangStats&& aOther)
262 0 : : mName(mozilla::Move(aOther.mName))
263 0 : , mActivity(mozilla::Move(aOther.mActivity))
264 0 : , mHangs(mozilla::Move(aOther.mHangs))
265 0 : , mNativeStackCnt(aOther.mNativeStackCnt)
266 0 : , mCombinedStacks(mozilla::Move(aOther.mCombinedStacks))
267 : {
268 0 : aOther.mNativeStackCnt = 0;
269 0 : }
270 0 : const char* GetName() const {
271 0 : return mName.get();
272 : }
273 : };
274 :
275 : /**
276 : * Reflects thread hang stats object as a JS object.
277 : *
278 : * @param JSContext* cx javascript context.
279 : * @param JSContext* cx thread hang statistics.
280 : *
281 : * @return JSObject* Javascript reflection of the statistics.
282 : */
283 : JSObject*
284 : CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread);
285 :
286 : } // namespace Telemetry
287 : } // namespace mozilla
288 :
289 : #endif // mozilla_BackgroundHangTelemetry_h
|