new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/CombinedStacks.cpp
@@ -0,0 +1,205 @@
+/* -*- 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 "HangAnnotations.h"
+#include "mozilla/HangAnnotations.h"
+#include "jsapi.h"
+
+namespace mozilla {
+namespace 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::GetModuleCount() const {
+ return mModules.size();
+}
+
+const Telemetry::ProcessedStack::Module&
+CombinedStacks::GetModule(unsigned aIndex) const {
+ return mModules[aIndex];
+}
+
+size_t
+CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
+ // Advance the indices of the circular queue holding the stacks.
+ size_t index = mNextIndex++ % mMaxStacksCount;
+ // Grow the vector up to the maximum size, if needed.
+ if (mStacks.size() < mMaxStacksCount) {
+ mStacks.resize(mStacks.size() + 1);
+ }
+ // Get a reference to the location holding the new stack.
+ CombinedStacks::Stack& adjustedStack = mStacks[index];
+ // If we're using an old stack to hold aStack, clear it.
+ adjustedStack.clear();
+
+ size_t stackSize = aStack.GetStackSize();
+ for (size_t i = 0; i < stackSize; ++i) {
+ const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i);
+ uint16_t modIndex;
+ if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) {
+ modIndex = frame.mModIndex;
+ } else {
+ const Telemetry::ProcessedStack::Module& module =
+ aStack.GetModule(frame.mModIndex);
+ std::vector<Telemetry::ProcessedStack::Module>::iterator 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();
+ }
+ }
+ Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex };
+ adjustedStack.push_back(adjustedFrame);
+ }
+ return index;
+}
+
+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;
+}
+
+#if defined(MOZ_GECKO_PROFILER)
+void
+CombinedStacks::Clear() {
+ mNextIndex = 0;
+ mStacks.clear();
+ mModules.clear();
+}
+#endif
+
+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.c_str()));
+ 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 Telemetry
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/CombinedStacks.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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/. */
+
+#ifndef CombinedStacks_h__
+#define CombinedStacks_h__
+
+#include <vector>
+
+#include "ProcessedStack.h"
+
+class JSObject;
+struct JSContext;
+
+namespace mozilla {
+namespace Telemetry {
+
+/**
+ * This class is conceptually a list of ProcessedStack objects, but it represents them
+ * more efficiently by keeping a single global list of modules.
+ */
+class CombinedStacks {
+public:
+ explicit CombinedStacks();
+ explicit CombinedStacks(size_t aMaxStacksCount);
+
+ typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
+ const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
+ size_t GetModuleCount() const;
+ const Stack& GetStack(unsigned aIndex) const;
+ size_t AddStack(const Telemetry::ProcessedStack& aStack);
+ size_t GetStackCount() const;
+ size_t SizeOfExcludingThis() const;
+
+#if defined(MOZ_GECKO_PROFILER)
+ /** Clears the contents of vectors and resets the index. */
+ void Clear();
+#endif
+
+private:
+ std::vector<Telemetry::ProcessedStack::Module> mModules;
+ // A circular buffer to hold the stacks.
+ std::vector<Stack> mStacks;
+ // The index of the next buffer element to write to in mStacks.
+ size_t mNextIndex;
+ // The maximum number of stacks to keep in the CombinedStacks object.
+ size_t mMaxStacksCount;
+};
+
+/**
+ * Creates a JSON representation of given combined stacks object.
+ */
+JSObject *
+CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks);
+
+} // namespace Telemetry
+} // namespace mozilla
+
+#endif // CombinedStacks_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/HangReports.cpp
@@ -0,0 +1,156 @@
+/* -*- 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 "HangReports.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+using namespace HangMonitor;
+
+/** The maximum number of stacks that we're keeping for hang reports. */
+const size_t kMaxHangStacksKept = 50;
+
+// This utility function generates a string key that is used to index the annotations
+// in a hash map from |HangReports::AddHang|.
+nsresult
+ComputeAnnotationsKey(const HangAnnotationsPtr& aAnnotations, nsAString& aKeyOut)
+{
+ UniquePtr<HangAnnotations::Enumerator> annotationsEnum = aAnnotations->GetEnumerator();
+ if (!annotationsEnum) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Append all the attributes to the key, to uniquely identify this annotation.
+ nsAutoString key;
+ nsAutoString value;
+ while (annotationsEnum->Next(key, value)) {
+ aKeyOut.Append(key);
+ aKeyOut.Append(value);
+ }
+
+ return NS_OK;
+}
+
+#if defined(MOZ_GECKO_PROFILER)
+void
+HangReports::AddHang(const Telemetry::ProcessedStack& aStack,
+ uint32_t aDuration,
+ int32_t aSystemUptime,
+ int32_t aFirefoxUptime,
+ HangAnnotationsPtr aAnnotations) {
+ // Append the new stack to the stack's circular queue.
+ size_t hangIndex = mStacks.AddStack(aStack);
+ // Append the hang info at the same index, in mHangInfo.
+ HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime };
+ if (mHangInfo.size() < kMaxHangStacksKept) {
+ mHangInfo.push_back(info);
+ } else {
+ mHangInfo[hangIndex] = info;
+ // Remove any reference to the stack overwritten in the circular queue
+ // from the annotations.
+ PruneStackReferences(hangIndex);
+ }
+
+ if (!aAnnotations) {
+ return;
+ }
+
+ nsAutoString annotationsKey;
+ // Generate a key to index aAnnotations in the hash map.
+ nsresult rv = ComputeAnnotationsKey(aAnnotations, annotationsKey);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ AnnotationInfo* annotationsEntry = mAnnotationInfo.Get(annotationsKey);
+ if (annotationsEntry) {
+ // If the key is already in the hash map, append the index of the chrome hang
+ // to its indices.
+ annotationsEntry->mHangIndices.AppendElement(hangIndex);
+ return;
+ }
+
+ // If the key was not found, add the annotations to the hash map.
+ mAnnotationInfo.Put(annotationsKey, new AnnotationInfo(hangIndex, Move(aAnnotations)));
+}
+
+/**
+ * This function removes links to discarded chrome hangs stacks and prunes unused
+ * annotations.
+ */
+void
+HangReports::PruneStackReferences(const size_t aRemovedStackIndex) {
+ // We need to adjust the indices that link annotations to chrome hangs. Since we
+ // removed a stack, we must remove all references to it and prune annotations
+ // linked to no stacks.
+ for (auto iter = mAnnotationInfo.Iter(); !iter.Done(); iter.Next()) {
+ nsTArray<uint32_t>& stackIndices = iter.Data()->mHangIndices;
+ size_t toRemove = stackIndices.NoIndex;
+ for (size_t k = 0; k < stackIndices.Length(); k++) {
+ // Is this index referencing the removed stack?
+ if (stackIndices[k] == aRemovedStackIndex) {
+ toRemove = k;
+ break;
+ }
+ }
+
+ // Remove the index referencing the old stack from the annotation.
+ if (toRemove != stackIndices.NoIndex) {
+ stackIndices.RemoveElementAt(toRemove);
+ }
+
+ // If this annotation no longer references any stack, drop it.
+ if (!stackIndices.Length()) {
+ iter.Remove();
+ }
+ }
+}
+#endif
+
+size_t
+HangReports::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ n += mStacks.SizeOfExcludingThis();
+ // This is a crude approximation. See comment on
+ // CombinedStacks::SizeOfExcludingThis.
+ n += mHangInfo.capacity() * sizeof(HangInfo);
+ n += mAnnotationInfo.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mAnnotationInfo.Count() * sizeof(AnnotationInfo);
+ for (auto iter = mAnnotationInfo.ConstIter(); !iter.Done(); iter.Next()) {
+ n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += iter.Data()->mAnnotations->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+const CombinedStacks&
+HangReports::GetStacks() const {
+ return mStacks;
+}
+
+uint32_t
+HangReports::GetDuration(unsigned aIndex) const {
+ return mHangInfo[aIndex].mDuration;
+}
+
+int32_t
+HangReports::GetSystemUptime(unsigned aIndex) const {
+ return mHangInfo[aIndex].mSystemUptime;
+}
+
+int32_t
+HangReports::GetFirefoxUptime(unsigned aIndex) const {
+ return mHangInfo[aIndex].mFirefoxUptime;
+}
+
+const nsClassHashtable<nsStringHashKey, HangReports::AnnotationInfo>&
+HangReports::GetAnnotationInfo() const {
+ return mAnnotationInfo;
+}
+
+} // namespace Telemetry
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/HangReports.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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/. */
+
+#ifndef HangReports_h__
+#define HangReports_h__
+
+#include <vector>
+#include "mozilla/HangAnnotations.h"
+#include "ProcessedStack.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsClassHashtable.h"
+#include "CombinedStacks.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+nsresult
+ComputeAnnotationsKey(const HangMonitor::HangAnnotationsPtr& aAnnotations, nsAString& aKeyOut);
+
+class HangReports {
+public:
+ /**
+ * This struct encapsulates information for an individual ChromeHang annotation.
+ * mHangIndex is the index of the corresponding ChromeHang.
+ */
+ struct AnnotationInfo {
+ AnnotationInfo(uint32_t aHangIndex,
+ HangMonitor::HangAnnotationsPtr aAnnotations)
+ : mAnnotations(Move(aAnnotations))
+ {
+ mHangIndices.AppendElement(aHangIndex);
+ }
+ AnnotationInfo(AnnotationInfo&& aOther)
+ : mHangIndices(aOther.mHangIndices)
+ , mAnnotations(Move(aOther.mAnnotations))
+ {}
+ ~AnnotationInfo() = default;
+ AnnotationInfo& operator=(AnnotationInfo&& aOther)
+ {
+ mHangIndices = aOther.mHangIndices;
+ mAnnotations = Move(aOther.mAnnotations);
+ return *this;
+ }
+ // To save memory, a single AnnotationInfo can be associated to multiple chrome
+ // hangs. The following array holds the index of each related chrome hang.
+ nsTArray<uint32_t> mHangIndices;
+ HangMonitor::HangAnnotationsPtr mAnnotations;
+
+ private:
+ // Force move constructor
+ AnnotationInfo(const AnnotationInfo& aOther) = delete;
+ void operator=(const AnnotationInfo& aOther) = delete;
+ };
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+#if defined(MOZ_GECKO_PROFILER)
+ void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration,
+ int32_t aSystemUptime, int32_t aFirefoxUptime,
+ HangMonitor::HangAnnotationsPtr aAnnotations);
+ void PruneStackReferences(const size_t aRemovedStackIndex);
+#endif
+ uint32_t GetDuration(unsigned aIndex) const;
+ int32_t GetSystemUptime(unsigned aIndex) const;
+ int32_t GetFirefoxUptime(unsigned aIndex) const;
+ const nsClassHashtable<nsStringHashKey, AnnotationInfo>& GetAnnotationInfo() const;
+ const CombinedStacks& GetStacks() const;
+private:
+ /**
+ * This struct encapsulates the data for an individual ChromeHang, excluding
+ * annotations.
+ */
+ struct HangInfo {
+ // Hang duration (in seconds)
+ uint32_t mDuration;
+ // System uptime (in minutes) at the time of the hang
+ int32_t mSystemUptime;
+ // Firefox uptime (in minutes) at the time of the hang
+ int32_t mFirefoxUptime;
+ };
+ std::vector<HangInfo> mHangInfo;
+ nsClassHashtable<nsStringHashKey, AnnotationInfo> mAnnotationInfo;
+ CombinedStacks mStacks;
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+#endif // CombinedStacks_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/KeyedStackCapturer.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "KeyedStackCapturer.h"
+#include "nsPrintfCString.h"
+#include "mozilla/StackWalk.h"
+#include "ProcessedStack.h"
+#include "jsapi.h"
+
+namespace {
+
+#if defined(MOZ_GECKO_PROFILER)
+
+/** Defines the size of the keyed stack dictionary. */
+const uint8_t kMaxKeyLength = 50;
+
+/** The maximum number of captured stacks that we're keeping. */
+const size_t kMaxCapturedStacksKept = 50;
+
+/**
+ * Checks if a single character of the key string is valid.
+ *
+ * @param aChar a character to validate.
+ * @return True, if the char is valid, False - otherwise.
+ */
+bool
+IsKeyCharValid(const char aChar)
+{
+ return (aChar >= 'A' && aChar <= 'Z')
+ || (aChar >= 'a' && aChar <= 'z')
+ || (aChar >= '0' && aChar <= '9')
+ || aChar == '-';
+}
+
+/**
+ * Checks if a given string is a valid telemetry key.
+ *
+ * @param aKey is the key string.
+ * @return True, if the key is valid, False - otherwise.
+ */
+bool
+IsKeyValid(const nsACString& aKey)
+{
+ // Check key length.
+ if (aKey.Length() > kMaxKeyLength) {
+ return false;
+ }
+
+ // Check key characters.
+ const char* cur = aKey.BeginReading();
+ const char* end = aKey.EndReading();
+
+ for (; cur < end; ++cur) {
+ if (!IsKeyCharValid(*cur)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace Telemetry {
+
+void KeyedStackCapturer::Capture(const nsACString& aKey) {
+ MutexAutoLock captureStackMutex(mStackCapturerMutex);
+
+ // Check if the key is ok.
+ if (!IsKeyValid(aKey)) {
+ NS_WARNING(nsPrintfCString(
+ "Invalid key is used to capture stack in telemetry: '%s'",
+ PromiseFlatCString(aKey).get()
+ ).get());
+ return;
+ }
+
+ // Trying to find and update the stack information.
+ StackFrequencyInfo* info = mStackInfos.Get(aKey);
+ if (info) {
+ // We already recorded this stack before, only increase the count.
+ info->mCount++;
+ return;
+ }
+
+ // Check if we have room for new captures.
+ if (mStackInfos.Count() >= kMaxCapturedStacksKept) {
+ // Addressed by Bug 1316793.
+ return;
+ }
+
+ // We haven't captured a stack for this key before, do it now.
+ // Note that this does a stackwalk and is an expensive operation.
+ std::vector<uintptr_t> rawStack;
+ auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
+ std::vector<uintptr_t>* stack =
+ static_cast<std::vector<uintptr_t>*>(aClosure);
+ stack->push_back(reinterpret_cast<uintptr_t>(aPC));
+ };
+ MozStackWalk(callback, /* skipFrames */ 0,
+ /* maxFrames */ 0, reinterpret_cast<void*>(&rawStack), 0, nullptr);
+ ProcessedStack stack = GetStackAndModules(rawStack);
+
+ // Store the new stack info.
+ size_t stackIndex = mStacks.AddStack(stack);
+ mStackInfos.Put(aKey, new StackFrequencyInfo(1, stackIndex));
+}
+
+NS_IMETHODIMP
+KeyedStackCapturer::ReflectCapturedStacks(JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+ MutexAutoLock capturedStackMutex(mStackCapturerMutex);
+
+ // this adds the memoryMap and stacks properties.
+ JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, mStacks));
+ if (!fullReportObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::RootedObject keysArray(cx, JS_NewArrayObject(cx, 0));
+ if (!keysArray) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool ok = JS_DefineProperty(cx, fullReportObj, "captures",
+ keysArray, JSPROP_ENUMERATE);
+ if (!ok) {
+ return NS_ERROR_FAILURE;
+ }
+
+ size_t keyIndex = 0;
+ for (auto iter = mStackInfos.ConstIter(); !iter.Done(); iter.Next(), ++keyIndex) {
+ const StackFrequencyInfo* info = iter.Data();
+
+ JS::RootedObject infoArray(cx, JS_NewArrayObject(cx, 0));
+ if (!keysArray) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::RootedString str(cx, JS_NewStringCopyZ(cx,
+ PromiseFlatCString(iter.Key()).get()));
+ if (!str ||
+ !JS_DefineElement(cx, infoArray, 0, str, JSPROP_ENUMERATE) ||
+ !JS_DefineElement(cx, infoArray, 1, info->mIndex, JSPROP_ENUMERATE) ||
+ !JS_DefineElement(cx, infoArray, 2, info->mCount, JSPROP_ENUMERATE) ||
+ !JS_DefineElement(cx, keysArray, keyIndex, infoArray, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ ret.setObject(*fullReportObj);
+ return NS_OK;
+}
+
+void
+KeyedStackCapturer::Clear()
+{
+ MutexAutoLock captureStackMutex(mStackCapturerMutex);
+ mStackInfos.Clear();
+ mStacks.Clear();
+}
+#endif
+
+} // namespace Telemetry
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/KeyedStackCapturer.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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/. */
+
+#ifndef KeyedStackCapturer_h__
+#define KeyedStackCapturer_h__
+
+#ifdef MOZ_GECKO_PROFILER
+
+#include "Telemetry.h"
+#include "nsString.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Mutex.h"
+#include "CombinedStacks.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace Telemetry {
+
+/**
+* Allows taking a snapshot of a call stack on demand. Captured stacks are
+* indexed by a string key in a hash table. The stack is only captured Once
+* for each key. Consequent captures with the same key result in incrementing
+* capture counter without re-capturing the stack.
+*/
+class KeyedStackCapturer
+{
+public:
+ KeyedStackCapturer()
+ : mStackCapturerMutex("Telemetry::StackCapturerMutex")
+ {}
+
+ /**
+ * Captures a stack for the given key.
+ */
+ void Capture(const nsACString& aKey);
+
+ /**
+ * Transforms captured stacks into a JS object.
+ */
+ NS_IMETHODIMP ReflectCapturedStacks(
+ JSContext *cx, JS::MutableHandle<JS::Value> ret);
+
+ /**
+ * Resets captured stacks and the information related to them.
+ */
+ void Clear();
+
+private:
+ /**
+ * Describes how often a stack was captured.
+ */
+ struct StackFrequencyInfo {
+ // A number of times the stack was captured.
+ uint32_t mCount;
+ // Index of the stack inside stacks array.
+ uint32_t mIndex;
+
+ StackFrequencyInfo(uint32_t aCount, uint32_t aIndex)
+ : mCount(aCount)
+ , mIndex(aIndex)
+ {}
+ };
+
+ typedef nsClassHashtable<nsCStringHashKey, StackFrequencyInfo> FrequencyInfoMapType;
+
+ FrequencyInfoMapType mStackInfos;
+ CombinedStacks mStacks;
+ Mutex mStackCapturerMutex;
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+#endif // MOZ_GECKO_PROFILER
+
+#endif // KeyedStackCapturer_h__
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -79,541 +79,34 @@
#include "mozilla/Preferences.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/PoisonIOInterposer.h"
#include "mozilla/StartupTimeline.h"
#include "mozilla/HangMonitor.h"
#include "nsNativeCharsetUtils.h"
#include "nsProxyRelease.h"
+#include "HangReports.h"
#if defined(MOZ_GECKO_PROFILER)
#include "shared-libraries.h"
-#define ENABLE_STACK_CAPTURE
-#include "mozilla/StackWalk.h"
-#include "nsPrintfCString.h"
+#include "KeyedStackCapturer.h"
#endif // MOZ_GECKO_PROFILER
namespace {
using namespace mozilla;
using namespace mozilla::HangMonitor;
using Telemetry::Common::AutoHashtable;
using mozilla::dom::Promise;
using mozilla::dom::AutoJSAPI;
-
-// The maximum number of chrome hangs stacks that we're keeping.
-const size_t kMaxChromeStacksKept = 50;
-// The maximum depth of a single chrome hang stack.
-const size_t kMaxChromeStackDepth = 50;
-
-// This class is conceptually a list of ProcessedStack objects, but it represents them
-// more efficiently by keeping a single global list of modules.
-class CombinedStacks {
-public:
- explicit CombinedStacks(size_t aMaxStacksCount = kMaxChromeStacksKept)
- : mNextIndex(0)
- , mMaxStacksCount(aMaxStacksCount)
- {}
-
- typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
- const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
- size_t GetModuleCount() const;
- const Stack& GetStack(unsigned aIndex) const;
- size_t AddStack(const Telemetry::ProcessedStack& aStack);
- size_t GetStackCount() const;
- size_t SizeOfExcludingThis() const;
-
-#if defined(ENABLE_STACK_CAPTURE)
- /** Clears the contents of vectors and resets the index. */
- void Clear();
-#endif
-private:
- std::vector<Telemetry::ProcessedStack::Module> mModules;
- // A circular buffer to hold the stacks.
- std::vector<Stack> mStacks;
- // The index of the next buffer element to write to in mStacks.
- size_t mNextIndex;
- // The maximum number of stacks to keep in the CombinedStacks object.
- size_t mMaxStacksCount;
-};
-
-static JSObject *
-CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks);
-
-size_t
-CombinedStacks::GetModuleCount() const {
- return mModules.size();
-}
-
-const Telemetry::ProcessedStack::Module&
-CombinedStacks::GetModule(unsigned aIndex) const {
- return mModules[aIndex];
-}
-
-size_t
-CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
- // Advance the indices of the circular queue holding the stacks.
- size_t index = mNextIndex++ % mMaxStacksCount;
- // Grow the vector up to the maximum size, if needed.
- if (mStacks.size() < mMaxStacksCount) {
- mStacks.resize(mStacks.size() + 1);
- }
- // Get a reference to the location holding the new stack.
- CombinedStacks::Stack& adjustedStack = mStacks[index];
- // If we're using an old stack to hold aStack, clear it.
- adjustedStack.clear();
-
- size_t stackSize = aStack.GetStackSize();
- for (size_t i = 0; i < stackSize; ++i) {
- const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i);
- uint16_t modIndex;
- if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) {
- modIndex = frame.mModIndex;
- } else {
- const Telemetry::ProcessedStack::Module& module =
- aStack.GetModule(frame.mModIndex);
- std::vector<Telemetry::ProcessedStack::Module>::iterator 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();
- }
- }
- Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex };
- adjustedStack.push_back(adjustedFrame);
- }
- return index;
-}
-
-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;
-}
-
-// This utility function generates a string key that is used to index the annotations
-// in a hash map from |HangReports::AddHang|.
-nsresult
-ComputeAnnotationsKey(const HangAnnotationsPtr& aAnnotations, nsAString& aKeyOut)
-{
- UniquePtr<HangAnnotations::Enumerator> annotationsEnum = aAnnotations->GetEnumerator();
- if (!annotationsEnum) {
- return NS_ERROR_FAILURE;
- }
-
- // Append all the attributes to the key, to uniquely identify this annotation.
- nsAutoString key;
- nsAutoString value;
- while (annotationsEnum->Next(key, value)) {
- aKeyOut.Append(key);
- aKeyOut.Append(value);
- }
-
- return NS_OK;
-}
-
-#if defined(ENABLE_STACK_CAPTURE)
-void
-CombinedStacks::Clear() {
- mNextIndex = 0;
- mStacks.clear();
- mModules.clear();
-}
-#endif
-
-class HangReports {
-public:
- /**
- * This struct encapsulates information for an individual ChromeHang annotation.
- * mHangIndex is the index of the corresponding ChromeHang.
- */
- struct AnnotationInfo {
- AnnotationInfo(uint32_t aHangIndex,
- HangAnnotationsPtr aAnnotations)
- : mAnnotations(Move(aAnnotations))
- {
- mHangIndices.AppendElement(aHangIndex);
- }
- AnnotationInfo(AnnotationInfo&& aOther)
- : mHangIndices(aOther.mHangIndices)
- , mAnnotations(Move(aOther.mAnnotations))
- {}
- ~AnnotationInfo() = default;
- AnnotationInfo& operator=(AnnotationInfo&& aOther)
- {
- mHangIndices = aOther.mHangIndices;
- mAnnotations = Move(aOther.mAnnotations);
- return *this;
- }
- // To save memory, a single AnnotationInfo can be associated to multiple chrome
- // hangs. The following array holds the index of each related chrome hang.
- nsTArray<uint32_t> mHangIndices;
- HangAnnotationsPtr mAnnotations;
-
- private:
- // Force move constructor
- AnnotationInfo(const AnnotationInfo& aOther) = delete;
- void operator=(const AnnotationInfo& aOther) = delete;
- };
- size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
-#if defined(MOZ_GECKO_PROFILER)
- void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration,
- int32_t aSystemUptime, int32_t aFirefoxUptime,
- HangAnnotationsPtr aAnnotations);
- void PruneStackReferences(const size_t aRemovedStackIndex);
-#endif
- uint32_t GetDuration(unsigned aIndex) const;
- int32_t GetSystemUptime(unsigned aIndex) const;
- int32_t GetFirefoxUptime(unsigned aIndex) const;
- const nsClassHashtable<nsStringHashKey, AnnotationInfo>& GetAnnotationInfo() const;
- const CombinedStacks& GetStacks() const;
-private:
- /**
- * This struct encapsulates the data for an individual ChromeHang, excluding
- * annotations.
- */
- struct HangInfo {
- // Hang duration (in seconds)
- uint32_t mDuration;
- // System uptime (in minutes) at the time of the hang
- int32_t mSystemUptime;
- // Firefox uptime (in minutes) at the time of the hang
- int32_t mFirefoxUptime;
- };
- std::vector<HangInfo> mHangInfo;
- nsClassHashtable<nsStringHashKey, AnnotationInfo> mAnnotationInfo;
- CombinedStacks mStacks;
-};
-
-#if defined(MOZ_GECKO_PROFILER)
-void
-HangReports::AddHang(const Telemetry::ProcessedStack& aStack,
- uint32_t aDuration,
- int32_t aSystemUptime,
- int32_t aFirefoxUptime,
- HangAnnotationsPtr aAnnotations) {
- // Append the new stack to the stack's circular queue.
- size_t hangIndex = mStacks.AddStack(aStack);
- // Append the hang info at the same index, in mHangInfo.
- HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime };
- if (mHangInfo.size() < kMaxChromeStacksKept) {
- mHangInfo.push_back(info);
- } else {
- mHangInfo[hangIndex] = info;
- // Remove any reference to the stack overwritten in the circular queue
- // from the annotations.
- PruneStackReferences(hangIndex);
- }
-
- if (!aAnnotations) {
- return;
- }
-
- nsAutoString annotationsKey;
- // Generate a key to index aAnnotations in the hash map.
- nsresult rv = ComputeAnnotationsKey(aAnnotations, annotationsKey);
- if (NS_FAILED(rv)) {
- return;
- }
-
- AnnotationInfo* annotationsEntry = mAnnotationInfo.Get(annotationsKey);
- if (annotationsEntry) {
- // If the key is already in the hash map, append the index of the chrome hang
- // to its indices.
- annotationsEntry->mHangIndices.AppendElement(hangIndex);
- return;
- }
-
- // If the key was not found, add the annotations to the hash map.
- mAnnotationInfo.Put(annotationsKey, new AnnotationInfo(hangIndex, Move(aAnnotations)));
-}
-
-/**
- * This function removes links to discarded chrome hangs stacks and prunes unused
- * annotations.
- */
-void
-HangReports::PruneStackReferences(const size_t aRemovedStackIndex) {
- // We need to adjust the indices that link annotations to chrome hangs. Since we
- // removed a stack, we must remove all references to it and prune annotations
- // linked to no stacks.
- for (auto iter = mAnnotationInfo.Iter(); !iter.Done(); iter.Next()) {
- nsTArray<uint32_t>& stackIndices = iter.Data()->mHangIndices;
- size_t toRemove = stackIndices.NoIndex;
- for (size_t k = 0; k < stackIndices.Length(); k++) {
- // Is this index referencing the removed stack?
- if (stackIndices[k] == aRemovedStackIndex) {
- toRemove = k;
- break;
- }
- }
-
- // Remove the index referencing the old stack from the annotation.
- if (toRemove != stackIndices.NoIndex) {
- stackIndices.RemoveElementAt(toRemove);
- }
-
- // If this annotation no longer references any stack, drop it.
- if (!stackIndices.Length()) {
- iter.Remove();
- }
- }
-}
-#endif
-
-size_t
-HangReports::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
- size_t n = 0;
- n += mStacks.SizeOfExcludingThis();
- // This is a crude approximation. See comment on
- // CombinedStacks::SizeOfExcludingThis.
- n += mHangInfo.capacity() * sizeof(HangInfo);
- n += mAnnotationInfo.ShallowSizeOfExcludingThis(aMallocSizeOf);
- n += mAnnotationInfo.Count() * sizeof(AnnotationInfo);
- for (auto iter = mAnnotationInfo.ConstIter(); !iter.Done(); iter.Next()) {
- n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
- n += iter.Data()->mAnnotations->SizeOfIncludingThis(aMallocSizeOf);
- }
- return n;
-}
-
-const CombinedStacks&
-HangReports::GetStacks() const {
- return mStacks;
-}
-
-uint32_t
-HangReports::GetDuration(unsigned aIndex) const {
- return mHangInfo[aIndex].mDuration;
-}
-
-int32_t
-HangReports::GetSystemUptime(unsigned aIndex) const {
- return mHangInfo[aIndex].mSystemUptime;
-}
-
-int32_t
-HangReports::GetFirefoxUptime(unsigned aIndex) const {
- return mHangInfo[aIndex].mFirefoxUptime;
-}
-
-const nsClassHashtable<nsStringHashKey, HangReports::AnnotationInfo>&
-HangReports::GetAnnotationInfo() const {
- return mAnnotationInfo;
-}
-
-#if defined(ENABLE_STACK_CAPTURE)
-
-const uint8_t kMaxKeyLength = 50;
-
-/**
- * Checks if a single character of the key string is valid.
- *
- * @param aChar a character to validate.
- * @return True, if the char is valid, False - otherwise.
- */
-bool
-IsKeyCharValid(const char aChar)
-{
- return (aChar >= 'A' && aChar <= 'Z')
- || (aChar >= 'a' && aChar <= 'z')
- || (aChar >= '0' && aChar <= '9')
- || aChar == '-';
-}
-
-/**
- * Checks if a given string is a valid telemetry key.
- *
- * @param aKey is the key string.
- * @return True, if the key is valid, False - otherwise.
- */
-bool
-IsKeyValid(const nsACString& aKey)
-{
- // Check key length.
- if (aKey.Length() > kMaxKeyLength) {
- return false;
- }
-
- // Check key characters.
- const char* cur = aKey.BeginReading();
- const char* end = aKey.EndReading();
-
- for (; cur < end; ++cur) {
- if (!IsKeyCharValid(*cur)) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * Allows taking a snapshot of a call stack on demand. Captured stacks are
- * indexed by a string key in a hash table. The stack is only captured Once
- * for each key. Consequent captures with the same key result in incrementing
- * capture counter without re-capturing the stack.
- */
-class KeyedStackCapturer {
-public:
- KeyedStackCapturer();
-
- void Capture(const nsACString& aKey);
- NS_IMETHODIMP ReflectCapturedStacks(JSContext *cx, JS::MutableHandle<JS::Value> ret);
-
- /**
- * Resets captured stacks and the information related to them.
- */
- void Clear();
-private:
- /**
- * Describes how often a stack was captured.
- */
- struct StackFrequencyInfo {
- // A number of times the stack was captured.
- uint32_t mCount;
- // Index of the stack inside stacks array.
- uint32_t mIndex;
-
- StackFrequencyInfo(uint32_t aCount, uint32_t aIndex)
- : mCount(aCount)
- , mIndex(aIndex)
- {}
- };
-
- typedef nsClassHashtable<nsCStringHashKey, StackFrequencyInfo> FrequencyInfoMapType;
-
- FrequencyInfoMapType mStackInfos;
- CombinedStacks mStacks;
- Mutex mStackCapturerMutex;
-};
-
-KeyedStackCapturer::KeyedStackCapturer()
- : mStackCapturerMutex("Telemetry::StackCapturerMutex")
-{}
-
-void KeyedStackCapturer::Capture(const nsACString& aKey) {
- MutexAutoLock captureStackMutex(mStackCapturerMutex);
-
- // Check if the key is ok.
- if (!IsKeyValid(aKey)) {
- NS_WARNING(nsPrintfCString(
- "Invalid key is used to capture stack in telemetry: '%s'",
- PromiseFlatCString(aKey).get()
- ).get());
- return;
- }
-
- // Trying to find and update the stack information.
- StackFrequencyInfo* info = mStackInfos.Get(aKey);
- if (info) {
- // We already recorded this stack before, only increase the count.
- info->mCount++;
- return;
- }
-
- // Check if we have room for new captures.
- if (mStackInfos.Count() >= kMaxChromeStacksKept) {
- // Addressed by Bug 1316793.
- return;
- }
-
- // We haven't captured a stack for this key before, do it now.
- // Note that this does a stackwalk and is an expensive operation.
- std::vector<uintptr_t> rawStack;
- auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
- std::vector<uintptr_t>* stack =
- static_cast<std::vector<uintptr_t>*>(aClosure);
- stack->push_back(reinterpret_cast<uintptr_t>(aPC));
- };
- MozStackWalk(callback, /* skipFrames */ 0,
- /* maxFrames */ 0, reinterpret_cast<void*>(&rawStack), 0, nullptr);
- Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
-
- // Store the new stack info.
- size_t stackIndex = mStacks.AddStack(stack);
- mStackInfos.Put(aKey, new StackFrequencyInfo(1, stackIndex));
-}
-
-NS_IMETHODIMP
-KeyedStackCapturer::ReflectCapturedStacks(JSContext *cx, JS::MutableHandle<JS::Value> ret)
-{
- MutexAutoLock capturedStackMutex(mStackCapturerMutex);
-
- // this adds the memoryMap and stacks properties.
- JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, mStacks));
- if (!fullReportObj) {
- return NS_ERROR_FAILURE;
- }
-
- JS::RootedObject keysArray(cx, JS_NewArrayObject(cx, 0));
- if (!keysArray) {
- return NS_ERROR_FAILURE;
- }
-
- bool ok = JS_DefineProperty(cx, fullReportObj, "captures",
- keysArray, JSPROP_ENUMERATE);
- if (!ok) {
- return NS_ERROR_FAILURE;
- }
-
- size_t keyIndex = 0;
- for (auto iter = mStackInfos.ConstIter(); !iter.Done(); iter.Next(), ++keyIndex) {
- const StackFrequencyInfo* info = iter.Data();
-
- JS::RootedObject infoArray(cx, JS_NewArrayObject(cx, 0));
- if (!keysArray) {
- return NS_ERROR_FAILURE;
- }
- JS::RootedString str(cx, JS_NewStringCopyZ(cx,
- PromiseFlatCString(iter.Key()).get()));
- if (!str ||
- !JS_DefineElement(cx, infoArray, 0, str, JSPROP_ENUMERATE) ||
- !JS_DefineElement(cx, infoArray, 1, info->mIndex, JSPROP_ENUMERATE) ||
- !JS_DefineElement(cx, infoArray, 2, info->mCount, JSPROP_ENUMERATE) ||
- !JS_DefineElement(cx, keysArray, keyIndex, infoArray, JSPROP_ENUMERATE)) {
- return NS_ERROR_FAILURE;
- }
- }
-
- ret.setObject(*fullReportObj);
- return NS_OK;
-}
-
-void
-KeyedStackCapturer::Clear()
-{
- MutexAutoLock captureStackMutex(mStackCapturerMutex);
- mStackInfos.Clear();
- mStacks.Clear();
-}
-#endif
+using mozilla::Telemetry::HangReports;
+using mozilla::Telemetry::KeyedStackCapturer;
+using mozilla::Telemetry::CombinedStacks;
+using mozilla::Telemetry::ComputeAnnotationsKey;
/**
* IOInterposeObserver recording statistics of main-thread I/O during execution,
* aimed at consumption by TelemetryImpl
*/
class TelemetryIOInterposeObserver : public IOInterposeObserver
{
/** File-level statistics structure */
@@ -765,17 +258,17 @@ void TelemetryIOInterposeObserver::Obser
}
if (aOb.Duration() < kTelemetryReportThreshold) {
return;
}
// Get the filename
const char16_t* filename = aOb.Filename();
-
+
// Discard observations without filename
if (!filename) {
return;
}
#if defined(XP_WIN)
nsCaseInsensitiveStringComparator comparator;
#else
@@ -909,17 +402,17 @@ public:
uint32_t delay);
#if defined(MOZ_GECKO_PROFILER)
static void RecordChromeHang(uint32_t aDuration,
Telemetry::ProcessedStack &aStack,
int32_t aSystemUptime,
int32_t aFirefoxUptime,
HangAnnotationsPtr aAnnotations);
#endif
-#if defined(ENABLE_STACK_CAPTURE)
+#if defined(MOZ_GECKO_PROFILER)
static void DoStackCapture(const nsACString& aKey);
#endif
static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
struct Stat {
uint32_t hitCount;
uint32_t totalTime;
};
@@ -962,17 +455,17 @@ private:
AutoHashtable<SlowSQLEntryType> mPrivateSQL;
AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
Mutex mHashMutex;
HangReports mHangReports;
Mutex mHangReportsMutex;
Atomic<bool> mCanRecordBase;
Atomic<bool> mCanRecordExtended;
-#if defined(ENABLE_STACK_CAPTURE)
+#if defined(MOZ_GECKO_PROFILER)
// Stores data about stacks captured on demand.
KeyedStackCapturer mStackCapturer;
#endif
// mThreadHangStats stores recorded, inactive thread hang stats
Vector<Telemetry::ThreadHangStats> mThreadHangStats;
Mutex mThreadHangStatsMutex;
@@ -1076,17 +569,17 @@ public:
for (unsigned int i = 0, n = mTelemetry->mCallbacks.Count(); i < n; ++i) {
mTelemetry->mCallbacks[i]->Complete();
}
mTelemetry->mCallbacks.Clear();
}
NS_IMETHOD Run() override {
LoadFailedLockCount(mTelemetry->mFailedLockCount);
- mTelemetry->mLastShutdownTime =
+ mTelemetry->mLastShutdownTime =
ReadLastShutdownDuration(mShutdownTimeFilename);
mTelemetry->ReadLateWritesStacks(mProfileDir);
nsCOMPtr<nsIRunnable> e =
NewRunnableMethod(this, &nsFetchTelemetryData::MainThread);
NS_ENSURE_STATE(e);
NS_DispatchToMainThread(e);
return NS_OK;
}
@@ -1536,121 +1029,27 @@ TelemetryImpl::GetChromeHangs(JSContext
}
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::SnapshotCapturedStacks(bool clear, JSContext *cx, JS::MutableHandle<JS::Value> ret)
{
-#if defined(ENABLE_STACK_CAPTURE)
+#if defined(MOZ_GECKO_PROFILER)
nsresult rv = mStackCapturer.ReflectCapturedStacks(cx, ret);
if (clear) {
mStackCapturer.Clear();
}
return rv;
#else
return NS_OK;
#endif
}
-static 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.c_str()));
- 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;
-}
-
#if defined(MOZ_GECKO_PROFILER)
class GetLoadedModulesResultRunnable final : public Runnable
{
nsMainThreadPtrHandle<Promise> mPromise;
SharedLibraryInfo mRawModules;
nsCOMPtr<nsIThread> mWorkerThread;
public:
@@ -2669,29 +2068,29 @@ TelemetryImpl::RecordChromeHang(uint32_t
MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
sTelemetry->mHangReports.AddHang(aStack, aDuration,
aSystemUptime, aFirefoxUptime,
Move(annotations));
}
-#if defined(ENABLE_STACK_CAPTURE)
+#if defined(MOZ_GECKO_PROFILER)
void
TelemetryImpl::DoStackCapture(const nsACString& aKey) {
if (Telemetry::CanRecordExtended() && XRE_IsParentProcess()) {
sTelemetry->mStackCapturer.Capture(aKey);
}
}
#endif
#endif
nsresult
TelemetryImpl::CaptureStack(const nsACString& aKey) {
-#if defined(ENABLE_STACK_CAPTURE)
+#if defined(MOZ_GECKO_PROFILER)
TelemetryImpl::DoStackCapture(aKey);
#endif
return NS_OK;
}
void
TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
{
@@ -3052,16 +2451,18 @@ RecordShutdownEndTimeStamp() {
////////////////////////////////////////////////////////////////////////
//
// EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry::
// These are NOT listed in Telemetry.h
namespace mozilla {
namespace Telemetry {
+const size_t kMaxChromeStackDepth = 50;
+
ProcessedStack::ProcessedStack() = default;
size_t ProcessedStack::GetStackSize() const
{
return mStack.size();
}
size_t ProcessedStack::GetNumModules() const
@@ -3376,17 +2777,17 @@ void RecordChromeHang(uint32_t duration,
{
TelemetryImpl::RecordChromeHang(duration, aStack,
aSystemUptime, aFirefoxUptime,
Move(aAnnotations));
}
void CaptureStack(const nsACString& aKey)
{
-#if defined(ENABLE_STACK_CAPTURE)
+#if defined(MOZ_GECKO_PROFILER)
TelemetryImpl::DoStackCapture(aKey);
#endif
}
#endif
void RecordThreadHangStats(ThreadHangStats& aStats)
{
TelemetryImpl::RecordThreadHangStats(aStats);
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -48,18 +48,21 @@ EXPORTS.mozilla += [
'ipc/TelemetryComms.h',
'ipc/TelemetryIPC.h',
'ProcessedStack.h',
'Telemetry.h',
'ThreadHangStats.h',
]
SOURCES += [
+ 'CombinedStacks.cpp',
+ 'HangReports.cpp',
'ipc/TelemetryIPC.cpp',
'ipc/TelemetryIPCAccumulator.cpp',
+ 'KeyedStackCapturer.cpp',
'Telemetry.cpp',
'TelemetryCommon.cpp',
'TelemetryEvent.cpp',
'TelemetryHistogram.cpp',
'TelemetryScalar.cpp',
'WebrtcTelemetry.cpp',
]