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