toolkit/components/telemetry/Telemetry.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include <algorithm>
     9 #include <fstream>
    11 #include <prio.h>
    13 #include "mozilla/Attributes.h"
    14 #include "mozilla/DebugOnly.h"
    15 #include "mozilla/Likely.h"
    16 #include "mozilla/MathAlgorithms.h"
    18 #include "base/histogram.h"
    19 #include "base/pickle.h"
    20 #include "nsIComponentManager.h"
    21 #include "nsIServiceManager.h"
    22 #include "nsThreadManager.h"
    23 #include "nsCOMArray.h"
    24 #include "nsCOMPtr.h"
    25 #include "nsXPCOMPrivate.h"
    26 #include "nsIXULAppInfo.h"
    27 #include "nsVersionComparator.h"
    28 #include "mozilla/MemoryReporting.h"
    29 #include "mozilla/ModuleUtils.h"
    30 #include "nsIXPConnect.h"
    31 #include "mozilla/Services.h"
    32 #include "jsapi.h"
    33 #include "jsfriendapi.h"
    34 #include "js/GCAPI.h"
    35 #include "nsString.h"
    36 #include "nsITelemetry.h"
    37 #include "nsIFile.h"
    38 #include "nsIFileStreams.h"
    39 #include "nsIMemoryReporter.h"
    40 #include "nsISeekableStream.h"
    41 #include "Telemetry.h"
    42 #include "nsTHashtable.h"
    43 #include "nsHashKeys.h"
    44 #include "nsBaseHashtable.h"
    45 #include "nsXULAppAPI.h"
    46 #include "nsReadableUtils.h"
    47 #include "nsThreadUtils.h"
    48 #if defined(XP_WIN)
    49 #include "nsUnicharUtils.h"
    50 #endif
    51 #include "nsNetCID.h"
    52 #include "nsNetUtil.h"
    53 #include "plstr.h"
    54 #include "nsAppDirectoryServiceDefs.h"
    55 #include "mozilla/BackgroundHangMonitor.h"
    56 #include "mozilla/ThreadHangStats.h"
    57 #include "mozilla/ProcessedStack.h"
    58 #include "mozilla/Mutex.h"
    59 #include "mozilla/FileUtils.h"
    60 #include "mozilla/Preferences.h"
    61 #include "mozilla/StaticPtr.h"
    62 #include "mozilla/IOInterposer.h"
    63 #include "mozilla/PoisonIOInterposer.h"
    64 #include "mozilla/StartupTimeline.h"
    65 #if defined(MOZ_ENABLE_PROFILER_SPS)
    66 #include "shared-libraries.h"
    67 #endif
    69 #define EXPIRED_ID "__expired__"
    71 namespace {
    73 using namespace base;
    74 using namespace mozilla;
    76 template<class EntryType>
    77 class AutoHashtable : public nsTHashtable<EntryType>
    78 {
    79 public:
    80   AutoHashtable(uint32_t initSize = PL_DHASH_MIN_SIZE);
    81   typedef bool (*ReflectEntryFunc)(EntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj);
    82   bool ReflectIntoJS(ReflectEntryFunc entryFunc, JSContext *cx, JS::Handle<JSObject*> obj);
    83 private:
    84   struct EnumeratorArgs {
    85     JSContext *cx;
    86     JS::Handle<JSObject*> obj;
    87     ReflectEntryFunc entryFunc;
    88   };
    89   static PLDHashOperator ReflectEntryStub(EntryType *entry, void *arg);
    90 };
    92 template<class EntryType>
    93 AutoHashtable<EntryType>::AutoHashtable(uint32_t initSize)
    94   : nsTHashtable<EntryType>(initSize)
    95 {
    96 }
    98 template<typename EntryType>
    99 PLDHashOperator
   100 AutoHashtable<EntryType>::ReflectEntryStub(EntryType *entry, void *arg)
   101 {
   102   EnumeratorArgs *args = static_cast<EnumeratorArgs *>(arg);
   103   if (!args->entryFunc(entry, args->cx, args->obj)) {
   104     return PL_DHASH_STOP;
   105   }
   106   return PL_DHASH_NEXT;
   107 }
   109 /**
   110  * Reflect the individual entries of table into JS, usually by defining
   111  * some property and value of obj.  entryFunc is called for each entry.
   112  */
   113 template<typename EntryType>
   114 bool
   115 AutoHashtable<EntryType>::ReflectIntoJS(ReflectEntryFunc entryFunc,
   116                                         JSContext *cx, JS::Handle<JSObject*> obj)
   117 {
   118   EnumeratorArgs args = { cx, obj, entryFunc };
   119   uint32_t num = this->EnumerateEntries(ReflectEntryStub, static_cast<void*>(&args));
   120   return num == this->Count();
   121 }
   123 // This class is conceptually a list of ProcessedStack objects, but it represents them
   124 // more efficiently by keeping a single global list of modules.
   125 class CombinedStacks {
   126 public:
   127   typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
   128   const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
   129   size_t GetModuleCount() const;
   130   const Stack& GetStack(unsigned aIndex) const;
   131   void AddStack(const Telemetry::ProcessedStack& aStack);
   132   size_t GetStackCount() const;
   133   size_t SizeOfExcludingThis() const;
   134 private:
   135   std::vector<Telemetry::ProcessedStack::Module> mModules;
   136   std::vector<Stack> mStacks;
   137 };
   139 static JSObject *
   140 CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks);
   142 size_t
   143 CombinedStacks::GetModuleCount() const {
   144   return mModules.size();
   145 }
   147 const Telemetry::ProcessedStack::Module&
   148 CombinedStacks::GetModule(unsigned aIndex) const {
   149   return mModules[aIndex];
   150 }
   152 void
   153 CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
   154   mStacks.resize(mStacks.size() + 1);
   155   CombinedStacks::Stack& adjustedStack = mStacks.back();
   157   size_t stackSize = aStack.GetStackSize();
   158   for (size_t i = 0; i < stackSize; ++i) {
   159     const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i);
   160     uint16_t modIndex;
   161     if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) {
   162       modIndex = frame.mModIndex;
   163     } else {
   164       const Telemetry::ProcessedStack::Module& module =
   165         aStack.GetModule(frame.mModIndex);
   166       std::vector<Telemetry::ProcessedStack::Module>::iterator modIterator =
   167         std::find(mModules.begin(), mModules.end(), module);
   168       if (modIterator == mModules.end()) {
   169         mModules.push_back(module);
   170         modIndex = mModules.size() - 1;
   171       } else {
   172         modIndex = modIterator - mModules.begin();
   173       }
   174     }
   175     Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex };
   176     adjustedStack.push_back(adjustedFrame);
   177   }
   178 }
   180 const CombinedStacks::Stack&
   181 CombinedStacks::GetStack(unsigned aIndex) const {
   182   return mStacks[aIndex];
   183 }
   185 size_t
   186 CombinedStacks::GetStackCount() const {
   187   return mStacks.size();
   188 }
   190 size_t
   191 CombinedStacks::SizeOfExcludingThis() const {
   192   // This is a crude approximation. We would like to do something like
   193   // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call
   194   // malloc_usable_size which is only safe on the pointers returned by malloc.
   195   // While it works on current libstdc++, it is better to be safe and not assume
   196   // that &vec[0] points to one. We could use a custom allocator, but
   197   // it doesn't seem worth it.
   198   size_t n = 0;
   199   n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module);
   200   n += mStacks.capacity() * sizeof(Stack);
   201   for (std::vector<Stack>::const_iterator i = mStacks.begin(),
   202          e = mStacks.end(); i != e; ++i) {
   203     const Stack& s = *i;
   204     n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame);
   205   }
   206   return n;
   207 }
   209 class HangReports {
   210 public:
   211   size_t SizeOfExcludingThis() const;
   212   void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration,
   213                int32_t aSystemUptime, int32_t aFirefoxUptime);
   214   uint32_t GetDuration(unsigned aIndex) const;
   215   int32_t GetSystemUptime(unsigned aIndex) const;
   216   int32_t GetFirefoxUptime(unsigned aIndex) const;
   217   const CombinedStacks& GetStacks() const;
   218 private:
   219   struct HangInfo {
   220     // Hang duration (in seconds)
   221     uint32_t mDuration;
   222     // System uptime (in minutes) at the time of the hang
   223     int32_t mSystemUptime;
   224     // Firefox uptime (in minutes) at the time of the hang
   225     int32_t mFirefoxUptime;
   226   };
   227   std::vector<HangInfo> mHangInfo;
   228   CombinedStacks mStacks;
   229 };
   231 void
   232 HangReports::AddHang(const Telemetry::ProcessedStack& aStack,
   233                      uint32_t aDuration,
   234                      int32_t aSystemUptime,
   235                      int32_t aFirefoxUptime) {
   236   HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime };
   237   mHangInfo.push_back(info);
   238   mStacks.AddStack(aStack);
   239 }
   241 size_t
   242 HangReports::SizeOfExcludingThis() const {
   243   size_t n = 0;
   244   n += mStacks.SizeOfExcludingThis();
   245   // This is a crude approximation. See comment on
   246   // CombinedStacks::SizeOfExcludingThis.
   247   n += mHangInfo.capacity() * sizeof(HangInfo);
   248   return n;
   249 }
   251 const CombinedStacks&
   252 HangReports::GetStacks() const {
   253   return mStacks;
   254 }
   256 uint32_t
   257 HangReports::GetDuration(unsigned aIndex) const {
   258   return mHangInfo[aIndex].mDuration;
   259 }
   261 int32_t
   262 HangReports::GetSystemUptime(unsigned aIndex) const {
   263   return mHangInfo[aIndex].mSystemUptime;
   264 }
   266 int32_t
   267 HangReports::GetFirefoxUptime(unsigned aIndex) const {
   268   return mHangInfo[aIndex].mFirefoxUptime;
   269 }
   271 /**
   272  * IOInterposeObserver recording statistics of main-thread I/O during execution,
   273  * aimed at consumption by TelemetryImpl
   274  */
   275 class TelemetryIOInterposeObserver : public IOInterposeObserver
   276 {
   277   /** File-level statistics structure */
   278   struct FileStats {
   279     FileStats()
   280       : creates(0)
   281       , reads(0)
   282       , writes(0)
   283       , fsyncs(0)
   284       , stats(0)
   285       , totalTime(0)
   286     {}
   287     uint32_t  creates;      /** Number of create/open operations */
   288     uint32_t  reads;        /** Number of read operations */
   289     uint32_t  writes;       /** Number of write operations */
   290     uint32_t  fsyncs;       /** Number of fsync operations */
   291     uint32_t  stats;        /** Number of stat operations */
   292     double    totalTime;    /** Accumulated duration of all operations */
   293   };
   295   struct SafeDir {
   296     SafeDir(const nsAString& aPath, const nsAString& aSubstName)
   297       : mPath(aPath)
   298       , mSubstName(aSubstName)
   299     {}
   300     size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
   301       return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
   302              mSubstName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   303     }
   304     nsString  mPath;        /** Path to the directory */
   305     nsString  mSubstName;   /** Name to substitute with */
   306   };
   308 public:
   309   TelemetryIOInterposeObserver(nsIFile* aXreDir);
   311   /**
   312    * An implementation of Observe that records statistics of all
   313    * file IO operations.
   314    */
   315   void Observe(Observation& aOb);
   317   /**
   318    * Reflect recorded file IO statistics into Javascript
   319    */
   320   bool ReflectIntoJS(JSContext *cx, JS::Handle<JSObject*> rootObj);
   322   /**
   323    * Adds a path for inclusion in main thread I/O report.
   324    * @param aPath Directory path
   325    * @param aSubstName Name to substitute for aPath for privacy reasons
   326    */
   327   void AddPath(const nsAString& aPath, const nsAString& aSubstName);
   329   /**
   330    * Get size of hash table with file stats
   331    */
   332   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
   333     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   334   }
   336   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
   337     size_t size;
   338     size = mFileStats.SizeOfExcludingThis(SizeOfFileIOEntryTypeExcludingThis,
   339                                           aMallocSizeOf) +
   340            mSafeDirs.SizeOfExcludingThis(aMallocSizeOf);
   341     uint32_t safeDirsLen = mSafeDirs.Length();
   342     for (uint32_t i = 0; i < safeDirsLen; ++i) {
   343       size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf);
   344     }
   345     return size;
   346   }
   348 private:
   349   enum Stage
   350   {
   351     STAGE_STARTUP = 0,
   352     STAGE_NORMAL,
   353     STAGE_SHUTDOWN,
   354     NUM_STAGES
   355   };
   356   static inline Stage NextStage(Stage aStage)
   357   {
   358     switch (aStage) {
   359       case STAGE_STARTUP:
   360         return STAGE_NORMAL;
   361       case STAGE_NORMAL:
   362         return STAGE_SHUTDOWN;
   363       case STAGE_SHUTDOWN:
   364         return STAGE_SHUTDOWN;
   365       default:
   366         return NUM_STAGES;
   367     }
   368   }
   370   struct FileStatsByStage
   371   {
   372     FileStats mStats[NUM_STAGES];
   373   };
   374   typedef nsBaseHashtableET<nsStringHashKey, FileStatsByStage> FileIOEntryType;
   376   // Statistics for each filename
   377   AutoHashtable<FileIOEntryType> mFileStats;
   378   // Container for whitelisted directories
   379   nsTArray<SafeDir> mSafeDirs;
   380   Stage             mCurStage;
   382   /**
   383    * Reflect a FileIOEntryType object to a Javascript property on obj with
   384    * filename as key containing array:
   385    * [totalTime, creates, reads, writes, fsyncs, stats]
   386    */
   387   static bool ReflectFileStats(FileIOEntryType* entry, JSContext *cx,
   388                                JS::Handle<JSObject*> obj);
   390   static size_t SizeOfFileIOEntryTypeExcludingThis(FileIOEntryType* aEntry,
   391                                                    mozilla::MallocSizeOf mallocSizeOf,
   392                                                    void*)
   393   {
   394     return aEntry->GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf);
   395   }
   396 };
   398 TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir)
   399   : mCurStage(STAGE_STARTUP)
   400 {
   401   nsAutoString xreDirPath;
   402   nsresult rv = aXreDir->GetPath(xreDirPath);
   403   if (NS_SUCCEEDED(rv)) {
   404     AddPath(xreDirPath, NS_LITERAL_STRING("{xre}"));
   405   }
   406 }
   408 void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath,
   409                                            const nsAString& aSubstName)
   410 {
   411   mSafeDirs.AppendElement(SafeDir(aPath, aSubstName));
   412 }
   414 void TelemetryIOInterposeObserver::Observe(Observation& aOb)
   415 {
   416   // We only report main-thread I/O
   417   if (!IsMainThread()) {
   418     return;
   419   }
   421   if (aOb.ObservedOperation() == OpNextStage) {
   422     mCurStage = NextStage(mCurStage);
   423     MOZ_ASSERT(mCurStage < NUM_STAGES);
   424     return;
   425   }
   427   // Get the filename
   428   const char16_t* filename = aOb.Filename();
   430   // Discard observations without filename
   431   if (!filename) {
   432     return;
   433   }
   435 #if defined(XP_WIN)
   436   nsCaseInsensitiveStringComparator comparator;
   437 #else
   438   nsDefaultStringComparator comparator;
   439 #endif
   440   nsAutoString      processedName;
   441   nsDependentString filenameStr(filename);
   442   uint32_t safeDirsLen = mSafeDirs.Length();
   443   for (uint32_t i = 0; i < safeDirsLen; ++i) {
   444     if (StringBeginsWith(filenameStr, mSafeDirs[i].mPath, comparator)) {
   445       processedName = mSafeDirs[i].mSubstName;
   446       processedName += Substring(filenameStr, mSafeDirs[i].mPath.Length());
   447       break;
   448     }
   449   }
   451   if (processedName.IsEmpty()) {
   452     return;
   453   }
   455   // Create a new entry or retrieve the existing one
   456   FileIOEntryType* entry = mFileStats.PutEntry(processedName);
   457   if (entry) {
   458     FileStats& stats = entry->mData.mStats[mCurStage];
   459     // Update the statistics
   460     stats.totalTime += (double) aOb.Duration().ToMilliseconds();
   461     switch (aOb.ObservedOperation()) {
   462       case OpCreateOrOpen:
   463         stats.creates++;
   464         break;
   465       case OpRead:
   466         stats.reads++;
   467         break;
   468       case OpWrite:
   469         stats.writes++;
   470         break;
   471       case OpFSync:
   472         stats.fsyncs++;
   473         break;
   474       case OpStat:
   475         stats.stats++;
   476         break;
   477       default:
   478         break;
   479     }
   480   }
   481 }
   483 bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry,
   484                                                     JSContext *cx,
   485                                                     JS::Handle<JSObject*> obj)
   486 {
   487   JS::AutoValueArray<NUM_STAGES> stages(cx);
   489   FileStatsByStage& statsByStage = entry->mData;
   490   for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) {
   491     FileStats& fileStats = statsByStage.mStats[s];
   493     if (fileStats.totalTime == 0 && fileStats.creates == 0 &&
   494         fileStats.reads == 0 && fileStats.writes == 0 &&
   495         fileStats.fsyncs == 0 && fileStats.stats == 0) {
   496       // Don't add an array that contains no information
   497       stages[s].setNull();
   498       continue;
   499     }
   501     // Array we want to report
   502     JS::AutoValueArray<6> stats(cx);
   503     stats[0].setNumber(fileStats.totalTime);
   504     stats[1].setNumber(fileStats.creates);
   505     stats[2].setNumber(fileStats.reads);
   506     stats[3].setNumber(fileStats.writes);
   507     stats[4].setNumber(fileStats.fsyncs);
   508     stats[5].setNumber(fileStats.stats);
   510     // Create jsStats as array of elements above
   511     JS::RootedObject jsStats(cx, JS_NewArrayObject(cx, stats));
   512     if (!jsStats) {
   513       continue;
   514     }
   516     stages[s].setObject(*jsStats);
   517   }
   519   JS::RootedObject jsEntry(cx, JS_NewArrayObject(cx, stages));
   520   if (!jsEntry) {
   521     return false;
   522   }
   524   // Add jsEntry to top-level dictionary
   525   const nsAString& key = entry->GetKey();
   526   return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(),
   527                              OBJECT_TO_JSVAL(jsEntry), nullptr, nullptr,
   528                              JSPROP_ENUMERATE | JSPROP_READONLY);
   529 }
   531 bool TelemetryIOInterposeObserver::ReflectIntoJS(JSContext *cx,
   532                                                  JS::Handle<JSObject*> rootObj)
   533 {
   534   return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj);
   535 }
   537 // This is not a member of TelemetryImpl because we want to record I/O during
   538 // startup.
   539 StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver;
   541 void
   542 ClearIOReporting()
   543 {
   544   if (!sTelemetryIOObserver) {
   545     return;
   546   }
   547   IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
   548                            sTelemetryIOObserver);
   549   sTelemetryIOObserver = nullptr;
   550 }
   552 class TelemetryImpl MOZ_FINAL
   553   : public nsITelemetry
   554   , public nsIMemoryReporter
   555 {
   556   NS_DECL_THREADSAFE_ISUPPORTS
   557   NS_DECL_NSITELEMETRY
   558   NS_DECL_NSIMEMORYREPORTER
   560 public:
   561   ~TelemetryImpl();
   563   void InitMemoryReporter();
   565   static bool CanRecord();
   566   static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
   567   static void ShutdownTelemetry();
   568   static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
   569                                   uint32_t delay);
   570 #if defined(MOZ_ENABLE_PROFILER_SPS)
   571   static void RecordChromeHang(uint32_t aDuration,
   572                                Telemetry::ProcessedStack &aStack,
   573                                int32_t aSystemUptime,
   574                                int32_t aFirefoxUptime);
   575 #endif
   576   static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
   577   static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
   578   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
   579   struct Stat {
   580     uint32_t hitCount;
   581     uint32_t totalTime;
   582   };
   583   struct StmtStats {
   584     struct Stat mainThread;
   585     struct Stat otherThreads;
   586   };
   587   typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
   589 private:
   590   TelemetryImpl();
   592   static nsCString SanitizeSQL(const nsACString& sql);
   594   enum SanitizedState { Sanitized, Unsanitized };
   596   static void StoreSlowSQL(const nsACString &offender, uint32_t delay,
   597                            SanitizedState state);
   599   static bool ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx,
   600                                    JS::Handle<JSObject*> obj);
   601   static bool ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx,
   602                                      JS::Handle<JSObject*> obj);
   603   static bool ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat,
   604                          JSContext *cx, JS::Handle<JSObject*> obj);
   606   bool AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread,
   607                   bool privateSQL);
   608   bool GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret,
   609                    bool includePrivateSql);
   611   // Like GetHistogramById, but returns the underlying C++ object, not the JS one.
   612   nsresult GetHistogramByName(const nsACString &name, Histogram **ret);
   613   bool ShouldReflectHistogram(Histogram *h);
   614   void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs);
   615   typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
   617   struct AddonHistogramInfo {
   618     uint32_t min;
   619     uint32_t max;
   620     uint32_t bucketCount;
   621     uint32_t histogramType;
   622     Histogram *h;
   623   };
   624   typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo> AddonHistogramEntryType;
   625   typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType;
   626   typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *> AddonEntryType;
   627   typedef AutoHashtable<AddonEntryType> AddonMapType;
   628   static bool AddonHistogramReflector(AddonHistogramEntryType *entry,
   629                                       JSContext *cx, JS::Handle<JSObject*> obj);
   630   static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj);
   631   static bool CreateHistogramForAddon(const nsACString &name,
   632                                       AddonHistogramInfo &info);
   633   void ReadLateWritesStacks(nsIFile* aProfileDir);
   634   AddonMapType mAddonMap;
   636   // This is used for speedy string->Telemetry::ID conversions
   637   typedef nsBaseHashtableET<nsCharPtrHashKey, Telemetry::ID> CharPtrEntryType;
   638   typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
   639   HistogramMapType mHistogramMap;
   640   bool mCanRecord;
   641   static TelemetryImpl *sTelemetry;
   642   AutoHashtable<SlowSQLEntryType> mPrivateSQL;
   643   AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
   644   // This gets marked immutable in debug builds, so we can't use
   645   // AutoHashtable here.
   646   nsTHashtable<nsCStringHashKey> mTrackedDBs;
   647   Mutex mHashMutex;
   648   HangReports mHangReports;
   649   Mutex mHangReportsMutex;
   650   // mThreadHangStats stores recorded, inactive thread hang stats
   651   Vector<Telemetry::ThreadHangStats> mThreadHangStats;
   652   Mutex mThreadHangStatsMutex;
   654   CombinedStacks mLateWritesStacks; // This is collected out of the main thread.
   655   bool mCachedTelemetryData;
   656   uint32_t mLastShutdownTime;
   657   uint32_t mFailedLockCount;
   658   nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks;
   659   friend class nsFetchTelemetryData;
   660 };
   662 TelemetryImpl*  TelemetryImpl::sTelemetry = nullptr;
   664 MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf)
   666 NS_IMETHODIMP
   667 TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
   668                               nsISupports* aData)
   669 {
   670   return MOZ_COLLECT_REPORT(
   671     "explicit/telemetry", KIND_HEAP, UNITS_BYTES,
   672     SizeOfIncludingThis(TelemetryMallocSizeOf),
   673     "Memory used by the telemetry system.");
   674 }
   676 // A initializer to initialize histogram collection
   677 StatisticsRecorder gStatisticsRecorder;
   679 // Hardcoded probes
   680 struct TelemetryHistogram {
   681   uint32_t min;
   682   uint32_t max;
   683   uint32_t bucketCount;
   684   uint32_t histogramType;
   685   uint32_t id_offset;
   686   uint32_t expiration_offset;
   687   bool extendedStatisticsOK;
   689   const char *id() const;
   690   const char *expiration() const;
   691 };
   693 #include "TelemetryHistogramData.inc"
   694 bool gCorruptHistograms[Telemetry::HistogramCount];
   696 const char *
   697 TelemetryHistogram::id() const
   698 {
   699   return &gHistogramStringTable[this->id_offset];
   700 }
   702 const char *
   703 TelemetryHistogram::expiration() const
   704 {
   705   return &gHistogramStringTable[this->expiration_offset];
   706 }
   708 bool
   709 IsExpired(const char *expiration){
   710   static Version current_version = Version(MOZ_APP_VERSION);
   711   MOZ_ASSERT(expiration);
   712   return strcmp(expiration, "never") && (mozilla::Version(expiration) <= current_version);
   713 }
   715 bool
   716 IsExpired(const Histogram *histogram){
   717   return histogram->histogram_name() == EXPIRED_ID;
   718 }
   720 nsresult
   721 HistogramGet(const char *name, const char *expiration, uint32_t min, uint32_t max,
   722              uint32_t bucketCount, uint32_t histogramType, Histogram **result)
   723 {
   724   if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
   725       && histogramType != nsITelemetry::HISTOGRAM_FLAG) {
   726     // Sanity checks for histogram parameters.
   727     if (min >= max)
   728       return NS_ERROR_ILLEGAL_VALUE;
   730     if (bucketCount <= 2)
   731       return NS_ERROR_ILLEGAL_VALUE;
   733     if (min < 1)
   734       return NS_ERROR_ILLEGAL_VALUE;
   735   }
   737   if (IsExpired(expiration)) {
   738     name = EXPIRED_ID;
   739     min = 1;
   740     max = 2;
   741     bucketCount = 3;
   742     histogramType = nsITelemetry::HISTOGRAM_LINEAR;
   743   }
   745   switch (histogramType) {
   746   case nsITelemetry::HISTOGRAM_EXPONENTIAL:
   747     *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
   748     break;
   749   case nsITelemetry::HISTOGRAM_LINEAR:
   750     *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
   751     break;
   752   case nsITelemetry::HISTOGRAM_BOOLEAN:
   753     *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
   754     break;
   755   case nsITelemetry::HISTOGRAM_FLAG:
   756     *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
   757     break;
   758   default:
   759     return NS_ERROR_INVALID_ARG;
   760   }
   761   return NS_OK;
   762 }
   764 // O(1) histogram lookup by numeric id
   765 nsresult
   766 GetHistogramByEnumId(Telemetry::ID id, Histogram **ret)
   767 {
   768   static Histogram* knownHistograms[Telemetry::HistogramCount] = {0};
   769   Histogram *h = knownHistograms[id];
   770   if (h) {
   771     *ret = h;
   772     return NS_OK;
   773   }
   775   const TelemetryHistogram &p = gHistograms[id];
   776   nsresult rv = HistogramGet(p.id(), p.expiration(), p.min, p.max, p.bucketCount, p.histogramType, &h);
   777   if (NS_FAILED(rv))
   778     return rv;
   780 #ifdef DEBUG
   781   // Check that the C++ Histogram code computes the same ranges as the
   782   // Python histogram code.
   783   if (!IsExpired(p.expiration())) {
   784     const struct bounds &b = gBucketLowerBoundIndex[id];
   785     if (b.length != 0) {
   786       MOZ_ASSERT(size_t(b.length) == h->bucket_count(),
   787                  "C++/Python bucket # mismatch");
   788       for (int i = 0; i < b.length; ++i) {
   789         MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
   790                    "C++/Python bucket mismatch");
   791       }
   792     }
   793   }
   794 #endif
   796   if (p.extendedStatisticsOK) {
   797     h->SetFlags(Histogram::kExtendedStatisticsFlag);
   798   }
   799   *ret = knownHistograms[id] = h;
   800   return NS_OK;
   801 }
   803 bool
   804 FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
   805 {
   806   JS::Rooted<JS::Value> range(cx);
   807   for (size_t i = 0; i < h->bucket_count(); i++) {
   808     range = INT_TO_JSVAL(h->ranges(i));
   809     if (!JS_DefineElement(cx, array, i, range, nullptr, nullptr, JSPROP_ENUMERATE))
   810       return false;
   811   }
   812   return true;
   813 }
   815 enum reflectStatus {
   816   REFLECT_OK,
   817   REFLECT_CORRUPT,
   818   REFLECT_FAILURE
   819 };
   821 enum reflectStatus
   822 ReflectHistogramAndSamples(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h,
   823                            const Histogram::SampleSet &ss)
   824 {
   825   // We don't want to reflect corrupt histograms.
   826   if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) {
   827     return REFLECT_CORRUPT;
   828   }
   830   if (!(JS_DefineProperty(cx, obj, "min", h->declared_min(), JSPROP_ENUMERATE)
   831         && JS_DefineProperty(cx, obj, "max", h->declared_max(), JSPROP_ENUMERATE)
   832         && JS_DefineProperty(cx, obj, "histogram_type", h->histogram_type(), JSPROP_ENUMERATE)
   833         && JS_DefineProperty(cx, obj, "sum", double(ss.sum()), JSPROP_ENUMERATE))) {
   834     return REFLECT_FAILURE;
   835   }
   837   if (h->histogram_type() == Histogram::HISTOGRAM) {
   838     if (!(JS_DefineProperty(cx, obj, "log_sum", ss.log_sum(), JSPROP_ENUMERATE)
   839           && JS_DefineProperty(cx, obj, "log_sum_squares", ss.log_sum_squares(), JSPROP_ENUMERATE))) {
   840       return REFLECT_FAILURE;
   841     }
   842   } else {
   843     // Export |sum_squares| as two separate 32-bit properties so that we
   844     // can accurately reconstruct it on the analysis side.
   845     uint64_t sum_squares = ss.sum_squares();
   846     // Cast to avoid implicit truncation warnings.
   847     uint32_t lo = static_cast<uint32_t>(sum_squares);
   848     uint32_t hi = static_cast<uint32_t>(sum_squares >> 32);
   849     if (!(JS_DefineProperty(cx, obj, "sum_squares_lo", lo, JSPROP_ENUMERATE)
   850           && JS_DefineProperty(cx, obj, "sum_squares_hi", hi, JSPROP_ENUMERATE))) {
   851       return REFLECT_FAILURE;
   852     }
   853   }
   855   const size_t count = h->bucket_count();
   856   JS::Rooted<JSObject*> rarray(cx, JS_NewArrayObject(cx, count));
   857   if (!rarray) {
   858     return REFLECT_FAILURE;
   859   }
   860   if (!(FillRanges(cx, rarray, h)
   861         && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) {
   862     return REFLECT_FAILURE;
   863   }
   865   JS::Rooted<JSObject*> counts_array(cx, JS_NewArrayObject(cx, count));
   866   if (!counts_array) {
   867     return REFLECT_FAILURE;
   868   }
   869   if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) {
   870     return REFLECT_FAILURE;
   871   }
   872   for (size_t i = 0; i < count; i++) {
   873     if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)),
   874                           nullptr, nullptr, JSPROP_ENUMERATE)) {
   875       return REFLECT_FAILURE;
   876     }
   877   }
   879   return REFLECT_OK;
   880 }
   882 enum reflectStatus
   883 ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h)
   884 {
   885   Histogram::SampleSet ss;
   886   h->SnapshotSample(&ss);
   887   return ReflectHistogramAndSamples(cx, obj, h, ss);
   888 }
   890 bool
   891 IsEmpty(const Histogram *h)
   892 {
   893   Histogram::SampleSet ss;
   894   h->SnapshotSample(&ss);
   896   return ss.counts(0) == 0 && ss.sum() == 0;
   897 }
   899 bool
   900 JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
   901 {
   902   JS::CallArgs args = CallArgsFromVp(argc, vp);
   903   if (!args.length()) {
   904     JS_ReportError(cx, "Expected one argument");
   905     return false;
   906   }
   908   if (!(args[0].isNumber() || args[0].isBoolean())) {
   909     JS_ReportError(cx, "Not a number");
   910     return false;
   911   }
   913   int32_t value;
   914   if (!JS::ToInt32(cx, args[0], &value)) {
   915     return false;
   916   }
   918   if (TelemetryImpl::CanRecord()) {
   919     JSObject *obj = JS_THIS_OBJECT(cx, vp);
   920     if (!obj) {
   921       return false;
   922     }
   924     Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
   925     h->Add(value);
   926   }
   927   return true;
   929 }
   931 bool
   932 JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
   933 {
   934   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   935   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   936   if (!obj) {
   937     return false;
   938   }
   940   Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
   941   JS::Rooted<JSObject*> snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
   942   if (!snapshot)
   943     return false;
   945   switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
   946   case REFLECT_FAILURE:
   947     return false;
   948   case REFLECT_CORRUPT:
   949     JS_ReportError(cx, "Histogram is corrupt");
   950     return false;
   951   case REFLECT_OK:
   952     args.rval().setObject(*snapshot);
   953     return true;
   954   default:
   955     MOZ_CRASH("unhandled reflection status");
   956   }
   957 }
   959 bool
   960 JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
   961 {
   962   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   963   if (!obj) {
   964     return false;
   965   }
   967   Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
   968   h->Clear();
   969   return true;
   970 }
   972 nsresult
   973 WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
   974 {
   975   static const JSClass JSHistogram_class = {
   976     "JSHistogram",  /* name */
   977     JSCLASS_HAS_PRIVATE, /* flags */
   978     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
   979     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
   980   };
   982   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class, JS::NullPtr(), JS::NullPtr()));
   983   if (!obj)
   984     return NS_ERROR_FAILURE;
   985   if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0)
   986         && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0)
   987         && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0))) {
   988     return NS_ERROR_FAILURE;
   989   }
   990   JS_SetPrivate(obj, h);
   991   ret.setObject(*obj);
   992   return NS_OK;
   993 }
   995 static uint32_t
   996 ReadLastShutdownDuration(const char *filename) {
   997   FILE *f = fopen(filename, "r");
   998   if (!f) {
   999     return 0;
  1002   int shutdownTime;
  1003   int r = fscanf(f, "%d\n", &shutdownTime);
  1004   fclose(f);
  1005   if (r != 1) {
  1006     return 0;
  1009   return shutdownTime;
  1012 const int32_t kMaxFailedProfileLockFileSize = 10;
  1014 bool
  1015 GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount,
  1016                    unsigned int& result)
  1018   nsAutoCString bufStr;
  1019   nsresult rv;
  1020   rv = NS_ReadInputStreamToString(inStream, bufStr, aCount);
  1021   NS_ENSURE_SUCCESS(rv, false);
  1022   result = bufStr.ToInteger(&rv);
  1023   return NS_SUCCEEDED(rv) && result > 0;
  1026 nsresult
  1027 GetFailedProfileLockFile(nsIFile* *aFile, nsIFile* aProfileDir)
  1029   NS_ENSURE_ARG_POINTER(aProfileDir);
  1031   nsresult rv = aProfileDir->Clone(aFile);
  1032   NS_ENSURE_SUCCESS(rv, rv);
  1034   (*aFile)->AppendNative(NS_LITERAL_CSTRING("Telemetry.FailedProfileLocks.txt"));
  1035   return NS_OK;
  1038 class nsFetchTelemetryData : public nsRunnable
  1040 public:
  1041   nsFetchTelemetryData(const char* aShutdownTimeFilename,
  1042                        nsIFile* aFailedProfileLockFile,
  1043                        nsIFile* aProfileDir)
  1044     : mShutdownTimeFilename(aShutdownTimeFilename),
  1045       mFailedProfileLockFile(aFailedProfileLockFile),
  1046       mTelemetry(TelemetryImpl::sTelemetry),
  1047       mProfileDir(aProfileDir)
  1051 private:
  1052   const char* mShutdownTimeFilename;
  1053   nsCOMPtr<nsIFile> mFailedProfileLockFile;
  1054   nsRefPtr<TelemetryImpl> mTelemetry;
  1055   nsCOMPtr<nsIFile> mProfileDir;
  1057 public:
  1058   void MainThread() {
  1059     mTelemetry->mCachedTelemetryData = true;
  1060     for (unsigned int i = 0, n = mTelemetry->mCallbacks.Count(); i < n; ++i) {
  1061       mTelemetry->mCallbacks[i]->Complete();
  1063     mTelemetry->mCallbacks.Clear();
  1066   NS_IMETHOD Run() {
  1067     LoadFailedLockCount(mTelemetry->mFailedLockCount);
  1068     mTelemetry->mLastShutdownTime = 
  1069       ReadLastShutdownDuration(mShutdownTimeFilename);
  1070     mTelemetry->ReadLateWritesStacks(mProfileDir);
  1071     nsCOMPtr<nsIRunnable> e =
  1072       NS_NewRunnableMethod(this, &nsFetchTelemetryData::MainThread);
  1073     NS_ENSURE_STATE(e);
  1074     NS_DispatchToMainThread(e, NS_DISPATCH_NORMAL);
  1075     return NS_OK;
  1078 private:
  1079   nsresult
  1080   LoadFailedLockCount(uint32_t& failedLockCount)
  1082     failedLockCount = 0;
  1083     int64_t fileSize = 0;
  1084     nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize);
  1085     if (NS_FAILED(rv)) {
  1086       return rv;
  1088     NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize,
  1089                    NS_ERROR_UNEXPECTED);
  1090     nsCOMPtr<nsIInputStream> inStream;
  1091     rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream),
  1092                                     mFailedProfileLockFile,
  1093                                     PR_RDONLY);
  1094     NS_ENSURE_SUCCESS(rv, rv);
  1095     NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount),
  1096                    NS_ERROR_UNEXPECTED);
  1097     inStream->Close();
  1099     mFailedProfileLockFile->Remove(false);
  1100     return NS_OK;
  1102 };
  1104 static TimeStamp gRecordedShutdownStartTime;
  1105 static bool gAlreadyFreedShutdownTimeFileName = false;
  1106 static char *gRecordedShutdownTimeFileName = nullptr;
  1108 static char *
  1109 GetShutdownTimeFileName()
  1111   if (gAlreadyFreedShutdownTimeFileName) {
  1112     return nullptr;
  1115   if (!gRecordedShutdownTimeFileName) {
  1116     nsCOMPtr<nsIFile> mozFile;
  1117     NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
  1118     if (!mozFile)
  1119       return nullptr;
  1121     mozFile->AppendNative(NS_LITERAL_CSTRING("Telemetry.ShutdownTime.txt"));
  1122     nsAutoCString nativePath;
  1123     nsresult rv = mozFile->GetNativePath(nativePath);
  1124     if (!NS_SUCCEEDED(rv))
  1125       return nullptr;
  1127     gRecordedShutdownTimeFileName = PL_strdup(nativePath.get());
  1130   return gRecordedShutdownTimeFileName;
  1133 NS_IMETHODIMP
  1134 TelemetryImpl::GetLastShutdownDuration(uint32_t *aResult)
  1136   // The user must call AsyncFetchTelemetryData first. We return zero instead of
  1137   // reporting a failure so that the rest of telemetry can uniformly handle
  1138   // the read not being available yet.
  1139   if (!mCachedTelemetryData) {
  1140     *aResult = 0;
  1141     return NS_OK;
  1144   *aResult = mLastShutdownTime;
  1145   return NS_OK;
  1148 NS_IMETHODIMP
  1149 TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult)
  1151   // The user must call AsyncFetchTelemetryData first. We return zero instead of
  1152   // reporting a failure so that the rest of telemetry can uniformly handle
  1153   // the read not being available yet.
  1154   if (!mCachedTelemetryData) {
  1155     *aResult = 0;
  1156     return NS_OK;
  1159   *aResult = mFailedLockCount;
  1160   return NS_OK;
  1163 NS_IMETHODIMP
  1164 TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback)
  1166   // We have finished reading the data already, just call the callback.
  1167   if (mCachedTelemetryData) {
  1168     aCallback->Complete();
  1169     return NS_OK;
  1172   // We already have a read request running, just remember the callback.
  1173   if (mCallbacks.Count() != 0) {
  1174     mCallbacks.AppendObject(aCallback);
  1175     return NS_OK;
  1178   // We make this check so that GetShutdownTimeFileName() doesn't get
  1179   // called; calling that function without telemetry enabled violates
  1180   // assumptions that the write-the-shutdown-timestamp machinery makes.
  1181   if (!Telemetry::CanRecord()) {
  1182     mCachedTelemetryData = true;
  1183     aCallback->Complete();
  1184     return NS_OK;
  1187   // Send the read to a background thread provided by the stream transport
  1188   // service to avoid a read in the main thread.
  1189   nsCOMPtr<nsIEventTarget> targetThread =
  1190     do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
  1191   if (!targetThread) {
  1192     mCachedTelemetryData = true;
  1193     aCallback->Complete();
  1194     return NS_OK;
  1197   // We have to get the filename from the main thread.
  1198   const char *shutdownTimeFilename = GetShutdownTimeFileName();
  1199   if (!shutdownTimeFilename) {
  1200     mCachedTelemetryData = true;
  1201     aCallback->Complete();
  1202     return NS_OK;
  1205   nsCOMPtr<nsIFile> profileDir;
  1206   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
  1207                                        getter_AddRefs(profileDir));
  1208   if (NS_FAILED(rv)) {
  1209     mCachedTelemetryData = true;
  1210     aCallback->Complete();
  1211     return NS_OK;
  1214   nsCOMPtr<nsIFile> failedProfileLockFile;
  1215   rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile),
  1216                                 profileDir);
  1217   if (NS_FAILED(rv)) {
  1218     mCachedTelemetryData = true;
  1219     aCallback->Complete();
  1220     return NS_OK;
  1223   mCallbacks.AppendObject(aCallback);
  1225   nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(shutdownTimeFilename,
  1226                                                          failedProfileLockFile,
  1227                                                          profileDir);
  1229   targetThread->Dispatch(event, NS_DISPATCH_NORMAL);
  1230   return NS_OK;
  1233 TelemetryImpl::TelemetryImpl():
  1234 mHistogramMap(Telemetry::HistogramCount),
  1235 mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default),
  1236 mHashMutex("Telemetry::mHashMutex"),
  1237 mHangReportsMutex("Telemetry::mHangReportsMutex"),
  1238 mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex"),
  1239 mCachedTelemetryData(false),
  1240 mLastShutdownTime(0),
  1241 mFailedLockCount(0)
  1243   // A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs
  1244   const char *trackedDBs[] = {
  1245     "addons.sqlite", "content-prefs.sqlite", "cookies.sqlite",
  1246     "downloads.sqlite", "extensions.sqlite", "formhistory.sqlite",
  1247     "index.sqlite", "healthreport.sqlite", "netpredictions.sqlite",
  1248     "permissions.sqlite", "places.sqlite", "search.sqlite", "signons.sqlite",
  1249     "urlclassifier3.sqlite", "webappsstore.sqlite"
  1250   };
  1252   for (size_t i = 0; i < ArrayLength(trackedDBs); i++)
  1253     mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i]));
  1255 #ifdef DEBUG
  1256   // Mark immutable to prevent asserts on simultaneous access from multiple threads
  1257   mTrackedDBs.MarkImmutable();
  1258 #endif
  1261 TelemetryImpl::~TelemetryImpl() {
  1262   UnregisterWeakMemoryReporter(this);
  1265 void
  1266 TelemetryImpl::InitMemoryReporter() {
  1267   RegisterWeakMemoryReporter(this);
  1270 NS_IMETHODIMP
  1271 TelemetryImpl::NewHistogram(const nsACString &name, const nsACString &expiration, uint32_t min,
  1272                             uint32_t max, uint32_t bucketCount, uint32_t histogramType,
  1273                             JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1275   Histogram *h;
  1276   nsresult rv = HistogramGet(PromiseFlatCString(name).get(), PromiseFlatCString(expiration).get(),
  1277                              min, max, bucketCount, histogramType, &h);
  1278   if (NS_FAILED(rv))
  1279     return rv;
  1280   h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
  1281   h->SetFlags(Histogram::kExtendedStatisticsFlag);
  1282   return WrapAndReturnHistogram(h, cx, ret);
  1285 bool
  1286 TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry,
  1287                           const Stat *stat,
  1288                           JSContext *cx,
  1289                           JS::Handle<JSObject*> obj)
  1291   if (stat->hitCount == 0)
  1292     return true;
  1294   const nsACString &sql = entry->GetKey();
  1296   JS::Rooted<JSObject*> arrayObj(cx, JS_NewArrayObject(cx, 0));
  1297   if (!arrayObj) {
  1298     return false;
  1300   return (JS_SetElement(cx, arrayObj, 0, stat->hitCount)
  1301           && JS_SetElement(cx, arrayObj, 1, stat->totalTime)
  1302           && JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj,
  1303                                JSPROP_ENUMERATE));
  1306 bool
  1307 TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx,
  1308                                     JS::Handle<JSObject*> obj)
  1310   return ReflectSQL(entry, &entry->mData.mainThread, cx, obj);
  1313 bool
  1314 TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx,
  1315                                       JS::Handle<JSObject*> obj)
  1317   return ReflectSQL(entry, &entry->mData.otherThreads, cx, obj);
  1320 bool
  1321 TelemetryImpl::AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread,
  1322                           bool privateSQL)
  1324   JS::Rooted<JSObject*> statsObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1325   if (!statsObj)
  1326     return false;
  1328   AutoHashtable<SlowSQLEntryType> &sqlMap =
  1329     (privateSQL ? mPrivateSQL : mSanitizedSQL);
  1330   AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction =
  1331     (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL);
  1332   if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) {
  1333     return false;
  1336   return JS_DefineProperty(cx, rootObj,
  1337                            mainThread ? "mainThread" : "otherThreads",
  1338                            statsObj, JSPROP_ENUMERATE);
  1341 nsresult
  1342 TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id)
  1344   if (!sTelemetry) {
  1345     return NS_ERROR_FAILURE;
  1348   // Cache names
  1349   // Note the histogram names are statically allocated
  1350   TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap;
  1351   if (!map->Count()) {
  1352     for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) {
  1353       CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id());
  1354       if (MOZ_UNLIKELY(!entry)) {
  1355         map->Clear();
  1356         return NS_ERROR_OUT_OF_MEMORY;
  1358       entry->mData = (Telemetry::ID) i;
  1362   CharPtrEntryType *entry = map->GetEntry(name);
  1363   if (!entry) {
  1364     return NS_ERROR_INVALID_ARG;
  1366   *id = entry->mData;
  1367   return NS_OK;
  1370 nsresult
  1371 TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret)
  1373   Telemetry::ID id;
  1374   nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
  1375   if (NS_FAILED(rv)) {
  1376     return rv;
  1379   rv = GetHistogramByEnumId(id, ret);
  1380   if (NS_FAILED(rv))
  1381     return rv;
  1383   return NS_OK;
  1386 NS_IMETHODIMP
  1387 TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name,
  1388                              JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1390   Telemetry::ID id;
  1391   nsresult rv = GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id);
  1392   if (NS_FAILED(rv)) {
  1393     return rv;
  1395   const TelemetryHistogram &p = gHistograms[id];
  1397   Histogram *existing;
  1398   rv = GetHistogramByEnumId(id, &existing);
  1399   if (NS_FAILED(rv)) {
  1400     return rv;
  1403   Histogram *clone;
  1404   rv = HistogramGet(PromiseFlatCString(name).get(), p.expiration(),
  1405                     existing->declared_min(), existing->declared_max(),
  1406                     existing->bucket_count(), p.histogramType, &clone);
  1407   if (NS_FAILED(rv))
  1408     return rv;
  1410   Histogram::SampleSet ss;
  1411   existing->SnapshotSample(&ss);
  1412   clone->AddSampleSet(ss);
  1413   return WrapAndReturnHistogram(clone, cx, ret);
  1416 void
  1417 TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
  1419   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
  1420     Histogram *h = *it;
  1422     Telemetry::ID id;
  1423     nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id);
  1424     // This histogram isn't a static histogram, just ignore it.
  1425     if (NS_FAILED(rv)) {
  1426       continue;
  1429     if (gCorruptHistograms[id]) {
  1430       continue;
  1433     Histogram::SampleSet ss;
  1434     h->SnapshotSample(&ss);
  1435     Histogram::Inconsistencies check = h->FindCorruption(ss);
  1436     bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
  1438     if (corrupt) {
  1439       Telemetry::ID corruptID = Telemetry::HistogramCount;
  1440       if (check & Histogram::RANGE_CHECKSUM_ERROR) {
  1441         corruptID = Telemetry::RANGE_CHECKSUM_ERRORS;
  1442       } else if (check & Histogram::BUCKET_ORDER_ERROR) {
  1443         corruptID = Telemetry::BUCKET_ORDER_ERRORS;
  1444       } else if (check & Histogram::COUNT_HIGH_ERROR) {
  1445         corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS;
  1446       } else if (check & Histogram::COUNT_LOW_ERROR) {
  1447         corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS;
  1449       Telemetry::Accumulate(corruptID, 1);
  1452     gCorruptHistograms[id] = corrupt;
  1456 bool
  1457 TelemetryImpl::ShouldReflectHistogram(Histogram *h)
  1459   const char *name = h->histogram_name().c_str();
  1460   Telemetry::ID id;
  1461   nsresult rv = GetHistogramEnumId(name, &id);
  1462   if (NS_FAILED(rv)) {
  1463     // GetHistogramEnumId generally should not fail.  But a lookup
  1464     // failure shouldn't prevent us from reflecting histograms into JS.
  1465     //
  1466     // However, these two histograms are created by Histogram itself for
  1467     // tracking corruption.  We have our own histograms for that, so
  1468     // ignore these two.
  1469     if (strcmp(name, "Histogram.InconsistentCountHigh") == 0
  1470         || strcmp(name, "Histogram.InconsistentCountLow") == 0) {
  1471       return false;
  1473     return true;
  1474   } else {
  1475     return !gCorruptHistograms[id];
  1479 // Compute the name to pass into Histogram for the addon histogram
  1480 // 'name' from the addon 'id'.  We can't use 'name' directly because it
  1481 // might conflict with other histograms in other addons or even with our
  1482 // own.
  1483 void
  1484 AddonHistogramName(const nsACString &id, const nsACString &name,
  1485                    nsACString &ret)
  1487   ret.Append(id);
  1488   ret.Append(NS_LITERAL_CSTRING(":"));
  1489   ret.Append(name);
  1492 NS_IMETHODIMP
  1493 TelemetryImpl::RegisterAddonHistogram(const nsACString &id,
  1494                                       const nsACString &name,
  1495                                       uint32_t min, uint32_t max,
  1496                                       uint32_t bucketCount,
  1497                                       uint32_t histogramType)
  1499   AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
  1500   if (!addonEntry) {
  1501     addonEntry = mAddonMap.PutEntry(id);
  1502     if (MOZ_UNLIKELY(!addonEntry)) {
  1503       return NS_ERROR_OUT_OF_MEMORY;
  1505     addonEntry->mData = new AddonHistogramMapType();
  1508   AddonHistogramMapType *histogramMap = addonEntry->mData;
  1509   AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
  1510   // Can't re-register the same histogram.
  1511   if (histogramEntry) {
  1512     return NS_ERROR_FAILURE;
  1515   histogramEntry = histogramMap->PutEntry(name);
  1516   if (MOZ_UNLIKELY(!histogramEntry)) {
  1517     return NS_ERROR_OUT_OF_MEMORY;
  1520   AddonHistogramInfo &info = histogramEntry->mData;
  1521   info.min = min;
  1522   info.max = max;
  1523   info.bucketCount = bucketCount;
  1524   info.histogramType = histogramType;
  1526   return NS_OK;
  1529 NS_IMETHODIMP
  1530 TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name,
  1531                                  JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1533   AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
  1534   // The given id has not been registered.
  1535   if (!addonEntry) {
  1536     return NS_ERROR_INVALID_ARG;
  1539   AddonHistogramMapType *histogramMap = addonEntry->mData;
  1540   AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
  1541   // The given histogram name has not been registered.
  1542   if (!histogramEntry) {
  1543     return NS_ERROR_INVALID_ARG;
  1546   AddonHistogramInfo &info = histogramEntry->mData;
  1547   if (!info.h) {
  1548     nsAutoCString actualName;
  1549     AddonHistogramName(id, name, actualName);
  1550     if (!CreateHistogramForAddon(actualName, info)) {
  1551       return NS_ERROR_FAILURE;
  1554   return WrapAndReturnHistogram(info.h, cx, ret);
  1557 NS_IMETHODIMP
  1558 TelemetryImpl::UnregisterAddonHistograms(const nsACString &id)
  1560   AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
  1561   if (addonEntry) {
  1562     // Histogram's destructor is private, so this is the best we can do.
  1563     // The histograms the addon created *will* stick around, but they
  1564     // will be deleted if and when the addon registers histograms with
  1565     // the same names.
  1566     delete addonEntry->mData;
  1567     mAddonMap.RemoveEntry(id);
  1570   return NS_OK;
  1573 NS_IMETHODIMP
  1574 TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1576   JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1577   if (!root_obj)
  1578     return NS_ERROR_FAILURE;
  1579   ret.setObject(*root_obj);
  1581   // Ensure that all the HISTOGRAM_FLAG histograms have been created, so
  1582   // that their values are snapshotted.
  1583   for (size_t i = 0; i < Telemetry::HistogramCount; ++i) {
  1584     if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) {
  1585       Histogram *h;
  1586       DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h);
  1587       MOZ_ASSERT(NS_SUCCEEDED(rv));
  1589   };
  1591   StatisticsRecorder::Histograms hs;
  1592   StatisticsRecorder::GetHistograms(&hs);
  1594   // We identify corrupt histograms first, rather than interspersing it
  1595   // in the loop below, to ensure that our corruption statistics don't
  1596   // depend on histogram enumeration order.
  1597   //
  1598   // Of course, we hope that all of these corruption-statistics
  1599   // histograms are not themselves corrupt...
  1600   IdentifyCorruptHistograms(hs);
  1602   // OK, now we can actually reflect things.
  1603   JS::Rooted<JSObject*> hobj(cx);
  1604   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
  1605     Histogram *h = *it;
  1606     if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) {
  1607       continue;
  1610     hobj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
  1611     if (!hobj) {
  1612       return NS_ERROR_FAILURE;
  1614     switch (ReflectHistogramSnapshot(cx, hobj, h)) {
  1615     case REFLECT_CORRUPT:
  1616       // We can still hit this case even if ShouldReflectHistograms
  1617       // returns true.  The histogram lies outside of our control
  1618       // somehow; just skip it.
  1619       continue;
  1620     case REFLECT_FAILURE:
  1621       return NS_ERROR_FAILURE;
  1622     case REFLECT_OK:
  1623       if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), hobj,
  1624                              JSPROP_ENUMERATE)) {
  1625         return NS_ERROR_FAILURE;
  1629   return NS_OK;
  1632 bool
  1633 TelemetryImpl::CreateHistogramForAddon(const nsACString &name,
  1634                                        AddonHistogramInfo &info)
  1636   Histogram *h;
  1637   nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never",
  1638                              info.min, info.max, info.bucketCount,
  1639                              info.histogramType, &h);
  1640   if (NS_FAILED(rv)) {
  1641     return false;
  1643   // Don't let this histogram be reported via the normal means
  1644   // (e.g. Telemetry.registeredHistograms); we'll make it available in
  1645   // other ways.
  1646   h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
  1647   info.h = h;
  1648   return true;
  1651 bool
  1652 TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry,
  1653                                        JSContext *cx, JS::Handle<JSObject*> obj)
  1655   AddonHistogramInfo &info = entry->mData;
  1657   // Never even accessed the histogram.
  1658   if (!info.h) {
  1659     // Have to force creation of HISTOGRAM_FLAG histograms.
  1660     if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) 
  1661       return true;
  1663     if (!CreateHistogramForAddon(entry->GetKey(), info)) {
  1664       return false;
  1668   if (IsEmpty(info.h)) {
  1669     return true;
  1672   JS::Rooted<JSObject*> snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1673   if (!snapshot) {
  1674     // Just consider this to be skippable.
  1675     return true;
  1677   switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) {
  1678   case REFLECT_FAILURE:
  1679   case REFLECT_CORRUPT:
  1680     return false;
  1681   case REFLECT_OK:
  1682     const nsACString &histogramName = entry->GetKey();
  1683     if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
  1684                            snapshot, JSPROP_ENUMERATE)) {
  1685       return false;
  1687     break;
  1689   return true;
  1692 bool
  1693 TelemetryImpl::AddonReflector(AddonEntryType *entry,
  1694                               JSContext *cx, JS::Handle<JSObject*> obj)
  1696   const nsACString &addonId = entry->GetKey();
  1697   JS::Rooted<JSObject*> subobj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1698   if (!subobj) {
  1699     return false;
  1702   AddonHistogramMapType *map = entry->mData;
  1703   if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj)
  1704         && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
  1705                              subobj, JSPROP_ENUMERATE))) {
  1706     return false;
  1708   return true;
  1711 NS_IMETHODIMP
  1712 TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1714   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1715   if (!obj) {
  1716     return NS_ERROR_FAILURE;
  1719   if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) {
  1720     return NS_ERROR_FAILURE;
  1722   ret.setObject(*obj);
  1723   return NS_OK;
  1726 bool
  1727 TelemetryImpl::GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, bool includePrivateSql)
  1729   JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1730   if (!root_obj)
  1731     return false;
  1732   ret.setObject(*root_obj);
  1734   MutexAutoLock hashMutex(mHashMutex);
  1735   // Add info about slow SQL queries on the main thread
  1736   if (!AddSQLInfo(cx, root_obj, true, includePrivateSql))
  1737     return false;
  1738   // Add info about slow SQL queries on other threads
  1739   if (!AddSQLInfo(cx, root_obj, false, includePrivateSql))
  1740     return false;
  1742   return true;
  1745 NS_IMETHODIMP
  1746 TelemetryImpl::GetSlowSQL(JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1748   if (GetSQLStats(cx, ret, false))
  1749     return NS_OK;
  1750   return NS_ERROR_FAILURE;
  1753 NS_IMETHODIMP
  1754 TelemetryImpl::GetDebugSlowSQL(JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1756   bool revealPrivateSql =
  1757     Preferences::GetBool("toolkit.telemetry.debugSlowSql", false);
  1758   if (GetSQLStats(cx, ret, revealPrivateSql))
  1759     return NS_OK;
  1760   return NS_ERROR_FAILURE;
  1763 NS_IMETHODIMP
  1764 TelemetryImpl::GetMaximalNumberOfConcurrentThreads(uint32_t *ret)
  1766   *ret = nsThreadManager::get()->GetHighestNumberOfThreads();
  1767   return NS_OK;
  1770 NS_IMETHODIMP
  1771 TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle<JS::Value> ret)
  1773   MutexAutoLock hangReportMutex(mHangReportsMutex);
  1775   const CombinedStacks& stacks = mHangReports.GetStacks();
  1776   JS::Rooted<JSObject*> fullReportObj(cx, CreateJSStackObject(cx, stacks));
  1777   if (!fullReportObj) {
  1778     return NS_ERROR_FAILURE;
  1781   ret.setObject(*fullReportObj);
  1783   JS::Rooted<JSObject*> durationArray(cx, JS_NewArrayObject(cx, 0));
  1784   JS::Rooted<JSObject*> systemUptimeArray(cx, JS_NewArrayObject(cx, 0));
  1785   JS::Rooted<JSObject*> firefoxUptimeArray(cx, JS_NewArrayObject(cx, 0));
  1786   if (!durationArray || !systemUptimeArray || !firefoxUptimeArray) {
  1787     return NS_ERROR_FAILURE;
  1790   bool ok = JS_DefineProperty(cx, fullReportObj, "durations",
  1791                               durationArray, JSPROP_ENUMERATE);
  1792   if (!ok) {
  1793     return NS_ERROR_FAILURE;
  1796   ok = JS_DefineProperty(cx, fullReportObj, "systemUptime",
  1797                          systemUptimeArray, JSPROP_ENUMERATE);
  1798   if (!ok) {
  1799     return NS_ERROR_FAILURE;
  1802   ok = JS_DefineProperty(cx, fullReportObj, "firefoxUptime",
  1803                          firefoxUptimeArray, JSPROP_ENUMERATE);
  1804   if (!ok) {
  1805     return NS_ERROR_FAILURE;
  1808   const size_t length = stacks.GetStackCount();
  1809   for (size_t i = 0; i < length; ++i) {
  1810     if (!JS_SetElement(cx, durationArray, i, mHangReports.GetDuration(i))) {
  1811       return NS_ERROR_FAILURE;
  1813     if (!JS_SetElement(cx, systemUptimeArray, i, mHangReports.GetSystemUptime(i))) {
  1814       return NS_ERROR_FAILURE;
  1816     if (!JS_SetElement(cx, firefoxUptimeArray, i, mHangReports.GetFirefoxUptime(i))) {
  1817       return NS_ERROR_FAILURE;
  1821   return NS_OK;
  1824 static JSObject *
  1825 CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks) {
  1826   JS::Rooted<JSObject*> ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  1827   if (!ret) {
  1828     return nullptr;
  1831   JS::Rooted<JSObject*> moduleArray(cx, JS_NewArrayObject(cx, 0));
  1832   if (!moduleArray) {
  1833     return nullptr;
  1835   bool ok = JS_DefineProperty(cx, ret, "memoryMap", moduleArray,
  1836                               JSPROP_ENUMERATE);
  1837   if (!ok) {
  1838     return nullptr;
  1841   const size_t moduleCount = stacks.GetModuleCount();
  1842   for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
  1843     // Current module
  1844     const Telemetry::ProcessedStack::Module& module =
  1845       stacks.GetModule(moduleIndex);
  1847     JS::Rooted<JSObject*> moduleInfoArray(cx, JS_NewArrayObject(cx, 0));
  1848     if (!moduleInfoArray) {
  1849       return nullptr;
  1851     if (!JS_SetElement(cx, moduleArray, moduleIndex, moduleInfoArray)) {
  1852       return nullptr;
  1855     unsigned index = 0;
  1857     // Module name
  1858     JS::Rooted<JSString*> str(cx, JS_NewStringCopyZ(cx, module.mName.c_str()));
  1859     if (!str) {
  1860       return nullptr;
  1862     if (!JS_SetElement(cx, moduleInfoArray, index++, str)) {
  1863       return nullptr;
  1866     // Module breakpad identifier
  1867     JS::Rooted<JSString*> id(cx, JS_NewStringCopyZ(cx, module.mBreakpadId.c_str()));
  1868     if (!id) {
  1869       return nullptr;
  1871     if (!JS_SetElement(cx, moduleInfoArray, index++, id)) {
  1872       return nullptr;
  1876   JS::Rooted<JSObject*> reportArray(cx, JS_NewArrayObject(cx, 0));
  1877   if (!reportArray) {
  1878     return nullptr;
  1880   ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE);
  1881   if (!ok) {
  1882     return nullptr;
  1885   const size_t length = stacks.GetStackCount();
  1886   for (size_t i = 0; i < length; ++i) {
  1887     // Represent call stack PCs as (module index, offset) pairs.
  1888     JS::Rooted<JSObject*> pcArray(cx, JS_NewArrayObject(cx, 0));
  1889     if (!pcArray) {
  1890       return nullptr;
  1893     if (!JS_SetElement(cx, reportArray, i, pcArray)) {
  1894       return nullptr;
  1897     const CombinedStacks::Stack& stack = stacks.GetStack(i);
  1898     const uint32_t pcCount = stack.size();
  1899     for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
  1900       const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex];
  1901       JS::Rooted<JSObject*> framePair(cx, JS_NewArrayObject(cx, 0));
  1902       if (!framePair) {
  1903         return nullptr;
  1905       int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex) ?
  1906         -1 : frame.mModIndex;
  1907       if (!JS_SetElement(cx, framePair, 0, modIndex)) {
  1908         return nullptr;
  1910       if (!JS_SetElement(cx, framePair, 1, static_cast<double>(frame.mOffset))) {
  1911         return nullptr;
  1913       if (!JS_SetElement(cx, pcArray, pcIndex, framePair)) {
  1914         return nullptr;
  1919   return ret;
  1922 static bool
  1923 IsValidBreakpadId(const std::string &breakpadId) {
  1924   if (breakpadId.size() < 33) {
  1925     return false;
  1927   for (unsigned i = 0, n = breakpadId.size(); i < n; ++i) {
  1928     char c = breakpadId[i];
  1929     if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
  1930       return false;
  1933   return true;
  1936 // Read a stack from the given file name. In case of any error, aStack is
  1937 // unchanged.
  1938 static void
  1939 ReadStack(const char *aFileName, Telemetry::ProcessedStack &aStack)
  1941   std::ifstream file(aFileName);
  1943   size_t numModules;
  1944   file >> numModules;
  1945   if (file.fail()) {
  1946     return;
  1949   char newline = file.get();
  1950   if (file.fail() || newline != '\n') {
  1951     return;
  1954   Telemetry::ProcessedStack stack;
  1955   for (size_t i = 0; i < numModules; ++i) {
  1956     std::string breakpadId;
  1957     file >> breakpadId;
  1958     if (file.fail() || !IsValidBreakpadId(breakpadId)) {
  1959       return;
  1962     char space = file.get();
  1963     if (file.fail() || space != ' ') {
  1964       return;
  1967     std::string moduleName;
  1968     getline(file, moduleName);
  1969     if (file.fail() || moduleName[0] == ' ') {
  1970       return;
  1973     Telemetry::ProcessedStack::Module module = {
  1974       moduleName,
  1975       breakpadId
  1976     };
  1977     stack.AddModule(module);
  1980   size_t numFrames;
  1981   file >> numFrames;
  1982   if (file.fail()) {
  1983     return;
  1986   newline = file.get();
  1987   if (file.fail() || newline != '\n') {
  1988     return;
  1991   for (size_t i = 0; i < numFrames; ++i) {
  1992     uint16_t index;
  1993     file >> index;
  1994     uintptr_t offset;
  1995     file >> std::hex >> offset >> std::dec;
  1996     if (file.fail()) {
  1997       return;
  2000     Telemetry::ProcessedStack::Frame frame = {
  2001       offset,
  2002       index
  2003     };
  2004     stack.AddFrame(frame);
  2007   aStack = stack;
  2010 static JSObject*
  2011 CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time)
  2013   /* Create JS representation of TimeHistogram,
  2014      in the format of Chromium-style histograms. */
  2015   JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  2016   if (!ret) {
  2017     return nullptr;
  2020   if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0),
  2021                          JSPROP_ENUMERATE) ||
  2022       !JS_DefineProperty(cx, ret, "max",
  2023                          time.GetBucketMax(ArrayLength(time) - 1),
  2024                          JSPROP_ENUMERATE) ||
  2025       !JS_DefineProperty(cx, ret, "histogram_type",
  2026                          nsITelemetry::HISTOGRAM_EXPONENTIAL,
  2027                          JSPROP_ENUMERATE)) {
  2028     return nullptr;
  2030   // TODO: calculate "sum", "log_sum", and "log_sum_squares"
  2031   if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE) ||
  2032       !JS_DefineProperty(cx, ret, "log_sum", 0.0, JSPROP_ENUMERATE) ||
  2033       !JS_DefineProperty(cx, ret, "log_sum_squares", 0.0, JSPROP_ENUMERATE)) {
  2034     return nullptr;
  2037   JS::RootedObject ranges(
  2038     cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
  2039   JS::RootedObject counts(
  2040     cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
  2041   if (!ranges || !counts) {
  2042     return nullptr;
  2044   /* In a Chromium-style histogram, the first bucket is an "under" bucket
  2045      that represents all values below the histogram's range. */
  2046   if (!JS_SetElement(cx, ranges, 0, time.GetBucketMin(0)) ||
  2047       !JS_SetElement(cx, counts, 0, 0)) {
  2048     return nullptr;
  2050   for (size_t i = 0; i < ArrayLength(time); i++) {
  2051     if (!JS_SetElement(cx, ranges, i + 1, time.GetBucketMax(i)) ||
  2052         !JS_SetElement(cx, counts, i + 1, time[i])) {
  2053       return nullptr;
  2056   if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) ||
  2057       !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) {
  2058     return nullptr;
  2060   return ret;
  2063 static JSObject*
  2064 CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
  2066   JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  2067   if (!ret) {
  2068     return nullptr;
  2071   const Telemetry::HangHistogram::Stack& hangStack = hang.GetStack();
  2072   JS::RootedObject stack(cx,
  2073     JS_NewArrayObject(cx, hangStack.length()));
  2074   if (!ret) {
  2075     return nullptr;
  2077   for (size_t i = 0; i < hangStack.length(); i++) {
  2078     JS::RootedString string(cx, JS_NewStringCopyZ(cx, hangStack[i]));
  2079     if (!JS_SetElement(cx, stack, i, string)) {
  2080       return nullptr;
  2084   JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
  2085   if (!time ||
  2086       !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
  2087       !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE)) {
  2088     return nullptr;
  2090   return ret;
  2093 static JSObject*
  2094 CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
  2096   JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
  2097   if (!ret) {
  2098     return nullptr;
  2100   JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
  2101   if (!name ||
  2102       !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) {
  2103     return nullptr;
  2106   JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
  2107   if (!activity ||
  2108       !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) {
  2109     return nullptr;
  2112   JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
  2113   if (!hangs) {
  2114     return nullptr;
  2116   for (size_t i = 0; i < thread.mHangs.length(); i++) {
  2117     JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
  2118     if (!JS_SetElement(cx, hangs, i, obj)) {
  2119       return nullptr;
  2122   if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) {
  2123     return nullptr;
  2125   return ret;
  2128 NS_IMETHODIMP
  2129 TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::MutableHandle<JS::Value> ret)
  2131   JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0));
  2132   if (!retObj) {
  2133     return NS_ERROR_FAILURE;
  2135   size_t threadIndex = 0;
  2137 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
  2138   /* First add active threads; we need to hold |iter| (and its lock)
  2139      throughout this method to avoid a race condition where a thread can
  2140      be recorded twice if the thread is destroyed while this method is
  2141      running */
  2142   BackgroundHangMonitor::ThreadHangStatsIterator iter;
  2143   for (Telemetry::ThreadHangStats* histogram = iter.GetNext();
  2144        histogram; histogram = iter.GetNext()) {
  2145     JS::RootedObject obj(cx,
  2146       CreateJSThreadHangStats(cx, *histogram));
  2147     if (!JS_SetElement(cx, retObj, threadIndex++, obj)) {
  2148       return NS_ERROR_FAILURE;
  2151 #endif
  2153   // Add saved threads next
  2154   MutexAutoLock autoLock(mThreadHangStatsMutex);
  2155   for (size_t i = 0; i < mThreadHangStats.length(); i++) {
  2156     JS::RootedObject obj(cx,
  2157       CreateJSThreadHangStats(cx, mThreadHangStats[i]));
  2158     if (!JS_SetElement(cx, retObj, threadIndex++, obj)) {
  2159       return NS_ERROR_FAILURE;
  2162   ret.setObject(*retObj);
  2163   return NS_OK;
  2166 void
  2167 TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir)
  2169   nsAutoCString nativePath;
  2170   nsresult rv = aProfileDir->GetNativePath(nativePath);
  2171   if (NS_FAILED(rv)) {
  2172     return;
  2175   const char *name = nativePath.get();
  2176   PRDir *dir = PR_OpenDir(name);
  2177   if (!dir) {
  2178     return;
  2181   PRDirEntry *ent;
  2182   const char *prefix = "Telemetry.LateWriteFinal-";
  2183   unsigned int prefixLen = strlen(prefix);
  2184   while ((ent = PR_ReadDir(dir, PR_SKIP_NONE))) {
  2185     if (strncmp(prefix, ent->name, prefixLen) != 0) {
  2186       continue;
  2189     nsAutoCString stackNativePath = nativePath;
  2190     stackNativePath += XPCOM_FILE_PATH_SEPARATOR;
  2191     stackNativePath += nsDependentCString(ent->name);
  2193     Telemetry::ProcessedStack stack;
  2194     ReadStack(stackNativePath.get(), stack);
  2195     if (stack.GetStackSize() != 0) {
  2196       mLateWritesStacks.AddStack(stack);
  2198     // Delete the file so that we don't report it again on the next run.
  2199     PR_Delete(stackNativePath.get());
  2201   PR_CloseDir(dir);
  2204 NS_IMETHODIMP
  2205 TelemetryImpl::GetLateWrites(JSContext *cx, JS::MutableHandle<JS::Value> ret)
  2207   // The user must call AsyncReadTelemetryData first. We return an empty list
  2208   // instead of reporting a failure so that the rest of telemetry can uniformly
  2209   // handle the read not being available yet.
  2211   // FIXME: we allocate the js object again and again in the getter. We should
  2212   // figure out a way to cache it. In order to do that we have to call
  2213   // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl
  2214   // constructor, but it is not clear how to get a JSContext in there.
  2215   // Another option would be to call it in here when we first call
  2216   // CreateJSStackObject, but we would still need to figure out where to call
  2217   // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot
  2218   // and just set the pointer to nullptr is the telemetry destructor?
  2220   JSObject *report;
  2221   if (!mCachedTelemetryData) {
  2222     CombinedStacks empty;
  2223     report = CreateJSStackObject(cx, empty);
  2224   } else {
  2225     report = CreateJSStackObject(cx, mLateWritesStacks);
  2228   if (report == nullptr) {
  2229     return NS_ERROR_FAILURE;
  2232   ret.setObject(*report);
  2233   return NS_OK;
  2236 NS_IMETHODIMP
  2237 TelemetryImpl::RegisteredHistograms(uint32_t *aCount, char*** aHistograms)
  2239   size_t count = ArrayLength(gHistograms);
  2240   size_t offset = 0;
  2241   char** histograms = static_cast<char**>(nsMemory::Alloc(count * sizeof(char*)));
  2243   for (size_t i = 0; i < count; ++i) {
  2244     if (IsExpired(gHistograms[i].expiration())) {
  2245       offset++;
  2246       continue;
  2249     const char* h = gHistograms[i].id();
  2250     size_t len = strlen(h);
  2251     histograms[i - offset] = static_cast<char*>(nsMemory::Clone(h, len+1));
  2254   *aCount = count - offset;
  2255   *aHistograms = histograms;
  2256   return NS_OK;
  2259 NS_IMETHODIMP
  2260 TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx,
  2261                                 JS::MutableHandle<JS::Value> ret)
  2263   Histogram *h;
  2264   nsresult rv = GetHistogramByName(name, &h);
  2265   if (NS_FAILED(rv))
  2266     return rv;
  2268   return WrapAndReturnHistogram(h, cx, ret);
  2271 NS_IMETHODIMP
  2272 TelemetryImpl::GetCanRecord(bool *ret) {
  2273   *ret = mCanRecord;
  2274   return NS_OK;
  2277 NS_IMETHODIMP
  2278 TelemetryImpl::SetCanRecord(bool canRecord) {
  2279   mCanRecord = !!canRecord;
  2280   return NS_OK;
  2283 bool
  2284 TelemetryImpl::CanRecord() {
  2285   return !sTelemetry || sTelemetry->mCanRecord;
  2288 NS_IMETHODIMP
  2289 TelemetryImpl::GetCanSend(bool *ret) {
  2290 #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING)
  2291   *ret = true;
  2292 #else
  2293   *ret = false;
  2294 #endif
  2295   return NS_OK;
  2298 already_AddRefed<nsITelemetry>
  2299 TelemetryImpl::CreateTelemetryInstance()
  2301   NS_ABORT_IF_FALSE(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()");
  2302   sTelemetry = new TelemetryImpl();
  2303   // AddRef for the local reference
  2304   NS_ADDREF(sTelemetry);
  2305   // AddRef for the caller
  2306   nsCOMPtr<nsITelemetry> ret = sTelemetry;
  2308   sTelemetry->InitMemoryReporter();
  2310   return ret.forget();
  2313 void
  2314 TelemetryImpl::ShutdownTelemetry()
  2316   // No point in collecting IO beyond this point
  2317   ClearIOReporting();
  2318   NS_IF_RELEASE(sTelemetry);
  2321 void
  2322 TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay,
  2323                             SanitizedState state)
  2325   AutoHashtable<SlowSQLEntryType> *slowSQLMap = nullptr;
  2326   if (state == Sanitized)
  2327     slowSQLMap = &(sTelemetry->mSanitizedSQL);
  2328   else
  2329     slowSQLMap = &(sTelemetry->mPrivateSQL);
  2331   MutexAutoLock hashMutex(sTelemetry->mHashMutex);
  2333   SlowSQLEntryType *entry = slowSQLMap->GetEntry(sql);
  2334   if (!entry) {
  2335     entry = slowSQLMap->PutEntry(sql);
  2336     if (MOZ_UNLIKELY(!entry))
  2337       return;
  2338     entry->mData.mainThread.hitCount = 0;
  2339     entry->mData.mainThread.totalTime = 0;
  2340     entry->mData.otherThreads.hitCount = 0;
  2341     entry->mData.otherThreads.totalTime = 0;
  2344   if (NS_IsMainThread()) {
  2345     entry->mData.mainThread.hitCount++;
  2346     entry->mData.mainThread.totalTime += delay;
  2347   } else {
  2348     entry->mData.otherThreads.hitCount++;
  2349     entry->mData.otherThreads.totalTime += delay;
  2353 /**
  2354  * This method replaces string literals in SQL strings with the word :private
  2356  * States used in this state machine:
  2358  * NORMAL:
  2359  *  - This is the active state when not iterating over a string literal or
  2360  *  comment
  2362  * SINGLE_QUOTE:
  2363  *  - Defined here: http://www.sqlite.org/lang_expr.html
  2364  *  - This state represents iterating over a string literal opened with
  2365  *  a single quote.
  2366  *  - A single quote within the string can be encoded by putting 2 single quotes
  2367  *  in a row, e.g. 'This literal contains an escaped quote '''
  2368  *  - Any double quotes found within a single-quoted literal are ignored
  2369  *  - This state covers BLOB literals, e.g. X'ABC123'
  2370  *  - The string literal and the enclosing quotes will be replaced with
  2371  *  the text :private
  2373  * DOUBLE_QUOTE:
  2374  *  - Same rules as the SINGLE_QUOTE state.
  2375  *  - According to http://www.sqlite.org/lang_keywords.html,
  2376  *  SQLite interprets text in double quotes as an identifier unless it's used in
  2377  *  a context where it cannot be resolved to an identifier and a string literal
  2378  *  is allowed. This method removes text in double-quotes for safety.
  2380  *  DASH_COMMENT:
  2381  *  - http://www.sqlite.org/lang_comment.html
  2382  *  - A dash comment starts with two dashes in a row,
  2383  *  e.g. DROP TABLE foo -- a comment
  2384  *  - Any text following two dashes in a row is interpreted as a comment until
  2385  *  end of input or a newline character
  2386  *  - Any quotes found within the comment are ignored and no replacements made
  2388  *  C_STYLE_COMMENT:
  2389  *  - http://www.sqlite.org/lang_comment.html
  2390  *  - A C-style comment starts with a forward slash and an asterisk, and ends
  2391  *  with an asterisk and a forward slash
  2392  *  - Any text following comment start is interpreted as a comment up to end of
  2393  *  input or comment end
  2394  *  - Any quotes found within the comment are ignored and no replacements made
  2395  */
  2396 nsCString
  2397 TelemetryImpl::SanitizeSQL(const nsACString &sql) {
  2398   nsCString output;
  2399   int length = sql.Length();
  2401   typedef enum {
  2402     NORMAL,
  2403     SINGLE_QUOTE,
  2404     DOUBLE_QUOTE,
  2405     DASH_COMMENT,
  2406     C_STYLE_COMMENT,
  2407   } State;
  2409   State state = NORMAL;
  2410   int fragmentStart = 0;
  2411   for (int i = 0; i < length; i++) {
  2412     char character = sql[i];
  2413     char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0';
  2415     switch (character) {
  2416       case '\'':
  2417       case '"':
  2418         if (state == NORMAL) {
  2419           state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE;
  2420           output += nsDependentCSubstring(sql, fragmentStart, i - fragmentStart);
  2421           output += ":private";
  2422           fragmentStart = -1;
  2423         } else if ((state == SINGLE_QUOTE && character == '\'') ||
  2424                    (state == DOUBLE_QUOTE && character == '"')) {
  2425           if (nextCharacter == character) {
  2426             // Two consecutive quotes within a string literal are a single escaped quote
  2427             i++;
  2428           } else {
  2429             state = NORMAL;
  2430             fragmentStart = i + 1;
  2433         break;
  2434       case '-':
  2435         if (state == NORMAL) {
  2436           if (nextCharacter == '-') {
  2437             state = DASH_COMMENT;
  2438             i++;
  2441         break;
  2442       case '\n':
  2443         if (state == DASH_COMMENT) {
  2444           state = NORMAL;
  2446         break;
  2447       case '/':
  2448         if (state == NORMAL) {
  2449           if (nextCharacter == '*') {
  2450             state = C_STYLE_COMMENT;
  2451             i++;
  2454         break;
  2455       case '*':
  2456         if (state == C_STYLE_COMMENT) {
  2457           if (nextCharacter == '/') {
  2458             state = NORMAL;
  2461         break;
  2462       default:
  2463         continue;
  2467   if ((fragmentStart >= 0) && fragmentStart < length)
  2468     output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart);
  2470   return output;
  2473 // Slow SQL statements will be automatically
  2474 // trimmed to kMaxSlowStatementLength characters.
  2475 // This limit doesn't include the ellipsis and DB name,
  2476 // that are appended at the end of the stored statement.
  2477 const uint32_t kMaxSlowStatementLength = 1000;
  2479 void
  2480 TelemetryImpl::RecordSlowStatement(const nsACString &sql,
  2481                                    const nsACString &dbName,
  2482                                    uint32_t delay)
  2484   if (!sTelemetry || !sTelemetry->mCanRecord)
  2485     return;
  2487   bool isFirefoxDB = sTelemetry->mTrackedDBs.Contains(dbName);
  2488   if (isFirefoxDB) {
  2489     nsAutoCString sanitizedSQL(SanitizeSQL(sql));
  2490     if (sanitizedSQL.Length() > kMaxSlowStatementLength) {
  2491       sanitizedSQL.SetLength(kMaxSlowStatementLength);
  2492       sanitizedSQL += "...";
  2494     sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get());
  2495     StoreSlowSQL(sanitizedSQL, delay, Sanitized);
  2496   } else {
  2497     // Report aggregate DB-level statistics for addon DBs
  2498     nsAutoCString aggregate;
  2499     aggregate.AppendPrintf("Untracked SQL for %s",
  2500                            nsPromiseFlatCString(dbName).get());
  2501     StoreSlowSQL(aggregate, delay, Sanitized);
  2504   nsAutoCString fullSQL;
  2505   fullSQL.AppendPrintf("%s /* %s */",
  2506                        nsPromiseFlatCString(sql).get(),
  2507                        nsPromiseFlatCString(dbName).get());
  2508   StoreSlowSQL(fullSQL, delay, Unsanitized);
  2511 #if defined(MOZ_ENABLE_PROFILER_SPS)
  2512 void
  2513 TelemetryImpl::RecordChromeHang(uint32_t aDuration,
  2514                                 Telemetry::ProcessedStack &aStack,
  2515                                 int32_t aSystemUptime,
  2516                                 int32_t aFirefoxUptime)
  2518   if (!sTelemetry || !sTelemetry->mCanRecord)
  2519     return;
  2521   MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
  2523   sTelemetry->mHangReports.AddHang(aStack, aDuration,
  2524                                    aSystemUptime, aFirefoxUptime);
  2526 #endif
  2528 void
  2529 TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
  2531   if (!sTelemetry || !sTelemetry->mCanRecord)
  2532     return;
  2534   MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex);
  2536   sTelemetry->mThreadHangStats.append(Move(aStats));
  2539 NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter)
  2540 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
  2542 #define NS_TELEMETRY_CID \
  2543   {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}}
  2544 NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID);
  2546 const Module::CIDEntry kTelemetryCIDs[] = {
  2547   { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor },
  2548   { nullptr }
  2549 };
  2551 const Module::ContractIDEntry kTelemetryContracts[] = {
  2552   { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID },
  2553   { nullptr }
  2554 };
  2556 const Module kTelemetryModule = {
  2557   Module::kVersion,
  2558   kTelemetryCIDs,
  2559   kTelemetryContracts,
  2560   nullptr,
  2561   nullptr,
  2562   nullptr,
  2563   TelemetryImpl::ShutdownTelemetry
  2564 };
  2566 NS_IMETHODIMP
  2567 TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret)
  2569   if (sTelemetryIOObserver) {
  2570     JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(),
  2571                                                JS::NullPtr()));
  2572     if (!obj) {
  2573       return NS_ERROR_FAILURE;
  2576     if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) {
  2577       return NS_ERROR_FAILURE;
  2579     ret.setObject(*obj);
  2580     return NS_OK;
  2582   ret.setNull();
  2583   return NS_OK;
  2586 NS_IMETHODIMP
  2587 TelemetryImpl::MsSinceProcessStart(double* aResult)
  2589   bool error;
  2590   *aResult = (TimeStamp::NowLoRes() -
  2591               TimeStamp::ProcessCreation(error)).ToMilliseconds();
  2592   if (error) {
  2593     return NS_ERROR_NOT_AVAILABLE;
  2595   return NS_OK;
  2598 size_t
  2599 TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
  2601   size_t n = aMallocSizeOf(this);
  2602   // Ignore the hashtables in mAddonMap; they are not significant.
  2603   n += mAddonMap.SizeOfExcludingThis(nullptr, aMallocSizeOf);
  2604   n += mHistogramMap.SizeOfExcludingThis(nullptr, aMallocSizeOf);
  2605   { // Scope for mHashMutex lock
  2606     MutexAutoLock lock(mHashMutex);
  2607     n += mPrivateSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf);
  2608     n += mSanitizedSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf);
  2610   n += mTrackedDBs.SizeOfExcludingThis(nullptr, aMallocSizeOf);
  2611   { // Scope for mHangReportsMutex lock
  2612     MutexAutoLock lock(mHangReportsMutex);
  2613     n += mHangReports.SizeOfExcludingThis();
  2615   { // Scope for mThreadHangStatsMutex lock
  2616     MutexAutoLock lock(mThreadHangStatsMutex);
  2617     n += mThreadHangStats.sizeOfExcludingThis(aMallocSizeOf);
  2620   // It's a bit gross that we measure this other stuff that lives outside of
  2621   // TelemetryImpl... oh well.
  2622   if (sTelemetryIOObserver) {
  2623     n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf);
  2625   StatisticsRecorder::Histograms hs;
  2626   StatisticsRecorder::GetHistograms(&hs);
  2627   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
  2628     Histogram *h = *it;
  2629     n += h->SizeOfIncludingThis(aMallocSizeOf);
  2632   return n;
  2635 } // anonymous namespace
  2637 namespace mozilla {
  2638 void
  2639 RecordShutdownStartTimeStamp() {
  2640 #ifdef DEBUG
  2641   // FIXME: this function should only be called once, since it should be called
  2642   // at the earliest point we *know* we are shutting down. Unfortunately
  2643   // this assert has been firing. Given that if we are called multiple times
  2644   // we just keep the last timestamp, the assert is commented for now.
  2645   static bool recorded = false;
  2646   //  MOZ_ASSERT(!recorded);
  2647   (void)recorded; // Silence unused-var warnings (remove when assert re-enabled)
  2648   recorded = true;
  2649 #endif
  2651   if (!Telemetry::CanRecord())
  2652     return;
  2654   gRecordedShutdownStartTime = TimeStamp::Now();
  2656   GetShutdownTimeFileName();
  2659 void
  2660 RecordShutdownEndTimeStamp() {
  2661   if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName)
  2662     return;
  2664   nsCString name(gRecordedShutdownTimeFileName);
  2665   PL_strfree(gRecordedShutdownTimeFileName);
  2666   gRecordedShutdownTimeFileName = nullptr;
  2667   gAlreadyFreedShutdownTimeFileName = true;
  2669   nsCString tmpName = name;
  2670   tmpName += ".tmp";
  2671   FILE *f = fopen(tmpName.get(), "w");
  2672   if (!f)
  2673     return;
  2674   // On a normal release build this should be called just before
  2675   // calling _exit, but on a debug build or when the user forces a full
  2676   // shutdown this is called as late as possible, so we have to
  2677   // white list this write as write poisoning will be enabled.
  2678   MozillaRegisterDebugFILE(f);
  2680   TimeStamp now = TimeStamp::Now();
  2681   MOZ_ASSERT(now >= gRecordedShutdownStartTime);
  2682   TimeDuration diff = now - gRecordedShutdownStartTime;
  2683   uint32_t diff2 = diff.ToMilliseconds();
  2684   int written = fprintf(f, "%d\n", diff2);
  2685   MozillaUnRegisterDebugFILE(f);
  2686   int rv = fclose(f);
  2687   if (written < 0 || rv != 0) {
  2688     PR_Delete(tmpName.get());
  2689     return;
  2691   PR_Delete(name.get());
  2692   PR_Rename(tmpName.get(), name.get());
  2695 namespace Telemetry {
  2697 void
  2698 Accumulate(ID aHistogram, uint32_t aSample)
  2700   if (!TelemetryImpl::CanRecord()) {
  2701     return;
  2703   Histogram *h;
  2704   nsresult rv = GetHistogramByEnumId(aHistogram, &h);
  2705   if (NS_SUCCEEDED(rv))
  2706     h->Add(aSample);
  2709 void
  2710 Accumulate(const char* name, uint32_t sample)
  2712   if (!TelemetryImpl::CanRecord()) {
  2713     return;
  2715   ID id;
  2716   nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id);
  2717   if (NS_FAILED(rv)) {
  2718     return;
  2721   Histogram *h;
  2722   rv = GetHistogramByEnumId(id, &h);
  2723   if (NS_SUCCEEDED(rv)) {
  2724     h->Add(sample);
  2728 void
  2729 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
  2731   Accumulate(aHistogram,
  2732              static_cast<uint32_t>((end - start).ToMilliseconds()));
  2735 bool
  2736 CanRecord()
  2738   return TelemetryImpl::CanRecord();
  2741 base::Histogram*
  2742 GetHistogramById(ID id)
  2744   Histogram *h = nullptr;
  2745   GetHistogramByEnumId(id, &h);
  2746   return h;
  2749 void
  2750 RecordSlowSQLStatement(const nsACString &statement,
  2751                        const nsACString &dbName,
  2752                        uint32_t delay)
  2754   TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
  2757 void Init()
  2759   // Make the service manager hold a long-lived reference to the service
  2760   nsCOMPtr<nsITelemetry> telemetryService =
  2761     do_GetService("@mozilla.org/base/telemetry;1");
  2762   MOZ_ASSERT(telemetryService);
  2765 #if defined(MOZ_ENABLE_PROFILER_SPS)
  2766 void RecordChromeHang(uint32_t duration,
  2767                       ProcessedStack &aStack,
  2768                       int32_t aSystemUptime,
  2769                       int32_t aFirefoxUptime)
  2771   TelemetryImpl::RecordChromeHang(duration, aStack,
  2772                                   aSystemUptime, aFirefoxUptime);
  2774 #endif
  2776 void RecordThreadHangStats(ThreadHangStats& aStats)
  2778   TelemetryImpl::RecordThreadHangStats(aStats);
  2781 ProcessedStack::ProcessedStack()
  2785 size_t ProcessedStack::GetStackSize() const
  2787   return mStack.size();
  2790 const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const
  2792   MOZ_ASSERT(aIndex < mStack.size());
  2793   return mStack[aIndex];
  2796 void ProcessedStack::AddFrame(const Frame &aFrame)
  2798   mStack.push_back(aFrame);
  2801 size_t ProcessedStack::GetNumModules() const
  2803   return mModules.size();
  2806 const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
  2808   MOZ_ASSERT(aIndex < mModules.size());
  2809   return mModules[aIndex];
  2812 void ProcessedStack::AddModule(const Module &aModule)
  2814   mModules.push_back(aModule);
  2817 void ProcessedStack::Clear() {
  2818   mModules.clear();
  2819   mStack.clear();
  2822 bool ProcessedStack::Module::operator==(const Module& aOther) const {
  2823   return  mName == aOther.mName &&
  2824     mBreakpadId == aOther.mBreakpadId;
  2827 struct StackFrame
  2829   uintptr_t mPC;      // The program counter at this position in the call stack.
  2830   uint16_t mIndex;    // The number of this frame in the call stack.
  2831   uint16_t mModIndex; // The index of module that has this program counter.
  2832 };
  2835 #ifdef MOZ_ENABLE_PROFILER_SPS
  2836 static bool CompareByPC(const StackFrame &a, const StackFrame &b)
  2838   return a.mPC < b.mPC;
  2841 static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
  2843   return a.mIndex < b.mIndex;
  2845 #endif
  2847 ProcessedStack
  2848 GetStackAndModules(const std::vector<uintptr_t>& aPCs)
  2850   std::vector<StackFrame> rawStack;
  2851   for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(),
  2852          e = aPCs.end(); i != e; ++i) {
  2853     uintptr_t aPC = *i;
  2854     StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
  2855                         std::numeric_limits<uint16_t>::max()};
  2856     rawStack.push_back(Frame);
  2859 #ifdef MOZ_ENABLE_PROFILER_SPS
  2860   // Remove all modules not referenced by a PC on the stack
  2861   std::sort(rawStack.begin(), rawStack.end(), CompareByPC);
  2863   size_t moduleIndex = 0;
  2864   size_t stackIndex = 0;
  2865   size_t stackSize = rawStack.size();
  2867   SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
  2868   rawModules.SortByAddress();
  2870   while (moduleIndex < rawModules.GetSize()) {
  2871     const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
  2872     uintptr_t moduleStart = module.GetStart();
  2873     uintptr_t moduleEnd = module.GetEnd() - 1;
  2874     // the interval is [moduleStart, moduleEnd)
  2876     bool moduleReferenced = false;
  2877     for (;stackIndex < stackSize; ++stackIndex) {
  2878       uintptr_t pc = rawStack[stackIndex].mPC;
  2879       if (pc >= moduleEnd)
  2880         break;
  2882       if (pc >= moduleStart) {
  2883         // If the current PC is within the current module, mark
  2884         // module as used
  2885         moduleReferenced = true;
  2886         rawStack[stackIndex].mPC -= moduleStart;
  2887         rawStack[stackIndex].mModIndex = moduleIndex;
  2888       } else {
  2889         // PC does not belong to any module. It is probably from
  2890         // the JIT. Use a fixed mPC so that we don't get different
  2891         // stacks on different runs.
  2892         rawStack[stackIndex].mPC =
  2893           std::numeric_limits<uintptr_t>::max();
  2897     if (moduleReferenced) {
  2898       ++moduleIndex;
  2899     } else {
  2900       // Remove module if no PCs within its address range
  2901       rawModules.RemoveEntries(moduleIndex, moduleIndex + 1);
  2905   for (;stackIndex < stackSize; ++stackIndex) {
  2906     // These PCs are past the last module.
  2907     rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
  2910   std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
  2911 #endif
  2913   // Copy the information to the return value.
  2914   ProcessedStack Ret;
  2915   for (std::vector<StackFrame>::iterator i = rawStack.begin(),
  2916          e = rawStack.end(); i != e; ++i) {
  2917     const StackFrame &rawFrame = *i;
  2918     ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex };
  2919     Ret.AddFrame(frame);
  2922 #ifdef MOZ_ENABLE_PROFILER_SPS
  2923   for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
  2924     const SharedLibrary &info = rawModules.GetEntry(i);
  2925     const std::string &name = info.GetName();
  2926     std::string basename = name;
  2927 #ifdef XP_MACOSX
  2928     // FIXME: We want to use just the basename as the libname, but the
  2929     // current profiler addon needs the full path name, so we compute the
  2930     // basename in here.
  2931     size_t pos = name.rfind('/');
  2932     if (pos != std::string::npos) {
  2933       basename = name.substr(pos + 1);
  2935 #endif
  2936     ProcessedStack::Module module = {
  2937       basename,
  2938       info.GetBreakpadId()
  2939     };
  2940     Ret.AddModule(module);
  2942 #endif
  2944   return Ret;
  2947 void
  2948 WriteFailedProfileLock(nsIFile* aProfileDir)
  2950   nsCOMPtr<nsIFile> file;
  2951   nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir);
  2952   NS_ENSURE_SUCCESS_VOID(rv);
  2953   int64_t fileSize = 0;
  2954   rv = file->GetFileSize(&fileSize);
  2955   // It's expected that the file might not exist yet
  2956   if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
  2957     return;
  2959   nsCOMPtr<nsIFileStream> fileStream;
  2960   rv = NS_NewLocalFileStream(getter_AddRefs(fileStream), file,
  2961                              PR_RDWR | PR_CREATE_FILE, 0640);
  2962   NS_ENSURE_SUCCESS_VOID(rv);
  2963   NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize);
  2964   unsigned int failedLockCount = 0;
  2965   if (fileSize > 0) {
  2966     nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(fileStream);
  2967     NS_ENSURE_TRUE_VOID(inStream);
  2968     if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) {
  2969       failedLockCount = 0;
  2972   ++failedLockCount;
  2973   nsAutoCString bufStr;
  2974   bufStr.AppendInt(static_cast<int>(failedLockCount));
  2975   nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(fileStream);
  2976   NS_ENSURE_TRUE_VOID(seekStream);
  2977   // If we read in an existing failed lock count, we need to reset the file ptr
  2978   if (fileSize > 0) {
  2979     rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
  2980     NS_ENSURE_SUCCESS_VOID(rv);
  2982   nsCOMPtr<nsIOutputStream> outStream = do_QueryInterface(fileStream);
  2983   uint32_t bytesLeft = bufStr.Length();
  2984   const char* bytes = bufStr.get();
  2985   do {
  2986     uint32_t written = 0;
  2987     rv = outStream->Write(bytes, bytesLeft, &written);
  2988     if (NS_FAILED(rv)) {
  2989       break;
  2991     bytes += written;
  2992     bytesLeft -= written;
  2993   } while (bytesLeft > 0);
  2994   seekStream->SetEOF();
  2997 void
  2998 InitIOReporting(nsIFile* aXreDir)
  3000   // Never initialize twice
  3001   if (sTelemetryIOObserver) {
  3002     return;
  3005   sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir);
  3006   IOInterposer::Register(IOInterposeObserver::OpAllWithStaging,
  3007                          sTelemetryIOObserver);
  3010 void
  3011 SetProfileDir(nsIFile* aProfD)
  3013   if (!sTelemetryIOObserver || !aProfD) {
  3014     return;
  3016   nsAutoString profDirPath;
  3017   nsresult rv = aProfD->GetPath(profDirPath);
  3018   if (NS_FAILED(rv)) {
  3019     return;
  3021   sTelemetryIOObserver->AddPath(profDirPath, NS_LITERAL_STRING("{profile}"));
  3024 void
  3025 TimeHistogram::Add(PRIntervalTime aTime)
  3027   uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
  3028   size_t index = mozilla::FloorLog2(timeMs);
  3029   operator[](index)++;
  3032 uint32_t
  3033 HangHistogram::GetHash(const Stack& aStack)
  3035   uint32_t hash = 0;
  3036   for (const char* const* label = aStack.begin();
  3037        label != aStack.end(); label++) {
  3038     /* We only need to hash the pointer instead of the text content
  3039        because we are assuming constant pointers */
  3040     hash = AddToHash(hash, *label);
  3042   return hash;
  3045 bool
  3046 HangHistogram::operator==(const HangHistogram& aOther) const
  3048   if (mHash != aOther.mHash) {
  3049     return false;
  3051   if (mStack.length() != aOther.mStack.length()) {
  3052     return false;
  3054   return PodEqual(mStack.begin(), aOther.mStack.begin(), mStack.length());
  3058 } // namespace Telemetry
  3059 } // namespace mozilla
  3061 NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule;
  3063 /**
  3064  * The XRE_TelemetryAdd function is to be used by embedding applications
  3065  * that can't use mozilla::Telemetry::Accumulate() directly.
  3066  */
  3067 void
  3068 XRE_TelemetryAccumulate(int aID, uint32_t aSample)
  3070   mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample);

mercurial