1 /* -*- Mode: C++; tab-width: 8; 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/. */
6
7 #include "CombinedStacks.h"
8
9 #include "jsapi.h"
10 #include "js/Array.h" // JS::NewArrayObject
11 #include "mozilla/HangAnnotations.h"
12
13 namespace mozilla::Telemetry {
14
15 // The maximum number of chrome hangs stacks that we're keeping.
16 const size_t kMaxChromeStacksKept = 50;
17
CombinedStacks()18 CombinedStacks::CombinedStacks() : CombinedStacks(kMaxChromeStacksKept) {}
19
CombinedStacks(size_t aMaxStacksCount)20 CombinedStacks::CombinedStacks(size_t aMaxStacksCount)
21 : mNextIndex(0),
22 mMaxStacksCount(aMaxStacksCount),
23 mIsFromTerminatorWatchdog(false) {}
24
GetModuleCount() const25 size_t CombinedStacks::GetModuleCount() const { return mModules.size(); }
26
GetModule(unsigned aIndex) const27 const Telemetry::ProcessedStack::Module& CombinedStacks::GetModule(
28 unsigned aIndex) const {
29 return mModules[aIndex];
30 }
31
AddStack(const Telemetry::ProcessedStack & aStack)32 size_t CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
33 size_t index = mNextIndex;
34 // Advance the indices of the circular queue holding the stacks.
35 mNextIndex = (mNextIndex + 1) % mMaxStacksCount;
36 // Grow the vector up to the maximum size, if needed.
37 if (mStacks.size() < mMaxStacksCount) {
38 mStacks.resize(mStacks.size() + 1);
39 }
40 // Get a reference to the location holding the new stack.
41 CombinedStacks::Stack& adjustedStack = mStacks[index];
42 // If we're using an old stack to hold aStack, clear it.
43 adjustedStack.clear();
44
45 size_t stackSize = aStack.GetStackSize();
46 for (size_t i = 0; i < stackSize; ++i) {
47 const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i);
48 uint16_t modIndex;
49 if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) {
50 modIndex = frame.mModIndex;
51 } else {
52 const Telemetry::ProcessedStack::Module& module =
53 aStack.GetModule(frame.mModIndex);
54 std::vector<Telemetry::ProcessedStack::Module>::iterator modIterator =
55 std::find(mModules.begin(), mModules.end(), module);
56 if (modIterator == mModules.end()) {
57 mModules.push_back(module);
58 modIndex = mModules.size() - 1;
59 } else {
60 modIndex = modIterator - mModules.begin();
61 }
62 }
63 Telemetry::ProcessedStack::Frame adjustedFrame = {frame.mOffset, modIndex};
64 adjustedStack.push_back(adjustedFrame);
65 }
66 return index;
67 }
68
GetStack(unsigned aIndex) const69 const CombinedStacks::Stack& CombinedStacks::GetStack(unsigned aIndex) const {
70 return mStacks[aIndex];
71 }
72
GetStackCount() const73 size_t CombinedStacks::GetStackCount() const { return mStacks.size(); }
74
SizeOfExcludingThis() const75 size_t CombinedStacks::SizeOfExcludingThis() const {
76 // This is a crude approximation. We would like to do something like
77 // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call
78 // malloc_usable_size which is only safe on the pointers returned by malloc.
79 // While it works on current libstdc++, it is better to be safe and not assume
80 // that &vec[0] points to one. We could use a custom allocator, but
81 // it doesn't seem worth it.
82 size_t n = 0;
83 n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module);
84 n += mStacks.capacity() * sizeof(Stack);
85 for (const auto& s : mStacks) {
86 n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame);
87 }
88 return n;
89 }
90
RemoveStack(unsigned aIndex)91 void CombinedStacks::RemoveStack(unsigned aIndex) {
92 MOZ_ASSERT(aIndex < mStacks.size());
93
94 mStacks.erase(mStacks.begin() + aIndex);
95
96 if (aIndex < mNextIndex) {
97 if (mNextIndex == 0) {
98 mNextIndex = mStacks.size();
99 } else {
100 mNextIndex--;
101 }
102 }
103
104 if (mNextIndex > mStacks.size()) {
105 mNextIndex = mStacks.size();
106 }
107 }
108
GetIsFromTerminatorWatchdog()109 bool CombinedStacks::GetIsFromTerminatorWatchdog() {
110 return mIsFromTerminatorWatchdog;
111 }
112
SetIsFromTerminatorWatchdog(bool aIsFromTerminatorWatchdog)113 void CombinedStacks::SetIsFromTerminatorWatchdog(
114 bool aIsFromTerminatorWatchdog) {
115 mIsFromTerminatorWatchdog = aIsFromTerminatorWatchdog;
116 }
117
Swap(CombinedStacks & aOther)118 void CombinedStacks::Swap(CombinedStacks& aOther) {
119 mModules.swap(aOther.mModules);
120 mStacks.swap(aOther.mStacks);
121
122 size_t nextIndex = aOther.mNextIndex;
123 aOther.mNextIndex = mNextIndex;
124 mNextIndex = nextIndex;
125
126 size_t maxStacksCount = aOther.mMaxStacksCount;
127 aOther.mMaxStacksCount = mMaxStacksCount;
128 mMaxStacksCount = maxStacksCount;
129 }
130
131 #if defined(MOZ_GECKO_PROFILER)
Clear()132 void CombinedStacks::Clear() {
133 mNextIndex = 0;
134 mStacks.clear();
135 mModules.clear();
136 }
137 #endif
138
CreateJSStackObject(JSContext * cx,const CombinedStacks & stacks)139 JSObject* CreateJSStackObject(JSContext* cx, const CombinedStacks& stacks) {
140 JS::Rooted<JSObject*> ret(cx, JS_NewPlainObject(cx));
141 if (!ret) {
142 return nullptr;
143 }
144
145 JS::Rooted<JSObject*> moduleArray(cx, JS::NewArrayObject(cx, 0));
146 if (!moduleArray) {
147 return nullptr;
148 }
149 bool ok =
150 JS_DefineProperty(cx, ret, "memoryMap", moduleArray, JSPROP_ENUMERATE);
151 if (!ok) {
152 return nullptr;
153 }
154
155 const size_t moduleCount = stacks.GetModuleCount();
156 for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
157 // Current module
158 const Telemetry::ProcessedStack::Module& module =
159 stacks.GetModule(moduleIndex);
160
161 JS::Rooted<JSObject*> moduleInfoArray(cx, JS::NewArrayObject(cx, 0));
162 if (!moduleInfoArray) {
163 return nullptr;
164 }
165 if (!JS_DefineElement(cx, moduleArray, moduleIndex, moduleInfoArray,
166 JSPROP_ENUMERATE)) {
167 return nullptr;
168 }
169
170 unsigned index = 0;
171
172 // Module name
173 JS::Rooted<JSString*> str(cx, JS_NewUCStringCopyZ(cx, module.mName.get()));
174 if (!str || !JS_DefineElement(cx, moduleInfoArray, index++, str,
175 JSPROP_ENUMERATE)) {
176 return nullptr;
177 }
178
179 // Module breakpad identifier
180 JS::Rooted<JSString*> id(cx,
181 JS_NewStringCopyZ(cx, module.mBreakpadId.get()));
182 if (!id ||
183 !JS_DefineElement(cx, moduleInfoArray, index, id, JSPROP_ENUMERATE)) {
184 return nullptr;
185 }
186 }
187
188 JS::Rooted<JSObject*> reportArray(cx, JS::NewArrayObject(cx, 0));
189 if (!reportArray) {
190 return nullptr;
191 }
192 ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE);
193 if (!ok) {
194 return nullptr;
195 }
196
197 const size_t length = stacks.GetStackCount();
198 for (size_t i = 0; i < length; ++i) {
199 // Represent call stack PCs as (module index, offset) pairs.
200 JS::Rooted<JSObject*> pcArray(cx, JS::NewArrayObject(cx, 0));
201 if (!pcArray) {
202 return nullptr;
203 }
204
205 if (!JS_DefineElement(cx, reportArray, i, pcArray, JSPROP_ENUMERATE)) {
206 return nullptr;
207 }
208
209 const CombinedStacks::Stack& stack = stacks.GetStack(i);
210 const uint32_t pcCount = stack.size();
211 for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
212 const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex];
213 JS::Rooted<JSObject*> framePair(cx, JS::NewArrayObject(cx, 0));
214 if (!framePair) {
215 return nullptr;
216 }
217 int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex)
218 ? -1
219 : frame.mModIndex;
220 if (!JS_DefineElement(cx, framePair, 0, modIndex, JSPROP_ENUMERATE)) {
221 return nullptr;
222 }
223 if (!JS_DefineElement(cx, framePair, 1,
224 static_cast<double>(frame.mOffset),
225 JSPROP_ENUMERATE)) {
226 return nullptr;
227 }
228 if (!JS_DefineElement(cx, pcArray, pcIndex, framePair,
229 JSPROP_ENUMERATE)) {
230 return nullptr;
231 }
232 }
233 }
234
235 return ret;
236 }
237
238 } // namespace mozilla::Telemetry
239