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 : #include "mozilla/HangAnnotations.h"
7 : #include "ThreadHangStats.h"
8 : #include "nsITelemetry.h"
9 : #include "HangReports.h"
10 : #include "jsapi.h"
11 :
12 : namespace {
13 :
14 : using namespace mozilla;
15 : using namespace mozilla::HangMonitor;
16 : using namespace mozilla::Telemetry;
17 :
18 : static JSObject*
19 0 : CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time)
20 : {
21 : /* Create JS representation of TimeHistogram,
22 : in the format of Chromium-style histograms. */
23 0 : JS::RootedObject ret(cx, JS_NewPlainObject(cx));
24 0 : if (!ret) {
25 0 : return nullptr;
26 : }
27 :
28 0 : if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0),
29 0 : JSPROP_ENUMERATE) ||
30 0 : !JS_DefineProperty(cx, ret, "max",
31 0 : time.GetBucketMax(ArrayLength(time) - 1),
32 0 : JSPROP_ENUMERATE) ||
33 0 : !JS_DefineProperty(cx, ret, "histogram_type",
34 : nsITelemetry::HISTOGRAM_EXPONENTIAL,
35 : JSPROP_ENUMERATE)) {
36 0 : return nullptr;
37 : }
38 : // TODO: calculate "sum"
39 0 : if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE)) {
40 0 : return nullptr;
41 : }
42 :
43 : JS::RootedObject ranges(
44 0 : cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
45 : JS::RootedObject counts(
46 0 : cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
47 0 : if (!ranges || !counts) {
48 0 : return nullptr;
49 : }
50 : /* In a Chromium-style histogram, the first bucket is an "under" bucket
51 : that represents all values below the histogram's range. */
52 0 : if (!JS_DefineElement(cx, ranges, 0, time.GetBucketMin(0), JSPROP_ENUMERATE) ||
53 0 : !JS_DefineElement(cx, counts, 0, 0, JSPROP_ENUMERATE)) {
54 0 : return nullptr;
55 : }
56 0 : for (size_t i = 0; i < ArrayLength(time); i++) {
57 0 : if (!JS_DefineElement(cx, ranges, i + 1, time.GetBucketMax(i),
58 0 : JSPROP_ENUMERATE) ||
59 0 : !JS_DefineElement(cx, counts, i + 1, time[i], JSPROP_ENUMERATE)) {
60 0 : return nullptr;
61 : }
62 : }
63 0 : if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) ||
64 0 : !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) {
65 0 : return nullptr;
66 : }
67 0 : return ret;
68 : }
69 :
70 : static JSObject*
71 0 : CreateJSHangStack(JSContext* cx, const Telemetry::HangStack& stack)
72 : {
73 0 : JS::RootedObject ret(cx, JS_NewArrayObject(cx, stack.length()));
74 0 : if (!ret) {
75 0 : return nullptr;
76 : }
77 0 : for (size_t i = 0; i < stack.length(); i++) {
78 0 : JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i]));
79 0 : if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) {
80 0 : return nullptr;
81 : }
82 : }
83 0 : return ret;
84 : }
85 :
86 : static void
87 0 : CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations,
88 : JS::MutableHandleObject returnedObject)
89 : {
90 0 : JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0));
91 0 : if (!annotationsArray) {
92 0 : returnedObject.set(nullptr);
93 0 : return;
94 : }
95 : // We keep track of the annotations we reported in this hash set, so we can
96 : // discard duplicated ones.
97 0 : nsTHashtable<nsStringHashKey> reportedAnnotations;
98 0 : size_t annotationIndex = 0;
99 0 : for (const auto & curAnnotations : annotations) {
100 0 : JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx));
101 0 : if (!jsAnnotation) {
102 0 : continue;
103 : }
104 : // Build a key to index the current annotations in our hash set.
105 0 : nsAutoString annotationsKey;
106 0 : nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey);
107 0 : if (NS_FAILED(rv)) {
108 0 : continue;
109 : }
110 : // Check if the annotations are in the set. If that's the case, don't double report.
111 0 : if (reportedAnnotations.GetEntry(annotationsKey)) {
112 0 : continue;
113 : }
114 : // If not, report them.
115 0 : reportedAnnotations.PutEntry(annotationsKey);
116 : UniquePtr<HangAnnotations::Enumerator> annotationsEnum =
117 0 : curAnnotations->GetEnumerator();
118 0 : if (!annotationsEnum) {
119 0 : continue;
120 : }
121 0 : nsAutoString key;
122 0 : nsAutoString value;
123 0 : while (annotationsEnum->Next(key, value)) {
124 0 : JS::RootedValue jsValue(cx);
125 0 : jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
126 0 : if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
127 : jsValue, JSPROP_ENUMERATE)) {
128 0 : returnedObject.set(nullptr);
129 0 : return;
130 : }
131 : }
132 0 : if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) {
133 0 : continue;
134 : }
135 0 : ++annotationIndex;
136 : }
137 : // Return the array using a |MutableHandleObject| to avoid triggering a false
138 : // positive rooting issue in the hazard analysis build.
139 0 : returnedObject.set(annotationsArray);
140 : }
141 :
142 : static JSObject*
143 0 : CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
144 : {
145 0 : JS::RootedObject ret(cx, JS_NewPlainObject(cx));
146 0 : if (!ret) {
147 0 : return nullptr;
148 : }
149 :
150 0 : JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack()));
151 0 : JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
152 0 : auto& hangAnnotations = hang.GetAnnotations();
153 0 : JS::RootedObject annotations(cx);
154 0 : CreateJSHangAnnotations(cx, hangAnnotations, &annotations);
155 :
156 0 : if (!stack ||
157 0 : !time ||
158 0 : !annotations ||
159 0 : !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
160 0 : !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
161 0 : (!hangAnnotations.empty() && // <-- Only define annotations when nonempty
162 0 : !JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
163 0 : return nullptr;
164 : }
165 :
166 0 : return ret;
167 : }
168 :
169 : } // namespace
170 :
171 : namespace mozilla {
172 : namespace Telemetry {
173 :
174 : JSObject*
175 0 : CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
176 : {
177 0 : JS::RootedObject ret(cx, JS_NewPlainObject(cx));
178 0 : if (!ret) {
179 0 : return nullptr;
180 : }
181 0 : JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
182 0 : if (!name ||
183 0 : !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) {
184 0 : return nullptr;
185 : }
186 :
187 0 : JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
188 0 : if (!activity ||
189 0 : !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) {
190 0 : return nullptr;
191 : }
192 :
193 : // Process the hangs into a hangs object.
194 0 : JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
195 0 : if (!hangs) {
196 0 : return nullptr;
197 : }
198 0 : for (size_t i = 0; i < thread.mHangs.length(); i++) {
199 0 : JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
200 0 : if (!ret) {
201 0 : return nullptr;
202 : }
203 :
204 0 : JS::RootedString runnableName(cx, JS_NewStringCopyZ(cx, thread.mHangs[i].GetRunnableName()));
205 0 : if (!runnableName ||
206 0 : !JS_DefineProperty(cx, ret, "runnableName", runnableName, JSPROP_ENUMERATE)) {
207 0 : return nullptr;
208 : }
209 :
210 : // Check if we have a cached native stack index, and if we do record it.
211 0 : uint32_t index = thread.mHangs[i].GetNativeStackIndex();
212 0 : if (index != Telemetry::HangHistogram::NO_NATIVE_STACK_INDEX) {
213 0 : if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) {
214 0 : return nullptr;
215 : }
216 : }
217 :
218 0 : if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) {
219 0 : return nullptr;
220 : }
221 : }
222 0 : if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) {
223 0 : return nullptr;
224 : }
225 :
226 : // We should already have a CombinedStacks object on the ThreadHangStats, so
227 : // add that one.
228 0 : JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, thread.mCombinedStacks));
229 0 : if (!fullReportObj) {
230 0 : return nullptr;
231 : }
232 :
233 0 : if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) {
234 0 : return nullptr;
235 : }
236 :
237 0 : return ret;
238 : }
239 :
240 : void
241 0 : TimeHistogram::Add(PRIntervalTime aTime)
242 : {
243 0 : uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
244 0 : size_t index = mozilla::FloorLog2(timeMs);
245 0 : operator[](index)++;
246 0 : }
247 :
248 : const char*
249 0 : HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength)
250 : {
251 0 : MOZ_ASSERT(this->canAppendWithoutRealloc(1));
252 : // Include null-terminator in length count.
253 0 : MOZ_ASSERT(mBuffer.canAppendWithoutRealloc(aLength + 1));
254 :
255 0 : const char* const entry = mBuffer.end();
256 0 : mBuffer.infallibleAppend(aText, aLength);
257 0 : mBuffer.infallibleAppend('\0'); // Explicitly append null-terminator
258 0 : this->infallibleAppend(entry);
259 0 : return entry;
260 : }
261 :
262 : const char*
263 0 : HangStack::AppendViaBuffer(const char* aText, size_t aLength)
264 : {
265 0 : if (!this->reserve(this->length() + 1)) {
266 0 : return nullptr;
267 : }
268 :
269 : // Keep track of the previous buffer in case we need to adjust pointers later.
270 0 : const char* const prevStart = mBuffer.begin();
271 0 : const char* const prevEnd = mBuffer.end();
272 :
273 : // Include null-terminator in length count.
274 0 : if (!mBuffer.reserve(mBuffer.length() + aLength + 1)) {
275 0 : return nullptr;
276 : }
277 :
278 0 : if (prevStart != mBuffer.begin()) {
279 : // The buffer has moved; we have to adjust pointers in the stack.
280 0 : for (auto & entry : *this) {
281 0 : if (entry >= prevStart && entry < prevEnd) {
282 : // Move from old buffer to new buffer.
283 0 : entry += mBuffer.begin() - prevStart;
284 : }
285 : }
286 : }
287 :
288 0 : return InfallibleAppendViaBuffer(aText, aLength);
289 : }
290 :
291 : uint32_t
292 0 : HangHistogram::GetHash(const HangStack& aStack)
293 : {
294 0 : uint32_t hash = 0;
295 0 : for (const char* const* label = aStack.begin();
296 0 : label != aStack.end(); label++) {
297 : /* If the string is within our buffer, we need to hash its content.
298 : Otherwise, the string is statically allocated, and we only need
299 : to hash the pointer instead of the content. */
300 0 : if (aStack.IsInBuffer(*label)) {
301 0 : hash = AddToHash(hash, HashString(*label));
302 : } else {
303 0 : hash = AddToHash(hash, *label);
304 : }
305 : }
306 0 : return hash;
307 : }
308 :
309 : bool
310 0 : HangHistogram::operator==(const HangHistogram& aOther) const
311 : {
312 0 : if (mHash != aOther.mHash) {
313 0 : return false;
314 : }
315 0 : if (mStack.length() != aOther.mStack.length()) {
316 0 : return false;
317 : }
318 0 : return mStack == aOther.mStack;
319 : }
320 :
321 : } // namespace Telemetry
322 : } // namespace mozilla
|