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