summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/other/CombinedStacks.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/telemetry/other/CombinedStacks.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/telemetry/other/CombinedStacks.cpp')
-rw-r--r--toolkit/components/telemetry/other/CombinedStacks.cpp257
1 files changed, 257 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/other/CombinedStacks.cpp b/toolkit/components/telemetry/other/CombinedStacks.cpp
new file mode 100644
index 0000000000..61248f9b40
--- /dev/null
+++ b/toolkit/components/telemetry/other/CombinedStacks.cpp
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CombinedStacks.h"
+
+#include "jsapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
+#include "js/String.h"
+
+namespace mozilla::Telemetry {
+
+// The maximum number of chrome hangs stacks that we're keeping.
+const size_t kMaxChromeStacksKept = 50;
+
+CombinedStacks::CombinedStacks() : CombinedStacks(kMaxChromeStacksKept) {}
+
+CombinedStacks::CombinedStacks(size_t aMaxStacksCount)
+ : mNextIndex(0), mMaxStacksCount(aMaxStacksCount) {}
+
+size_t CombinedStacks::GetMaxStacksCount() const { return mMaxStacksCount; }
+size_t CombinedStacks::GetModuleCount() const { return mModules.size(); }
+
+const Telemetry::ProcessedStack::Module& CombinedStacks::GetModule(
+ unsigned aIndex) const {
+ return mModules[aIndex];
+}
+
+void CombinedStacks::AddFrame(
+ size_t aStackIndex, const ProcessedStack::Frame& aFrame,
+ const std::function<const ProcessedStack::Module&(int)>& aModuleGetter) {
+ uint16_t modIndex;
+ if (aFrame.mModIndex == std::numeric_limits<uint16_t>::max()) {
+ modIndex = aFrame.mModIndex;
+ } else {
+ const ProcessedStack::Module& module = aModuleGetter(aFrame.mModIndex);
+ auto modIterator = std::find(mModules.begin(), mModules.end(), module);
+ if (modIterator == mModules.end()) {
+ mModules.push_back(module);
+ modIndex = mModules.size() - 1;
+ } else {
+ modIndex = modIterator - mModules.begin();
+ }
+ }
+ mStacks[aStackIndex].push_back(
+ ProcessedStack::Frame{aFrame.mOffset, modIndex});
+}
+
+size_t CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
+ size_t index = mNextIndex;
+ // Advance the indices of the circular queue holding the stacks.
+ mNextIndex = (mNextIndex + 1) % mMaxStacksCount;
+ // Grow the vector up to the maximum size, if needed.
+ if (mStacks.size() < mMaxStacksCount) {
+ mStacks.resize(mStacks.size() + 1);
+ }
+
+ // Clear the old stack before set.
+ mStacks[index].clear();
+
+ size_t stackSize = aStack.GetStackSize();
+ for (size_t i = 0; i < stackSize; ++i) {
+ // Need to specify a return type in the following lambda,
+ // otherwise it's incorrectly deduced to be a non-reference type.
+ AddFrame(index, aStack.GetFrame(i),
+ [&aStack](int aIdx) -> const ProcessedStack::Module& {
+ return aStack.GetModule(aIdx);
+ });
+ }
+ return index;
+}
+
+void CombinedStacks::AddStacks(const CombinedStacks& aStacks) {
+ mStacks.resize(
+ std::min(mStacks.size() + aStacks.GetStackCount(), mMaxStacksCount));
+
+ for (const auto& stack : aStacks.mStacks) {
+ size_t index = mNextIndex;
+ // Advance the indices of the circular queue holding the stacks.
+ mNextIndex = (mNextIndex + 1) % mMaxStacksCount;
+
+ // Clear the old stack before set.
+ mStacks[index].clear();
+
+ for (const auto& frame : stack) {
+ // Need to specify a return type in the following lambda,
+ // otherwise it's incorrectly deduced to be a non-reference type.
+ AddFrame(index, frame,
+ [&aStacks](int aIdx) -> const ProcessedStack::Module& {
+ return aStacks.mModules[aIdx];
+ });
+ }
+ }
+}
+
+const CombinedStacks::Stack& CombinedStacks::GetStack(unsigned aIndex) const {
+ return mStacks[aIndex];
+}
+
+size_t CombinedStacks::GetStackCount() const { return mStacks.size(); }
+
+size_t CombinedStacks::SizeOfExcludingThis() const {
+ // This is a crude approximation. We would like to do something like
+ // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call
+ // malloc_usable_size which is only safe on the pointers returned by malloc.
+ // While it works on current libstdc++, it is better to be safe and not assume
+ // that &vec[0] points to one. We could use a custom allocator, but
+ // it doesn't seem worth it.
+ size_t n = 0;
+ n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module);
+ n += mStacks.capacity() * sizeof(Stack);
+ for (const auto& s : mStacks) {
+ n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame);
+ }
+ return n;
+}
+
+void CombinedStacks::RemoveStack(unsigned aIndex) {
+ MOZ_ASSERT(aIndex < mStacks.size());
+
+ mStacks.erase(mStacks.begin() + aIndex);
+
+ if (aIndex < mNextIndex) {
+ if (mNextIndex == 0) {
+ mNextIndex = mStacks.size();
+ } else {
+ mNextIndex--;
+ }
+ }
+
+ if (mNextIndex > mStacks.size()) {
+ mNextIndex = mStacks.size();
+ }
+}
+
+void CombinedStacks::Swap(CombinedStacks& aOther) {
+ mModules.swap(aOther.mModules);
+ mStacks.swap(aOther.mStacks);
+
+ size_t nextIndex = aOther.mNextIndex;
+ aOther.mNextIndex = mNextIndex;
+ mNextIndex = nextIndex;
+
+ size_t maxStacksCount = aOther.mMaxStacksCount;
+ aOther.mMaxStacksCount = mMaxStacksCount;
+ mMaxStacksCount = maxStacksCount;
+}
+
+void CombinedStacks::Clear() {
+ mNextIndex = 0;
+ mStacks.clear();
+ mModules.clear();
+}
+
+JSObject* CreateJSStackObject(JSContext* cx, const CombinedStacks& stacks) {
+ JS::Rooted<JSObject*> ret(cx, JS_NewPlainObject(cx));
+ if (!ret) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> moduleArray(cx, JS::NewArrayObject(cx, 0));
+ if (!moduleArray) {
+ return nullptr;
+ }
+ bool ok =
+ JS_DefineProperty(cx, ret, "memoryMap", moduleArray, JSPROP_ENUMERATE);
+ if (!ok) {
+ return nullptr;
+ }
+
+ const size_t moduleCount = stacks.GetModuleCount();
+ for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
+ // Current module
+ const Telemetry::ProcessedStack::Module& module =
+ stacks.GetModule(moduleIndex);
+
+ JS::Rooted<JSObject*> moduleInfoArray(cx, JS::NewArrayObject(cx, 0));
+ if (!moduleInfoArray) {
+ return nullptr;
+ }
+ if (!JS_DefineElement(cx, moduleArray, moduleIndex, moduleInfoArray,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+
+ unsigned index = 0;
+
+ // Module name
+ JS::Rooted<JSString*> str(cx, JS_NewUCStringCopyZ(cx, module.mName.get()));
+ if (!str || !JS_DefineElement(cx, moduleInfoArray, index++, str,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+
+ // Module breakpad identifier
+ JS::Rooted<JSString*> id(cx,
+ JS_NewStringCopyZ(cx, module.mBreakpadId.get()));
+ if (!id ||
+ !JS_DefineElement(cx, moduleInfoArray, index, id, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ JS::Rooted<JSObject*> reportArray(cx, JS::NewArrayObject(cx, 0));
+ if (!reportArray) {
+ return nullptr;
+ }
+ ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE);
+ if (!ok) {
+ return nullptr;
+ }
+
+ const size_t length = stacks.GetStackCount();
+ for (size_t i = 0; i < length; ++i) {
+ // Represent call stack PCs as (module index, offset) pairs.
+ JS::Rooted<JSObject*> pcArray(cx, JS::NewArrayObject(cx, 0));
+ if (!pcArray) {
+ return nullptr;
+ }
+
+ if (!JS_DefineElement(cx, reportArray, i, pcArray, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+
+ const CombinedStacks::Stack& stack = stacks.GetStack(i);
+ const uint32_t pcCount = stack.size();
+ for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
+ const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex];
+ JS::Rooted<JSObject*> framePair(cx, JS::NewArrayObject(cx, 0));
+ if (!framePair) {
+ return nullptr;
+ }
+ int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex)
+ ? -1
+ : frame.mModIndex;
+ if (!JS_DefineElement(cx, framePair, 0, modIndex, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ if (!JS_DefineElement(cx, framePair, 1,
+ static_cast<double>(frame.mOffset),
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ if (!JS_DefineElement(cx, pcArray, pcIndex, framePair,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+ }
+
+ return ret;
+}
+
+} // namespace mozilla::Telemetry