gecko-dev/toolkit/components/telemetry/Telemetry.cpp

1798 lines
51 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2012-05-21 11:12:37 +00:00
/* 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 <algorithm>
#include "base/histogram.h"
#include "base/pickle.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsCOMPtr.h"
#include "mozilla/ModuleUtils.h"
#include "nsIXPConnect.h"
#include "mozilla/Services.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "nsStringGlue.h"
#include "nsITelemetry.h"
#include "nsIFile.h"
#include "nsIMemoryReporter.h"
#include "Telemetry.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "nsBaseHashtable.h"
#include "nsXULAppAPI.h"
#include "nsThreadUtils.h"
#include "mozilla/ProcessedStack.h"
#include "mozilla/Mutex.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/Attributes.h"
namespace {
using namespace base;
using namespace mozilla;
template<class EntryType>
class AutoHashtable : public nsTHashtable<EntryType>
{
public:
AutoHashtable(uint32_t initSize = PL_DHASH_MIN_SIZE);
~AutoHashtable();
typedef bool (*ReflectEntryFunc)(EntryType *entry, JSContext *cx, JSObject *obj);
bool ReflectIntoJS(ReflectEntryFunc entryFunc, JSContext *cx, JSObject *obj);
private:
struct EnumeratorArgs {
JSContext *cx;
JSObject *obj;
ReflectEntryFunc entryFunc;
};
static PLDHashOperator ReflectEntryStub(EntryType *entry, void *arg);
};
template<class EntryType>
AutoHashtable<EntryType>::AutoHashtable(uint32_t initSize)
{
this->Init(initSize);
}
template<class EntryType>
AutoHashtable<EntryType>::~AutoHashtable()
{
this->Clear();
}
template<typename EntryType>
PLDHashOperator
AutoHashtable<EntryType>::ReflectEntryStub(EntryType *entry, void *arg)
{
EnumeratorArgs *args = static_cast<EnumeratorArgs *>(arg);
if (!args->entryFunc(entry, args->cx, args->obj)) {
return PL_DHASH_STOP;
}
return PL_DHASH_NEXT;
}
/**
* Reflect the individual entries of table into JS, usually by defining
* some property and value of obj. entryFunc is called for each entry.
*/
template<typename EntryType>
bool
AutoHashtable<EntryType>::ReflectIntoJS(ReflectEntryFunc entryFunc,
JSContext *cx, JSObject *obj)
{
EnumeratorArgs args = { cx, obj, entryFunc };
uint32_t num = this->EnumerateEntries(ReflectEntryStub, static_cast<void*>(&args));
return num == this->Count();
}
class TelemetryImpl MOZ_FINAL : public nsITelemetry
{
NS_DECL_ISUPPORTS
NS_DECL_NSITELEMETRY
public:
TelemetryImpl();
~TelemetryImpl();
static bool CanRecord();
static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
static void ShutdownTelemetry();
static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
uint32_t delay);
#if defined(MOZ_ENABLE_PROFILER_SPS)
static void RecordChromeHang(uint32_t duration,
Telemetry::ProcessedStack &aStack);
#endif
static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
static int64_t GetTelemetryMemoryUsed();
size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf);
struct Stat {
uint32_t hitCount;
uint32_t totalTime;
};
struct StmtStats {
struct Stat mainThread;
struct Stat otherThreads;
};
typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
struct HangReport {
uint32_t duration;
Telemetry::ProcessedStack mStack;
};
private:
// We don't need to poke inside any of our hashtables for more
// information, so we just have One Function To Size Them All.
template<typename EntryType>
struct impl {
static size_t SizeOfEntryExcludingThis(EntryType *,
nsMallocSizeOfFun,
void *) {
return 0;
};
};
static nsCString SanitizeSQL(const nsACString& sql);
enum SanitizedState { Sanitized, Unsanitized };
static void StoreSlowSQL(const nsACString &offender, uint32_t delay,
SanitizedState state);
static bool ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx,
JSObject *obj);
static bool ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx,
JSObject *obj);
static bool ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat,
JSContext *cx, JSObject *obj);
bool AddSQLInfo(JSContext *cx, JSObject *rootObj, bool mainThread,
bool privateSQL);
bool GetSQLStats(JSContext *cx, jsval *ret, bool includePrivateSql);
// Like GetHistogramById, but returns the underlying C++ object, not the JS one.
nsresult GetHistogramByName(const nsACString &name, Histogram **ret);
bool ShouldReflectHistogram(Histogram *h);
void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs);
typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
struct AddonHistogramInfo {
uint32_t min;
uint32_t max;
uint32_t bucketCount;
uint32_t histogramType;
Histogram *h;
};
typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo> AddonHistogramEntryType;
typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType;
typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *> AddonEntryType;
typedef AutoHashtable<AddonEntryType> AddonMapType;
static bool AddonHistogramReflector(AddonHistogramEntryType *entry,
JSContext *cx, JSObject *obj);
static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JSObject *obj);
static bool CreateHistogramForAddon(const nsACString &name,
AddonHistogramInfo &info);
AddonMapType mAddonMap;
// This is used for speedy string->Telemetry::ID conversions
typedef nsBaseHashtableET<nsCharPtrHashKey, Telemetry::ID> CharPtrEntryType;
typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
HistogramMapType mHistogramMap;
bool mCanRecord;
static TelemetryImpl *sTelemetry;
AutoHashtable<SlowSQLEntryType> mPrivateSQL;
AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
// This gets marked immutable in debug builds, so we can't use
// AutoHashtable here.
nsTHashtable<nsCStringHashKey> mTrackedDBs;
Mutex mHashMutex;
nsTArray<HangReport> mHangReports;
Mutex mHangReportsMutex;
nsIMemoryReporter *mMemoryReporter;
};
TelemetryImpl* TelemetryImpl::sTelemetry = NULL;
NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(TelemetryMallocSizeOf, "telemetry")
size_t
TelemetryImpl::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf)
{
size_t n = 0;
n += aMallocSizeOf(this);
// Ignore the hashtables in mAddonMap; they are not significant.
n += mAddonMap.SizeOfExcludingThis(impl<AddonEntryType>::SizeOfEntryExcludingThis,
aMallocSizeOf);
n += mHistogramMap.SizeOfExcludingThis(impl<CharPtrEntryType>::SizeOfEntryExcludingThis,
aMallocSizeOf);
n += mPrivateSQL.SizeOfExcludingThis(impl<SlowSQLEntryType>::SizeOfEntryExcludingThis,
aMallocSizeOf);
n += mSanitizedSQL.SizeOfExcludingThis(impl<SlowSQLEntryType>::SizeOfEntryExcludingThis,
aMallocSizeOf);
n += mTrackedDBs.SizeOfExcludingThis(impl<nsCStringHashKey>::SizeOfEntryExcludingThis,
aMallocSizeOf);
n += mHangReports.SizeOfExcludingThis(aMallocSizeOf);
return n;
}
int64_t
TelemetryImpl::GetTelemetryMemoryUsed()
{
int64_t n = 0;
if (sTelemetry) {
n += sTelemetry->SizeOfIncludingThis(TelemetryMallocSizeOf);
}
StatisticsRecorder::Histograms hs;
StatisticsRecorder::GetHistograms(&hs);
for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
Histogram *h = *it;
n += h->SizeOfIncludingThis(TelemetryMallocSizeOf);
}
return n;
}
NS_MEMORY_REPORTER_IMPLEMENT(Telemetry,
"explicit/telemetry",
KIND_HEAP,
UNITS_BYTES,
TelemetryImpl::GetTelemetryMemoryUsed,
"Memory used by the telemetry system.")
// A initializer to initialize histogram collection
StatisticsRecorder gStatisticsRecorder;
// Hardcoded probes
struct TelemetryHistogram {
uint32_t min;
uint32_t max;
uint32_t bucketCount;
uint32_t histogramType;
uint16_t id_offset;
uint16_t comment_offset;
const char *id() const;
const char *comment() const;
};
#include "TelemetryHistogramData.inc"
bool gCorruptHistograms[Telemetry::HistogramCount];
const char *
TelemetryHistogram::id() const
{
return &gHistogramStringTable[this->id_offset];
}
const char *
TelemetryHistogram::comment() const
{
return &gHistogramStringTable[this->comment_offset];
}
bool
TelemetryHistogramType(Histogram *h, uint32_t *result)
{
switch (h->histogram_type()) {
case Histogram::HISTOGRAM:
*result = nsITelemetry::HISTOGRAM_EXPONENTIAL;
break;
case Histogram::LINEAR_HISTOGRAM:
*result = nsITelemetry::HISTOGRAM_LINEAR;
break;
case Histogram::BOOLEAN_HISTOGRAM:
*result = nsITelemetry::HISTOGRAM_BOOLEAN;
break;
case Histogram::FLAG_HISTOGRAM:
*result = nsITelemetry::HISTOGRAM_FLAG;
break;
default:
return false;
}
return true;
}
nsresult
HistogramGet(const char *name, uint32_t min, uint32_t max, uint32_t bucketCount,
uint32_t histogramType, Histogram **result)
{
if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
&& histogramType != nsITelemetry::HISTOGRAM_FLAG) {
// Sanity checks for histogram parameters.
if (min >= max)
return NS_ERROR_ILLEGAL_VALUE;
if (bucketCount <= 2)
return NS_ERROR_ILLEGAL_VALUE;
if (min < 1)
return NS_ERROR_ILLEGAL_VALUE;
}
switch (histogramType) {
case nsITelemetry::HISTOGRAM_EXPONENTIAL:
*result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
break;
case nsITelemetry::HISTOGRAM_LINEAR:
*result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
break;
case nsITelemetry::HISTOGRAM_BOOLEAN:
*result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
break;
case nsITelemetry::HISTOGRAM_FLAG:
*result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
break;
default:
return NS_ERROR_INVALID_ARG;
}
return NS_OK;
}
// O(1) histogram lookup by numeric id
nsresult
GetHistogramByEnumId(Telemetry::ID id, Histogram **ret)
{
static Histogram* knownHistograms[Telemetry::HistogramCount] = {0};
Histogram *h = knownHistograms[id];
if (h) {
*ret = h;
return NS_OK;
}
const TelemetryHistogram &p = gHistograms[id];
nsresult rv = HistogramGet(p.id(), p.min, p.max, p.bucketCount, p.histogramType, &h);
if (NS_FAILED(rv))
return rv;
#ifdef DEBUG
// Check that the C++ Histogram code computes the same ranges as the
// Python histogram code.
const struct bounds &b = gBucketLowerBoundIndex[id];
if (b.length != 0) {
MOZ_ASSERT(size_t(b.length) == h->bucket_count(),
"C++/Python bucket # mismatch");
for (int i = 0; i < b.length; ++i) {
MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
"C++/Python bucket mismatch");
}
}
#endif
*ret = knownHistograms[id] = h;
return NS_OK;
}
bool
FillRanges(JSContext *cx, JSObject *array, Histogram *h)
{
for (size_t i = 0; i < h->bucket_count(); i++) {
if (!JS_DefineElement(cx, array, i, INT_TO_JSVAL(h->ranges(i)), NULL, NULL, JSPROP_ENUMERATE))
return false;
}
return true;
}
enum reflectStatus {
REFLECT_OK,
REFLECT_CORRUPT,
REFLECT_FAILURE
};
enum reflectStatus
ReflectHistogramAndSamples(JSContext *cx, JSObject *obj, Histogram *h,
const Histogram::SampleSet &ss)
{
// We don't want to reflect corrupt histograms.
if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) {
return REFLECT_CORRUPT;
}
if (!(JS_DefineProperty(cx, obj, "min", INT_TO_JSVAL(h->declared_min()), NULL, NULL, JSPROP_ENUMERATE)
&& JS_DefineProperty(cx, obj, "max", INT_TO_JSVAL(h->declared_max()), NULL, NULL, JSPROP_ENUMERATE)
&& JS_DefineProperty(cx, obj, "histogram_type", INT_TO_JSVAL(h->histogram_type()), NULL, NULL, JSPROP_ENUMERATE)
&& JS_DefineProperty(cx, obj, "sum", DOUBLE_TO_JSVAL(ss.sum()), NULL, NULL, JSPROP_ENUMERATE))) {
return REFLECT_FAILURE;
}
const size_t count = h->bucket_count();
JSObject *rarray = JS_NewArrayObject(cx, count, nullptr);
if (!rarray) {
return REFLECT_FAILURE;
}
JS::AutoObjectRooter aroot(cx, rarray);
if (!(FillRanges(cx, rarray, h)
&& JS_DefineProperty(cx, obj, "ranges", OBJECT_TO_JSVAL(rarray),
NULL, NULL, JSPROP_ENUMERATE))) {
return REFLECT_FAILURE;
}
JSObject *counts_array = JS_NewArrayObject(cx, count, NULL);
if (!counts_array) {
return REFLECT_FAILURE;
}
JS::AutoObjectRooter croot(cx, counts_array);
if (!JS_DefineProperty(cx, obj, "counts", OBJECT_TO_JSVAL(counts_array),
NULL, NULL, JSPROP_ENUMERATE)) {
return REFLECT_FAILURE;
}
for (size_t i = 0; i < count; i++) {
if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)),
NULL, NULL, JSPROP_ENUMERATE)) {
return REFLECT_FAILURE;
}
}
return REFLECT_OK;
}
enum reflectStatus
ReflectHistogramSnapshot(JSContext *cx, JSObject *obj, Histogram *h)
{
Histogram::SampleSet ss;
h->SnapshotSample(&ss);
return ReflectHistogramAndSamples(cx, obj, h, ss);
}
bool
IsEmpty(const Histogram *h)
{
Histogram::SampleSet ss;
h->SnapshotSample(&ss);
return ss.counts(0) == 0 && ss.sum() == 0;
}
JSBool
JSHistogram_Add(JSContext *cx, unsigned argc, jsval *vp)
{
if (!argc) {
JS_ReportError(cx, "Expected one argument");
return JS_FALSE;
}
jsval v = JS_ARGV(cx, vp)[0];
if (!(JSVAL_IS_NUMBER(v) || JSVAL_IS_BOOLEAN(v))) {
JS_ReportError(cx, "Not a number");
return JS_FALSE;
}
int32_t value;
if (!JS_ValueToECMAInt32(cx, v, &value)) {
return JS_FALSE;
}
if (TelemetryImpl::CanRecord()) {
JSObject *obj = JS_THIS_OBJECT(cx, vp);
if (!obj) {
return JS_FALSE;
}
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
if (h->histogram_type() == Histogram::BOOLEAN_HISTOGRAM)
h->Add(!!value);
else
h->Add(value);
}
return JS_TRUE;
}
JSBool
JSHistogram_Snapshot(JSContext *cx, unsigned argc, jsval *vp)
{
JSObject *obj = JS_THIS_OBJECT(cx, vp);
if (!obj) {
return JS_FALSE;
}
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
JSObject *snapshot = JS_NewObject(cx, nullptr, nullptr, nullptr);
if (!snapshot)
return JS_FALSE;
JS::AutoObjectRooter sroot(cx, snapshot);
switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
case REFLECT_FAILURE:
return JS_FALSE;
case REFLECT_CORRUPT:
JS_ReportError(cx, "Histogram is corrupt");
return JS_FALSE;
case REFLECT_OK:
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(snapshot));
return JS_TRUE;
default:
MOZ_NOT_REACHED("unhandled reflection status");
return JS_FALSE;
}
}
JSBool
JSHistogram_Clear(JSContext *cx, unsigned argc, jsval *vp)
{
JSObject *obj = JS_THIS_OBJECT(cx, vp);
if (!obj) {
return JS_FALSE;
}
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
h->Clear();
return JS_TRUE;
}
nsresult
WrapAndReturnHistogram(Histogram *h, JSContext *cx, jsval *ret)
{
static JSClass JSHistogram_class = {
"JSHistogram", /* name */
JSCLASS_HAS_PRIVATE, /* flags */
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
};
JSObject *obj = JS_NewObject(cx, &JSHistogram_class, NULL, NULL);
if (!obj)
return NS_ERROR_FAILURE;
JS::AutoObjectRooter root(cx, obj);
if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0)
&& JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0)
&& JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0))) {
return NS_ERROR_FAILURE;
}
*ret = OBJECT_TO_JSVAL(obj);
JS_SetPrivate(obj, h);
return NS_OK;
}
TelemetryImpl::TelemetryImpl():
mHistogramMap(Telemetry::HistogramCount),
mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default),
mHashMutex("Telemetry::mHashMutex"),
mHangReportsMutex("Telemetry::mHangReportsMutex")
{
// A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs
const char *trackedDBs[] = {
"addons.sqlite", "chromeappsstore.sqlite", "content-prefs.sqlite",
"cookies.sqlite", "downloads.sqlite", "extensions.sqlite",
"formhistory.sqlite", "index.sqlite", "permissions.sqlite", "places.sqlite",
"search.sqlite", "signons.sqlite", "urlclassifier3.sqlite",
"webappsstore.sqlite"
};
mTrackedDBs.Init();
for (size_t i = 0; i < ArrayLength(trackedDBs); i++)
mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i]));
#ifdef DEBUG
// Mark immutable to prevent asserts on simultaneous access from multiple threads
mTrackedDBs.MarkImmutable();
#endif
mMemoryReporter = new NS_MEMORY_REPORTER_NAME(Telemetry);
NS_RegisterMemoryReporter(mMemoryReporter);
}
TelemetryImpl::~TelemetryImpl() {
NS_UnregisterMemoryReporter(mMemoryReporter);
mMemoryReporter = nullptr;
}
NS_IMETHODIMP
TelemetryImpl::NewHistogram(const nsACString &name, uint32_t min, uint32_t max, uint32_t bucketCount, uint32_t histogramType, JSContext *cx, jsval *ret)
{
Histogram *h;
nsresult rv = HistogramGet(PromiseFlatCString(name).get(), min, max, bucketCount, histogramType, &h);
if (NS_FAILED(rv))
return rv;
h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
return WrapAndReturnHistogram(h, cx, ret);
}
bool
TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry,
const Stat *stat,
JSContext *cx,
JSObject *obj)
{
if (stat->hitCount == 0)
return true;
const nsACString &sql = entry->GetKey();
jsval hitCount = UINT_TO_JSVAL(stat->hitCount);
jsval totalTime = UINT_TO_JSVAL(stat->totalTime);
JSObject *arrayObj = JS_NewArrayObject(cx, 0, nullptr);
if (!arrayObj) {
return false;
}
JS::AutoObjectRooter root(cx, arrayObj);
return (JS_SetElement(cx, arrayObj, 0, &hitCount)
&& JS_SetElement(cx, arrayObj, 1, &totalTime)
&& JS_DefineProperty(cx, obj,
sql.BeginReading(),
OBJECT_TO_JSVAL(arrayObj),
NULL, NULL, JSPROP_ENUMERATE));
}
bool
TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx,
JSObject *obj)
{
return ReflectSQL(entry, &entry->mData.mainThread, cx, obj);
}
bool
TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx,
JSObject *obj)
{
return ReflectSQL(entry, &entry->mData.otherThreads, cx, obj);
}
bool
TelemetryImpl::AddSQLInfo(JSContext *cx, JSObject *rootObj, bool mainThread,
bool privateSQL)
{
JSObject *statsObj = JS_NewObject(cx, NULL, NULL, NULL);
if (!statsObj)
return false;
JS::AutoObjectRooter root(cx, statsObj);
AutoHashtable<SlowSQLEntryType> &sqlMap =
(privateSQL ? mPrivateSQL : mSanitizedSQL);
AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction =
(mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL);
if(!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) {
return false;
}
return JS_DefineProperty(cx, rootObj,
mainThread ? "mainThread" : "otherThreads",
OBJECT_TO_JSVAL(statsObj),
NULL, NULL, JSPROP_ENUMERATE);
}
nsresult
TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id)
{
if (!sTelemetry) {
return NS_ERROR_FAILURE;
}
// Cache names
// Note the histogram names are statically allocated
TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap;
if (!map->Count()) {
for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) {
CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id());
if (NS_UNLIKELY(!entry)) {
map->Clear();
return NS_ERROR_OUT_OF_MEMORY;
}
entry->mData = (Telemetry::ID) i;
}
}
CharPtrEntryType *entry = map->GetEntry(name);
if (!entry) {
return NS_ERROR_INVALID_ARG;
}
*id = entry->mData;
return NS_OK;
}
nsresult
TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret)
{
Telemetry::ID id;
nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
if (NS_FAILED(rv)) {
return rv;
}
rv = GetHistogramByEnumId(id, ret);
if (NS_FAILED(rv))
return rv;
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name,
JSContext *cx, jsval *ret)
{
Histogram *existing;
nsresult rv = GetHistogramByName(existing_name, &existing);
if (NS_FAILED(rv))
return rv;
uint32_t histogramType;
bool success = TelemetryHistogramType(existing, &histogramType);
if (!success)
return NS_ERROR_INVALID_ARG;
Histogram *clone;
rv = HistogramGet(PromiseFlatCString(name).get(), existing->declared_min(),
existing->declared_max(), existing->bucket_count(),
histogramType, &clone);
if (NS_FAILED(rv))
return rv;
Histogram::SampleSet ss;
existing->SnapshotSample(&ss);
clone->AddSampleSet(ss);
return WrapAndReturnHistogram(clone, cx, ret);
}
void
TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
{
for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
Histogram *h = *it;
Telemetry::ID id;
nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id);
// This histogram isn't a static histogram, just ignore it.
if (NS_FAILED(rv)) {
continue;
}
if (gCorruptHistograms[id]) {
continue;
}
Histogram::SampleSet ss;
h->SnapshotSample(&ss);
Histogram::Inconsistencies check = h->FindCorruption(ss);
bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
if (corrupt) {
Telemetry::ID corruptID = Telemetry::HistogramCount;
if (check & Histogram::RANGE_CHECKSUM_ERROR) {
corruptID = Telemetry::RANGE_CHECKSUM_ERRORS;
} else if (check & Histogram::BUCKET_ORDER_ERROR) {
corruptID = Telemetry::BUCKET_ORDER_ERRORS;
} else if (check & Histogram::COUNT_HIGH_ERROR) {
corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS;
} else if (check & Histogram::COUNT_LOW_ERROR) {
corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS;
}
Telemetry::Accumulate(corruptID, 1);
}
gCorruptHistograms[id] = corrupt;
}
}
bool
TelemetryImpl::ShouldReflectHistogram(Histogram *h)
{
const char *name = h->histogram_name().c_str();
Telemetry::ID id;
nsresult rv = GetHistogramEnumId(name, &id);
if (NS_FAILED(rv)) {
// GetHistogramEnumId generally should not fail. But a lookup
// failure shouldn't prevent us from reflecting histograms into JS.
//
// However, these two histograms are created by Histogram itself for
// tracking corruption. We have our own histograms for that, so
// ignore these two.
if (strcmp(name, "Histogram.InconsistentCountHigh") == 0
|| strcmp(name, "Histogram.InconsistentCountLow") == 0) {
return false;
}
return true;
} else {
return !gCorruptHistograms[id];
}
}
// Compute the name to pass into Histogram for the addon histogram
// 'name' from the addon 'id'. We can't use 'name' directly because it
// might conflict with other histograms in other addons or even with our
// own.
void
AddonHistogramName(const nsACString &id, const nsACString &name,
nsACString &ret)
{
ret.Append(id);
ret.Append(NS_LITERAL_CSTRING(":"));
ret.Append(name);
}
NS_IMETHODIMP
TelemetryImpl::RegisterAddonHistogram(const nsACString &id,
const nsACString &name,
uint32_t min, uint32_t max,
uint32_t bucketCount,
uint32_t histogramType)
{
AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
if (!addonEntry) {
addonEntry = mAddonMap.PutEntry(id);
if (NS_UNLIKELY(!addonEntry)) {
return NS_ERROR_OUT_OF_MEMORY;
}
addonEntry->mData = new AddonHistogramMapType();
}
AddonHistogramMapType *histogramMap = addonEntry->mData;
AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
// Can't re-register the same histogram.
if (histogramEntry) {
return NS_ERROR_FAILURE;
}
histogramEntry = histogramMap->PutEntry(name);
if (NS_UNLIKELY(!histogramEntry)) {
return NS_ERROR_OUT_OF_MEMORY;
}
AddonHistogramInfo &info = histogramEntry->mData;
info.min = min;
info.max = max;
info.bucketCount = bucketCount;
info.histogramType = histogramType;
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name,
JSContext *cx, jsval *ret)
{
AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
// The given id has not been registered.
if (!addonEntry) {
return NS_ERROR_INVALID_ARG;
}
AddonHistogramMapType *histogramMap = addonEntry->mData;
AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
// The given histogram name has not been registered.
if (!histogramEntry) {
return NS_ERROR_INVALID_ARG;
}
AddonHistogramInfo &info = histogramEntry->mData;
if (!info.h) {
nsAutoCString actualName;
AddonHistogramName(id, name, actualName);
if (!CreateHistogramForAddon(actualName, info)) {
return NS_ERROR_FAILURE;
}
}
return WrapAndReturnHistogram(info.h, cx, ret);
}
NS_IMETHODIMP
TelemetryImpl::UnregisterAddonHistograms(const nsACString &id)
{
AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
if (addonEntry) {
// Histogram's destructor is private, so this is the best we can do.
// The histograms the addon created *will* stick around, but they
// will be deleted if and when the addon registers histograms with
// the same names.
delete addonEntry->mData;
mAddonMap.RemoveEntry(id);
}
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::GetHistogramSnapshots(JSContext *cx, jsval *ret)
{
JSObject *root_obj = JS_NewObject(cx, NULL, NULL, NULL);
if (!root_obj)
return NS_ERROR_FAILURE;
*ret = OBJECT_TO_JSVAL(root_obj);
// Ensure that all the HISTOGRAM_FLAG histograms have been created, so
// that their values are snapshotted.
for (size_t i = 0; i < Telemetry::HistogramCount; ++i) {
if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) {
Histogram *h;
DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
};
StatisticsRecorder::Histograms hs;
StatisticsRecorder::GetHistograms(&hs);
// We identify corrupt histograms first, rather than interspersing it
// in the loop below, to ensure that our corruption statistics don't
// depend on histogram enumeration order.
//
// Of course, we hope that all of these corruption-statistics
// histograms are not themselves corrupt...
IdentifyCorruptHistograms(hs);
// OK, now we can actually reflect things.
for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
Histogram *h = *it;
if (!ShouldReflectHistogram(h) || IsEmpty(h)) {
continue;
}
JSObject *hobj = JS_NewObject(cx, NULL, NULL, NULL);
if (!hobj) {
return NS_ERROR_FAILURE;
}
JS::AutoObjectRooter root(cx, hobj);
switch (ReflectHistogramSnapshot(cx, hobj, h)) {
case REFLECT_CORRUPT:
// We can still hit this case even if ShouldReflectHistograms
// returns true. The histogram lies outside of our control
// somehow; just skip it.
continue;
case REFLECT_FAILURE:
return NS_ERROR_FAILURE;
case REFLECT_OK:
if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(),
OBJECT_TO_JSVAL(hobj), NULL, NULL, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
}
}
return NS_OK;
}
bool
TelemetryImpl::CreateHistogramForAddon(const nsACString &name,
AddonHistogramInfo &info)
{
Histogram *h;
nsresult rv = HistogramGet(PromiseFlatCString(name).get(),
info.min, info.max, info.bucketCount,
info.histogramType, &h);
if (NS_FAILED(rv)) {
return false;
}
// Don't let this histogram be reported via the normal means
// (e.g. Telemetry.registeredHistograms); we'll make it available in
// other ways.
h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
info.h = h;
return true;
}
bool
TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry,
JSContext *cx, JSObject *obj)
{
AddonHistogramInfo &info = entry->mData;
// Never even accessed the histogram.
if (!info.h) {
// Have to force creation of HISTOGRAM_FLAG histograms.
if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
return true;
if (!CreateHistogramForAddon(entry->GetKey(), info)) {
return false;
}
}
if (IsEmpty(info.h)) {
return true;
}
JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL);
if (!snapshot) {
// Just consider this to be skippable.
return true;
}
JS::AutoObjectRooter r(cx, snapshot);
switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) {
case REFLECT_FAILURE:
case REFLECT_CORRUPT:
return false;
case REFLECT_OK:
const nsACString &histogramName = entry->GetKey();
if (!JS_DefineProperty(cx, obj,
PromiseFlatCString(histogramName).get(),
OBJECT_TO_JSVAL(snapshot), NULL, NULL,
JSPROP_ENUMERATE)) {
return false;
}
break;
}
return true;
}
bool
TelemetryImpl::AddonReflector(AddonEntryType *entry,
JSContext *cx, JSObject *obj)
{
const nsACString &addonId = entry->GetKey();
JSObject *subobj = JS_NewObject(cx, NULL, NULL, NULL);
if (!subobj) {
return false;
}
JS::AutoObjectRooter r(cx, subobj);
AddonHistogramMapType *map = entry->mData;
if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj)
&& JS_DefineProperty(cx, obj,
PromiseFlatCString(addonId).get(),
OBJECT_TO_JSVAL(subobj), NULL, NULL,
JSPROP_ENUMERATE))) {
return false;
}
return true;
}
NS_IMETHODIMP
TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, jsval *ret)
{
*ret = JSVAL_VOID;
JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
if (!obj) {
return NS_ERROR_FAILURE;
}
JS::AutoObjectRooter r(cx, obj);
if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) {
return NS_ERROR_FAILURE;
}
*ret = OBJECT_TO_JSVAL(obj);
return NS_OK;
}
bool
TelemetryImpl::GetSQLStats(JSContext *cx, jsval *ret, bool includePrivateSql)
{
JSObject *root_obj = JS_NewObject(cx, NULL, NULL, NULL);
if (!root_obj)
return false;
*ret = OBJECT_TO_JSVAL(root_obj);
MutexAutoLock hashMutex(mHashMutex);
// Add info about slow SQL queries on the main thread
if (!AddSQLInfo(cx, root_obj, true, includePrivateSql))
return false;
// Add info about slow SQL queries on other threads
if (!AddSQLInfo(cx, root_obj, false, includePrivateSql))
return false;
return true;
}
NS_IMETHODIMP
TelemetryImpl::GetSlowSQL(JSContext *cx, jsval *ret)
{
if (GetSQLStats(cx, ret, false))
return NS_OK;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
TelemetryImpl::GetDebugSlowSQL(JSContext *cx, jsval *ret)
{
bool revealPrivateSql =
Preferences::GetBool("toolkit.telemetry.debugSlowSql", false);
if (GetSQLStats(cx, ret, revealPrivateSql))
return NS_OK;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
{
MutexAutoLock hangReportMutex(mHangReportsMutex);
JSObject *reportArray = JS_NewArrayObject(cx, 0, nullptr);
if (!reportArray) {
return NS_ERROR_FAILURE;
}
*ret = OBJECT_TO_JSVAL(reportArray);
// Each hang report is an object in the 'chromeHangs' array
for (size_t i = 0; i < mHangReports.Length(); ++i) {
Telemetry::ProcessedStack &stack = mHangReports[i].mStack;
JSObject *reportObj = JS_NewObject(cx, NULL, NULL, NULL);
if (!reportObj) {
return NS_ERROR_FAILURE;
}
jsval reportObjVal = OBJECT_TO_JSVAL(reportObj);
if (!JS_SetElement(cx, reportArray, i, &reportObjVal)) {
return NS_ERROR_FAILURE;
}
// Record the hang duration (expressed in seconds)
JSBool ok = JS_DefineProperty(cx, reportObj, "duration",
INT_TO_JSVAL(mHangReports[i].duration),
NULL, NULL, JSPROP_ENUMERATE);
if (!ok) {
return NS_ERROR_FAILURE;
}
// Represent call stack PCs as strings
// (JS can't represent all 64-bit integer values)
JSObject *pcArray = JS_NewArrayObject(cx, 0, nullptr);
if (!pcArray) {
return NS_ERROR_FAILURE;
}
ok = JS_DefineProperty(cx, reportObj, "stack", OBJECT_TO_JSVAL(pcArray),
NULL, NULL, JSPROP_ENUMERATE);
if (!ok) {
return NS_ERROR_FAILURE;
}
const uint32_t pcCount = stack.GetStackSize();
for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
nsAutoCString pcString;
const Telemetry::ProcessedStack::Frame &Frame = stack.GetFrame(pcIndex);
pcString.AppendPrintf("0x%p", Frame.mOffset);
JSString *str = JS_NewStringCopyZ(cx, pcString.get());
if (!str) {
return NS_ERROR_FAILURE;
}
jsval v = STRING_TO_JSVAL(str);
if (!JS_SetElement(cx, pcArray, pcIndex, &v)) {
return NS_ERROR_FAILURE;
}
}
// Record memory map info
JSObject *moduleArray = JS_NewArrayObject(cx, 0, nullptr);
if (!moduleArray) {
return NS_ERROR_FAILURE;
}
ok = JS_DefineProperty(cx, reportObj, "memoryMap",
OBJECT_TO_JSVAL(moduleArray),
NULL, NULL, JSPROP_ENUMERATE);
if (!ok) {
return NS_ERROR_FAILURE;
}
const uint32_t moduleCount = stack.GetNumModules();
for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
// Current module
const Telemetry::ProcessedStack::Module &module =
stack.GetModule(moduleIndex);
JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nullptr);
if (!moduleInfoArray) {
return NS_ERROR_FAILURE;
}
jsval val = OBJECT_TO_JSVAL(moduleInfoArray);
if (!JS_SetElement(cx, moduleArray, moduleIndex, &val)) {
return NS_ERROR_FAILURE;
}
// Start address
nsAutoCString addressString;
addressString.AppendPrintf("0x%p", module.mStart);
JSString *str = JS_NewStringCopyZ(cx, addressString.get());
if (!str) {
return NS_ERROR_FAILURE;
}
val = STRING_TO_JSVAL(str);
if (!JS_SetElement(cx, moduleInfoArray, 0, &val)) {
return NS_ERROR_FAILURE;
}
// Module name
str = JS_NewStringCopyZ(cx, module.mName.c_str());
if (!str) {
return NS_ERROR_FAILURE;
}
val = STRING_TO_JSVAL(str);
if (!JS_SetElement(cx, moduleInfoArray, 1, &val)) {
return NS_ERROR_FAILURE;
}
// Module size in memory
val = INT_TO_JSVAL(int32_t(module.mMappingSize));
if (!JS_SetElement(cx, moduleInfoArray, 2, &val)) {
return NS_ERROR_FAILURE;
}
// "PDB Age" identifier
val = INT_TO_JSVAL(module.mPdbAge);
if (!JS_SetElement(cx, moduleInfoArray, 3, &val)) {
return NS_ERROR_FAILURE;
}
// "PDB Signature" GUID
str = JS_NewStringCopyZ(cx, module.mPdbSignature.c_str());
if (!str) {
return NS_ERROR_FAILURE;
}
val = STRING_TO_JSVAL(str);
if (!JS_SetElement(cx, moduleInfoArray, 4, &val)) {
return NS_ERROR_FAILURE;
}
// Name of associated PDB file
str = JS_NewStringCopyZ(cx, module.mPdbName.c_str());
if (!str) {
return NS_ERROR_FAILURE;
}
val = STRING_TO_JSVAL(str);
if (!JS_SetElement(cx, moduleInfoArray, 5, &val)) {
return NS_ERROR_FAILURE;
}
}
}
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::GetRegisteredHistograms(JSContext *cx, jsval *ret)
{
size_t count = ArrayLength(gHistograms);
JSObject *info = JS_NewObject(cx, NULL, NULL, NULL);
if (!info)
return NS_ERROR_FAILURE;
JS::AutoObjectRooter root(cx, info);
for (size_t i = 0; i < count; ++i) {
JSString *comment = JS_InternString(cx, gHistograms[i].comment());
if (!(comment
&& JS_DefineProperty(cx, info, gHistograms[i].id(),
STRING_TO_JSVAL(comment), NULL, NULL,
JSPROP_ENUMERATE))) {
return NS_ERROR_FAILURE;
}
}
*ret = OBJECT_TO_JSVAL(info);
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, jsval *ret)
{
Histogram *h;
nsresult rv = GetHistogramByName(name, &h);
if (NS_FAILED(rv))
return rv;
return WrapAndReturnHistogram(h, cx, ret);
}
NS_IMETHODIMP
TelemetryImpl::GetCanRecord(bool *ret) {
*ret = mCanRecord;
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::SetCanRecord(bool canRecord) {
mCanRecord = !!canRecord;
return NS_OK;
}
bool
TelemetryImpl::CanRecord() {
return !sTelemetry || sTelemetry->mCanRecord;
}
NS_IMETHODIMP
TelemetryImpl::GetCanSend(bool *ret) {
#if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING)
*ret = true;
#else
*ret = false;
#endif
return NS_OK;
}
already_AddRefed<nsITelemetry>
TelemetryImpl::CreateTelemetryInstance()
{
NS_ABORT_IF_FALSE(sTelemetry == NULL, "CreateTelemetryInstance may only be called once, via GetService()");
sTelemetry = new TelemetryImpl();
// AddRef for the local reference
NS_ADDREF(sTelemetry);
// AddRef for the caller
NS_ADDREF(sTelemetry);
return sTelemetry;
}
void
TelemetryImpl::ShutdownTelemetry()
{
NS_IF_RELEASE(sTelemetry);
}
void
TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay,
SanitizedState state)
{
AutoHashtable<SlowSQLEntryType> *slowSQLMap = NULL;
if (state == Sanitized)
slowSQLMap = &(sTelemetry->mSanitizedSQL);
else
slowSQLMap = &(sTelemetry->mPrivateSQL);
MutexAutoLock hashMutex(sTelemetry->mHashMutex);
SlowSQLEntryType *entry = slowSQLMap->GetEntry(sql);
if (!entry) {
entry = slowSQLMap->PutEntry(sql);
if (NS_UNLIKELY(!entry))
return;
entry->mData.mainThread.hitCount = 0;
entry->mData.mainThread.totalTime = 0;
entry->mData.otherThreads.hitCount = 0;
entry->mData.otherThreads.totalTime = 0;
}
if (NS_IsMainThread()) {
entry->mData.mainThread.hitCount++;
entry->mData.mainThread.totalTime += delay;
} else {
entry->mData.otherThreads.hitCount++;
entry->mData.otherThreads.totalTime += delay;
}
}
/**
* This method replaces string literals in SQL strings with the word :private
*
* States used in this state machine:
*
* NORMAL:
* - This is the active state when not iterating over a string literal or
* comment
*
* SINGLE_QUOTE:
* - Defined here: http://www.sqlite.org/lang_expr.html
* - This state represents iterating over a string literal opened with
* a single quote.
* - A single quote within the string can be encoded by putting 2 single quotes
* in a row, e.g. 'This literal contains an escaped quote '''
* - Any double quotes found within a single-quoted literal are ignored
* - This state covers BLOB literals, e.g. X'ABC123'
* - The string literal and the enclosing quotes will be replaced with
* the text :private
*
* DOUBLE_QUOTE:
* - Same rules as the SINGLE_QUOTE state.
* - According to http://www.sqlite.org/lang_keywords.html,
* SQLite interprets text in double quotes as an identifier unless it's used in
* a context where it cannot be resolved to an identifier and a string literal
* is allowed. This method removes text in double-quotes for safety.
*
* DASH_COMMENT:
* - http://www.sqlite.org/lang_comment.html
* - A dash comment starts with two dashes in a row,
* e.g. DROP TABLE foo -- a comment
* - Any text following two dashes in a row is interpreted as a comment until
* end of input or a newline character
* - Any quotes found within the comment are ignored and no replacements made
*
* C_STYLE_COMMENT:
* - http://www.sqlite.org/lang_comment.html
* - A C-style comment starts with a forward slash and an asterisk, and ends
* with an asterisk and a forward slash
* - Any text following comment start is interpreted as a comment up to end of
* input or comment end
* - Any quotes found within the comment are ignored and no replacements made
*/
nsCString
TelemetryImpl::SanitizeSQL(const nsACString &sql) {
nsCString output;
int length = sql.Length();
typedef enum {
NORMAL,
SINGLE_QUOTE,
DOUBLE_QUOTE,
DASH_COMMENT,
C_STYLE_COMMENT,
} State;
State state = NORMAL;
int fragmentStart = 0;
for (int i = 0; i < length; i++) {
char character = sql[i];
char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0';
switch (character) {
case '\'':
case '"':
if (state == NORMAL) {
state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE;
output += nsDependentCSubstring(sql, fragmentStart, i - fragmentStart);
output += ":private";
fragmentStart = -1;
} else if ((state == SINGLE_QUOTE && character == '\'') ||
(state == DOUBLE_QUOTE && character == '"')) {
if (nextCharacter == character) {
// Two consecutive quotes within a string literal are a single escaped quote
i++;
} else {
state = NORMAL;
fragmentStart = i + 1;
}
}
break;
case '-':
if (state == NORMAL) {
if (nextCharacter == '-') {
state = DASH_COMMENT;
i++;
}
}
break;
case '\n':
if (state == DASH_COMMENT) {
state = NORMAL;
}
break;
case '/':
if (state == NORMAL) {
if (nextCharacter == '*') {
state = C_STYLE_COMMENT;
i++;
}
}
break;
case '*':
if (state == C_STYLE_COMMENT) {
if (nextCharacter == '/') {
state = NORMAL;
}
}
break;
default:
continue;
}
}
if ((fragmentStart >= 0) && fragmentStart < length)
output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart);
return output;
}
void
TelemetryImpl::RecordSlowStatement(const nsACString &sql,
const nsACString &dbName,
uint32_t delay)
{
if (!sTelemetry || !sTelemetry->mCanRecord)
return;
nsAutoCString fullSQL(sql);
fullSQL.AppendPrintf(" /* %s */", dbName.BeginReading());
bool isFirefoxDB = sTelemetry->mTrackedDBs.Contains(dbName);
if (isFirefoxDB) {
nsAutoCString sanitizedSQL(SanitizeSQL(fullSQL));
StoreSlowSQL(sanitizedSQL, delay, Sanitized);
} else {
// Report aggregate DB-level statistics for addon DBs
nsAutoCString aggregate;
aggregate.AppendPrintf("Untracked SQL for %s", dbName.BeginReading());
StoreSlowSQL(aggregate, delay, Sanitized);
}
StoreSlowSQL(fullSQL, delay, Unsanitized);
}
#if defined(MOZ_ENABLE_PROFILER_SPS)
void
TelemetryImpl::RecordChromeHang(uint32_t duration,
Telemetry::ProcessedStack &aStack)
{
if (!sTelemetry || !sTelemetry->mCanRecord)
return;
MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
// Only report the modules which changed since the first hang report
if (sTelemetry->mHangReports.Length()) {
Telemetry::ProcessedStack &firstStack =
sTelemetry->mHangReports[0].mStack;
for (size_t i = 0; i < aStack.GetNumModules(); ++i) {
const Telemetry::ProcessedStack::Module &module = aStack.GetModule(i);
if (firstStack.HasModule(module)) {
aStack.RemoveModule(i);
--i;
}
}
}
HangReport newReport = { duration, aStack };
sTelemetry->mHangReports.AppendElement(newReport);
}
#endif
NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetryImpl, nsITelemetry)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
#define NS_TELEMETRY_CID \
{0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}}
NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID);
const Module::CIDEntry kTelemetryCIDs[] = {
{ &kNS_TELEMETRY_CID, false, NULL, nsITelemetryConstructor },
{ NULL }
};
const Module::ContractIDEntry kTelemetryContracts[] = {
{ "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID },
{ NULL }
};
const Module kTelemetryModule = {
Module::kVersion,
kTelemetryCIDs,
kTelemetryContracts,
NULL,
NULL,
NULL,
TelemetryImpl::ShutdownTelemetry
};
} // anonymous namespace
namespace mozilla {
namespace Telemetry {
void
Accumulate(ID aHistogram, uint32_t aSample)
{
if (!TelemetryImpl::CanRecord()) {
return;
}
Histogram *h;
nsresult rv = GetHistogramByEnumId(aHistogram, &h);
if (NS_SUCCEEDED(rv))
h->Add(aSample);
}
void
AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
{
Accumulate(aHistogram,
static_cast<uint32_t>((end - start).ToMilliseconds()));
}
bool
CanRecord()
{
return TelemetryImpl::CanRecord();
}
base::Histogram*
GetHistogramById(ID id)
{
Histogram *h = NULL;
GetHistogramByEnumId(id, &h);
return h;
}
void
RecordSlowSQLStatement(const nsACString &statement,
const nsACString &dbName,
uint32_t delay)
{
TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
}
void Init()
{
// Make the service manager hold a long-lived reference to the service
nsCOMPtr<nsITelemetry> telemetryService =
do_GetService("@mozilla.org/base/telemetry;1");
MOZ_ASSERT(telemetryService);
}
#if defined(MOZ_ENABLE_PROFILER_SPS)
void RecordChromeHang(uint32_t duration,
ProcessedStack &aStack)
{
TelemetryImpl::RecordChromeHang(duration, aStack);
}
#endif
ProcessedStack::ProcessedStack()
{
}
size_t ProcessedStack::GetStackSize() const
{
return mStack.size();
}
const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const
{
MOZ_ASSERT(aIndex < mStack.size());
return mStack[aIndex];
}
void ProcessedStack::AddFrame(const Frame &aFrame)
{
mStack.push_back(aFrame);
}
size_t ProcessedStack::GetNumModules() const
{
return mModules.size();
}
const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
{
MOZ_ASSERT(aIndex < mModules.size());
return mModules[aIndex];
}
bool ProcessedStack::HasModule(const Module &aModule) const {
return mModules.end() !=
std::find(mModules.begin(), mModules.end(), aModule);
}
void ProcessedStack::RemoveModule(unsigned aIndex) {
mModules.erase(mModules.begin() + aIndex);
}
void ProcessedStack::AddModule(const Module &aModule)
{
mModules.push_back(aModule);
}
void ProcessedStack::Clear() {
mModules.clear();
mStack.clear();
}
bool ProcessedStack::Module::operator==(const Module& aOther) const {
return mName == aOther.mName &&
mStart == aOther.mStart &&
mMappingSize == aOther.mMappingSize &&
mPdbAge == aOther.mPdbAge &&
mPdbSignature == aOther.mPdbSignature &&
mPdbName == aOther.mPdbName;
}
struct StackFrame
{
uintptr_t mPC; // The program counter at this position in the call stack.
uint16_t mIndex; // The number of this frame in the call stack.
uint16_t mModIndex; // The index of module that has this program counter.
};
#ifdef MOZ_ENABLE_PROFILER_SPS
static bool CompareByPC(const StackFrame &a, const StackFrame &b)
{
return a.mPC < b.mPC;
}
static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
{
return a.mIndex < b.mIndex;
}
#endif
ProcessedStack GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative)
{
std::vector<StackFrame> rawStack;
for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(),
e = aPCs.end(); i != e; ++i) {
uintptr_t aPC = *i;
StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
std::numeric_limits<uint16_t>::max()};
rawStack.push_back(Frame);
}
#ifdef MOZ_ENABLE_PROFILER_SPS
// Remove all modules not referenced by a PC on the stack
std::sort(rawStack.begin(), rawStack.end(), CompareByPC);
size_t moduleIndex = 0;
size_t stackIndex = 0;
size_t stackSize = rawStack.size();
SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
rawModules.SortByAddress();
while (moduleIndex < rawModules.GetSize()) {
const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
uintptr_t moduleStart = module.GetStart();
uintptr_t moduleEnd = module.GetEnd() - 1;
// the interval is [moduleStart, moduleEnd)
bool moduleReferenced = false;
for (;stackIndex < stackSize; ++stackIndex) {
uintptr_t pc = rawStack[stackIndex].mPC;
if (pc >= moduleEnd)
break;
if (pc >= moduleStart) {
// If the current PC is within the current module, mark
// module as used
moduleReferenced = true;
if (aRelative)
rawStack[stackIndex].mPC -= moduleStart;
rawStack[stackIndex].mModIndex = moduleIndex;
} else {
// PC does not belong to any module. It is probably from
// the JIT. Use a fixed mPC so that we don't get different
// stacks on different runs.
rawStack[stackIndex].mPC =
std::numeric_limits<uintptr_t>::max();
}
}
if (moduleReferenced) {
++moduleIndex;
} else {
// Remove module if no PCs within its address range
rawModules.RemoveEntries(moduleIndex, moduleIndex + 1);
}
}
for (;stackIndex < stackSize; ++stackIndex) {
// These PCs are past the last module.
rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
}
std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
#endif
// Copy the information to the return value.
ProcessedStack Ret;
for (std::vector<StackFrame>::iterator i = rawStack.begin(),
e = rawStack.end(); i != e; ++i) {
const StackFrame &rawFrame = *i;
ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex };
Ret.AddFrame(frame);
}
#ifdef MOZ_ENABLE_PROFILER_SPS
for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
const SharedLibrary &info = rawModules.GetEntry(i);
ProcessedStack::Module module = {
info.GetName(),
info.GetStart(),
info.GetEnd() - info.GetStart(),
#ifdef XP_WIN
info.GetPdbAge(),
"", // mPdbSignature
info.GetPdbName(),
#else
0, // mPdbAge
"", // mPdbSignature
"" // mPdbName
#endif
};
#ifdef XP_WIN
char guidString[NSID_LENGTH] = { 0 };
info.GetPdbSignature().ToProvidedString(guidString);
module.mPdbSignature = guidString;
#endif
Ret.AddModule(module);
}
#endif
return Ret;
}
} // namespace Telemetry
} // namespace mozilla
NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule;
/**
* The XRE_TelemetryAdd function is to be used by embedding applications
* that can't use mozilla::Telemetry::Accumulate() directly.
*/
void
XRE_TelemetryAccumulate(int aID, uint32_t aSample)
{
mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample);
}