зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1335461: Pulling out stack capturing into standalone files. r=gfritzsche
MozReview-Commit-ID: APuclfFyJri --HG-- extra : rebase_source : 599942110a813d954983f961dd9da06045b6a6a5
This commit is contained in:
Родитель
6ed7ad2776
Коммит
92953bfc67
|
@ -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
|
|
@ -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__
|
|
@ -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
|
|
@ -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__
|
|
@ -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
|
|
@ -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__
|
|
@ -84,12 +84,11 @@
|
||||||
#include "mozilla/HangMonitor.h"
|
#include "mozilla/HangMonitor.h"
|
||||||
#include "nsNativeCharsetUtils.h"
|
#include "nsNativeCharsetUtils.h"
|
||||||
#include "nsProxyRelease.h"
|
#include "nsProxyRelease.h"
|
||||||
|
#include "HangReports.h"
|
||||||
|
|
||||||
#if defined(MOZ_GECKO_PROFILER)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
#include "shared-libraries.h"
|
#include "shared-libraries.h"
|
||||||
#define ENABLE_STACK_CAPTURE
|
#include "KeyedStackCapturer.h"
|
||||||
#include "mozilla/StackWalk.h"
|
|
||||||
#include "nsPrintfCString.h"
|
|
||||||
#endif // MOZ_GECKO_PROFILER
|
#endif // MOZ_GECKO_PROFILER
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -99,516 +98,10 @@ using namespace mozilla::HangMonitor;
|
||||||
using Telemetry::Common::AutoHashtable;
|
using Telemetry::Common::AutoHashtable;
|
||||||
using mozilla::dom::Promise;
|
using mozilla::dom::Promise;
|
||||||
using mozilla::dom::AutoJSAPI;
|
using mozilla::dom::AutoJSAPI;
|
||||||
|
using mozilla::Telemetry::HangReports;
|
||||||
// The maximum number of chrome hangs stacks that we're keeping.
|
using mozilla::Telemetry::KeyedStackCapturer;
|
||||||
const size_t kMaxChromeStacksKept = 50;
|
using mozilla::Telemetry::CombinedStacks;
|
||||||
// The maximum depth of a single chrome hang stack.
|
using mozilla::Telemetry::ComputeAnnotationsKey;
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IOInterposeObserver recording statistics of main-thread I/O during execution,
|
* IOInterposeObserver recording statistics of main-thread I/O during execution,
|
||||||
|
@ -914,7 +407,7 @@ public:
|
||||||
int32_t aFirefoxUptime,
|
int32_t aFirefoxUptime,
|
||||||
HangAnnotationsPtr aAnnotations);
|
HangAnnotationsPtr aAnnotations);
|
||||||
#endif
|
#endif
|
||||||
#if defined(ENABLE_STACK_CAPTURE)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
static void DoStackCapture(const nsACString& aKey);
|
static void DoStackCapture(const nsACString& aKey);
|
||||||
#endif
|
#endif
|
||||||
static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
|
static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
|
||||||
|
@ -967,7 +460,7 @@ private:
|
||||||
Atomic<bool> mCanRecordBase;
|
Atomic<bool> mCanRecordBase;
|
||||||
Atomic<bool> mCanRecordExtended;
|
Atomic<bool> mCanRecordExtended;
|
||||||
|
|
||||||
#if defined(ENABLE_STACK_CAPTURE)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
// Stores data about stacks captured on demand.
|
// Stores data about stacks captured on demand.
|
||||||
KeyedStackCapturer mStackCapturer;
|
KeyedStackCapturer mStackCapturer;
|
||||||
#endif
|
#endif
|
||||||
|
@ -1541,7 +1034,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle<JS::Value> ret)
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
TelemetryImpl::SnapshotCapturedStacks(bool clear, JSContext *cx, JS::MutableHandle<JS::Value> ret)
|
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);
|
nsresult rv = mStackCapturer.ReflectCapturedStacks(cx, ret);
|
||||||
if (clear) {
|
if (clear) {
|
||||||
mStackCapturer.Clear();
|
mStackCapturer.Clear();
|
||||||
|
@ -1552,100 +1045,6 @@ TelemetryImpl::SnapshotCapturedStacks(bool clear, JSContext *cx, JS::MutableHand
|
||||||
#endif
|
#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)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
class GetLoadedModulesResultRunnable final : public Runnable
|
class GetLoadedModulesResultRunnable final : public Runnable
|
||||||
{
|
{
|
||||||
|
@ -2674,7 +2073,7 @@ TelemetryImpl::RecordChromeHang(uint32_t aDuration,
|
||||||
Move(annotations));
|
Move(annotations));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ENABLE_STACK_CAPTURE)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
void
|
void
|
||||||
TelemetryImpl::DoStackCapture(const nsACString& aKey) {
|
TelemetryImpl::DoStackCapture(const nsACString& aKey) {
|
||||||
if (Telemetry::CanRecordExtended() && XRE_IsParentProcess()) {
|
if (Telemetry::CanRecordExtended() && XRE_IsParentProcess()) {
|
||||||
|
@ -2686,7 +2085,7 @@ TelemetryImpl::DoStackCapture(const nsACString& aKey) {
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
TelemetryImpl::CaptureStack(const nsACString& aKey) {
|
TelemetryImpl::CaptureStack(const nsACString& aKey) {
|
||||||
#if defined(ENABLE_STACK_CAPTURE)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
TelemetryImpl::DoStackCapture(aKey);
|
TelemetryImpl::DoStackCapture(aKey);
|
||||||
#endif
|
#endif
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
@ -3057,6 +2456,8 @@ RecordShutdownEndTimeStamp() {
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace Telemetry {
|
namespace Telemetry {
|
||||||
|
|
||||||
|
const size_t kMaxChromeStackDepth = 50;
|
||||||
|
|
||||||
ProcessedStack::ProcessedStack() = default;
|
ProcessedStack::ProcessedStack() = default;
|
||||||
|
|
||||||
size_t ProcessedStack::GetStackSize() const
|
size_t ProcessedStack::GetStackSize() const
|
||||||
|
@ -3381,7 +2782,7 @@ void RecordChromeHang(uint32_t duration,
|
||||||
|
|
||||||
void CaptureStack(const nsACString& aKey)
|
void CaptureStack(const nsACString& aKey)
|
||||||
{
|
{
|
||||||
#if defined(ENABLE_STACK_CAPTURE)
|
#if defined(MOZ_GECKO_PROFILER)
|
||||||
TelemetryImpl::DoStackCapture(aKey);
|
TelemetryImpl::DoStackCapture(aKey);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,11 @@ EXPORTS.mozilla += [
|
||||||
]
|
]
|
||||||
|
|
||||||
SOURCES += [
|
SOURCES += [
|
||||||
|
'CombinedStacks.cpp',
|
||||||
|
'HangReports.cpp',
|
||||||
'ipc/TelemetryIPC.cpp',
|
'ipc/TelemetryIPC.cpp',
|
||||||
'ipc/TelemetryIPCAccumulator.cpp',
|
'ipc/TelemetryIPCAccumulator.cpp',
|
||||||
|
'KeyedStackCapturer.cpp',
|
||||||
'Telemetry.cpp',
|
'Telemetry.cpp',
|
||||||
'TelemetryCommon.cpp',
|
'TelemetryCommon.cpp',
|
||||||
'TelemetryEvent.cpp',
|
'TelemetryEvent.cpp',
|
||||||
|
|
Загрузка…
Ссылка в новой задаче