зеркало из 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 "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 {
|
||||
|
@ -99,516 +98,10 @@ 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,
|
||||
|
@ -770,7 +263,7 @@ void TelemetryIOInterposeObserver::Observe(Observation& aOb)
|
|||
|
||||
// Get the filename
|
||||
const char16_t* filename = aOb.Filename();
|
||||
|
||||
|
||||
// Discard observations without filename
|
||||
if (!filename) {
|
||||
return;
|
||||
|
@ -914,7 +407,7 @@ public:
|
|||
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);
|
||||
|
@ -967,7 +460,7 @@ private:
|
|||
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
|
||||
|
@ -1081,7 +574,7 @@ public:
|
|||
|
||||
NS_IMETHOD Run() override {
|
||||
LoadFailedLockCount(mTelemetry->mFailedLockCount);
|
||||
mTelemetry->mLastShutdownTime =
|
||||
mTelemetry->mLastShutdownTime =
|
||||
ReadLastShutdownDuration(mShutdownTimeFilename);
|
||||
mTelemetry->ReadLateWritesStacks(mProfileDir);
|
||||
nsCOMPtr<nsIRunnable> e =
|
||||
|
@ -1541,7 +1034,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle<JS::Value> ret)
|
|||
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();
|
||||
|
@ -1552,100 +1045,6 @@ TelemetryImpl::SnapshotCapturedStacks(bool clear, JSContext *cx, JS::MutableHand
|
|||
#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
|
||||
{
|
||||
|
@ -2674,7 +2073,7 @@ TelemetryImpl::RecordChromeHang(uint32_t aDuration,
|
|||
Move(annotations));
|
||||
}
|
||||
|
||||
#if defined(ENABLE_STACK_CAPTURE)
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
void
|
||||
TelemetryImpl::DoStackCapture(const nsACString& aKey) {
|
||||
if (Telemetry::CanRecordExtended() && XRE_IsParentProcess()) {
|
||||
|
@ -2686,7 +2085,7 @@ TelemetryImpl::DoStackCapture(const nsACString& aKey) {
|
|||
|
||||
nsresult
|
||||
TelemetryImpl::CaptureStack(const nsACString& aKey) {
|
||||
#if defined(ENABLE_STACK_CAPTURE)
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
TelemetryImpl::DoStackCapture(aKey);
|
||||
#endif
|
||||
return NS_OK;
|
||||
|
@ -3057,6 +2456,8 @@ RecordShutdownEndTimeStamp() {
|
|||
namespace mozilla {
|
||||
namespace Telemetry {
|
||||
|
||||
const size_t kMaxChromeStackDepth = 50;
|
||||
|
||||
ProcessedStack::ProcessedStack() = default;
|
||||
|
||||
size_t ProcessedStack::GetStackSize() const
|
||||
|
@ -3381,7 +2782,7 @@ void RecordChromeHang(uint32_t duration,
|
|||
|
||||
void CaptureStack(const nsACString& aKey)
|
||||
{
|
||||
#if defined(ENABLE_STACK_CAPTURE)
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
TelemetryImpl::DoStackCapture(aKey);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -53,8 +53,11 @@ EXPORTS.mozilla += [
|
|||
]
|
||||
|
||||
SOURCES += [
|
||||
'CombinedStacks.cpp',
|
||||
'HangReports.cpp',
|
||||
'ipc/TelemetryIPC.cpp',
|
||||
'ipc/TelemetryIPCAccumulator.cpp',
|
||||
'KeyedStackCapturer.cpp',
|
||||
'Telemetry.cpp',
|
||||
'TelemetryCommon.cpp',
|
||||
'TelemetryEvent.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче