/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla code. * * The Initial Developer of the Original Code is * Mozilla Corporation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Taras Glek * Vladan Djeric * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #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 "nsStringGlue.h" #include "nsITelemetry.h" #include "nsIFile.h" #include "nsILocalFile.h" #include "Telemetry.h" #include "nsTHashtable.h" #include "nsHashKeys.h" #include "nsBaseHashtable.h" #include "nsXULAppAPI.h" #include "nsThreadUtils.h" #include "mozilla/Mutex.h" #include "mozilla/FileUtils.h" namespace { using namespace base; using namespace mozilla; class TelemetryImpl : public nsITelemetry { NS_DECL_ISUPPORTS NS_DECL_NSITELEMETRY public: TelemetryImpl(); ~TelemetryImpl(); static bool CanRecord(); static already_AddRefed CreateTelemetryInstance(); static void ShutdownTelemetry(); static void RecordSlowStatement(const nsACString &statement, const nsACString &dbName, PRUint32 delay); static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id); struct StmtStats { PRUint32 hitCount; PRUint32 totalTime; }; typedef nsBaseHashtableET SlowSQLEntryType; private: bool AddSQLInfo(JSContext *cx, JSObject *rootObj, bool mainThread); // 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; // This is used for speedy string->Telemetry::ID conversions typedef nsBaseHashtableET CharPtrEntryType; typedef nsTHashtable HistogramMapType; HistogramMapType mHistogramMap; bool mCanRecord; static TelemetryImpl *sTelemetry; nsTHashtable mSlowSQLOnMainThread; nsTHashtable mSlowSQLOnOtherThread; nsTHashtable mTrackedDBs; Mutex mHashMutex; }; TelemetryImpl* TelemetryImpl::sTelemetry = NULL; // A initializer to initialize histogram collection StatisticsRecorder gStatisticsRecorder; // Hardcoded probes struct TelemetryHistogram { const char *id; PRUint32 min; PRUint32 max; PRUint32 bucketCount; PRUint32 histogramType; const char *comment; }; // Perform the checks at the beginning of HistogramGet at compile time, so // that if people add incorrect histogram definitions, they get compiler // errors. #define HISTOGRAM(id, min, max, bucket_count, histogram_type, b) \ PR_STATIC_ASSERT(nsITelemetry::HISTOGRAM_ ## histogram_type == nsITelemetry::HISTOGRAM_BOOLEAN || \ (min < max && bucket_count > 2 && min >= 1)); #include "TelemetryHistograms.h" #undef HISTOGRAM const TelemetryHistogram gHistograms[] = { #define HISTOGRAM(id, min, max, bucket_count, histogram_type, comment) \ { NS_STRINGIFY(id), min, max, bucket_count, \ nsITelemetry::HISTOGRAM_ ## histogram_type, comment }, #include "TelemetryHistograms.h" #undef HISTOGRAM }; bool gCorruptHistograms[Telemetry::HistogramCount]; bool TelemetryHistogramType(Histogram *h, PRUint32 *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; default: return false; } return true; } nsresult HistogramGet(const char *name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, PRUint32 histogramType, Histogram **result) { if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN) { // 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; 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; *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; } JSObject *counts_array; JSObject *rarray; const size_t count = h->bucket_count(); 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) && (rarray = JS_NewArrayObject(cx, count, NULL)) && JS_DefineProperty(cx, obj, "ranges", OBJECT_TO_JSVAL(rarray), NULL, NULL, JSPROP_ENUMERATE) && FillRanges(cx, rarray, h) && (counts_array = JS_NewArrayObject(cx, count, NULL)) && 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); } JSBool JSHistogram_Add(JSContext *cx, uintN argc, jsval *vp) { if (!argc) { JS_ReportError(cx, "Expected one argument"); return JS_FALSE; } jsval v = JS_ARGV(cx, vp)[0]; int32 value; if (!(JSVAL_IS_NUMBER(v) || JSVAL_IS_BOOLEAN(v))) { JS_ReportError(cx, "Not a number"); return JS_FALSE; } 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(JS_GetPrivate(cx, obj)); if (h->histogram_type() == Histogram::BOOLEAN_HISTOGRAM) h->Add(!!value); else h->Add(value); } return JS_TRUE; } JSBool JSHistogram_Snapshot(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj = JS_THIS_OBJECT(cx, vp); if (!obj) { return JS_FALSE; } Histogram *h = static_cast(JS_GetPrivate(cx, obj)); JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL); if (!snapshot) return JS_FALSE; 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; } } 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, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; JSObject *obj = JS_NewObject(cx, &JSHistogram_class, NULL, NULL); if (!obj) return NS_ERROR_FAILURE; *ret = OBJECT_TO_JSVAL(obj); return (JS_SetPrivate(cx, obj, h) && JS_DefineFunction (cx, obj, "add", JSHistogram_Add, 1, 0) && JS_DefineFunction (cx, obj, "snapshot", JSHistogram_Snapshot, 1, 0)) ? NS_OK : NS_ERROR_FAILURE; } TelemetryImpl::TelemetryImpl(): mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default), mHashMutex("Telemetry::mHashMutex") { // 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 mSlowSQLOnMainThread.Init(); mSlowSQLOnOtherThread.Init(); mHistogramMap.Init(Telemetry::HistogramCount); } TelemetryImpl::~TelemetryImpl() { mSlowSQLOnMainThread.Clear(); mSlowSQLOnOtherThread.Clear(); mHistogramMap.Clear(); } NS_IMETHODIMP TelemetryImpl::NewHistogram(const nsACString &name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, PRUint32 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); } struct EnumeratorArgs { JSContext *cx; JSObject *statsObj; }; PLDHashOperator StatementEnumerator(TelemetryImpl::SlowSQLEntryType *entry, void *arg) { EnumeratorArgs *args = static_cast(arg); const nsACString &sql = entry->GetKey(); jsval hitCount = UINT_TO_JSVAL(entry->mData.hitCount); jsval totalTime = UINT_TO_JSVAL(entry->mData.totalTime); JSObject *arrayObj = JS_NewArrayObject(args->cx, 2, nsnull); if (!arrayObj || !JS_SetElement(args->cx, arrayObj, 0, &hitCount) || !JS_SetElement(args->cx, arrayObj, 1, &totalTime)) return PL_DHASH_STOP; JSBool success = JS_DefineProperty(args->cx, args->statsObj, sql.BeginReading(), OBJECT_TO_JSVAL(arrayObj), NULL, NULL, JSPROP_ENUMERATE); if (!success) return PL_DHASH_STOP; return PL_DHASH_NEXT; } bool TelemetryImpl::AddSQLInfo(JSContext *cx, JSObject *rootObj, bool mainThread) { JSObject *statsObj = JS_NewObject(cx, NULL, NULL, NULL); if (!statsObj) return false; JSBool ok = JS_DefineProperty(cx, rootObj, mainThread ? "mainThread" : "otherThreads", OBJECT_TO_JSVAL(statsObj), NULL, NULL, JSPROP_ENUMERATE); if (!ok) return false; EnumeratorArgs args = { cx, statsObj }; nsTHashtable *sqlMap; sqlMap = (mainThread ? &mSlowSQLOnMainThread : &mSlowSQLOnOtherThread); PRUint32 num = sqlMap->EnumerateEntries(StatementEnumerator, static_cast(&args)); if (num != sqlMap->Count()) return false; return true; } 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 (PRUint32 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; PRUint32 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]; } } 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); 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)) { continue; } JSObject *hobj = JS_NewObject(cx, NULL, NULL, NULL); if (!hobj) { return NS_ERROR_FAILURE; } 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; } NS_IMETHODIMP TelemetryImpl::GetSlowSQL(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); MutexAutoLock hashMutex(mHashMutex); // Add info about slow SQL queries on the main thread if (!AddSQLInfo(cx, root_obj, true)) return NS_ERROR_FAILURE; // Add info about slow SQL queries on other threads if (!AddSQLInfo(cx, root_obj, false)) 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; 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); } class TelemetrySessionData : public nsITelemetrySessionData { NS_DECL_ISUPPORTS NS_DECL_NSITELEMETRYSESSIONDATA public: static nsresult LoadFromDisk(nsIFile *, TelemetrySessionData **ptr); static nsresult SaveToDisk(nsIFile *, const nsACString &uuid); TelemetrySessionData(const char *uuid); ~TelemetrySessionData(); private: struct EnumeratorArgs { JSContext *cx; JSObject *snapshots; }; typedef nsBaseHashtableET EntryType; typedef nsTHashtable SessionMapType; static PLDHashOperator ReflectSamples(EntryType *entry, void *arg); SessionMapType mSampleSetMap; nsCString mUUID; bool DeserializeHistogramData(Pickle &pickle, void **iter); static bool SerializeHistogramData(Pickle &pickle); // The file format version. Should be incremented whenever we change // how individual SampleSets are stored in the file. static const unsigned int sVersion = 1; }; NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetrySessionData, nsITelemetrySessionData) TelemetrySessionData::TelemetrySessionData(const char *uuid) : mUUID(uuid) { mSampleSetMap.Init(); } TelemetrySessionData::~TelemetrySessionData() { mSampleSetMap.Clear(); } NS_IMETHODIMP TelemetrySessionData::GetUuid(nsACString &uuid) { uuid = mUUID; return NS_OK; } PLDHashOperator TelemetrySessionData::ReflectSamples(EntryType *entry, void *arg) { struct EnumeratorArgs *args = static_cast(arg); // This has the undesirable effect of creating a histogram for the // current session with the given ID. But there's no good way to // compute the ranges and buckets from scratch. Histogram *h = nsnull; nsresult rv = GetHistogramByEnumId(Telemetry::ID(entry->GetKey()), &h); if (NS_FAILED(rv)) { return PL_DHASH_STOP; } // Don't reflect histograms with no data associated with them. if (entry->mData.sum() == 0) { return PL_DHASH_NEXT; } JSObject *snapshot = JS_NewObject(args->cx, NULL, NULL, NULL); if (!(snapshot && ReflectHistogramAndSamples(args->cx, snapshot, h, entry->mData) && JS_DefineProperty(args->cx, args->snapshots, h->histogram_name().c_str(), OBJECT_TO_JSVAL(snapshot), NULL, NULL, JSPROP_ENUMERATE))) { return PL_DHASH_STOP; } return PL_DHASH_NEXT; } NS_IMETHODIMP TelemetrySessionData::GetSnapshots(JSContext *cx, jsval *ret) { JSObject *snapshots = JS_NewObject(cx, NULL, NULL, NULL); if (!snapshots) { return NS_ERROR_FAILURE; } struct EnumeratorArgs args = { cx, snapshots }; PRUint32 count = mSampleSetMap.EnumerateEntries(ReflectSamples, static_cast(&args)); if (count != mSampleSetMap.Count()) { return NS_ERROR_FAILURE; } *ret = OBJECT_TO_JSVAL(snapshots); return NS_OK; } bool TelemetrySessionData::DeserializeHistogramData(Pickle &pickle, void **iter) { PRUint32 count = 0; if (!pickle.ReadUInt32(iter, &count)) { return false; } for (size_t i = 0; i < count; ++i) { int stored_length; const char *name; if (!pickle.ReadData(iter, &name, &stored_length)) { return false; } Telemetry::ID id; nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id); if (NS_FAILED(rv)) { // We serialized a non-static histogram. Just drop its data on // the floor. If we can't deserialize the data, though, we're in // trouble. Histogram::SampleSet ss; if (!ss.Deserialize(iter, pickle)) { return false; } } EntryType *entry = mSampleSetMap.GetEntry(id); if (!entry) { entry = mSampleSetMap.PutEntry(id); if (NS_UNLIKELY(!entry)) { return false; } if (!entry->mData.Deserialize(iter, pickle)) { return false; } } } return true; } nsresult TelemetrySessionData::LoadFromDisk(nsIFile *file, TelemetrySessionData **ptr) { *ptr = nsnull; nsresult rv; nsCOMPtr f(do_QueryInterface(file, &rv)); if (NS_FAILED(rv)) { return rv; } AutoFDClose fd; rv = f->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } PRInt32 size = PR_Available(fd); if (size == -1) { return NS_ERROR_FAILURE; } nsAutoArrayPtr data(new char[size]); PRInt32 amount = PR_Read(fd, data, size); if (amount != size) { return NS_ERROR_FAILURE; } Pickle pickle(data, size); void *iter = NULL; unsigned int storedVersion; if (!(pickle.ReadUInt32(&iter, &storedVersion) && storedVersion == sVersion)) { return NS_ERROR_FAILURE; } const char *uuid; int uuidLength; if (!pickle.ReadData(&iter, &uuid, &uuidLength)) { return NS_ERROR_FAILURE; } nsAutoPtr sessionData(new TelemetrySessionData(uuid)); if (!sessionData->DeserializeHistogramData(pickle, &iter)) { return NS_ERROR_FAILURE; } *ptr = sessionData.forget(); return NS_OK; } bool TelemetrySessionData::SerializeHistogramData(Pickle &pickle) { StatisticsRecorder::Histograms hs; StatisticsRecorder::GetHistograms(&hs); if (!pickle.WriteUInt32(hs.size())) { return false; } for (StatisticsRecorder::Histograms::const_iterator it = hs.begin(); it != hs.end(); ++it) { const Histogram *h = *it; const char *name = h->histogram_name().c_str(); Histogram::SampleSet ss; h->SnapshotSample(&ss); if (!(pickle.WriteData(name, strlen(name)+1) && ss.Serialize(&pickle))) { return false; } } return true; } nsresult TelemetrySessionData::SaveToDisk(nsIFile *file, const nsACString &uuid) { nsresult rv; nsCOMPtr f(do_QueryInterface(file, &rv)); if (NS_FAILED(rv)) { return rv; } AutoFDClose fd; rv = f->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd); if (NS_FAILED(rv)) { return rv; } Pickle pickle; if (!pickle.WriteUInt32(sVersion)) { return NS_ERROR_FAILURE; } // Include the trailing NULL for the UUID to make reading easier. const char *data; size_t length = uuid.GetData(&data); if (!(pickle.WriteData(data, length+1) && SerializeHistogramData(pickle))) { return NS_ERROR_FAILURE; } PRInt32 amount = PR_Write(fd, static_cast(pickle.data()), pickle.size()); if (amount != pickle.size()) { return NS_ERROR_FAILURE; } return NS_OK; } class SaveHistogramEvent : public nsRunnable { public: SaveHistogramEvent(nsIFile *file, const nsACString &uuid, nsITelemetrySaveSessionDataCallback *callback) : mFile(file), mUUID(uuid), mCallback(callback) {} NS_IMETHOD Run() { nsresult rv = TelemetrySessionData::SaveToDisk(mFile, mUUID); mCallback->Handle(!!NS_SUCCEEDED(rv)); return rv; } private: nsCOMPtr mFile; nsCString mUUID; nsCOMPtr mCallback; }; NS_IMETHODIMP TelemetryImpl::SaveHistograms(nsIFile *file, const nsACString &uuid, nsITelemetrySaveSessionDataCallback *callback, bool isSynchronous) { nsCOMPtr event = new SaveHistogramEvent(file, uuid, callback); if (isSynchronous) { return event ? event->Run() : NS_ERROR_FAILURE; } else { return NS_DispatchToCurrentThread(event); } } class LoadHistogramEvent : public nsRunnable { public: LoadHistogramEvent(nsIFile *file, nsITelemetryLoadSessionDataCallback *callback) : mFile(file), mCallback(callback) {} NS_IMETHOD Run() { TelemetrySessionData *sessionData = nsnull; nsresult rv = TelemetrySessionData::LoadFromDisk(mFile, &sessionData); if (NS_FAILED(rv)) { mCallback->Handle(nsnull); } else { nsCOMPtr data(sessionData); mCallback->Handle(data); } return rv; } private: nsCOMPtr mFile; nsCOMPtr mCallback; }; NS_IMETHODIMP TelemetryImpl::LoadHistograms(nsIFile *file, nsITelemetryLoadSessionDataCallback *callback) { nsCOMPtr event = new LoadHistogramEvent(file, callback); return NS_DispatchToCurrentThread(event); } 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; } already_AddRefed 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::RecordSlowStatement(const nsACString &statement, const nsACString &dbName, PRUint32 delay) { MOZ_ASSERT(sTelemetry); if (!sTelemetry->mCanRecord || !sTelemetry->mTrackedDBs.GetEntry(dbName)) return; nsTHashtable *slowSQLMap = NULL; if (NS_IsMainThread()) slowSQLMap = &(sTelemetry->mSlowSQLOnMainThread); else slowSQLMap = &(sTelemetry->mSlowSQLOnOtherThread); MutexAutoLock hashMutex(sTelemetry->mHashMutex); SlowSQLEntryType *entry = slowSQLMap->GetEntry(statement); if (!entry) { entry = slowSQLMap->PutEntry(statement); if (NS_UNLIKELY(!entry)) return; entry->mData.hitCount = 0; entry->mData.totalTime = 0; } entry->mData.hitCount++; entry->mData.totalTime += delay; } 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, PRUint32 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((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, PRUint32 delay) { TelemetryImpl::RecordSlowStatement(statement, dbName, delay); } void Init() { // Make the service manager hold a long-lived reference to the service nsCOMPtr telemetryService = do_GetService("@mozilla.org/base/telemetry;1"); MOZ_ASSERT(telemetryService); } } // 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, PRUint32 aSample) { mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample); }