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