Files
tubestation/toolkit/components/telemetry/ThreadHangStats.cpp
2017-07-04 15:16:19 -04:00

323 lines
9.7 KiB
C++

/* -*- 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/. */
#include "mozilla/HangAnnotations.h"
#include "ThreadHangStats.h"
#include "nsITelemetry.h"
#include "HangReports.h"
#include "jsapi.h"
namespace {
using namespace mozilla;
using namespace mozilla::HangMonitor;
using namespace mozilla::Telemetry;
static JSObject*
CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time)
{
/* Create JS representation of TimeHistogram,
in the format of Chromium-style histograms. */
JS::RootedObject ret(cx, JS_NewPlainObject(cx));
if (!ret) {
return nullptr;
}
if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0),
JSPROP_ENUMERATE) ||
!JS_DefineProperty(cx, ret, "max",
time.GetBucketMax(ArrayLength(time) - 1),
JSPROP_ENUMERATE) ||
!JS_DefineProperty(cx, ret, "histogram_type",
nsITelemetry::HISTOGRAM_EXPONENTIAL,
JSPROP_ENUMERATE)) {
return nullptr;
}
// TODO: calculate "sum"
if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE)) {
return nullptr;
}
JS::RootedObject ranges(
cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
JS::RootedObject counts(
cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
if (!ranges || !counts) {
return nullptr;
}
/* In a Chromium-style histogram, the first bucket is an "under" bucket
that represents all values below the histogram's range. */
if (!JS_DefineElement(cx, ranges, 0, time.GetBucketMin(0), JSPROP_ENUMERATE) ||
!JS_DefineElement(cx, counts, 0, 0, JSPROP_ENUMERATE)) {
return nullptr;
}
for (size_t i = 0; i < ArrayLength(time); i++) {
if (!JS_DefineElement(cx, ranges, i + 1, time.GetBucketMax(i),
JSPROP_ENUMERATE) ||
!JS_DefineElement(cx, counts, i + 1, time[i], JSPROP_ENUMERATE)) {
return nullptr;
}
}
if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) ||
!JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) {
return nullptr;
}
return ret;
}
static JSObject*
CreateJSHangStack(JSContext* cx, const Telemetry::HangStack& stack)
{
JS::RootedObject ret(cx, JS_NewArrayObject(cx, stack.length()));
if (!ret) {
return nullptr;
}
for (size_t i = 0; i < stack.length(); i++) {
JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i]));
if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) {
return nullptr;
}
}
return ret;
}
static void
CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations,
JS::MutableHandleObject returnedObject)
{
JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0));
if (!annotationsArray) {
returnedObject.set(nullptr);
return;
}
// We keep track of the annotations we reported in this hash set, so we can
// discard duplicated ones.
nsTHashtable<nsStringHashKey> reportedAnnotations;
size_t annotationIndex = 0;
for (const auto & curAnnotations : annotations) {
JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx));
if (!jsAnnotation) {
continue;
}
// Build a key to index the current annotations in our hash set.
nsAutoString annotationsKey;
nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey);
if (NS_FAILED(rv)) {
continue;
}
// Check if the annotations are in the set. If that's the case, don't double report.
if (reportedAnnotations.GetEntry(annotationsKey)) {
continue;
}
// If not, report them.
reportedAnnotations.PutEntry(annotationsKey);
UniquePtr<HangAnnotations::Enumerator> annotationsEnum =
curAnnotations->GetEnumerator();
if (!annotationsEnum) {
continue;
}
nsAutoString key;
nsAutoString value;
while (annotationsEnum->Next(key, value)) {
JS::RootedValue jsValue(cx);
jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
jsValue, JSPROP_ENUMERATE)) {
returnedObject.set(nullptr);
return;
}
}
if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) {
continue;
}
++annotationIndex;
}
// Return the array using a |MutableHandleObject| to avoid triggering a false
// positive rooting issue in the hazard analysis build.
returnedObject.set(annotationsArray);
}
static JSObject*
CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
{
JS::RootedObject ret(cx, JS_NewPlainObject(cx));
if (!ret) {
return nullptr;
}
JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack()));
JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
auto& hangAnnotations = hang.GetAnnotations();
JS::RootedObject annotations(cx);
CreateJSHangAnnotations(cx, hangAnnotations, &annotations);
if (!stack ||
!time ||
!annotations ||
!JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
!JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
(!hangAnnotations.empty() && // <-- Only define annotations when nonempty
!JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
return nullptr;
}
return ret;
}
} // namespace
namespace mozilla {
namespace Telemetry {
JSObject*
CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
{
JS::RootedObject ret(cx, JS_NewPlainObject(cx));
if (!ret) {
return nullptr;
}
JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
if (!name ||
!JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) {
return nullptr;
}
JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
if (!activity ||
!JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) {
return nullptr;
}
// Process the hangs into a hangs object.
JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
if (!hangs) {
return nullptr;
}
for (size_t i = 0; i < thread.mHangs.length(); i++) {
JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
if (!ret) {
return nullptr;
}
JS::RootedString runnableName(cx, JS_NewStringCopyZ(cx, thread.mHangs[i].GetRunnableName()));
if (!runnableName ||
!JS_DefineProperty(cx, ret, "runnableName", runnableName, JSPROP_ENUMERATE)) {
return nullptr;
}
// Check if we have a cached native stack index, and if we do record it.
uint32_t index = thread.mHangs[i].GetNativeStackIndex();
if (index != Telemetry::HangHistogram::NO_NATIVE_STACK_INDEX) {
if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) {
return nullptr;
}
}
if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) {
return nullptr;
}
}
if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) {
return nullptr;
}
// We should already have a CombinedStacks object on the ThreadHangStats, so
// add that one.
JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, thread.mCombinedStacks));
if (!fullReportObj) {
return nullptr;
}
if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) {
return nullptr;
}
return ret;
}
void
TimeHistogram::Add(PRIntervalTime aTime)
{
uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
size_t index = mozilla::FloorLog2(timeMs);
operator[](index)++;
}
const char*
HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength)
{
MOZ_ASSERT(this->canAppendWithoutRealloc(1));
// Include null-terminator in length count.
MOZ_ASSERT(mBuffer.canAppendWithoutRealloc(aLength + 1));
const char* const entry = mBuffer.end();
mBuffer.infallibleAppend(aText, aLength);
mBuffer.infallibleAppend('\0'); // Explicitly append null-terminator
this->infallibleAppend(entry);
return entry;
}
const char*
HangStack::AppendViaBuffer(const char* aText, size_t aLength)
{
if (!this->reserve(this->length() + 1)) {
return nullptr;
}
// Keep track of the previous buffer in case we need to adjust pointers later.
const char* const prevStart = mBuffer.begin();
const char* const prevEnd = mBuffer.end();
// Include null-terminator in length count.
if (!mBuffer.reserve(mBuffer.length() + aLength + 1)) {
return nullptr;
}
if (prevStart != mBuffer.begin()) {
// The buffer has moved; we have to adjust pointers in the stack.
for (auto & entry : *this) {
if (entry >= prevStart && entry < prevEnd) {
// Move from old buffer to new buffer.
entry += mBuffer.begin() - prevStart;
}
}
}
return InfallibleAppendViaBuffer(aText, aLength);
}
uint32_t
HangHistogram::GetHash(const HangStack& aStack)
{
uint32_t hash = 0;
for (const char* const* label = aStack.begin();
label != aStack.end(); label++) {
/* If the string is within our buffer, we need to hash its content.
Otherwise, the string is statically allocated, and we only need
to hash the pointer instead of the content. */
if (aStack.IsInBuffer(*label)) {
hash = AddToHash(hash, HashString(*label));
} else {
hash = AddToHash(hash, *label);
}
}
return hash;
}
bool
HangHistogram::operator==(const HangHistogram& aOther) const
{
if (mHash != aOther.mHash) {
return false;
}
if (mStack.length() != aOther.mStack.length()) {
return false;
}
return mStack == aOther.mStack;
}
} // namespace Telemetry
} // namespace mozilla