Bug 1941046 - Part 4: Send a callback request for impression and clicks of MARS Top...
[gecko.git] / toolkit / components / telemetry / other / CombinedStacks.cpp
blob61248f9b405ccb52c34b38d67af7d4114dcd0532
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/. */
7 #include "CombinedStacks.h"
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"
14 namespace mozilla::Telemetry {
16 // The maximum number of chrome hangs stacks that we're keeping.
17 const size_t kMaxChromeStacksKept = 50;
19 CombinedStacks::CombinedStacks() : CombinedStacks(kMaxChromeStacksKept) {}
21 CombinedStacks::CombinedStacks(size_t aMaxStacksCount)
22 : mNextIndex(0), mMaxStacksCount(aMaxStacksCount) {}
24 size_t CombinedStacks::GetMaxStacksCount() const { return mMaxStacksCount; }
25 size_t CombinedStacks::GetModuleCount() const { return mModules.size(); }
27 const Telemetry::ProcessedStack::Module& CombinedStacks::GetModule(
28 unsigned aIndex) const {
29 return mModules[aIndex];
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();
48 mStacks[aStackIndex].push_back(
49 ProcessedStack::Frame{aFrame.mOffset, modIndex});
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);
61 // Clear the old stack before set.
62 mStacks[index].clear();
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 });
73 return index;
76 void CombinedStacks::AddStacks(const CombinedStacks& aStacks) {
77 mStacks.resize(
78 std::min(mStacks.size() + aStacks.GetStackCount(), mMaxStacksCount));
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;
85 // Clear the old stack before set.
86 mStacks[index].clear();
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 });
99 const CombinedStacks::Stack& CombinedStacks::GetStack(unsigned aIndex) const {
100 return mStacks[aIndex];
103 size_t CombinedStacks::GetStackCount() const { return mStacks.size(); }
105 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);
118 return n;
121 void CombinedStacks::RemoveStack(unsigned aIndex) {
122 MOZ_ASSERT(aIndex < mStacks.size());
124 mStacks.erase(mStacks.begin() + aIndex);
126 if (aIndex < mNextIndex) {
127 if (mNextIndex == 0) {
128 mNextIndex = mStacks.size();
129 } else {
130 mNextIndex--;
134 if (mNextIndex > mStacks.size()) {
135 mNextIndex = mStacks.size();
139 void CombinedStacks::Swap(CombinedStacks& aOther) {
140 mModules.swap(aOther.mModules);
141 mStacks.swap(aOther.mStacks);
143 size_t nextIndex = aOther.mNextIndex;
144 aOther.mNextIndex = mNextIndex;
145 mNextIndex = nextIndex;
147 size_t maxStacksCount = aOther.mMaxStacksCount;
148 aOther.mMaxStacksCount = mMaxStacksCount;
149 mMaxStacksCount = maxStacksCount;
152 void CombinedStacks::Clear() {
153 mNextIndex = 0;
154 mStacks.clear();
155 mModules.clear();
158 JSObject* CreateJSStackObject(JSContext* cx, const CombinedStacks& stacks) {
159 JS::Rooted<JSObject*> ret(cx, JS_NewPlainObject(cx));
160 if (!ret) {
161 return nullptr;
164 JS::Rooted<JSObject*> moduleArray(cx, JS::NewArrayObject(cx, 0));
165 if (!moduleArray) {
166 return nullptr;
168 bool ok =
169 JS_DefineProperty(cx, ret, "memoryMap", moduleArray, JSPROP_ENUMERATE);
170 if (!ok) {
171 return nullptr;
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);
180 JS::Rooted<JSObject*> moduleInfoArray(cx, JS::NewArrayObject(cx, 0));
181 if (!moduleInfoArray) {
182 return nullptr;
184 if (!JS_DefineElement(cx, moduleArray, moduleIndex, moduleInfoArray,
185 JSPROP_ENUMERATE)) {
186 return nullptr;
189 unsigned index = 0;
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;
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;
207 JS::Rooted<JSObject*> reportArray(cx, JS::NewArrayObject(cx, 0));
208 if (!reportArray) {
209 return nullptr;
211 ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE);
212 if (!ok) {
213 return nullptr;
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;
224 if (!JS_DefineElement(cx, reportArray, i, pcArray, JSPROP_ENUMERATE)) {
225 return nullptr;
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;
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;
242 if (!JS_DefineElement(cx, framePair, 1,
243 static_cast<double>(frame.mOffset),
244 JSPROP_ENUMERATE)) {
245 return nullptr;
247 if (!JS_DefineElement(cx, pcArray, pcIndex, framePair,
248 JSPROP_ENUMERATE)) {
249 return nullptr;
254 return ret;
257 } // namespace mozilla::Telemetry