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:
Iaroslav (yarik) Sheptykin 2017-06-01 09:36:51 +02:00
Родитель 6ed7ad2776
Коммит 92953bfc67
8 изменённых файлов: 776 добавлений и 615 удалений

Просмотреть файл

@ -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',