michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #include "base/histogram.h" michael@0: #include "base/pickle.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsThreadManager.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsXPCOMPrivate.h" michael@0: #include "nsIXULAppInfo.h" michael@0: #include "nsVersionComparator.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/ModuleUtils.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "mozilla/Services.h" michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: #include "js/GCAPI.h" michael@0: #include "nsString.h" michael@0: #include "nsITelemetry.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIFileStreams.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "Telemetry.h" michael@0: #include "nsTHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsBaseHashtable.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #if defined(XP_WIN) michael@0: #include "nsUnicharUtils.h" michael@0: #endif michael@0: #include "nsNetCID.h" michael@0: #include "nsNetUtil.h" michael@0: #include "plstr.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "mozilla/BackgroundHangMonitor.h" michael@0: #include "mozilla/ThreadHangStats.h" michael@0: #include "mozilla/ProcessedStack.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/FileUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/PoisonIOInterposer.h" michael@0: #include "mozilla/StartupTimeline.h" michael@0: #if defined(MOZ_ENABLE_PROFILER_SPS) michael@0: #include "shared-libraries.h" michael@0: #endif michael@0: michael@0: #define EXPIRED_ID "__expired__" michael@0: michael@0: namespace { michael@0: michael@0: using namespace base; michael@0: using namespace mozilla; michael@0: michael@0: template michael@0: class AutoHashtable : public nsTHashtable michael@0: { michael@0: public: michael@0: AutoHashtable(uint32_t initSize = PL_DHASH_MIN_SIZE); michael@0: typedef bool (*ReflectEntryFunc)(EntryType *entry, JSContext *cx, JS::Handle obj); michael@0: bool ReflectIntoJS(ReflectEntryFunc entryFunc, JSContext *cx, JS::Handle obj); michael@0: private: michael@0: struct EnumeratorArgs { michael@0: JSContext *cx; michael@0: JS::Handle obj; michael@0: ReflectEntryFunc entryFunc; michael@0: }; michael@0: static PLDHashOperator ReflectEntryStub(EntryType *entry, void *arg); michael@0: }; michael@0: michael@0: template michael@0: AutoHashtable::AutoHashtable(uint32_t initSize) michael@0: : nsTHashtable(initSize) michael@0: { michael@0: } michael@0: michael@0: template michael@0: PLDHashOperator michael@0: AutoHashtable::ReflectEntryStub(EntryType *entry, void *arg) michael@0: { michael@0: EnumeratorArgs *args = static_cast(arg); michael@0: if (!args->entryFunc(entry, args->cx, args->obj)) { michael@0: return PL_DHASH_STOP; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /** michael@0: * Reflect the individual entries of table into JS, usually by defining michael@0: * some property and value of obj. entryFunc is called for each entry. michael@0: */ michael@0: template michael@0: bool michael@0: AutoHashtable::ReflectIntoJS(ReflectEntryFunc entryFunc, michael@0: JSContext *cx, JS::Handle obj) michael@0: { michael@0: EnumeratorArgs args = { cx, obj, entryFunc }; michael@0: uint32_t num = this->EnumerateEntries(ReflectEntryStub, static_cast(&args)); michael@0: return num == this->Count(); michael@0: } michael@0: michael@0: // This class is conceptually a list of ProcessedStack objects, but it represents them michael@0: // more efficiently by keeping a single global list of modules. michael@0: class CombinedStacks { michael@0: public: michael@0: typedef std::vector Stack; michael@0: const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const; michael@0: size_t GetModuleCount() const; michael@0: const Stack& GetStack(unsigned aIndex) const; michael@0: void AddStack(const Telemetry::ProcessedStack& aStack); michael@0: size_t GetStackCount() const; michael@0: size_t SizeOfExcludingThis() const; michael@0: private: michael@0: std::vector mModules; michael@0: std::vector mStacks; michael@0: }; michael@0: michael@0: static JSObject * michael@0: CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks); michael@0: michael@0: size_t michael@0: CombinedStacks::GetModuleCount() const { michael@0: return mModules.size(); michael@0: } michael@0: michael@0: const Telemetry::ProcessedStack::Module& michael@0: CombinedStacks::GetModule(unsigned aIndex) const { michael@0: return mModules[aIndex]; michael@0: } michael@0: michael@0: void michael@0: CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) { michael@0: mStacks.resize(mStacks.size() + 1); michael@0: CombinedStacks::Stack& adjustedStack = mStacks.back(); michael@0: michael@0: size_t stackSize = aStack.GetStackSize(); michael@0: for (size_t i = 0; i < stackSize; ++i) { michael@0: const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i); michael@0: uint16_t modIndex; michael@0: if (frame.mModIndex == std::numeric_limits::max()) { michael@0: modIndex = frame.mModIndex; michael@0: } else { michael@0: const Telemetry::ProcessedStack::Module& module = michael@0: aStack.GetModule(frame.mModIndex); michael@0: std::vector::iterator modIterator = michael@0: std::find(mModules.begin(), mModules.end(), module); michael@0: if (modIterator == mModules.end()) { michael@0: mModules.push_back(module); michael@0: modIndex = mModules.size() - 1; michael@0: } else { michael@0: modIndex = modIterator - mModules.begin(); michael@0: } michael@0: } michael@0: Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex }; michael@0: adjustedStack.push_back(adjustedFrame); michael@0: } michael@0: } michael@0: michael@0: const CombinedStacks::Stack& michael@0: CombinedStacks::GetStack(unsigned aIndex) const { michael@0: return mStacks[aIndex]; michael@0: } michael@0: michael@0: size_t michael@0: CombinedStacks::GetStackCount() const { michael@0: return mStacks.size(); michael@0: } michael@0: michael@0: size_t michael@0: CombinedStacks::SizeOfExcludingThis() const { michael@0: // This is a crude approximation. We would like to do something like michael@0: // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call michael@0: // malloc_usable_size which is only safe on the pointers returned by malloc. michael@0: // While it works on current libstdc++, it is better to be safe and not assume michael@0: // that &vec[0] points to one. We could use a custom allocator, but michael@0: // it doesn't seem worth it. michael@0: size_t n = 0; michael@0: n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module); michael@0: n += mStacks.capacity() * sizeof(Stack); michael@0: for (std::vector::const_iterator i = mStacks.begin(), michael@0: e = mStacks.end(); i != e; ++i) { michael@0: const Stack& s = *i; michael@0: n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: class HangReports { michael@0: public: michael@0: size_t SizeOfExcludingThis() const; michael@0: void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration, michael@0: int32_t aSystemUptime, int32_t aFirefoxUptime); michael@0: uint32_t GetDuration(unsigned aIndex) const; michael@0: int32_t GetSystemUptime(unsigned aIndex) const; michael@0: int32_t GetFirefoxUptime(unsigned aIndex) const; michael@0: const CombinedStacks& GetStacks() const; michael@0: private: michael@0: struct HangInfo { michael@0: // Hang duration (in seconds) michael@0: uint32_t mDuration; michael@0: // System uptime (in minutes) at the time of the hang michael@0: int32_t mSystemUptime; michael@0: // Firefox uptime (in minutes) at the time of the hang michael@0: int32_t mFirefoxUptime; michael@0: }; michael@0: std::vector mHangInfo; michael@0: CombinedStacks mStacks; michael@0: }; michael@0: michael@0: void michael@0: HangReports::AddHang(const Telemetry::ProcessedStack& aStack, michael@0: uint32_t aDuration, michael@0: int32_t aSystemUptime, michael@0: int32_t aFirefoxUptime) { michael@0: HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime }; michael@0: mHangInfo.push_back(info); michael@0: mStacks.AddStack(aStack); michael@0: } michael@0: michael@0: size_t michael@0: HangReports::SizeOfExcludingThis() const { michael@0: size_t n = 0; michael@0: n += mStacks.SizeOfExcludingThis(); michael@0: // This is a crude approximation. See comment on michael@0: // CombinedStacks::SizeOfExcludingThis. michael@0: n += mHangInfo.capacity() * sizeof(HangInfo); michael@0: return n; michael@0: } michael@0: michael@0: const CombinedStacks& michael@0: HangReports::GetStacks() const { michael@0: return mStacks; michael@0: } michael@0: michael@0: uint32_t michael@0: HangReports::GetDuration(unsigned aIndex) const { michael@0: return mHangInfo[aIndex].mDuration; michael@0: } michael@0: michael@0: int32_t michael@0: HangReports::GetSystemUptime(unsigned aIndex) const { michael@0: return mHangInfo[aIndex].mSystemUptime; michael@0: } michael@0: michael@0: int32_t michael@0: HangReports::GetFirefoxUptime(unsigned aIndex) const { michael@0: return mHangInfo[aIndex].mFirefoxUptime; michael@0: } michael@0: michael@0: /** michael@0: * IOInterposeObserver recording statistics of main-thread I/O during execution, michael@0: * aimed at consumption by TelemetryImpl michael@0: */ michael@0: class TelemetryIOInterposeObserver : public IOInterposeObserver michael@0: { michael@0: /** File-level statistics structure */ michael@0: struct FileStats { michael@0: FileStats() michael@0: : creates(0) michael@0: , reads(0) michael@0: , writes(0) michael@0: , fsyncs(0) michael@0: , stats(0) michael@0: , totalTime(0) michael@0: {} michael@0: uint32_t creates; /** Number of create/open operations */ michael@0: uint32_t reads; /** Number of read operations */ michael@0: uint32_t writes; /** Number of write operations */ michael@0: uint32_t fsyncs; /** Number of fsync operations */ michael@0: uint32_t stats; /** Number of stat operations */ michael@0: double totalTime; /** Accumulated duration of all operations */ michael@0: }; michael@0: michael@0: struct SafeDir { michael@0: SafeDir(const nsAString& aPath, const nsAString& aSubstName) michael@0: : mPath(aPath) michael@0: , mSubstName(aSubstName) michael@0: {} michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { michael@0: return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + michael@0: mSubstName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: } michael@0: nsString mPath; /** Path to the directory */ michael@0: nsString mSubstName; /** Name to substitute with */ michael@0: }; michael@0: michael@0: public: michael@0: TelemetryIOInterposeObserver(nsIFile* aXreDir); michael@0: michael@0: /** michael@0: * An implementation of Observe that records statistics of all michael@0: * file IO operations. michael@0: */ michael@0: void Observe(Observation& aOb); michael@0: michael@0: /** michael@0: * Reflect recorded file IO statistics into Javascript michael@0: */ michael@0: bool ReflectIntoJS(JSContext *cx, JS::Handle rootObj); michael@0: michael@0: /** michael@0: * Adds a path for inclusion in main thread I/O report. michael@0: * @param aPath Directory path michael@0: * @param aSubstName Name to substitute for aPath for privacy reasons michael@0: */ michael@0: void AddPath(const nsAString& aPath, const nsAString& aSubstName); michael@0: michael@0: /** michael@0: * Get size of hash table with file stats michael@0: */ michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { michael@0: size_t size; michael@0: size = mFileStats.SizeOfExcludingThis(SizeOfFileIOEntryTypeExcludingThis, michael@0: aMallocSizeOf) + michael@0: mSafeDirs.SizeOfExcludingThis(aMallocSizeOf); michael@0: uint32_t safeDirsLen = mSafeDirs.Length(); michael@0: for (uint32_t i = 0; i < safeDirsLen; ++i) { michael@0: size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: return size; michael@0: } michael@0: michael@0: private: michael@0: enum Stage michael@0: { michael@0: STAGE_STARTUP = 0, michael@0: STAGE_NORMAL, michael@0: STAGE_SHUTDOWN, michael@0: NUM_STAGES michael@0: }; michael@0: static inline Stage NextStage(Stage aStage) michael@0: { michael@0: switch (aStage) { michael@0: case STAGE_STARTUP: michael@0: return STAGE_NORMAL; michael@0: case STAGE_NORMAL: michael@0: return STAGE_SHUTDOWN; michael@0: case STAGE_SHUTDOWN: michael@0: return STAGE_SHUTDOWN; michael@0: default: michael@0: return NUM_STAGES; michael@0: } michael@0: } michael@0: michael@0: struct FileStatsByStage michael@0: { michael@0: FileStats mStats[NUM_STAGES]; michael@0: }; michael@0: typedef nsBaseHashtableET FileIOEntryType; michael@0: michael@0: // Statistics for each filename michael@0: AutoHashtable mFileStats; michael@0: // Container for whitelisted directories michael@0: nsTArray mSafeDirs; michael@0: Stage mCurStage; michael@0: michael@0: /** michael@0: * Reflect a FileIOEntryType object to a Javascript property on obj with michael@0: * filename as key containing array: michael@0: * [totalTime, creates, reads, writes, fsyncs, stats] michael@0: */ michael@0: static bool ReflectFileStats(FileIOEntryType* entry, JSContext *cx, michael@0: JS::Handle obj); michael@0: michael@0: static size_t SizeOfFileIOEntryTypeExcludingThis(FileIOEntryType* aEntry, michael@0: mozilla::MallocSizeOf mallocSizeOf, michael@0: void*) michael@0: { michael@0: return aEntry->GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: } michael@0: }; michael@0: michael@0: TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir) michael@0: : mCurStage(STAGE_STARTUP) michael@0: { michael@0: nsAutoString xreDirPath; michael@0: nsresult rv = aXreDir->GetPath(xreDirPath); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: AddPath(xreDirPath, NS_LITERAL_STRING("{xre}")); michael@0: } michael@0: } michael@0: michael@0: void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath, michael@0: const nsAString& aSubstName) michael@0: { michael@0: mSafeDirs.AppendElement(SafeDir(aPath, aSubstName)); michael@0: } michael@0: michael@0: void TelemetryIOInterposeObserver::Observe(Observation& aOb) michael@0: { michael@0: // We only report main-thread I/O michael@0: if (!IsMainThread()) { michael@0: return; michael@0: } michael@0: michael@0: if (aOb.ObservedOperation() == OpNextStage) { michael@0: mCurStage = NextStage(mCurStage); michael@0: MOZ_ASSERT(mCurStage < NUM_STAGES); michael@0: return; michael@0: } michael@0: michael@0: // Get the filename michael@0: const char16_t* filename = aOb.Filename(); michael@0: michael@0: // Discard observations without filename michael@0: if (!filename) { michael@0: return; michael@0: } michael@0: michael@0: #if defined(XP_WIN) michael@0: nsCaseInsensitiveStringComparator comparator; michael@0: #else michael@0: nsDefaultStringComparator comparator; michael@0: #endif michael@0: nsAutoString processedName; michael@0: nsDependentString filenameStr(filename); michael@0: uint32_t safeDirsLen = mSafeDirs.Length(); michael@0: for (uint32_t i = 0; i < safeDirsLen; ++i) { michael@0: if (StringBeginsWith(filenameStr, mSafeDirs[i].mPath, comparator)) { michael@0: processedName = mSafeDirs[i].mSubstName; michael@0: processedName += Substring(filenameStr, mSafeDirs[i].mPath.Length()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (processedName.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: // Create a new entry or retrieve the existing one michael@0: FileIOEntryType* entry = mFileStats.PutEntry(processedName); michael@0: if (entry) { michael@0: FileStats& stats = entry->mData.mStats[mCurStage]; michael@0: // Update the statistics michael@0: stats.totalTime += (double) aOb.Duration().ToMilliseconds(); michael@0: switch (aOb.ObservedOperation()) { michael@0: case OpCreateOrOpen: michael@0: stats.creates++; michael@0: break; michael@0: case OpRead: michael@0: stats.reads++; michael@0: break; michael@0: case OpWrite: michael@0: stats.writes++; michael@0: break; michael@0: case OpFSync: michael@0: stats.fsyncs++; michael@0: break; michael@0: case OpStat: michael@0: stats.stats++; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry, michael@0: JSContext *cx, michael@0: JS::Handle obj) michael@0: { michael@0: JS::AutoValueArray stages(cx); michael@0: michael@0: FileStatsByStage& statsByStage = entry->mData; michael@0: for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) { michael@0: FileStats& fileStats = statsByStage.mStats[s]; michael@0: michael@0: if (fileStats.totalTime == 0 && fileStats.creates == 0 && michael@0: fileStats.reads == 0 && fileStats.writes == 0 && michael@0: fileStats.fsyncs == 0 && fileStats.stats == 0) { michael@0: // Don't add an array that contains no information michael@0: stages[s].setNull(); michael@0: continue; michael@0: } michael@0: michael@0: // Array we want to report michael@0: JS::AutoValueArray<6> stats(cx); michael@0: stats[0].setNumber(fileStats.totalTime); michael@0: stats[1].setNumber(fileStats.creates); michael@0: stats[2].setNumber(fileStats.reads); michael@0: stats[3].setNumber(fileStats.writes); michael@0: stats[4].setNumber(fileStats.fsyncs); michael@0: stats[5].setNumber(fileStats.stats); michael@0: michael@0: // Create jsStats as array of elements above michael@0: JS::RootedObject jsStats(cx, JS_NewArrayObject(cx, stats)); michael@0: if (!jsStats) { michael@0: continue; michael@0: } michael@0: michael@0: stages[s].setObject(*jsStats); michael@0: } michael@0: michael@0: JS::RootedObject jsEntry(cx, JS_NewArrayObject(cx, stages)); michael@0: if (!jsEntry) { michael@0: return false; michael@0: } michael@0: michael@0: // Add jsEntry to top-level dictionary michael@0: const nsAString& key = entry->GetKey(); michael@0: return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), michael@0: OBJECT_TO_JSVAL(jsEntry), nullptr, nullptr, michael@0: JSPROP_ENUMERATE | JSPROP_READONLY); michael@0: } michael@0: michael@0: bool TelemetryIOInterposeObserver::ReflectIntoJS(JSContext *cx, michael@0: JS::Handle rootObj) michael@0: { michael@0: return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj); michael@0: } michael@0: michael@0: // This is not a member of TelemetryImpl because we want to record I/O during michael@0: // startup. michael@0: StaticAutoPtr sTelemetryIOObserver; michael@0: michael@0: void michael@0: ClearIOReporting() michael@0: { michael@0: if (!sTelemetryIOObserver) { michael@0: return; michael@0: } michael@0: IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging, michael@0: sTelemetryIOObserver); michael@0: sTelemetryIOObserver = nullptr; michael@0: } michael@0: michael@0: class TelemetryImpl MOZ_FINAL michael@0: : public nsITelemetry michael@0: , public nsIMemoryReporter michael@0: { michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITELEMETRY michael@0: NS_DECL_NSIMEMORYREPORTER michael@0: michael@0: public: michael@0: ~TelemetryImpl(); michael@0: michael@0: void InitMemoryReporter(); michael@0: michael@0: static bool CanRecord(); michael@0: static already_AddRefed CreateTelemetryInstance(); michael@0: static void ShutdownTelemetry(); michael@0: static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName, michael@0: uint32_t delay); michael@0: #if defined(MOZ_ENABLE_PROFILER_SPS) michael@0: static void RecordChromeHang(uint32_t aDuration, michael@0: Telemetry::ProcessedStack &aStack, michael@0: int32_t aSystemUptime, michael@0: int32_t aFirefoxUptime); michael@0: #endif michael@0: static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats); michael@0: static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id); michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); michael@0: struct Stat { michael@0: uint32_t hitCount; michael@0: uint32_t totalTime; michael@0: }; michael@0: struct StmtStats { michael@0: struct Stat mainThread; michael@0: struct Stat otherThreads; michael@0: }; michael@0: typedef nsBaseHashtableET SlowSQLEntryType; michael@0: michael@0: private: michael@0: TelemetryImpl(); michael@0: michael@0: static nsCString SanitizeSQL(const nsACString& sql); michael@0: michael@0: enum SanitizedState { Sanitized, Unsanitized }; michael@0: michael@0: static void StoreSlowSQL(const nsACString &offender, uint32_t delay, michael@0: SanitizedState state); michael@0: michael@0: static bool ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx, michael@0: JS::Handle obj); michael@0: static bool ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx, michael@0: JS::Handle obj); michael@0: static bool ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat, michael@0: JSContext *cx, JS::Handle obj); michael@0: michael@0: bool AddSQLInfo(JSContext *cx, JS::Handle rootObj, bool mainThread, michael@0: bool privateSQL); michael@0: bool GetSQLStats(JSContext *cx, JS::MutableHandle ret, michael@0: bool includePrivateSql); michael@0: michael@0: // Like GetHistogramById, but returns the underlying C++ object, not the JS one. michael@0: nsresult GetHistogramByName(const nsACString &name, Histogram **ret); michael@0: bool ShouldReflectHistogram(Histogram *h); michael@0: void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs); michael@0: typedef StatisticsRecorder::Histograms::iterator HistogramIterator; michael@0: michael@0: struct AddonHistogramInfo { michael@0: uint32_t min; michael@0: uint32_t max; michael@0: uint32_t bucketCount; michael@0: uint32_t histogramType; michael@0: Histogram *h; michael@0: }; michael@0: typedef nsBaseHashtableET AddonHistogramEntryType; michael@0: typedef AutoHashtable AddonHistogramMapType; michael@0: typedef nsBaseHashtableET AddonEntryType; michael@0: typedef AutoHashtable AddonMapType; michael@0: static bool AddonHistogramReflector(AddonHistogramEntryType *entry, michael@0: JSContext *cx, JS::Handle obj); michael@0: static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle obj); michael@0: static bool CreateHistogramForAddon(const nsACString &name, michael@0: AddonHistogramInfo &info); michael@0: void ReadLateWritesStacks(nsIFile* aProfileDir); michael@0: AddonMapType mAddonMap; michael@0: michael@0: // This is used for speedy string->Telemetry::ID conversions michael@0: typedef nsBaseHashtableET CharPtrEntryType; michael@0: typedef AutoHashtable HistogramMapType; michael@0: HistogramMapType mHistogramMap; michael@0: bool mCanRecord; michael@0: static TelemetryImpl *sTelemetry; michael@0: AutoHashtable mPrivateSQL; michael@0: AutoHashtable mSanitizedSQL; michael@0: // This gets marked immutable in debug builds, so we can't use michael@0: // AutoHashtable here. michael@0: nsTHashtable mTrackedDBs; michael@0: Mutex mHashMutex; michael@0: HangReports mHangReports; michael@0: Mutex mHangReportsMutex; michael@0: // mThreadHangStats stores recorded, inactive thread hang stats michael@0: Vector mThreadHangStats; michael@0: Mutex mThreadHangStatsMutex; michael@0: michael@0: CombinedStacks mLateWritesStacks; // This is collected out of the main thread. michael@0: bool mCachedTelemetryData; michael@0: uint32_t mLastShutdownTime; michael@0: uint32_t mFailedLockCount; michael@0: nsCOMArray mCallbacks; michael@0: friend class nsFetchTelemetryData; michael@0: }; michael@0: michael@0: TelemetryImpl* TelemetryImpl::sTelemetry = nullptr; michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf) michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: return MOZ_COLLECT_REPORT( michael@0: "explicit/telemetry", KIND_HEAP, UNITS_BYTES, michael@0: SizeOfIncludingThis(TelemetryMallocSizeOf), michael@0: "Memory used by the telemetry system."); michael@0: } michael@0: michael@0: // A initializer to initialize histogram collection michael@0: StatisticsRecorder gStatisticsRecorder; michael@0: michael@0: // Hardcoded probes michael@0: struct TelemetryHistogram { michael@0: uint32_t min; michael@0: uint32_t max; michael@0: uint32_t bucketCount; michael@0: uint32_t histogramType; michael@0: uint32_t id_offset; michael@0: uint32_t expiration_offset; michael@0: bool extendedStatisticsOK; michael@0: michael@0: const char *id() const; michael@0: const char *expiration() const; michael@0: }; michael@0: michael@0: #include "TelemetryHistogramData.inc" michael@0: bool gCorruptHistograms[Telemetry::HistogramCount]; michael@0: michael@0: const char * michael@0: TelemetryHistogram::id() const michael@0: { michael@0: return &gHistogramStringTable[this->id_offset]; michael@0: } michael@0: michael@0: const char * michael@0: TelemetryHistogram::expiration() const michael@0: { michael@0: return &gHistogramStringTable[this->expiration_offset]; michael@0: } michael@0: michael@0: bool michael@0: IsExpired(const char *expiration){ michael@0: static Version current_version = Version(MOZ_APP_VERSION); michael@0: MOZ_ASSERT(expiration); michael@0: return strcmp(expiration, "never") && (mozilla::Version(expiration) <= current_version); michael@0: } michael@0: michael@0: bool michael@0: IsExpired(const Histogram *histogram){ michael@0: return histogram->histogram_name() == EXPIRED_ID; michael@0: } michael@0: michael@0: nsresult michael@0: HistogramGet(const char *name, const char *expiration, uint32_t min, uint32_t max, michael@0: uint32_t bucketCount, uint32_t histogramType, Histogram **result) michael@0: { michael@0: if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN michael@0: && histogramType != nsITelemetry::HISTOGRAM_FLAG) { michael@0: // Sanity checks for histogram parameters. michael@0: if (min >= max) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: if (bucketCount <= 2) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: if (min < 1) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: if (IsExpired(expiration)) { michael@0: name = EXPIRED_ID; michael@0: min = 1; michael@0: max = 2; michael@0: bucketCount = 3; michael@0: histogramType = nsITelemetry::HISTOGRAM_LINEAR; michael@0: } michael@0: michael@0: switch (histogramType) { michael@0: case nsITelemetry::HISTOGRAM_EXPONENTIAL: michael@0: *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); michael@0: break; michael@0: case nsITelemetry::HISTOGRAM_LINEAR: michael@0: *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); michael@0: break; michael@0: case nsITelemetry::HISTOGRAM_BOOLEAN: michael@0: *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); michael@0: break; michael@0: case nsITelemetry::HISTOGRAM_FLAG: michael@0: *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); michael@0: break; michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // O(1) histogram lookup by numeric id michael@0: nsresult michael@0: GetHistogramByEnumId(Telemetry::ID id, Histogram **ret) michael@0: { michael@0: static Histogram* knownHistograms[Telemetry::HistogramCount] = {0}; michael@0: Histogram *h = knownHistograms[id]; michael@0: if (h) { michael@0: *ret = h; michael@0: return NS_OK; michael@0: } michael@0: michael@0: const TelemetryHistogram &p = gHistograms[id]; michael@0: nsresult rv = HistogramGet(p.id(), p.expiration(), p.min, p.max, p.bucketCount, p.histogramType, &h); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: #ifdef DEBUG michael@0: // Check that the C++ Histogram code computes the same ranges as the michael@0: // Python histogram code. michael@0: if (!IsExpired(p.expiration())) { michael@0: const struct bounds &b = gBucketLowerBoundIndex[id]; michael@0: if (b.length != 0) { michael@0: MOZ_ASSERT(size_t(b.length) == h->bucket_count(), michael@0: "C++/Python bucket # mismatch"); michael@0: for (int i = 0; i < b.length; ++i) { michael@0: MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i), michael@0: "C++/Python bucket mismatch"); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (p.extendedStatisticsOK) { michael@0: h->SetFlags(Histogram::kExtendedStatisticsFlag); michael@0: } michael@0: *ret = knownHistograms[id] = h; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: FillRanges(JSContext *cx, JS::Handle array, Histogram *h) michael@0: { michael@0: JS::Rooted range(cx); michael@0: for (size_t i = 0; i < h->bucket_count(); i++) { michael@0: range = INT_TO_JSVAL(h->ranges(i)); michael@0: if (!JS_DefineElement(cx, array, i, range, nullptr, nullptr, JSPROP_ENUMERATE)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: enum reflectStatus { michael@0: REFLECT_OK, michael@0: REFLECT_CORRUPT, michael@0: REFLECT_FAILURE michael@0: }; michael@0: michael@0: enum reflectStatus michael@0: ReflectHistogramAndSamples(JSContext *cx, JS::Handle obj, Histogram *h, michael@0: const Histogram::SampleSet &ss) michael@0: { michael@0: // We don't want to reflect corrupt histograms. michael@0: if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) { michael@0: return REFLECT_CORRUPT; michael@0: } michael@0: michael@0: if (!(JS_DefineProperty(cx, obj, "min", h->declared_min(), JSPROP_ENUMERATE) michael@0: && JS_DefineProperty(cx, obj, "max", h->declared_max(), JSPROP_ENUMERATE) michael@0: && JS_DefineProperty(cx, obj, "histogram_type", h->histogram_type(), JSPROP_ENUMERATE) michael@0: && JS_DefineProperty(cx, obj, "sum", double(ss.sum()), JSPROP_ENUMERATE))) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: michael@0: if (h->histogram_type() == Histogram::HISTOGRAM) { michael@0: if (!(JS_DefineProperty(cx, obj, "log_sum", ss.log_sum(), JSPROP_ENUMERATE) michael@0: && JS_DefineProperty(cx, obj, "log_sum_squares", ss.log_sum_squares(), JSPROP_ENUMERATE))) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: } else { michael@0: // Export |sum_squares| as two separate 32-bit properties so that we michael@0: // can accurately reconstruct it on the analysis side. michael@0: uint64_t sum_squares = ss.sum_squares(); michael@0: // Cast to avoid implicit truncation warnings. michael@0: uint32_t lo = static_cast(sum_squares); michael@0: uint32_t hi = static_cast(sum_squares >> 32); michael@0: if (!(JS_DefineProperty(cx, obj, "sum_squares_lo", lo, JSPROP_ENUMERATE) michael@0: && JS_DefineProperty(cx, obj, "sum_squares_hi", hi, JSPROP_ENUMERATE))) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: } michael@0: michael@0: const size_t count = h->bucket_count(); michael@0: JS::Rooted rarray(cx, JS_NewArrayObject(cx, count)); michael@0: if (!rarray) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: if (!(FillRanges(cx, rarray, h) michael@0: && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: michael@0: JS::Rooted counts_array(cx, JS_NewArrayObject(cx, count)); michael@0: if (!counts_array) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: for (size_t i = 0; i < count; i++) { michael@0: if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)), michael@0: nullptr, nullptr, JSPROP_ENUMERATE)) { michael@0: return REFLECT_FAILURE; michael@0: } michael@0: } michael@0: michael@0: return REFLECT_OK; michael@0: } michael@0: michael@0: enum reflectStatus michael@0: ReflectHistogramSnapshot(JSContext *cx, JS::Handle obj, Histogram *h) michael@0: { michael@0: Histogram::SampleSet ss; michael@0: h->SnapshotSample(&ss); michael@0: return ReflectHistogramAndSamples(cx, obj, h, ss); michael@0: } michael@0: michael@0: bool michael@0: IsEmpty(const Histogram *h) michael@0: { michael@0: Histogram::SampleSet ss; michael@0: h->SnapshotSample(&ss); michael@0: michael@0: return ss.counts(0) == 0 && ss.sum() == 0; michael@0: } michael@0: michael@0: bool michael@0: JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.length()) { michael@0: JS_ReportError(cx, "Expected one argument"); michael@0: return false; michael@0: } michael@0: michael@0: if (!(args[0].isNumber() || args[0].isBoolean())) { michael@0: JS_ReportError(cx, "Not a number"); michael@0: return false; michael@0: } michael@0: michael@0: int32_t value; michael@0: if (!JS::ToInt32(cx, args[0], &value)) { michael@0: return false; michael@0: } michael@0: michael@0: if (TelemetryImpl::CanRecord()) { michael@0: JSObject *obj = JS_THIS_OBJECT(cx, vp); michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: michael@0: Histogram *h = static_cast(JS_GetPrivate(obj)); michael@0: h->Add(value); michael@0: } michael@0: return true; michael@0: michael@0: } michael@0: michael@0: bool michael@0: JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JSObject *obj = JS_THIS_OBJECT(cx, vp); michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: michael@0: Histogram *h = static_cast(JS_GetPrivate(obj)); michael@0: JS::Rooted snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!snapshot) michael@0: return false; michael@0: michael@0: switch (ReflectHistogramSnapshot(cx, snapshot, h)) { michael@0: case REFLECT_FAILURE: michael@0: return false; michael@0: case REFLECT_CORRUPT: michael@0: JS_ReportError(cx, "Histogram is corrupt"); michael@0: return false; michael@0: case REFLECT_OK: michael@0: args.rval().setObject(*snapshot); michael@0: return true; michael@0: default: michael@0: MOZ_CRASH("unhandled reflection status"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JSObject *obj = JS_THIS_OBJECT(cx, vp); michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: michael@0: Histogram *h = static_cast(JS_GetPrivate(obj)); michael@0: h->Clear(); michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: static const JSClass JSHistogram_class = { michael@0: "JSHistogram", /* name */ michael@0: JSCLASS_HAS_PRIVATE, /* flags */ michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub michael@0: }; michael@0: michael@0: JS::Rooted obj(cx, JS_NewObject(cx, &JSHistogram_class, JS::NullPtr(), JS::NullPtr())); michael@0: if (!obj) michael@0: return NS_ERROR_FAILURE; michael@0: if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0) michael@0: && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0) michael@0: && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: JS_SetPrivate(obj, h); michael@0: ret.setObject(*obj); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static uint32_t michael@0: ReadLastShutdownDuration(const char *filename) { michael@0: FILE *f = fopen(filename, "r"); michael@0: if (!f) { michael@0: return 0; michael@0: } michael@0: michael@0: int shutdownTime; michael@0: int r = fscanf(f, "%d\n", &shutdownTime); michael@0: fclose(f); michael@0: if (r != 1) { michael@0: return 0; michael@0: } michael@0: michael@0: return shutdownTime; michael@0: } michael@0: michael@0: const int32_t kMaxFailedProfileLockFileSize = 10; michael@0: michael@0: bool michael@0: GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount, michael@0: unsigned int& result) michael@0: { michael@0: nsAutoCString bufStr; michael@0: nsresult rv; michael@0: rv = NS_ReadInputStreamToString(inStream, bufStr, aCount); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: result = bufStr.ToInteger(&rv); michael@0: return NS_SUCCEEDED(rv) && result > 0; michael@0: } michael@0: michael@0: nsresult michael@0: GetFailedProfileLockFile(nsIFile* *aFile, nsIFile* aProfileDir) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aProfileDir); michael@0: michael@0: nsresult rv = aProfileDir->Clone(aFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: (*aFile)->AppendNative(NS_LITERAL_CSTRING("Telemetry.FailedProfileLocks.txt")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class nsFetchTelemetryData : public nsRunnable michael@0: { michael@0: public: michael@0: nsFetchTelemetryData(const char* aShutdownTimeFilename, michael@0: nsIFile* aFailedProfileLockFile, michael@0: nsIFile* aProfileDir) michael@0: : mShutdownTimeFilename(aShutdownTimeFilename), michael@0: mFailedProfileLockFile(aFailedProfileLockFile), michael@0: mTelemetry(TelemetryImpl::sTelemetry), michael@0: mProfileDir(aProfileDir) michael@0: { michael@0: } michael@0: michael@0: private: michael@0: const char* mShutdownTimeFilename; michael@0: nsCOMPtr mFailedProfileLockFile; michael@0: nsRefPtr mTelemetry; michael@0: nsCOMPtr mProfileDir; michael@0: michael@0: public: michael@0: void MainThread() { michael@0: mTelemetry->mCachedTelemetryData = true; michael@0: for (unsigned int i = 0, n = mTelemetry->mCallbacks.Count(); i < n; ++i) { michael@0: mTelemetry->mCallbacks[i]->Complete(); michael@0: } michael@0: mTelemetry->mCallbacks.Clear(); michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: LoadFailedLockCount(mTelemetry->mFailedLockCount); michael@0: mTelemetry->mLastShutdownTime = michael@0: ReadLastShutdownDuration(mShutdownTimeFilename); michael@0: mTelemetry->ReadLateWritesStacks(mProfileDir); michael@0: nsCOMPtr e = michael@0: NS_NewRunnableMethod(this, &nsFetchTelemetryData::MainThread); michael@0: NS_ENSURE_STATE(e); michael@0: NS_DispatchToMainThread(e, NS_DISPATCH_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsresult michael@0: LoadFailedLockCount(uint32_t& failedLockCount) michael@0: { michael@0: failedLockCount = 0; michael@0: int64_t fileSize = 0; michael@0: nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize, michael@0: NS_ERROR_UNEXPECTED); michael@0: nsCOMPtr inStream; michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), michael@0: mFailedProfileLockFile, michael@0: PR_RDONLY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount), michael@0: NS_ERROR_UNEXPECTED); michael@0: inStream->Close(); michael@0: michael@0: mFailedProfileLockFile->Remove(false); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: static TimeStamp gRecordedShutdownStartTime; michael@0: static bool gAlreadyFreedShutdownTimeFileName = false; michael@0: static char *gRecordedShutdownTimeFileName = nullptr; michael@0: michael@0: static char * michael@0: GetShutdownTimeFileName() michael@0: { michael@0: if (gAlreadyFreedShutdownTimeFileName) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!gRecordedShutdownTimeFileName) { michael@0: nsCOMPtr mozFile; michael@0: NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); michael@0: if (!mozFile) michael@0: return nullptr; michael@0: michael@0: mozFile->AppendNative(NS_LITERAL_CSTRING("Telemetry.ShutdownTime.txt")); michael@0: nsAutoCString nativePath; michael@0: nsresult rv = mozFile->GetNativePath(nativePath); michael@0: if (!NS_SUCCEEDED(rv)) michael@0: return nullptr; michael@0: michael@0: gRecordedShutdownTimeFileName = PL_strdup(nativePath.get()); michael@0: } michael@0: michael@0: return gRecordedShutdownTimeFileName; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetLastShutdownDuration(uint32_t *aResult) michael@0: { michael@0: // The user must call AsyncFetchTelemetryData first. We return zero instead of michael@0: // reporting a failure so that the rest of telemetry can uniformly handle michael@0: // the read not being available yet. michael@0: if (!mCachedTelemetryData) { michael@0: *aResult = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aResult = mLastShutdownTime; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult) michael@0: { michael@0: // The user must call AsyncFetchTelemetryData first. We return zero instead of michael@0: // reporting a failure so that the rest of telemetry can uniformly handle michael@0: // the read not being available yet. michael@0: if (!mCachedTelemetryData) { michael@0: *aResult = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aResult = mFailedLockCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback) michael@0: { michael@0: // We have finished reading the data already, just call the callback. michael@0: if (mCachedTelemetryData) { michael@0: aCallback->Complete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We already have a read request running, just remember the callback. michael@0: if (mCallbacks.Count() != 0) { michael@0: mCallbacks.AppendObject(aCallback); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We make this check so that GetShutdownTimeFileName() doesn't get michael@0: // called; calling that function without telemetry enabled violates michael@0: // assumptions that the write-the-shutdown-timestamp machinery makes. michael@0: if (!Telemetry::CanRecord()) { michael@0: mCachedTelemetryData = true; michael@0: aCallback->Complete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Send the read to a background thread provided by the stream transport michael@0: // service to avoid a read in the main thread. michael@0: nsCOMPtr targetThread = michael@0: do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); michael@0: if (!targetThread) { michael@0: mCachedTelemetryData = true; michael@0: aCallback->Complete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We have to get the filename from the main thread. michael@0: const char *shutdownTimeFilename = GetShutdownTimeFileName(); michael@0: if (!shutdownTimeFilename) { michael@0: mCachedTelemetryData = true; michael@0: aCallback->Complete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr profileDir; michael@0: nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(profileDir)); michael@0: if (NS_FAILED(rv)) { michael@0: mCachedTelemetryData = true; michael@0: aCallback->Complete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr failedProfileLockFile; michael@0: rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile), michael@0: profileDir); michael@0: if (NS_FAILED(rv)) { michael@0: mCachedTelemetryData = true; michael@0: aCallback->Complete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mCallbacks.AppendObject(aCallback); michael@0: michael@0: nsCOMPtr event = new nsFetchTelemetryData(shutdownTimeFilename, michael@0: failedProfileLockFile, michael@0: profileDir); michael@0: michael@0: targetThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: TelemetryImpl::TelemetryImpl(): michael@0: mHistogramMap(Telemetry::HistogramCount), michael@0: mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default), michael@0: mHashMutex("Telemetry::mHashMutex"), michael@0: mHangReportsMutex("Telemetry::mHangReportsMutex"), michael@0: mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex"), michael@0: mCachedTelemetryData(false), michael@0: mLastShutdownTime(0), michael@0: mFailedLockCount(0) michael@0: { michael@0: // A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs michael@0: const char *trackedDBs[] = { michael@0: "addons.sqlite", "content-prefs.sqlite", "cookies.sqlite", michael@0: "downloads.sqlite", "extensions.sqlite", "formhistory.sqlite", michael@0: "index.sqlite", "healthreport.sqlite", "netpredictions.sqlite", michael@0: "permissions.sqlite", "places.sqlite", "search.sqlite", "signons.sqlite", michael@0: "urlclassifier3.sqlite", "webappsstore.sqlite" michael@0: }; michael@0: michael@0: for (size_t i = 0; i < ArrayLength(trackedDBs); i++) michael@0: mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i])); michael@0: michael@0: #ifdef DEBUG michael@0: // Mark immutable to prevent asserts on simultaneous access from multiple threads michael@0: mTrackedDBs.MarkImmutable(); michael@0: #endif michael@0: } michael@0: michael@0: TelemetryImpl::~TelemetryImpl() { michael@0: UnregisterWeakMemoryReporter(this); michael@0: } michael@0: michael@0: void michael@0: TelemetryImpl::InitMemoryReporter() { michael@0: RegisterWeakMemoryReporter(this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::NewHistogram(const nsACString &name, const nsACString &expiration, uint32_t min, michael@0: uint32_t max, uint32_t bucketCount, uint32_t histogramType, michael@0: JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: Histogram *h; michael@0: nsresult rv = HistogramGet(PromiseFlatCString(name).get(), PromiseFlatCString(expiration).get(), michael@0: min, max, bucketCount, histogramType, &h); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); michael@0: h->SetFlags(Histogram::kExtendedStatisticsFlag); michael@0: return WrapAndReturnHistogram(h, cx, ret); michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry, michael@0: const Stat *stat, michael@0: JSContext *cx, michael@0: JS::Handle obj) michael@0: { michael@0: if (stat->hitCount == 0) michael@0: return true; michael@0: michael@0: const nsACString &sql = entry->GetKey(); michael@0: michael@0: JS::Rooted arrayObj(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!arrayObj) { michael@0: return false; michael@0: } michael@0: return (JS_SetElement(cx, arrayObj, 0, stat->hitCount) michael@0: && JS_SetElement(cx, arrayObj, 1, stat->totalTime) michael@0: && JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj, michael@0: JSPROP_ENUMERATE)); michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx, michael@0: JS::Handle obj) michael@0: { michael@0: return ReflectSQL(entry, &entry->mData.mainThread, cx, obj); michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx, michael@0: JS::Handle obj) michael@0: { michael@0: return ReflectSQL(entry, &entry->mData.otherThreads, cx, obj); michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::AddSQLInfo(JSContext *cx, JS::Handle rootObj, bool mainThread, michael@0: bool privateSQL) michael@0: { michael@0: JS::Rooted statsObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!statsObj) michael@0: return false; michael@0: michael@0: AutoHashtable &sqlMap = michael@0: (privateSQL ? mPrivateSQL : mSanitizedSQL); michael@0: AutoHashtable::ReflectEntryFunc reflectFunction = michael@0: (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL); michael@0: if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) { michael@0: return false; michael@0: } michael@0: michael@0: return JS_DefineProperty(cx, rootObj, michael@0: mainThread ? "mainThread" : "otherThreads", michael@0: statsObj, JSPROP_ENUMERATE); michael@0: } michael@0: michael@0: nsresult michael@0: TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id) michael@0: { michael@0: if (!sTelemetry) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Cache names michael@0: // Note the histogram names are statically allocated michael@0: TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap; michael@0: if (!map->Count()) { michael@0: for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) { michael@0: CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id()); michael@0: if (MOZ_UNLIKELY(!entry)) { michael@0: map->Clear(); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: entry->mData = (Telemetry::ID) i; michael@0: } michael@0: } michael@0: michael@0: CharPtrEntryType *entry = map->GetEntry(name); michael@0: if (!entry) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: *id = entry->mData; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret) michael@0: { michael@0: Telemetry::ID id; michael@0: nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = GetHistogramByEnumId(id, ret); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name, michael@0: JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: Telemetry::ID id; michael@0: nsresult rv = GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: const TelemetryHistogram &p = gHistograms[id]; michael@0: michael@0: Histogram *existing; michael@0: rv = GetHistogramByEnumId(id, &existing); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: Histogram *clone; michael@0: rv = HistogramGet(PromiseFlatCString(name).get(), p.expiration(), michael@0: existing->declared_min(), existing->declared_max(), michael@0: existing->bucket_count(), p.histogramType, &clone); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: Histogram::SampleSet ss; michael@0: existing->SnapshotSample(&ss); michael@0: clone->AddSampleSet(ss); michael@0: return WrapAndReturnHistogram(clone, cx, ret); michael@0: } michael@0: michael@0: void michael@0: TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs) michael@0: { michael@0: for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { michael@0: Histogram *h = *it; michael@0: michael@0: Telemetry::ID id; michael@0: nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id); michael@0: // This histogram isn't a static histogram, just ignore it. michael@0: if (NS_FAILED(rv)) { michael@0: continue; michael@0: } michael@0: michael@0: if (gCorruptHistograms[id]) { michael@0: continue; michael@0: } michael@0: michael@0: Histogram::SampleSet ss; michael@0: h->SnapshotSample(&ss); michael@0: Histogram::Inconsistencies check = h->FindCorruption(ss); michael@0: bool corrupt = (check != Histogram::NO_INCONSISTENCIES); michael@0: michael@0: if (corrupt) { michael@0: Telemetry::ID corruptID = Telemetry::HistogramCount; michael@0: if (check & Histogram::RANGE_CHECKSUM_ERROR) { michael@0: corruptID = Telemetry::RANGE_CHECKSUM_ERRORS; michael@0: } else if (check & Histogram::BUCKET_ORDER_ERROR) { michael@0: corruptID = Telemetry::BUCKET_ORDER_ERRORS; michael@0: } else if (check & Histogram::COUNT_HIGH_ERROR) { michael@0: corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS; michael@0: } else if (check & Histogram::COUNT_LOW_ERROR) { michael@0: corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS; michael@0: } michael@0: Telemetry::Accumulate(corruptID, 1); michael@0: } michael@0: michael@0: gCorruptHistograms[id] = corrupt; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::ShouldReflectHistogram(Histogram *h) michael@0: { michael@0: const char *name = h->histogram_name().c_str(); michael@0: Telemetry::ID id; michael@0: nsresult rv = GetHistogramEnumId(name, &id); michael@0: if (NS_FAILED(rv)) { michael@0: // GetHistogramEnumId generally should not fail. But a lookup michael@0: // failure shouldn't prevent us from reflecting histograms into JS. michael@0: // michael@0: // However, these two histograms are created by Histogram itself for michael@0: // tracking corruption. We have our own histograms for that, so michael@0: // ignore these two. michael@0: if (strcmp(name, "Histogram.InconsistentCountHigh") == 0 michael@0: || strcmp(name, "Histogram.InconsistentCountLow") == 0) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } else { michael@0: return !gCorruptHistograms[id]; michael@0: } michael@0: } michael@0: michael@0: // Compute the name to pass into Histogram for the addon histogram michael@0: // 'name' from the addon 'id'. We can't use 'name' directly because it michael@0: // might conflict with other histograms in other addons or even with our michael@0: // own. michael@0: void michael@0: AddonHistogramName(const nsACString &id, const nsACString &name, michael@0: nsACString &ret) michael@0: { michael@0: ret.Append(id); michael@0: ret.Append(NS_LITERAL_CSTRING(":")); michael@0: ret.Append(name); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::RegisterAddonHistogram(const nsACString &id, michael@0: const nsACString &name, michael@0: uint32_t min, uint32_t max, michael@0: uint32_t bucketCount, michael@0: uint32_t histogramType) michael@0: { michael@0: AddonEntryType *addonEntry = mAddonMap.GetEntry(id); michael@0: if (!addonEntry) { michael@0: addonEntry = mAddonMap.PutEntry(id); michael@0: if (MOZ_UNLIKELY(!addonEntry)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: addonEntry->mData = new AddonHistogramMapType(); michael@0: } michael@0: michael@0: AddonHistogramMapType *histogramMap = addonEntry->mData; michael@0: AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); michael@0: // Can't re-register the same histogram. michael@0: if (histogramEntry) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: histogramEntry = histogramMap->PutEntry(name); michael@0: if (MOZ_UNLIKELY(!histogramEntry)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: AddonHistogramInfo &info = histogramEntry->mData; michael@0: info.min = min; michael@0: info.max = max; michael@0: info.bucketCount = bucketCount; michael@0: info.histogramType = histogramType; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name, michael@0: JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: AddonEntryType *addonEntry = mAddonMap.GetEntry(id); michael@0: // The given id has not been registered. michael@0: if (!addonEntry) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: AddonHistogramMapType *histogramMap = addonEntry->mData; michael@0: AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); michael@0: // The given histogram name has not been registered. michael@0: if (!histogramEntry) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: AddonHistogramInfo &info = histogramEntry->mData; michael@0: if (!info.h) { michael@0: nsAutoCString actualName; michael@0: AddonHistogramName(id, name, actualName); michael@0: if (!CreateHistogramForAddon(actualName, info)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: return WrapAndReturnHistogram(info.h, cx, ret); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::UnregisterAddonHistograms(const nsACString &id) michael@0: { michael@0: AddonEntryType *addonEntry = mAddonMap.GetEntry(id); michael@0: if (addonEntry) { michael@0: // Histogram's destructor is private, so this is the best we can do. michael@0: // The histograms the addon created *will* stick around, but they michael@0: // will be deleted if and when the addon registers histograms with michael@0: // the same names. michael@0: delete addonEntry->mData; michael@0: mAddonMap.RemoveEntry(id); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: JS::Rooted root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!root_obj) michael@0: return NS_ERROR_FAILURE; michael@0: ret.setObject(*root_obj); michael@0: michael@0: // Ensure that all the HISTOGRAM_FLAG histograms have been created, so michael@0: // that their values are snapshotted. michael@0: for (size_t i = 0; i < Telemetry::HistogramCount; ++i) { michael@0: if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) { michael@0: Histogram *h; michael@0: DebugOnly rv = GetHistogramByEnumId(Telemetry::ID(i), &h); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: }; michael@0: michael@0: StatisticsRecorder::Histograms hs; michael@0: StatisticsRecorder::GetHistograms(&hs); michael@0: michael@0: // We identify corrupt histograms first, rather than interspersing it michael@0: // in the loop below, to ensure that our corruption statistics don't michael@0: // depend on histogram enumeration order. michael@0: // michael@0: // Of course, we hope that all of these corruption-statistics michael@0: // histograms are not themselves corrupt... michael@0: IdentifyCorruptHistograms(hs); michael@0: michael@0: // OK, now we can actually reflect things. michael@0: JS::Rooted hobj(cx); michael@0: for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { michael@0: Histogram *h = *it; michael@0: if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) { michael@0: continue; michael@0: } michael@0: michael@0: hobj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()); michael@0: if (!hobj) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: switch (ReflectHistogramSnapshot(cx, hobj, h)) { michael@0: case REFLECT_CORRUPT: michael@0: // We can still hit this case even if ShouldReflectHistograms michael@0: // returns true. The histogram lies outside of our control michael@0: // somehow; just skip it. michael@0: continue; michael@0: case REFLECT_FAILURE: michael@0: return NS_ERROR_FAILURE; michael@0: case REFLECT_OK: michael@0: if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), hobj, michael@0: JSPROP_ENUMERATE)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::CreateHistogramForAddon(const nsACString &name, michael@0: AddonHistogramInfo &info) michael@0: { michael@0: Histogram *h; michael@0: nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never", michael@0: info.min, info.max, info.bucketCount, michael@0: info.histogramType, &h); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: // Don't let this histogram be reported via the normal means michael@0: // (e.g. Telemetry.registeredHistograms); we'll make it available in michael@0: // other ways. michael@0: h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); michael@0: info.h = h; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry, michael@0: JSContext *cx, JS::Handle obj) michael@0: { michael@0: AddonHistogramInfo &info = entry->mData; michael@0: michael@0: // Never even accessed the histogram. michael@0: if (!info.h) { michael@0: // Have to force creation of HISTOGRAM_FLAG histograms. michael@0: if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) michael@0: return true; michael@0: michael@0: if (!CreateHistogramForAddon(entry->GetKey(), info)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (IsEmpty(info.h)) { michael@0: return true; michael@0: } michael@0: michael@0: JS::Rooted snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!snapshot) { michael@0: // Just consider this to be skippable. michael@0: return true; michael@0: } michael@0: switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) { michael@0: case REFLECT_FAILURE: michael@0: case REFLECT_CORRUPT: michael@0: return false; michael@0: case REFLECT_OK: michael@0: const nsACString &histogramName = entry->GetKey(); michael@0: if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), michael@0: snapshot, JSPROP_ENUMERATE)) { michael@0: return false; michael@0: } michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::AddonReflector(AddonEntryType *entry, michael@0: JSContext *cx, JS::Handle obj) michael@0: { michael@0: const nsACString &addonId = entry->GetKey(); michael@0: JS::Rooted subobj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!subobj) { michael@0: return false; michael@0: } michael@0: michael@0: AddonHistogramMapType *map = entry->mData; michael@0: if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj) michael@0: && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), michael@0: subobj, JSPROP_ENUMERATE))) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: JS::Rooted obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!obj) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: ret.setObject(*obj); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::GetSQLStats(JSContext *cx, JS::MutableHandle ret, bool includePrivateSql) michael@0: { michael@0: JS::Rooted root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!root_obj) michael@0: return false; michael@0: ret.setObject(*root_obj); michael@0: michael@0: MutexAutoLock hashMutex(mHashMutex); michael@0: // Add info about slow SQL queries on the main thread michael@0: if (!AddSQLInfo(cx, root_obj, true, includePrivateSql)) michael@0: return false; michael@0: // Add info about slow SQL queries on other threads michael@0: if (!AddSQLInfo(cx, root_obj, false, includePrivateSql)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetSlowSQL(JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: if (GetSQLStats(cx, ret, false)) michael@0: return NS_OK; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetDebugSlowSQL(JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: bool revealPrivateSql = michael@0: Preferences::GetBool("toolkit.telemetry.debugSlowSql", false); michael@0: if (GetSQLStats(cx, ret, revealPrivateSql)) michael@0: return NS_OK; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetMaximalNumberOfConcurrentThreads(uint32_t *ret) michael@0: { michael@0: *ret = nsThreadManager::get()->GetHighestNumberOfThreads(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: MutexAutoLock hangReportMutex(mHangReportsMutex); michael@0: michael@0: const CombinedStacks& stacks = mHangReports.GetStacks(); michael@0: JS::Rooted fullReportObj(cx, CreateJSStackObject(cx, stacks)); michael@0: if (!fullReportObj) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: ret.setObject(*fullReportObj); michael@0: michael@0: JS::Rooted durationArray(cx, JS_NewArrayObject(cx, 0)); michael@0: JS::Rooted systemUptimeArray(cx, JS_NewArrayObject(cx, 0)); michael@0: JS::Rooted firefoxUptimeArray(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!durationArray || !systemUptimeArray || !firefoxUptimeArray) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: bool ok = JS_DefineProperty(cx, fullReportObj, "durations", michael@0: durationArray, JSPROP_ENUMERATE); michael@0: if (!ok) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: ok = JS_DefineProperty(cx, fullReportObj, "systemUptime", michael@0: systemUptimeArray, JSPROP_ENUMERATE); michael@0: if (!ok) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: ok = JS_DefineProperty(cx, fullReportObj, "firefoxUptime", michael@0: firefoxUptimeArray, JSPROP_ENUMERATE); michael@0: if (!ok) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: const size_t length = stacks.GetStackCount(); michael@0: for (size_t i = 0; i < length; ++i) { michael@0: if (!JS_SetElement(cx, durationArray, i, mHangReports.GetDuration(i))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (!JS_SetElement(cx, systemUptimeArray, i, mHangReports.GetSystemUptime(i))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (!JS_SetElement(cx, firefoxUptimeArray, i, mHangReports.GetFirefoxUptime(i))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static JSObject * michael@0: CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks) { michael@0: JS::Rooted ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!ret) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JS::Rooted moduleArray(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!moduleArray) { michael@0: return nullptr; michael@0: } michael@0: bool ok = JS_DefineProperty(cx, ret, "memoryMap", moduleArray, michael@0: JSPROP_ENUMERATE); michael@0: if (!ok) { michael@0: return nullptr; michael@0: } michael@0: michael@0: const size_t moduleCount = stacks.GetModuleCount(); michael@0: for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) { michael@0: // Current module michael@0: const Telemetry::ProcessedStack::Module& module = michael@0: stacks.GetModule(moduleIndex); michael@0: michael@0: JS::Rooted moduleInfoArray(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!moduleInfoArray) { michael@0: return nullptr; michael@0: } michael@0: if (!JS_SetElement(cx, moduleArray, moduleIndex, moduleInfoArray)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: unsigned index = 0; michael@0: michael@0: // Module name michael@0: JS::Rooted str(cx, JS_NewStringCopyZ(cx, module.mName.c_str())); michael@0: if (!str) { michael@0: return nullptr; michael@0: } michael@0: if (!JS_SetElement(cx, moduleInfoArray, index++, str)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Module breakpad identifier michael@0: JS::Rooted id(cx, JS_NewStringCopyZ(cx, module.mBreakpadId.c_str())); michael@0: if (!id) { michael@0: return nullptr; michael@0: } michael@0: if (!JS_SetElement(cx, moduleInfoArray, index++, id)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: JS::Rooted reportArray(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!reportArray) { michael@0: return nullptr; michael@0: } michael@0: ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE); michael@0: if (!ok) { michael@0: return nullptr; michael@0: } michael@0: michael@0: const size_t length = stacks.GetStackCount(); michael@0: for (size_t i = 0; i < length; ++i) { michael@0: // Represent call stack PCs as (module index, offset) pairs. michael@0: JS::Rooted pcArray(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!pcArray) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!JS_SetElement(cx, reportArray, i, pcArray)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: const CombinedStacks::Stack& stack = stacks.GetStack(i); michael@0: const uint32_t pcCount = stack.size(); michael@0: for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) { michael@0: const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex]; michael@0: JS::Rooted framePair(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!framePair) { michael@0: return nullptr; michael@0: } michael@0: int modIndex = (std::numeric_limits::max() == frame.mModIndex) ? michael@0: -1 : frame.mModIndex; michael@0: if (!JS_SetElement(cx, framePair, 0, modIndex)) { michael@0: return nullptr; michael@0: } michael@0: if (!JS_SetElement(cx, framePair, 1, static_cast(frame.mOffset))) { michael@0: return nullptr; michael@0: } michael@0: if (!JS_SetElement(cx, pcArray, pcIndex, framePair)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: static bool michael@0: IsValidBreakpadId(const std::string &breakpadId) { michael@0: if (breakpadId.size() < 33) { michael@0: return false; michael@0: } michael@0: for (unsigned i = 0, n = breakpadId.size(); i < n; ++i) { michael@0: char c = breakpadId[i]; michael@0: if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Read a stack from the given file name. In case of any error, aStack is michael@0: // unchanged. michael@0: static void michael@0: ReadStack(const char *aFileName, Telemetry::ProcessedStack &aStack) michael@0: { michael@0: std::ifstream file(aFileName); michael@0: michael@0: size_t numModules; michael@0: file >> numModules; michael@0: if (file.fail()) { michael@0: return; michael@0: } michael@0: michael@0: char newline = file.get(); michael@0: if (file.fail() || newline != '\n') { michael@0: return; michael@0: } michael@0: michael@0: Telemetry::ProcessedStack stack; michael@0: for (size_t i = 0; i < numModules; ++i) { michael@0: std::string breakpadId; michael@0: file >> breakpadId; michael@0: if (file.fail() || !IsValidBreakpadId(breakpadId)) { michael@0: return; michael@0: } michael@0: michael@0: char space = file.get(); michael@0: if (file.fail() || space != ' ') { michael@0: return; michael@0: } michael@0: michael@0: std::string moduleName; michael@0: getline(file, moduleName); michael@0: if (file.fail() || moduleName[0] == ' ') { michael@0: return; michael@0: } michael@0: michael@0: Telemetry::ProcessedStack::Module module = { michael@0: moduleName, michael@0: breakpadId michael@0: }; michael@0: stack.AddModule(module); michael@0: } michael@0: michael@0: size_t numFrames; michael@0: file >> numFrames; michael@0: if (file.fail()) { michael@0: return; michael@0: } michael@0: michael@0: newline = file.get(); michael@0: if (file.fail() || newline != '\n') { michael@0: return; michael@0: } michael@0: michael@0: for (size_t i = 0; i < numFrames; ++i) { michael@0: uint16_t index; michael@0: file >> index; michael@0: uintptr_t offset; michael@0: file >> std::hex >> offset >> std::dec; michael@0: if (file.fail()) { michael@0: return; michael@0: } michael@0: michael@0: Telemetry::ProcessedStack::Frame frame = { michael@0: offset, michael@0: index michael@0: }; michael@0: stack.AddFrame(frame); michael@0: } michael@0: michael@0: aStack = stack; michael@0: } michael@0: michael@0: static JSObject* michael@0: CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time) michael@0: { michael@0: /* Create JS representation of TimeHistogram, michael@0: in the format of Chromium-style histograms. */ michael@0: JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!ret) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0), michael@0: JSPROP_ENUMERATE) || michael@0: !JS_DefineProperty(cx, ret, "max", michael@0: time.GetBucketMax(ArrayLength(time) - 1), michael@0: JSPROP_ENUMERATE) || michael@0: !JS_DefineProperty(cx, ret, "histogram_type", michael@0: nsITelemetry::HISTOGRAM_EXPONENTIAL, michael@0: JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: // TODO: calculate "sum", "log_sum", and "log_sum_squares" michael@0: if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE) || michael@0: !JS_DefineProperty(cx, ret, "log_sum", 0.0, JSPROP_ENUMERATE) || michael@0: !JS_DefineProperty(cx, ret, "log_sum_squares", 0.0, JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JS::RootedObject ranges( michael@0: cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); michael@0: JS::RootedObject counts( michael@0: cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); michael@0: if (!ranges || !counts) { michael@0: return nullptr; michael@0: } michael@0: /* In a Chromium-style histogram, the first bucket is an "under" bucket michael@0: that represents all values below the histogram's range. */ michael@0: if (!JS_SetElement(cx, ranges, 0, time.GetBucketMin(0)) || michael@0: !JS_SetElement(cx, counts, 0, 0)) { michael@0: return nullptr; michael@0: } michael@0: for (size_t i = 0; i < ArrayLength(time); i++) { michael@0: if (!JS_SetElement(cx, ranges, i + 1, time.GetBucketMax(i)) || michael@0: !JS_SetElement(cx, counts, i + 1, time[i])) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) || michael@0: !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: static JSObject* michael@0: CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang) michael@0: { michael@0: JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!ret) { michael@0: return nullptr; michael@0: } michael@0: michael@0: const Telemetry::HangHistogram::Stack& hangStack = hang.GetStack(); michael@0: JS::RootedObject stack(cx, michael@0: JS_NewArrayObject(cx, hangStack.length())); michael@0: if (!ret) { michael@0: return nullptr; michael@0: } michael@0: for (size_t i = 0; i < hangStack.length(); i++) { michael@0: JS::RootedString string(cx, JS_NewStringCopyZ(cx, hangStack[i])); michael@0: if (!JS_SetElement(cx, stack, i, string)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang)); michael@0: if (!time || michael@0: !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) || michael@0: !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: static JSObject* michael@0: CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread) michael@0: { michael@0: JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!ret) { michael@0: return nullptr; michael@0: } michael@0: JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName())); michael@0: if (!name || michael@0: !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity)); michael@0: if (!activity || michael@0: !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!hangs) { michael@0: return nullptr; michael@0: } michael@0: for (size_t i = 0; i < thread.mHangs.length(); i++) { michael@0: JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i])); michael@0: if (!JS_SetElement(cx, hangs, i, obj)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::MutableHandle ret) michael@0: { michael@0: JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!retObj) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: size_t threadIndex = 0; michael@0: michael@0: #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR michael@0: /* First add active threads; we need to hold |iter| (and its lock) michael@0: throughout this method to avoid a race condition where a thread can michael@0: be recorded twice if the thread is destroyed while this method is michael@0: running */ michael@0: BackgroundHangMonitor::ThreadHangStatsIterator iter; michael@0: for (Telemetry::ThreadHangStats* histogram = iter.GetNext(); michael@0: histogram; histogram = iter.GetNext()) { michael@0: JS::RootedObject obj(cx, michael@0: CreateJSThreadHangStats(cx, *histogram)); michael@0: if (!JS_SetElement(cx, retObj, threadIndex++, obj)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Add saved threads next michael@0: MutexAutoLock autoLock(mThreadHangStatsMutex); michael@0: for (size_t i = 0; i < mThreadHangStats.length(); i++) { michael@0: JS::RootedObject obj(cx, michael@0: CreateJSThreadHangStats(cx, mThreadHangStats[i])); michael@0: if (!JS_SetElement(cx, retObj, threadIndex++, obj)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: ret.setObject(*retObj); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) michael@0: { michael@0: nsAutoCString nativePath; michael@0: nsresult rv = aProfileDir->GetNativePath(nativePath); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: const char *name = nativePath.get(); michael@0: PRDir *dir = PR_OpenDir(name); michael@0: if (!dir) { michael@0: return; michael@0: } michael@0: michael@0: PRDirEntry *ent; michael@0: const char *prefix = "Telemetry.LateWriteFinal-"; michael@0: unsigned int prefixLen = strlen(prefix); michael@0: while ((ent = PR_ReadDir(dir, PR_SKIP_NONE))) { michael@0: if (strncmp(prefix, ent->name, prefixLen) != 0) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoCString stackNativePath = nativePath; michael@0: stackNativePath += XPCOM_FILE_PATH_SEPARATOR; michael@0: stackNativePath += nsDependentCString(ent->name); michael@0: michael@0: Telemetry::ProcessedStack stack; michael@0: ReadStack(stackNativePath.get(), stack); michael@0: if (stack.GetStackSize() != 0) { michael@0: mLateWritesStacks.AddStack(stack); michael@0: } michael@0: // Delete the file so that we don't report it again on the next run. michael@0: PR_Delete(stackNativePath.get()); michael@0: } michael@0: PR_CloseDir(dir); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetLateWrites(JSContext *cx, JS::MutableHandle ret) michael@0: { michael@0: // The user must call AsyncReadTelemetryData first. We return an empty list michael@0: // instead of reporting a failure so that the rest of telemetry can uniformly michael@0: // handle the read not being available yet. michael@0: michael@0: // FIXME: we allocate the js object again and again in the getter. We should michael@0: // figure out a way to cache it. In order to do that we have to call michael@0: // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl michael@0: // constructor, but it is not clear how to get a JSContext in there. michael@0: // Another option would be to call it in here when we first call michael@0: // CreateJSStackObject, but we would still need to figure out where to call michael@0: // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot michael@0: // and just set the pointer to nullptr is the telemetry destructor? michael@0: michael@0: JSObject *report; michael@0: if (!mCachedTelemetryData) { michael@0: CombinedStacks empty; michael@0: report = CreateJSStackObject(cx, empty); michael@0: } else { michael@0: report = CreateJSStackObject(cx, mLateWritesStacks); michael@0: } michael@0: michael@0: if (report == nullptr) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: ret.setObject(*report); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::RegisteredHistograms(uint32_t *aCount, char*** aHistograms) michael@0: { michael@0: size_t count = ArrayLength(gHistograms); michael@0: size_t offset = 0; michael@0: char** histograms = static_cast(nsMemory::Alloc(count * sizeof(char*))); michael@0: michael@0: for (size_t i = 0; i < count; ++i) { michael@0: if (IsExpired(gHistograms[i].expiration())) { michael@0: offset++; michael@0: continue; michael@0: } michael@0: michael@0: const char* h = gHistograms[i].id(); michael@0: size_t len = strlen(h); michael@0: histograms[i - offset] = static_cast(nsMemory::Clone(h, len+1)); michael@0: } michael@0: michael@0: *aCount = count - offset; michael@0: *aHistograms = histograms; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, michael@0: JS::MutableHandle ret) michael@0: { michael@0: Histogram *h; michael@0: nsresult rv = GetHistogramByName(name, &h); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return WrapAndReturnHistogram(h, cx, ret); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetCanRecord(bool *ret) { michael@0: *ret = mCanRecord; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::SetCanRecord(bool canRecord) { michael@0: mCanRecord = !!canRecord; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: TelemetryImpl::CanRecord() { michael@0: return !sTelemetry || sTelemetry->mCanRecord; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetCanSend(bool *ret) { michael@0: #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) michael@0: *ret = true; michael@0: #else michael@0: *ret = false; michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: TelemetryImpl::CreateTelemetryInstance() michael@0: { michael@0: NS_ABORT_IF_FALSE(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()"); michael@0: sTelemetry = new TelemetryImpl(); michael@0: // AddRef for the local reference michael@0: NS_ADDREF(sTelemetry); michael@0: // AddRef for the caller michael@0: nsCOMPtr ret = sTelemetry; michael@0: michael@0: sTelemetry->InitMemoryReporter(); michael@0: michael@0: return ret.forget(); michael@0: } michael@0: michael@0: void michael@0: TelemetryImpl::ShutdownTelemetry() michael@0: { michael@0: // No point in collecting IO beyond this point michael@0: ClearIOReporting(); michael@0: NS_IF_RELEASE(sTelemetry); michael@0: } michael@0: michael@0: void michael@0: TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay, michael@0: SanitizedState state) michael@0: { michael@0: AutoHashtable *slowSQLMap = nullptr; michael@0: if (state == Sanitized) michael@0: slowSQLMap = &(sTelemetry->mSanitizedSQL); michael@0: else michael@0: slowSQLMap = &(sTelemetry->mPrivateSQL); michael@0: michael@0: MutexAutoLock hashMutex(sTelemetry->mHashMutex); michael@0: michael@0: SlowSQLEntryType *entry = slowSQLMap->GetEntry(sql); michael@0: if (!entry) { michael@0: entry = slowSQLMap->PutEntry(sql); michael@0: if (MOZ_UNLIKELY(!entry)) michael@0: return; michael@0: entry->mData.mainThread.hitCount = 0; michael@0: entry->mData.mainThread.totalTime = 0; michael@0: entry->mData.otherThreads.hitCount = 0; michael@0: entry->mData.otherThreads.totalTime = 0; michael@0: } michael@0: michael@0: if (NS_IsMainThread()) { michael@0: entry->mData.mainThread.hitCount++; michael@0: entry->mData.mainThread.totalTime += delay; michael@0: } else { michael@0: entry->mData.otherThreads.hitCount++; michael@0: entry->mData.otherThreads.totalTime += delay; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This method replaces string literals in SQL strings with the word :private michael@0: * michael@0: * States used in this state machine: michael@0: * michael@0: * NORMAL: michael@0: * - This is the active state when not iterating over a string literal or michael@0: * comment michael@0: * michael@0: * SINGLE_QUOTE: michael@0: * - Defined here: http://www.sqlite.org/lang_expr.html michael@0: * - This state represents iterating over a string literal opened with michael@0: * a single quote. michael@0: * - A single quote within the string can be encoded by putting 2 single quotes michael@0: * in a row, e.g. 'This literal contains an escaped quote ''' michael@0: * - Any double quotes found within a single-quoted literal are ignored michael@0: * - This state covers BLOB literals, e.g. X'ABC123' michael@0: * - The string literal and the enclosing quotes will be replaced with michael@0: * the text :private michael@0: * michael@0: * DOUBLE_QUOTE: michael@0: * - Same rules as the SINGLE_QUOTE state. michael@0: * - According to http://www.sqlite.org/lang_keywords.html, michael@0: * SQLite interprets text in double quotes as an identifier unless it's used in michael@0: * a context where it cannot be resolved to an identifier and a string literal michael@0: * is allowed. This method removes text in double-quotes for safety. michael@0: * michael@0: * DASH_COMMENT: michael@0: * - http://www.sqlite.org/lang_comment.html michael@0: * - A dash comment starts with two dashes in a row, michael@0: * e.g. DROP TABLE foo -- a comment michael@0: * - Any text following two dashes in a row is interpreted as a comment until michael@0: * end of input or a newline character michael@0: * - Any quotes found within the comment are ignored and no replacements made michael@0: * michael@0: * C_STYLE_COMMENT: michael@0: * - http://www.sqlite.org/lang_comment.html michael@0: * - A C-style comment starts with a forward slash and an asterisk, and ends michael@0: * with an asterisk and a forward slash michael@0: * - Any text following comment start is interpreted as a comment up to end of michael@0: * input or comment end michael@0: * - Any quotes found within the comment are ignored and no replacements made michael@0: */ michael@0: nsCString michael@0: TelemetryImpl::SanitizeSQL(const nsACString &sql) { michael@0: nsCString output; michael@0: int length = sql.Length(); michael@0: michael@0: typedef enum { michael@0: NORMAL, michael@0: SINGLE_QUOTE, michael@0: DOUBLE_QUOTE, michael@0: DASH_COMMENT, michael@0: C_STYLE_COMMENT, michael@0: } State; michael@0: michael@0: State state = NORMAL; michael@0: int fragmentStart = 0; michael@0: for (int i = 0; i < length; i++) { michael@0: char character = sql[i]; michael@0: char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0'; michael@0: michael@0: switch (character) { michael@0: case '\'': michael@0: case '"': michael@0: if (state == NORMAL) { michael@0: state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE; michael@0: output += nsDependentCSubstring(sql, fragmentStart, i - fragmentStart); michael@0: output += ":private"; michael@0: fragmentStart = -1; michael@0: } else if ((state == SINGLE_QUOTE && character == '\'') || michael@0: (state == DOUBLE_QUOTE && character == '"')) { michael@0: if (nextCharacter == character) { michael@0: // Two consecutive quotes within a string literal are a single escaped quote michael@0: i++; michael@0: } else { michael@0: state = NORMAL; michael@0: fragmentStart = i + 1; michael@0: } michael@0: } michael@0: break; michael@0: case '-': michael@0: if (state == NORMAL) { michael@0: if (nextCharacter == '-') { michael@0: state = DASH_COMMENT; michael@0: i++; michael@0: } michael@0: } michael@0: break; michael@0: case '\n': michael@0: if (state == DASH_COMMENT) { michael@0: state = NORMAL; michael@0: } michael@0: break; michael@0: case '/': michael@0: if (state == NORMAL) { michael@0: if (nextCharacter == '*') { michael@0: state = C_STYLE_COMMENT; michael@0: i++; michael@0: } michael@0: } michael@0: break; michael@0: case '*': michael@0: if (state == C_STYLE_COMMENT) { michael@0: if (nextCharacter == '/') { michael@0: state = NORMAL; michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if ((fragmentStart >= 0) && fragmentStart < length) michael@0: output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart); michael@0: michael@0: return output; michael@0: } michael@0: michael@0: // Slow SQL statements will be automatically michael@0: // trimmed to kMaxSlowStatementLength characters. michael@0: // This limit doesn't include the ellipsis and DB name, michael@0: // that are appended at the end of the stored statement. michael@0: const uint32_t kMaxSlowStatementLength = 1000; michael@0: michael@0: void michael@0: TelemetryImpl::RecordSlowStatement(const nsACString &sql, michael@0: const nsACString &dbName, michael@0: uint32_t delay) michael@0: { michael@0: if (!sTelemetry || !sTelemetry->mCanRecord) michael@0: return; michael@0: michael@0: bool isFirefoxDB = sTelemetry->mTrackedDBs.Contains(dbName); michael@0: if (isFirefoxDB) { michael@0: nsAutoCString sanitizedSQL(SanitizeSQL(sql)); michael@0: if (sanitizedSQL.Length() > kMaxSlowStatementLength) { michael@0: sanitizedSQL.SetLength(kMaxSlowStatementLength); michael@0: sanitizedSQL += "..."; michael@0: } michael@0: sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get()); michael@0: StoreSlowSQL(sanitizedSQL, delay, Sanitized); michael@0: } else { michael@0: // Report aggregate DB-level statistics for addon DBs michael@0: nsAutoCString aggregate; michael@0: aggregate.AppendPrintf("Untracked SQL for %s", michael@0: nsPromiseFlatCString(dbName).get()); michael@0: StoreSlowSQL(aggregate, delay, Sanitized); michael@0: } michael@0: michael@0: nsAutoCString fullSQL; michael@0: fullSQL.AppendPrintf("%s /* %s */", michael@0: nsPromiseFlatCString(sql).get(), michael@0: nsPromiseFlatCString(dbName).get()); michael@0: StoreSlowSQL(fullSQL, delay, Unsanitized); michael@0: } michael@0: michael@0: #if defined(MOZ_ENABLE_PROFILER_SPS) michael@0: void michael@0: TelemetryImpl::RecordChromeHang(uint32_t aDuration, michael@0: Telemetry::ProcessedStack &aStack, michael@0: int32_t aSystemUptime, michael@0: int32_t aFirefoxUptime) michael@0: { michael@0: if (!sTelemetry || !sTelemetry->mCanRecord) michael@0: return; michael@0: michael@0: MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex); michael@0: michael@0: sTelemetry->mHangReports.AddHang(aStack, aDuration, michael@0: aSystemUptime, aFirefoxUptime); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats) michael@0: { michael@0: if (!sTelemetry || !sTelemetry->mCanRecord) michael@0: return; michael@0: michael@0: MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex); michael@0: michael@0: sTelemetry->mThreadHangStats.append(Move(aStats)); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter) michael@0: NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance) michael@0: michael@0: #define NS_TELEMETRY_CID \ michael@0: {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}} michael@0: NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID); michael@0: michael@0: const Module::CIDEntry kTelemetryCIDs[] = { michael@0: { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor }, michael@0: { nullptr } michael@0: }; michael@0: michael@0: const Module::ContractIDEntry kTelemetryContracts[] = { michael@0: { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID }, michael@0: { nullptr } michael@0: }; michael@0: michael@0: const Module kTelemetryModule = { michael@0: Module::kVersion, michael@0: kTelemetryCIDs, michael@0: kTelemetryContracts, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: TelemetryImpl::ShutdownTelemetry michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret) michael@0: { michael@0: if (sTelemetryIOObserver) { michael@0: JS::Rooted obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), michael@0: JS::NullPtr())); michael@0: if (!obj) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: ret.setObject(*obj); michael@0: return NS_OK; michael@0: } michael@0: ret.setNull(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TelemetryImpl::MsSinceProcessStart(double* aResult) michael@0: { michael@0: bool error; michael@0: *aResult = (TimeStamp::NowLoRes() - michael@0: TimeStamp::ProcessCreation(error)).ToMilliseconds(); michael@0: if (error) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: size_t michael@0: TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) michael@0: { michael@0: size_t n = aMallocSizeOf(this); michael@0: // Ignore the hashtables in mAddonMap; they are not significant. michael@0: n += mAddonMap.SizeOfExcludingThis(nullptr, aMallocSizeOf); michael@0: n += mHistogramMap.SizeOfExcludingThis(nullptr, aMallocSizeOf); michael@0: { // Scope for mHashMutex lock michael@0: MutexAutoLock lock(mHashMutex); michael@0: n += mPrivateSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf); michael@0: n += mSanitizedSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf); michael@0: } michael@0: n += mTrackedDBs.SizeOfExcludingThis(nullptr, aMallocSizeOf); michael@0: { // Scope for mHangReportsMutex lock michael@0: MutexAutoLock lock(mHangReportsMutex); michael@0: n += mHangReports.SizeOfExcludingThis(); michael@0: } michael@0: { // Scope for mThreadHangStatsMutex lock michael@0: MutexAutoLock lock(mThreadHangStatsMutex); michael@0: n += mThreadHangStats.sizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: // It's a bit gross that we measure this other stuff that lives outside of michael@0: // TelemetryImpl... oh well. michael@0: if (sTelemetryIOObserver) { michael@0: n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: StatisticsRecorder::Histograms hs; michael@0: StatisticsRecorder::GetHistograms(&hs); michael@0: for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { michael@0: Histogram *h = *it; michael@0: n += h->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: void michael@0: RecordShutdownStartTimeStamp() { michael@0: #ifdef DEBUG michael@0: // FIXME: this function should only be called once, since it should be called michael@0: // at the earliest point we *know* we are shutting down. Unfortunately michael@0: // this assert has been firing. Given that if we are called multiple times michael@0: // we just keep the last timestamp, the assert is commented for now. michael@0: static bool recorded = false; michael@0: // MOZ_ASSERT(!recorded); michael@0: (void)recorded; // Silence unused-var warnings (remove when assert re-enabled) michael@0: recorded = true; michael@0: #endif michael@0: michael@0: if (!Telemetry::CanRecord()) michael@0: return; michael@0: michael@0: gRecordedShutdownStartTime = TimeStamp::Now(); michael@0: michael@0: GetShutdownTimeFileName(); michael@0: } michael@0: michael@0: void michael@0: RecordShutdownEndTimeStamp() { michael@0: if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName) michael@0: return; michael@0: michael@0: nsCString name(gRecordedShutdownTimeFileName); michael@0: PL_strfree(gRecordedShutdownTimeFileName); michael@0: gRecordedShutdownTimeFileName = nullptr; michael@0: gAlreadyFreedShutdownTimeFileName = true; michael@0: michael@0: nsCString tmpName = name; michael@0: tmpName += ".tmp"; michael@0: FILE *f = fopen(tmpName.get(), "w"); michael@0: if (!f) michael@0: return; michael@0: // On a normal release build this should be called just before michael@0: // calling _exit, but on a debug build or when the user forces a full michael@0: // shutdown this is called as late as possible, so we have to michael@0: // white list this write as write poisoning will be enabled. michael@0: MozillaRegisterDebugFILE(f); michael@0: michael@0: TimeStamp now = TimeStamp::Now(); michael@0: MOZ_ASSERT(now >= gRecordedShutdownStartTime); michael@0: TimeDuration diff = now - gRecordedShutdownStartTime; michael@0: uint32_t diff2 = diff.ToMilliseconds(); michael@0: int written = fprintf(f, "%d\n", diff2); michael@0: MozillaUnRegisterDebugFILE(f); michael@0: int rv = fclose(f); michael@0: if (written < 0 || rv != 0) { michael@0: PR_Delete(tmpName.get()); michael@0: return; michael@0: } michael@0: PR_Delete(name.get()); michael@0: PR_Rename(tmpName.get(), name.get()); michael@0: } michael@0: michael@0: namespace Telemetry { michael@0: michael@0: void michael@0: Accumulate(ID aHistogram, uint32_t aSample) michael@0: { michael@0: if (!TelemetryImpl::CanRecord()) { michael@0: return; michael@0: } michael@0: Histogram *h; michael@0: nsresult rv = GetHistogramByEnumId(aHistogram, &h); michael@0: if (NS_SUCCEEDED(rv)) michael@0: h->Add(aSample); michael@0: } michael@0: michael@0: void michael@0: Accumulate(const char* name, uint32_t sample) michael@0: { michael@0: if (!TelemetryImpl::CanRecord()) { michael@0: return; michael@0: } michael@0: ID id; michael@0: nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: Histogram *h; michael@0: rv = GetHistogramByEnumId(id, &h); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: h->Add(sample); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end) michael@0: { michael@0: Accumulate(aHistogram, michael@0: static_cast((end - start).ToMilliseconds())); michael@0: } michael@0: michael@0: bool michael@0: CanRecord() michael@0: { michael@0: return TelemetryImpl::CanRecord(); michael@0: } michael@0: michael@0: base::Histogram* michael@0: GetHistogramById(ID id) michael@0: { michael@0: Histogram *h = nullptr; michael@0: GetHistogramByEnumId(id, &h); michael@0: return h; michael@0: } michael@0: michael@0: void michael@0: RecordSlowSQLStatement(const nsACString &statement, michael@0: const nsACString &dbName, michael@0: uint32_t delay) michael@0: { michael@0: TelemetryImpl::RecordSlowStatement(statement, dbName, delay); michael@0: } michael@0: michael@0: void Init() michael@0: { michael@0: // Make the service manager hold a long-lived reference to the service michael@0: nsCOMPtr telemetryService = michael@0: do_GetService("@mozilla.org/base/telemetry;1"); michael@0: MOZ_ASSERT(telemetryService); michael@0: } michael@0: michael@0: #if defined(MOZ_ENABLE_PROFILER_SPS) michael@0: void RecordChromeHang(uint32_t duration, michael@0: ProcessedStack &aStack, michael@0: int32_t aSystemUptime, michael@0: int32_t aFirefoxUptime) michael@0: { michael@0: TelemetryImpl::RecordChromeHang(duration, aStack, michael@0: aSystemUptime, aFirefoxUptime); michael@0: } michael@0: #endif michael@0: michael@0: void RecordThreadHangStats(ThreadHangStats& aStats) michael@0: { michael@0: TelemetryImpl::RecordThreadHangStats(aStats); michael@0: } michael@0: michael@0: ProcessedStack::ProcessedStack() michael@0: { michael@0: } michael@0: michael@0: size_t ProcessedStack::GetStackSize() const michael@0: { michael@0: return mStack.size(); michael@0: } michael@0: michael@0: const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const michael@0: { michael@0: MOZ_ASSERT(aIndex < mStack.size()); michael@0: return mStack[aIndex]; michael@0: } michael@0: michael@0: void ProcessedStack::AddFrame(const Frame &aFrame) michael@0: { michael@0: mStack.push_back(aFrame); michael@0: } michael@0: michael@0: size_t ProcessedStack::GetNumModules() const michael@0: { michael@0: return mModules.size(); michael@0: } michael@0: michael@0: const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const michael@0: { michael@0: MOZ_ASSERT(aIndex < mModules.size()); michael@0: return mModules[aIndex]; michael@0: } michael@0: michael@0: void ProcessedStack::AddModule(const Module &aModule) michael@0: { michael@0: mModules.push_back(aModule); michael@0: } michael@0: michael@0: void ProcessedStack::Clear() { michael@0: mModules.clear(); michael@0: mStack.clear(); michael@0: } michael@0: michael@0: bool ProcessedStack::Module::operator==(const Module& aOther) const { michael@0: return mName == aOther.mName && michael@0: mBreakpadId == aOther.mBreakpadId; michael@0: } michael@0: michael@0: struct StackFrame michael@0: { michael@0: uintptr_t mPC; // The program counter at this position in the call stack. michael@0: uint16_t mIndex; // The number of this frame in the call stack. michael@0: uint16_t mModIndex; // The index of module that has this program counter. michael@0: }; michael@0: michael@0: michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: static bool CompareByPC(const StackFrame &a, const StackFrame &b) michael@0: { michael@0: return a.mPC < b.mPC; michael@0: } michael@0: michael@0: static bool CompareByIndex(const StackFrame &a, const StackFrame &b) michael@0: { michael@0: return a.mIndex < b.mIndex; michael@0: } michael@0: #endif michael@0: michael@0: ProcessedStack michael@0: GetStackAndModules(const std::vector& aPCs) michael@0: { michael@0: std::vector rawStack; michael@0: for (std::vector::const_iterator i = aPCs.begin(), michael@0: e = aPCs.end(); i != e; ++i) { michael@0: uintptr_t aPC = *i; michael@0: StackFrame Frame = {aPC, static_cast(rawStack.size()), michael@0: std::numeric_limits::max()}; michael@0: rawStack.push_back(Frame); michael@0: } michael@0: michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: // Remove all modules not referenced by a PC on the stack michael@0: std::sort(rawStack.begin(), rawStack.end(), CompareByPC); michael@0: michael@0: size_t moduleIndex = 0; michael@0: size_t stackIndex = 0; michael@0: size_t stackSize = rawStack.size(); michael@0: michael@0: SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf(); michael@0: rawModules.SortByAddress(); michael@0: michael@0: while (moduleIndex < rawModules.GetSize()) { michael@0: const SharedLibrary& module = rawModules.GetEntry(moduleIndex); michael@0: uintptr_t moduleStart = module.GetStart(); michael@0: uintptr_t moduleEnd = module.GetEnd() - 1; michael@0: // the interval is [moduleStart, moduleEnd) michael@0: michael@0: bool moduleReferenced = false; michael@0: for (;stackIndex < stackSize; ++stackIndex) { michael@0: uintptr_t pc = rawStack[stackIndex].mPC; michael@0: if (pc >= moduleEnd) michael@0: break; michael@0: michael@0: if (pc >= moduleStart) { michael@0: // If the current PC is within the current module, mark michael@0: // module as used michael@0: moduleReferenced = true; michael@0: rawStack[stackIndex].mPC -= moduleStart; michael@0: rawStack[stackIndex].mModIndex = moduleIndex; michael@0: } else { michael@0: // PC does not belong to any module. It is probably from michael@0: // the JIT. Use a fixed mPC so that we don't get different michael@0: // stacks on different runs. michael@0: rawStack[stackIndex].mPC = michael@0: std::numeric_limits::max(); michael@0: } michael@0: } michael@0: michael@0: if (moduleReferenced) { michael@0: ++moduleIndex; michael@0: } else { michael@0: // Remove module if no PCs within its address range michael@0: rawModules.RemoveEntries(moduleIndex, moduleIndex + 1); michael@0: } michael@0: } michael@0: michael@0: for (;stackIndex < stackSize; ++stackIndex) { michael@0: // These PCs are past the last module. michael@0: rawStack[stackIndex].mPC = std::numeric_limits::max(); michael@0: } michael@0: michael@0: std::sort(rawStack.begin(), rawStack.end(), CompareByIndex); michael@0: #endif michael@0: michael@0: // Copy the information to the return value. michael@0: ProcessedStack Ret; michael@0: for (std::vector::iterator i = rawStack.begin(), michael@0: e = rawStack.end(); i != e; ++i) { michael@0: const StackFrame &rawFrame = *i; michael@0: ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex }; michael@0: Ret.AddFrame(frame); michael@0: } michael@0: michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) { michael@0: const SharedLibrary &info = rawModules.GetEntry(i); michael@0: const std::string &name = info.GetName(); michael@0: std::string basename = name; michael@0: #ifdef XP_MACOSX michael@0: // FIXME: We want to use just the basename as the libname, but the michael@0: // current profiler addon needs the full path name, so we compute the michael@0: // basename in here. michael@0: size_t pos = name.rfind('/'); michael@0: if (pos != std::string::npos) { michael@0: basename = name.substr(pos + 1); michael@0: } michael@0: #endif michael@0: ProcessedStack::Module module = { michael@0: basename, michael@0: info.GetBreakpadId() michael@0: }; michael@0: Ret.AddModule(module); michael@0: } michael@0: #endif michael@0: michael@0: return Ret; michael@0: } michael@0: michael@0: void michael@0: WriteFailedProfileLock(nsIFile* aProfileDir) michael@0: { michael@0: nsCOMPtr file; michael@0: nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: int64_t fileSize = 0; michael@0: rv = file->GetFileSize(&fileSize); michael@0: // It's expected that the file might not exist yet michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { michael@0: return; michael@0: } michael@0: nsCOMPtr fileStream; michael@0: rv = NS_NewLocalFileStream(getter_AddRefs(fileStream), file, michael@0: PR_RDWR | PR_CREATE_FILE, 0640); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize); michael@0: unsigned int failedLockCount = 0; michael@0: if (fileSize > 0) { michael@0: nsCOMPtr inStream = do_QueryInterface(fileStream); michael@0: NS_ENSURE_TRUE_VOID(inStream); michael@0: if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) { michael@0: failedLockCount = 0; michael@0: } michael@0: } michael@0: ++failedLockCount; michael@0: nsAutoCString bufStr; michael@0: bufStr.AppendInt(static_cast(failedLockCount)); michael@0: nsCOMPtr seekStream = do_QueryInterface(fileStream); michael@0: NS_ENSURE_TRUE_VOID(seekStream); michael@0: // If we read in an existing failed lock count, we need to reset the file ptr michael@0: if (fileSize > 0) { michael@0: rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: nsCOMPtr outStream = do_QueryInterface(fileStream); michael@0: uint32_t bytesLeft = bufStr.Length(); michael@0: const char* bytes = bufStr.get(); michael@0: do { michael@0: uint32_t written = 0; michael@0: rv = outStream->Write(bytes, bytesLeft, &written); michael@0: if (NS_FAILED(rv)) { michael@0: break; michael@0: } michael@0: bytes += written; michael@0: bytesLeft -= written; michael@0: } while (bytesLeft > 0); michael@0: seekStream->SetEOF(); michael@0: } michael@0: michael@0: void michael@0: InitIOReporting(nsIFile* aXreDir) michael@0: { michael@0: // Never initialize twice michael@0: if (sTelemetryIOObserver) { michael@0: return; michael@0: } michael@0: michael@0: sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir); michael@0: IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, michael@0: sTelemetryIOObserver); michael@0: } michael@0: michael@0: void michael@0: SetProfileDir(nsIFile* aProfD) michael@0: { michael@0: if (!sTelemetryIOObserver || !aProfD) { michael@0: return; michael@0: } michael@0: nsAutoString profDirPath; michael@0: nsresult rv = aProfD->GetPath(profDirPath); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: sTelemetryIOObserver->AddPath(profDirPath, NS_LITERAL_STRING("{profile}")); michael@0: } michael@0: michael@0: void michael@0: TimeHistogram::Add(PRIntervalTime aTime) michael@0: { michael@0: uint32_t timeMs = PR_IntervalToMilliseconds(aTime); michael@0: size_t index = mozilla::FloorLog2(timeMs); michael@0: operator[](index)++; michael@0: } michael@0: michael@0: uint32_t michael@0: HangHistogram::GetHash(const Stack& aStack) michael@0: { michael@0: uint32_t hash = 0; michael@0: for (const char* const* label = aStack.begin(); michael@0: label != aStack.end(); label++) { michael@0: /* We only need to hash the pointer instead of the text content michael@0: because we are assuming constant pointers */ michael@0: hash = AddToHash(hash, *label); michael@0: } michael@0: return hash; michael@0: } michael@0: michael@0: bool michael@0: HangHistogram::operator==(const HangHistogram& aOther) const michael@0: { michael@0: if (mHash != aOther.mHash) { michael@0: return false; michael@0: } michael@0: if (mStack.length() != aOther.mStack.length()) { michael@0: return false; michael@0: } michael@0: return PodEqual(mStack.begin(), aOther.mStack.begin(), mStack.length()); michael@0: } michael@0: michael@0: michael@0: } // namespace Telemetry michael@0: } // namespace mozilla michael@0: michael@0: NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule; michael@0: michael@0: /** michael@0: * The XRE_TelemetryAdd function is to be used by embedding applications michael@0: * that can't use mozilla::Telemetry::Accumulate() directly. michael@0: */ michael@0: void michael@0: XRE_TelemetryAccumulate(int aID, uint32_t aSample) michael@0: { michael@0: mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample); michael@0: }