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/. */
6 
7 #include "Telemetry.h"
8 
9 #include <algorithm>
10 #include <prio.h>
11 #include <prproces.h>
12 #if defined(XP_UNIX) && !defined(XP_DARWIN)
13 #  include <time.h>
14 #else
15 #  include <chrono>
16 #endif
17 #include "base/pickle.h"
18 #include "base/process_util.h"
19 #if defined(MOZ_TELEMETRY_GECKOVIEW)
20 #  include "geckoview/TelemetryGeckoViewPersistence.h"
21 #endif
22 #include "ipc/TelemetryIPCAccumulator.h"
23 #include "jsapi.h"
24 #include "jsfriendapi.h"
25 #include "js/Array.h"  // JS::NewArrayObject
26 #include "js/GCAPI.h"
27 #include "mozilla/dom/ToJSValue.h"
28 #include "mozilla/dom/Promise.h"
29 #include "mozilla/Atomics.h"
30 #include "mozilla/Attributes.h"
31 #include "mozilla/BackgroundHangMonitor.h"
32 #include "mozilla/Components.h"
33 #include "mozilla/DataMutex.h"
34 #include "mozilla/DebugOnly.h"
35 #include "mozilla/FStream.h"
36 #include "mozilla/IOInterposer.h"
37 #include "mozilla/Likely.h"
38 #include "mozilla/MathAlgorithms.h"
39 #include "mozilla/MemoryReporting.h"
40 #include "mozilla/MemoryTelemetry.h"
41 #include "mozilla/ModuleUtils.h"
42 #include "mozilla/Mutex.h"
43 #include "mozilla/PoisonIOInterposer.h"
44 #include "mozilla/Preferences.h"
45 #include "mozilla/StartupTimeline.h"
46 #include "mozilla/StaticPtr.h"
47 #include "mozilla/Unused.h"
48 #if defined(XP_WIN)
49 #  include "mozilla/WinDllServices.h"
50 #endif
51 #include "nsAppDirectoryServiceDefs.h"
52 #include "nsBaseHashtable.h"
53 #include "nsClassHashtable.h"
54 #include "nsCOMArray.h"
55 #include "nsCOMPtr.h"
56 #include "nsTHashMap.h"
57 #include "nsHashKeys.h"
58 #include "nsIDirectoryEnumerator.h"
59 #include "nsDirectoryServiceDefs.h"
60 #include "nsIFileStreams.h"
61 #include "nsIMemoryReporter.h"
62 #include "nsISeekableStream.h"
63 #include "nsITelemetry.h"
64 #if defined(XP_WIN)
65 #  include "other/UntrustedModules.h"
66 #endif
67 #include "nsJSUtils.h"
68 #include "nsLocalFile.h"
69 #include "nsNativeCharsetUtils.h"
70 #include "nsNetCID.h"
71 #include "nsNetUtil.h"
72 #include "nsProxyRelease.h"
73 #include "nsReadableUtils.h"
74 #include "nsString.h"
75 #include "nsTHashtable.h"
76 #include "nsThreadUtils.h"
77 #if defined(XP_WIN)
78 #  include "nsUnicharUtils.h"
79 #endif
80 #include "nsVersionComparator.h"
81 #include "nsXPCOMCIDInternal.h"
82 #include "other/CombinedStacks.h"
83 #include "other/TelemetryIOInterposeObserver.h"
84 #include "plstr.h"
85 #include "TelemetryCommon.h"
86 #include "TelemetryEvent.h"
87 #include "TelemetryHistogram.h"
88 #include "TelemetryOrigin.h"
89 #include "TelemetryScalar.h"
90 #include "TelemetryUserInteraction.h"
91 
92 namespace {
93 
94 using namespace mozilla;
95 using mozilla::dom::AutoJSAPI;
96 using mozilla::dom::Promise;
97 using mozilla::Telemetry::CombinedStacks;
98 using mozilla::Telemetry::EventExtraEntry;
99 using mozilla::Telemetry::TelemetryIOInterposeObserver;
100 using Telemetry::Common::AutoHashtable;
101 using Telemetry::Common::GetCurrentProduct;
102 using Telemetry::Common::StringHashSet;
103 using Telemetry::Common::SupportedProduct;
104 using Telemetry::Common::ToJSString;
105 
106 // This is not a member of TelemetryImpl because we want to record I/O during
107 // startup.
108 StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver;
109 
ClearIOReporting()110 void ClearIOReporting() {
111   if (!sTelemetryIOObserver) {
112     return;
113   }
114   IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
115                            sTelemetryIOObserver);
116   sTelemetryIOObserver = nullptr;
117 }
118 
119 class TelemetryImpl final : public nsITelemetry, public nsIMemoryReporter {
120   NS_DECL_THREADSAFE_ISUPPORTS
121   NS_DECL_NSITELEMETRY
122   NS_DECL_NSIMEMORYREPORTER
123 
124  public:
125   void InitMemoryReporter();
126 
127   static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
128   static void ShutdownTelemetry();
129   static void RecordSlowStatement(const nsACString& sql,
130                                   const nsACString& dbName, uint32_t delay);
131   struct Stat {
132     uint32_t hitCount;
133     uint32_t totalTime;
134   };
135   struct StmtStats {
136     struct Stat mainThread;
137     struct Stat otherThreads;
138   };
139   typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
140 
141   static void RecordIceCandidates(const uint32_t iceCandidateBitmask,
142                                   const bool success);
143   static bool CanRecordBase();
144   static bool CanRecordExtended();
145   static bool CanRecordReleaseData();
146   static bool CanRecordPrereleaseData();
147 
148  private:
149   TelemetryImpl();
150   ~TelemetryImpl();
151 
152   static nsCString SanitizeSQL(const nsACString& sql);
153 
154   enum SanitizedState { Sanitized, Unsanitized };
155 
156   static void StoreSlowSQL(const nsACString& offender, uint32_t delay,
157                            SanitizedState state);
158 
159   static bool ReflectMainThreadSQL(SlowSQLEntryType* entry, JSContext* cx,
160                                    JS::Handle<JSObject*> obj);
161   static bool ReflectOtherThreadsSQL(SlowSQLEntryType* entry, JSContext* cx,
162                                      JS::Handle<JSObject*> obj);
163   static bool ReflectSQL(const SlowSQLEntryType* entry, const Stat* stat,
164                          JSContext* cx, JS::Handle<JSObject*> obj);
165 
166   bool AddSQLInfo(JSContext* cx, JS::Handle<JSObject*> rootObj, bool mainThread,
167                   bool privateSQL);
168   bool GetSQLStats(JSContext* cx, JS::MutableHandle<JS::Value> ret,
169                    bool includePrivateSql);
170 
171   void ReadLateWritesStacks(nsIFile* aProfileDir);
172 
173   static StaticDataMutex<TelemetryImpl*> sTelemetry;
174   AutoHashtable<SlowSQLEntryType> mPrivateSQL;
175   AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
176   Mutex mHashMutex;
177   Atomic<bool, SequentiallyConsistent> mCanRecordBase;
178   Atomic<bool, SequentiallyConsistent> mCanRecordExtended;
179 
180   CombinedStacks
181       mLateWritesStacks;  // This is collected out of the main thread.
182   bool mCachedTelemetryData;
183   uint32_t mLastShutdownTime;
184   uint32_t mFailedLockCount;
185   nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks;
186   friend class nsFetchTelemetryData;
187 };
188 
189 StaticDataMutex<TelemetryImpl*> TelemetryImpl::sTelemetry(nullptr, nullptr);
190 
MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf)191 MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf)
192 
193 NS_IMETHODIMP
194 TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
195                               nsISupports* aData, bool aAnonymize) {
196   mozilla::MallocSizeOf aMallocSizeOf = TelemetryMallocSizeOf;
197 
198 #define COLLECT_REPORT(name, size, desc) \
199   MOZ_COLLECT_REPORT(name, KIND_HEAP, UNITS_BYTES, size, desc)
200 
201   COLLECT_REPORT("explicit/telemetry/impl", aMallocSizeOf(this),
202                  "Memory used by the Telemetry core implemenation");
203 
204   COLLECT_REPORT(
205       "explicit/telemetry/scalar/shallow",
206       TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf),
207       "Memory used by the Telemetry Scalar implemenation");
208 
209   {  // Scope for mHashMutex lock
210     MutexAutoLock lock(mHashMutex);
211     COLLECT_REPORT("explicit/telemetry/PrivateSQL",
212                    mPrivateSQL.SizeOfExcludingThis(aMallocSizeOf),
213                    "Memory used by the PrivateSQL Telemetry");
214 
215     COLLECT_REPORT("explicit/telemetry/SanitizedSQL",
216                    mSanitizedSQL.SizeOfExcludingThis(aMallocSizeOf),
217                    "Memory used by the SanitizedSQL Telemetry");
218   }
219 
220   if (sTelemetryIOObserver) {
221     COLLECT_REPORT("explicit/telemetry/IOObserver",
222                    sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf),
223                    "Memory used by the Telemetry IO Observer");
224   }
225 
226   COLLECT_REPORT("explicit/telemetry/LateWritesStacks",
227                  mLateWritesStacks.SizeOfExcludingThis(),
228                  "Memory used by the Telemetry LateWrites Stack capturer");
229 
230   COLLECT_REPORT("explicit/telemetry/Callbacks",
231                  mCallbacks.ShallowSizeOfExcludingThis(aMallocSizeOf),
232                  "Memory used by the Telemetry Callbacks array (shallow)");
233 
234   COLLECT_REPORT(
235       "explicit/telemetry/histogram/data",
236       TelemetryHistogram::GetHistogramSizesOfIncludingThis(aMallocSizeOf),
237       "Memory used by Telemetry Histogram data");
238 
239   COLLECT_REPORT("explicit/telemetry/scalar/data",
240                  TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf),
241                  "Memory used by Telemetry Scalar data");
242 
243   COLLECT_REPORT("explicit/telemetry/event/data",
244                  TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf),
245                  "Memory used by Telemetry Event data");
246 
247   COLLECT_REPORT("explicit/telemetry/origin/data",
248                  TelemetryOrigin::SizeOfIncludingThis(aMallocSizeOf),
249                  "Memory used by Telemetry Origin data");
250 
251 #undef COLLECT_REPORT
252 
253   return NS_OK;
254 }
255 
InitHistogramRecordingEnabled()256 void InitHistogramRecordingEnabled() {
257   TelemetryHistogram::InitHistogramRecordingEnabled();
258 }
259 
260 using PathChar = filesystem::Path::value_type;
261 using PathCharPtr = const PathChar*;
262 
ReadLastShutdownDuration(PathCharPtr filename)263 static uint32_t ReadLastShutdownDuration(PathCharPtr filename) {
264   RefPtr<nsLocalFile> file =
265       new nsLocalFile(nsTDependentString<PathChar>(filename));
266   FILE* f;
267   if (NS_FAILED(file->OpenANSIFileDesc("r", &f)) || !f) {
268     return 0;
269   }
270 
271   int shutdownTime;
272   int r = fscanf(f, "%d\n", &shutdownTime);
273   fclose(f);
274   if (r != 1) {
275     return 0;
276   }
277 
278   return shutdownTime;
279 }
280 
281 const int32_t kMaxFailedProfileLockFileSize = 10;
282 
GetFailedLockCount(nsIInputStream * inStream,uint32_t aCount,unsigned int & result)283 bool GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount,
284                         unsigned int& result) {
285   nsAutoCString bufStr;
286   nsresult rv;
287   rv = NS_ReadInputStreamToString(inStream, bufStr, aCount);
288   NS_ENSURE_SUCCESS(rv, false);
289   result = bufStr.ToInteger(&rv);
290   return NS_SUCCEEDED(rv) && result > 0;
291 }
292 
GetFailedProfileLockFile(nsIFile ** aFile,nsIFile * aProfileDir)293 nsresult GetFailedProfileLockFile(nsIFile** aFile, nsIFile* aProfileDir) {
294   NS_ENSURE_ARG_POINTER(aProfileDir);
295 
296   nsresult rv = aProfileDir->Clone(aFile);
297   NS_ENSURE_SUCCESS(rv, rv);
298 
299   (*aFile)->AppendNative("Telemetry.FailedProfileLocks.txt"_ns);
300   return NS_OK;
301 }
302 
303 class nsFetchTelemetryData : public Runnable {
304  public:
nsFetchTelemetryData(PathCharPtr aShutdownTimeFilename,nsIFile * aFailedProfileLockFile,nsIFile * aProfileDir)305   nsFetchTelemetryData(PathCharPtr aShutdownTimeFilename,
306                        nsIFile* aFailedProfileLockFile, nsIFile* aProfileDir)
307       : mozilla::Runnable("nsFetchTelemetryData"),
308         mShutdownTimeFilename(aShutdownTimeFilename),
309         mFailedProfileLockFile(aFailedProfileLockFile),
310         mProfileDir(aProfileDir) {}
311 
312  private:
313   PathCharPtr mShutdownTimeFilename;
314   nsCOMPtr<nsIFile> mFailedProfileLockFile;
315   nsCOMPtr<nsIFile> mProfileDir;
316 
317  public:
MainThread()318   void MainThread() {
319     auto lock = TelemetryImpl::sTelemetry.Lock();
320     auto telemetry = lock.ref();
321     telemetry->mCachedTelemetryData = true;
322     for (unsigned int i = 0, n = telemetry->mCallbacks.Count(); i < n; ++i) {
323       telemetry->mCallbacks[i]->Complete();
324     }
325     telemetry->mCallbacks.Clear();
326   }
327 
Run()328   NS_IMETHOD Run() override {
329     uint32_t failedLockCount = 0;
330     uint32_t lastShutdownDuration = 0;
331     LoadFailedLockCount(failedLockCount);
332     lastShutdownDuration = ReadLastShutdownDuration(mShutdownTimeFilename);
333     {
334       auto lock = TelemetryImpl::sTelemetry.Lock();
335       auto telemetry = lock.ref();
336       telemetry->mFailedLockCount = failedLockCount;
337       telemetry->mLastShutdownTime = lastShutdownDuration;
338       telemetry->ReadLateWritesStacks(mProfileDir);
339     }
340 
341     TelemetryScalar::Set(Telemetry::ScalarID::BROWSER_TIMINGS_LAST_SHUTDOWN,
342                          lastShutdownDuration);
343 
344     nsCOMPtr<nsIRunnable> e =
345         NewRunnableMethod("nsFetchTelemetryData::MainThread", this,
346                           &nsFetchTelemetryData::MainThread);
347     NS_ENSURE_STATE(e);
348     NS_DispatchToMainThread(e);
349     return NS_OK;
350   }
351 
352  private:
LoadFailedLockCount(uint32_t & failedLockCount)353   nsresult LoadFailedLockCount(uint32_t& failedLockCount) {
354     failedLockCount = 0;
355     int64_t fileSize = 0;
356     nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize);
357     if (NS_FAILED(rv)) {
358       return rv;
359     }
360     NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize,
361                    NS_ERROR_UNEXPECTED);
362     nsCOMPtr<nsIInputStream> inStream;
363     rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream),
364                                     mFailedProfileLockFile, PR_RDONLY);
365     NS_ENSURE_SUCCESS(rv, rv);
366     NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount),
367                    NS_ERROR_UNEXPECTED);
368     inStream->Close();
369 
370     mFailedProfileLockFile->Remove(false);
371     return NS_OK;
372   }
373 };
374 
375 static TimeStamp gRecordedShutdownStartTime;
376 static bool gAlreadyFreedShutdownTimeFileName = false;
377 static PathCharPtr gRecordedShutdownTimeFileName = nullptr;
378 
GetShutdownTimeFileName()379 static PathCharPtr GetShutdownTimeFileName() {
380   if (gAlreadyFreedShutdownTimeFileName) {
381     return nullptr;
382   }
383 
384   if (!gRecordedShutdownTimeFileName) {
385     nsCOMPtr<nsIFile> mozFile;
386     NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
387     if (!mozFile) return nullptr;
388 
389     mozFile->AppendNative("Telemetry.ShutdownTime.txt"_ns);
390 
391     gRecordedShutdownTimeFileName = NS_xstrdup(mozFile->NativePath().get());
392   }
393 
394   return gRecordedShutdownTimeFileName;
395 }
396 
397 NS_IMETHODIMP
GetLastShutdownDuration(uint32_t * aResult)398 TelemetryImpl::GetLastShutdownDuration(uint32_t* aResult) {
399   // The user must call AsyncFetchTelemetryData first. We return zero instead of
400   // reporting a failure so that the rest of telemetry can uniformly handle
401   // the read not being available yet.
402   if (!mCachedTelemetryData) {
403     *aResult = 0;
404     return NS_OK;
405   }
406 
407   *aResult = mLastShutdownTime;
408   return NS_OK;
409 }
410 
411 NS_IMETHODIMP
GetFailedProfileLockCount(uint32_t * aResult)412 TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult) {
413   // The user must call AsyncFetchTelemetryData first. We return zero instead of
414   // reporting a failure so that the rest of telemetry can uniformly handle
415   // the read not being available yet.
416   if (!mCachedTelemetryData) {
417     *aResult = 0;
418     return NS_OK;
419   }
420 
421   *aResult = mFailedLockCount;
422   return NS_OK;
423 }
424 
425 NS_IMETHODIMP
AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback * aCallback)426 TelemetryImpl::AsyncFetchTelemetryData(
427     nsIFetchTelemetryDataCallback* aCallback) {
428   // We have finished reading the data already, just call the callback.
429   if (mCachedTelemetryData) {
430     aCallback->Complete();
431     return NS_OK;
432   }
433 
434   // We already have a read request running, just remember the callback.
435   if (mCallbacks.Count() != 0) {
436     mCallbacks.AppendObject(aCallback);
437     return NS_OK;
438   }
439 
440   // We make this check so that GetShutdownTimeFileName() doesn't get
441   // called; calling that function without telemetry enabled violates
442   // assumptions that the write-the-shutdown-timestamp machinery makes.
443   if (!Telemetry::CanRecordExtended()) {
444     mCachedTelemetryData = true;
445     aCallback->Complete();
446     return NS_OK;
447   }
448 
449   // Send the read to a background thread provided by the stream transport
450   // service to avoid a read in the main thread.
451   nsCOMPtr<nsIEventTarget> targetThread =
452       do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
453   if (!targetThread) {
454     mCachedTelemetryData = true;
455     aCallback->Complete();
456     return NS_OK;
457   }
458 
459   // We have to get the filename from the main thread.
460   PathCharPtr shutdownTimeFilename = GetShutdownTimeFileName();
461   if (!shutdownTimeFilename) {
462     mCachedTelemetryData = true;
463     aCallback->Complete();
464     return NS_OK;
465   }
466 
467   nsCOMPtr<nsIFile> profileDir;
468   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
469                                        getter_AddRefs(profileDir));
470   if (NS_FAILED(rv)) {
471     mCachedTelemetryData = true;
472     aCallback->Complete();
473     return NS_OK;
474   }
475 
476   nsCOMPtr<nsIFile> failedProfileLockFile;
477   rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile),
478                                 profileDir);
479   if (NS_FAILED(rv)) {
480     mCachedTelemetryData = true;
481     aCallback->Complete();
482     return NS_OK;
483   }
484 
485   mCallbacks.AppendObject(aCallback);
486 
487   nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(
488       shutdownTimeFilename, failedProfileLockFile, profileDir);
489 
490   targetThread->Dispatch(event, NS_DISPATCH_NORMAL);
491   return NS_OK;
492 }
493 
TelemetryImpl()494 TelemetryImpl::TelemetryImpl()
495     : mHashMutex("Telemetry::mHashMutex"),
496       mCanRecordBase(false),
497       mCanRecordExtended(false),
498       mCachedTelemetryData(false),
499       mLastShutdownTime(0),
500       mFailedLockCount(0) {
501   // We expect TelemetryHistogram::InitializeGlobalState() to have been
502   // called before we get to this point.
503   MOZ_ASSERT(TelemetryHistogram::GlobalStateHasBeenInitialized());
504 }
505 
~TelemetryImpl()506 TelemetryImpl::~TelemetryImpl() {
507   UnregisterWeakMemoryReporter(this);
508 
509   // This is still racey as access to these collections is guarded using
510   // sTelemetry. We will fix this in bug 1367344.
511   MutexAutoLock hashLock(mHashMutex);
512 }
513 
InitMemoryReporter()514 void TelemetryImpl::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
515 
ReflectSQL(const SlowSQLEntryType * entry,const Stat * stat,JSContext * cx,JS::Handle<JSObject * > obj)516 bool TelemetryImpl::ReflectSQL(const SlowSQLEntryType* entry, const Stat* stat,
517                                JSContext* cx, JS::Handle<JSObject*> obj) {
518   if (stat->hitCount == 0) return true;
519 
520   const nsACString& sql = entry->GetKey();
521 
522   JS::Rooted<JSObject*> arrayObj(cx, JS::NewArrayObject(cx, 0));
523   if (!arrayObj) {
524     return false;
525   }
526   return (
527       JS_DefineElement(cx, arrayObj, 0, stat->hitCount, JSPROP_ENUMERATE) &&
528       JS_DefineElement(cx, arrayObj, 1, stat->totalTime, JSPROP_ENUMERATE) &&
529       JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj,
530                         JSPROP_ENUMERATE));
531 }
532 
ReflectMainThreadSQL(SlowSQLEntryType * entry,JSContext * cx,JS::Handle<JSObject * > obj)533 bool TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType* entry, JSContext* cx,
534                                          JS::Handle<JSObject*> obj) {
535   return ReflectSQL(entry, &entry->GetModifiableData()->mainThread, cx, obj);
536 }
537 
ReflectOtherThreadsSQL(SlowSQLEntryType * entry,JSContext * cx,JS::Handle<JSObject * > obj)538 bool TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType* entry,
539                                            JSContext* cx,
540                                            JS::Handle<JSObject*> obj) {
541   return ReflectSQL(entry, &entry->GetModifiableData()->otherThreads, cx, obj);
542 }
543 
AddSQLInfo(JSContext * cx,JS::Handle<JSObject * > rootObj,bool mainThread,bool privateSQL)544 bool TelemetryImpl::AddSQLInfo(JSContext* cx, JS::Handle<JSObject*> rootObj,
545                                bool mainThread, bool privateSQL) {
546   JS::Rooted<JSObject*> statsObj(cx, JS_NewPlainObject(cx));
547   if (!statsObj) return false;
548 
549   AutoHashtable<SlowSQLEntryType>& sqlMap =
550       (privateSQL ? mPrivateSQL : mSanitizedSQL);
551   AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction =
552       (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL);
553   if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) {
554     return false;
555   }
556 
557   return JS_DefineProperty(cx, rootObj,
558                            mainThread ? "mainThread" : "otherThreads", statsObj,
559                            JSPROP_ENUMERATE);
560 }
561 
562 NS_IMETHODIMP
SetHistogramRecordingEnabled(const nsACString & id,bool aEnabled)563 TelemetryImpl::SetHistogramRecordingEnabled(const nsACString& id,
564                                             bool aEnabled) {
565   return TelemetryHistogram::SetHistogramRecordingEnabled(id, aEnabled);
566 }
567 
568 NS_IMETHODIMP
GetSnapshotForHistograms(const nsACString & aStoreName,bool aClearStore,bool aFilterTest,JSContext * aCx,JS::MutableHandleValue aResult)569 TelemetryImpl::GetSnapshotForHistograms(const nsACString& aStoreName,
570                                         bool aClearStore, bool aFilterTest,
571                                         JSContext* aCx,
572                                         JS::MutableHandleValue aResult) {
573   constexpr auto defaultStore = "main"_ns;
574   unsigned int dataset = mCanRecordExtended
575                              ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
576                              : nsITelemetry::DATASET_ALL_CHANNELS;
577   return TelemetryHistogram::CreateHistogramSnapshots(
578       aCx, aResult, aStoreName.IsVoid() ? defaultStore : aStoreName, dataset,
579       aClearStore, aFilterTest);
580 }
581 
582 NS_IMETHODIMP
GetSnapshotForKeyedHistograms(const nsACString & aStoreName,bool aClearStore,bool aFilterTest,JSContext * aCx,JS::MutableHandleValue aResult)583 TelemetryImpl::GetSnapshotForKeyedHistograms(const nsACString& aStoreName,
584                                              bool aClearStore, bool aFilterTest,
585                                              JSContext* aCx,
586                                              JS::MutableHandleValue aResult) {
587   constexpr auto defaultStore = "main"_ns;
588   unsigned int dataset = mCanRecordExtended
589                              ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
590                              : nsITelemetry::DATASET_ALL_CHANNELS;
591   return TelemetryHistogram::GetKeyedHistogramSnapshots(
592       aCx, aResult, aStoreName.IsVoid() ? defaultStore : aStoreName, dataset,
593       aClearStore, aFilterTest);
594 }
595 
596 NS_IMETHODIMP
GetCategoricalLabels(JSContext * aCx,JS::MutableHandleValue aResult)597 TelemetryImpl::GetCategoricalLabels(JSContext* aCx,
598                                     JS::MutableHandleValue aResult) {
599   return TelemetryHistogram::GetCategoricalHistogramLabels(aCx, aResult);
600 }
601 
602 NS_IMETHODIMP
GetSnapshotForScalars(const nsACString & aStoreName,bool aClearStore,bool aFilterTest,JSContext * aCx,JS::MutableHandleValue aResult)603 TelemetryImpl::GetSnapshotForScalars(const nsACString& aStoreName,
604                                      bool aClearStore, bool aFilterTest,
605                                      JSContext* aCx,
606                                      JS::MutableHandleValue aResult) {
607   constexpr auto defaultStore = "main"_ns;
608   unsigned int dataset = mCanRecordExtended
609                              ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
610                              : nsITelemetry::DATASET_ALL_CHANNELS;
611   return TelemetryScalar::CreateSnapshots(
612       dataset, aClearStore, aCx, 1, aResult, aFilterTest,
613       aStoreName.IsVoid() ? defaultStore : aStoreName);
614 }
615 
616 NS_IMETHODIMP
GetSnapshotForKeyedScalars(const nsACString & aStoreName,bool aClearStore,bool aFilterTest,JSContext * aCx,JS::MutableHandleValue aResult)617 TelemetryImpl::GetSnapshotForKeyedScalars(const nsACString& aStoreName,
618                                           bool aClearStore, bool aFilterTest,
619                                           JSContext* aCx,
620                                           JS::MutableHandleValue aResult) {
621   constexpr auto defaultStore = "main"_ns;
622   unsigned int dataset = mCanRecordExtended
623                              ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
624                              : nsITelemetry::DATASET_ALL_CHANNELS;
625   return TelemetryScalar::CreateKeyedSnapshots(
626       dataset, aClearStore, aCx, 1, aResult, aFilterTest,
627       aStoreName.IsVoid() ? defaultStore : aStoreName);
628 }
629 
GetSQLStats(JSContext * cx,JS::MutableHandle<JS::Value> ret,bool includePrivateSql)630 bool TelemetryImpl::GetSQLStats(JSContext* cx, JS::MutableHandle<JS::Value> ret,
631                                 bool includePrivateSql) {
632   JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
633   if (!root_obj) return false;
634   ret.setObject(*root_obj);
635 
636   MutexAutoLock hashMutex(mHashMutex);
637   // Add info about slow SQL queries on the main thread
638   if (!AddSQLInfo(cx, root_obj, true, includePrivateSql)) return false;
639   // Add info about slow SQL queries on other threads
640   if (!AddSQLInfo(cx, root_obj, false, includePrivateSql)) return false;
641 
642   return true;
643 }
644 
645 NS_IMETHODIMP
GetSlowSQL(JSContext * cx,JS::MutableHandle<JS::Value> ret)646 TelemetryImpl::GetSlowSQL(JSContext* cx, JS::MutableHandle<JS::Value> ret) {
647   if (GetSQLStats(cx, ret, false)) return NS_OK;
648   return NS_ERROR_FAILURE;
649 }
650 
651 NS_IMETHODIMP
GetDebugSlowSQL(JSContext * cx,JS::MutableHandle<JS::Value> ret)652 TelemetryImpl::GetDebugSlowSQL(JSContext* cx,
653                                JS::MutableHandle<JS::Value> ret) {
654   bool revealPrivateSql =
655       Preferences::GetBool("toolkit.telemetry.debugSlowSql", false);
656   if (GetSQLStats(cx, ret, revealPrivateSql)) return NS_OK;
657   return NS_ERROR_FAILURE;
658 }
659 
660 NS_IMETHODIMP
GetUntrustedModuleLoadEvents(uint32_t aFlags,JSContext * cx,Promise ** aPromise)661 TelemetryImpl::GetUntrustedModuleLoadEvents(uint32_t aFlags, JSContext* cx,
662                                             Promise** aPromise) {
663 #if defined(XP_WIN)
664   return Telemetry::GetUntrustedModuleLoadEvents(aFlags, cx, aPromise);
665 #else
666   return NS_ERROR_NOT_IMPLEMENTED;
667 #endif
668 }
669 
670 #if defined(MOZ_GECKO_PROFILER)
671 class GetLoadedModulesResultRunnable final : public Runnable {
672   nsMainThreadPtrHandle<Promise> mPromise;
673   SharedLibraryInfo mRawModules;
674   nsCOMPtr<nsIThread> mWorkerThread;
675 #  if defined(XP_WIN)
676   nsTHashMap<nsStringHashKey, nsString> mCertSubjects;
677 #  endif  // defined(XP_WIN)
678 
679  public:
GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle<Promise> & aPromise,const SharedLibraryInfo & rawModules)680   GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle<Promise>& aPromise,
681                                  const SharedLibraryInfo& rawModules)
682       : mozilla::Runnable("GetLoadedModulesResultRunnable"),
683         mPromise(aPromise),
684         mRawModules(rawModules),
685         mWorkerThread(do_GetCurrentThread()) {
686     MOZ_ASSERT(!NS_IsMainThread());
687 #  if defined(XP_WIN)
688     ObtainCertSubjects();
689 #  endif  // defined(XP_WIN)
690   }
691 
692   NS_IMETHOD
Run()693   Run() override {
694     MOZ_ASSERT(NS_IsMainThread());
695 
696     mWorkerThread->Shutdown();
697 
698     AutoJSAPI jsapi;
699     if (NS_WARN_IF(!jsapi.Init(mPromise->GetGlobalObject()))) {
700       mPromise->MaybeReject(NS_ERROR_FAILURE);
701       return NS_OK;
702     }
703 
704     JSContext* cx = jsapi.cx();
705 
706     JS::RootedObject moduleArray(cx, JS::NewArrayObject(cx, 0));
707     if (!moduleArray) {
708       mPromise->MaybeReject(NS_ERROR_FAILURE);
709       return NS_OK;
710     }
711 
712     for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) {
713       const SharedLibrary& info = mRawModules.GetEntry(i);
714 
715       JS::RootedObject moduleObj(cx, JS_NewPlainObject(cx));
716       if (!moduleObj) {
717         mPromise->MaybeReject(NS_ERROR_FAILURE);
718         return NS_OK;
719       }
720 
721       // Module name.
722       JS::RootedString moduleName(
723           cx, JS_NewUCStringCopyZ(cx, info.GetModuleName().get()));
724       if (!moduleName || !JS_DefineProperty(cx, moduleObj, "name", moduleName,
725                                             JSPROP_ENUMERATE)) {
726         mPromise->MaybeReject(NS_ERROR_FAILURE);
727         return NS_OK;
728       }
729 
730       // Module debug name.
731       JS::RootedValue moduleDebugName(cx);
732 
733       if (!info.GetDebugName().IsEmpty()) {
734         JS::RootedString str_moduleDebugName(
735             cx, JS_NewUCStringCopyZ(cx, info.GetDebugName().get()));
736         if (!str_moduleDebugName) {
737           mPromise->MaybeReject(NS_ERROR_FAILURE);
738           return NS_OK;
739         }
740         moduleDebugName.setString(str_moduleDebugName);
741       } else {
742         moduleDebugName.setNull();
743       }
744 
745       if (!JS_DefineProperty(cx, moduleObj, "debugName", moduleDebugName,
746                              JSPROP_ENUMERATE)) {
747         mPromise->MaybeReject(NS_ERROR_FAILURE);
748         return NS_OK;
749       }
750 
751       // Module Breakpad identifier.
752       JS::RootedValue id(cx);
753 
754       if (!info.GetBreakpadId().IsEmpty()) {
755         JS::RootedString str_id(
756             cx, JS_NewStringCopyZ(cx, info.GetBreakpadId().get()));
757         if (!str_id) {
758           mPromise->MaybeReject(NS_ERROR_FAILURE);
759           return NS_OK;
760         }
761         id.setString(str_id);
762       } else {
763         id.setNull();
764       }
765 
766       if (!JS_DefineProperty(cx, moduleObj, "debugID", id, JSPROP_ENUMERATE)) {
767         mPromise->MaybeReject(NS_ERROR_FAILURE);
768         return NS_OK;
769       }
770 
771       // Module version.
772       JS::RootedValue version(cx);
773 
774       if (!info.GetVersion().IsEmpty()) {
775         JS::RootedString v(
776             cx, JS_NewStringCopyZ(cx, info.GetVersion().BeginReading()));
777         if (!v) {
778           mPromise->MaybeReject(NS_ERROR_FAILURE);
779           return NS_OK;
780         }
781         version.setString(v);
782       } else {
783         version.setNull();
784       }
785 
786       if (!JS_DefineProperty(cx, moduleObj, "version", version,
787                              JSPROP_ENUMERATE)) {
788         mPromise->MaybeReject(NS_ERROR_FAILURE);
789         return NS_OK;
790       }
791 
792 #  if defined(XP_WIN)
793       // Cert Subject.
794       if (auto subject = mCertSubjects.Lookup(info.GetModulePath())) {
795         JS::RootedString jsOrg(cx, ToJSString(cx, *subject));
796         if (!jsOrg) {
797           mPromise->MaybeReject(NS_ERROR_FAILURE);
798           return NS_OK;
799         }
800 
801         JS::RootedValue certSubject(cx);
802         certSubject.setString(jsOrg);
803 
804         if (!JS_DefineProperty(cx, moduleObj, "certSubject", certSubject,
805                                JSPROP_ENUMERATE)) {
806           mPromise->MaybeReject(NS_ERROR_FAILURE);
807           return NS_OK;
808         }
809       }
810 #  endif  // defined(XP_WIN)
811 
812       if (!JS_DefineElement(cx, moduleArray, i, moduleObj, JSPROP_ENUMERATE)) {
813         mPromise->MaybeReject(NS_ERROR_FAILURE);
814         return NS_OK;
815       }
816     }
817 
818     mPromise->MaybeResolve(moduleArray);
819     return NS_OK;
820   }
821 
822  private:
823 #  if defined(XP_WIN)
ObtainCertSubjects()824   void ObtainCertSubjects() {
825     MOZ_ASSERT(!NS_IsMainThread());
826 
827     // NB: Currently we cannot lower this down to the profiler layer due to
828     // differing startup dependencies between the profiler and DllServices.
829     RefPtr<DllServices> dllSvc(DllServices::Get());
830 
831     for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) {
832       const SharedLibrary& info = mRawModules.GetEntry(i);
833 
834       auto orgName = dllSvc->GetBinaryOrgName(info.GetModulePath().get());
835       if (orgName) {
836         mCertSubjects.InsertOrUpdate(info.GetModulePath(),
837                                      nsDependentString(orgName.get()));
838       }
839     }
840   }
841 #  endif  // defined(XP_WIN)
842 };
843 
844 class GetLoadedModulesRunnable final : public Runnable {
845   nsMainThreadPtrHandle<Promise> mPromise;
846 
847  public:
GetLoadedModulesRunnable(const nsMainThreadPtrHandle<Promise> & aPromise)848   explicit GetLoadedModulesRunnable(
849       const nsMainThreadPtrHandle<Promise>& aPromise)
850       : mozilla::Runnable("GetLoadedModulesRunnable"), mPromise(aPromise) {}
851 
852   NS_IMETHOD
Run()853   Run() override {
854     nsCOMPtr<nsIRunnable> resultRunnable = new GetLoadedModulesResultRunnable(
855         mPromise, SharedLibraryInfo::GetInfoForSelf());
856     return NS_DispatchToMainThread(resultRunnable);
857   }
858 };
859 #endif  // MOZ_GECKO_PROFILER
860 
861 NS_IMETHODIMP
GetLoadedModules(JSContext * cx,Promise ** aPromise)862 TelemetryImpl::GetLoadedModules(JSContext* cx, Promise** aPromise) {
863 #if defined(MOZ_GECKO_PROFILER)
864   nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx);
865   if (NS_WARN_IF(!global)) {
866     return NS_ERROR_FAILURE;
867   }
868 
869   ErrorResult result;
870   RefPtr<Promise> promise = Promise::Create(global, result);
871   if (NS_WARN_IF(result.Failed())) {
872     return result.StealNSResult();
873   }
874 
875   nsCOMPtr<nsIThread> getModulesThread;
876   nsresult rv =
877       NS_NewNamedThread("TelemetryModule", getter_AddRefs(getModulesThread));
878   if (NS_WARN_IF(NS_FAILED(rv))) {
879     promise->MaybeReject(NS_ERROR_FAILURE);
880     return NS_OK;
881   }
882 
883   nsMainThreadPtrHandle<Promise> mainThreadPromise(
884       new nsMainThreadPtrHolder<Promise>(
885           "TelemetryImpl::GetLoadedModules::Promise", promise));
886   nsCOMPtr<nsIRunnable> runnable =
887       new GetLoadedModulesRunnable(mainThreadPromise);
888   promise.forget(aPromise);
889 
890   return getModulesThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
891 #else   // MOZ_GECKO_PROFILER
892   return NS_ERROR_NOT_IMPLEMENTED;
893 #endif  // MOZ_GECKO_PROFILER
894 }
895 
IsValidBreakpadId(const std::string & breakpadId)896 static bool IsValidBreakpadId(const std::string& breakpadId) {
897   if (breakpadId.size() < 33) {
898     return false;
899   }
900   for (char c : breakpadId) {
901     if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
902       return false;
903     }
904   }
905   return true;
906 }
907 
908 // Read a stack from the given file name. In case of any error, aStack is
909 // unchanged.
ReadStack(PathCharPtr aFileName,Telemetry::ProcessedStack & aStack)910 static void ReadStack(PathCharPtr aFileName,
911                       Telemetry::ProcessedStack& aStack) {
912   IFStream file(aFileName);
913 
914   size_t numModules;
915   file >> numModules;
916   if (file.fail()) {
917     return;
918   }
919 
920   char newline = file.get();
921   if (file.fail() || newline != '\n') {
922     return;
923   }
924 
925   Telemetry::ProcessedStack stack;
926   for (size_t i = 0; i < numModules; ++i) {
927     std::string breakpadId;
928     file >> breakpadId;
929     if (file.fail() || !IsValidBreakpadId(breakpadId)) {
930       return;
931     }
932 
933     char space = file.get();
934     if (file.fail() || space != ' ') {
935       return;
936     }
937 
938     std::string moduleName;
939     getline(file, moduleName);
940     if (file.fail() || moduleName[0] == ' ') {
941       return;
942     }
943 
944     Telemetry::ProcessedStack::Module module = {
945         NS_ConvertUTF8toUTF16(moduleName.c_str()),
946         nsCString(breakpadId.c_str(), breakpadId.size()),
947     };
948     stack.AddModule(module);
949   }
950 
951   size_t numFrames;
952   file >> numFrames;
953   if (file.fail()) {
954     return;
955   }
956 
957   newline = file.get();
958   if (file.fail() || newline != '\n') {
959     return;
960   }
961 
962   for (size_t i = 0; i < numFrames; ++i) {
963     uint16_t index;
964     file >> index;
965     uintptr_t offset;
966     file >> std::hex >> offset >> std::dec;
967     if (file.fail()) {
968       return;
969     }
970 
971     Telemetry::ProcessedStack::Frame frame = {offset, index};
972     stack.AddFrame(frame);
973   }
974 
975   aStack = stack;
976 }
977 
ReadLateWritesStacks(nsIFile * aProfileDir)978 void TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) {
979   nsCOMPtr<nsIDirectoryEnumerator> files;
980   if (NS_FAILED(aProfileDir->GetDirectoryEntries(getter_AddRefs(files)))) {
981     return;
982   }
983 
984   constexpr auto prefix = u"Telemetry.LateWriteFinal-"_ns;
985   nsCOMPtr<nsIFile> file;
986   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
987     nsAutoString leafName;
988     if (NS_FAILED(file->GetLeafName(leafName)) ||
989         !StringBeginsWith(leafName, prefix)) {
990       continue;
991     }
992 
993     Telemetry::ProcessedStack stack;
994     ReadStack(file->NativePath().get(), stack);
995     if (stack.GetStackSize() != 0) {
996       mLateWritesStacks.AddStack(stack);
997     }
998     // Delete the file so that we don't report it again on the next run.
999     file->Remove(false);
1000   }
1001 }
1002 
1003 NS_IMETHODIMP
GetLateWrites(JSContext * cx,JS::MutableHandle<JS::Value> ret)1004 TelemetryImpl::GetLateWrites(JSContext* cx, JS::MutableHandle<JS::Value> ret) {
1005   // The user must call AsyncReadTelemetryData first. We return an empty list
1006   // instead of reporting a failure so that the rest of telemetry can uniformly
1007   // handle the read not being available yet.
1008 
1009   // FIXME: we allocate the js object again and again in the getter. We should
1010   // figure out a way to cache it. In order to do that we have to call
1011   // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl
1012   // constructor, but it is not clear how to get a JSContext in there.
1013   // Another option would be to call it in here when we first call
1014   // CreateJSStackObject, but we would still need to figure out where to call
1015   // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot
1016   // and just set the pointer to nullptr is the telemetry destructor?
1017 
1018   JSObject* report;
1019   if (!mCachedTelemetryData) {
1020     CombinedStacks empty;
1021     report = CreateJSStackObject(cx, empty);
1022   } else {
1023     report = CreateJSStackObject(cx, mLateWritesStacks);
1024   }
1025 
1026   if (report == nullptr) {
1027     return NS_ERROR_FAILURE;
1028   }
1029 
1030   ret.setObject(*report);
1031   return NS_OK;
1032 }
1033 
1034 NS_IMETHODIMP
GetHistogramById(const nsACString & name,JSContext * cx,JS::MutableHandle<JS::Value> ret)1035 TelemetryImpl::GetHistogramById(const nsACString& name, JSContext* cx,
1036                                 JS::MutableHandle<JS::Value> ret) {
1037   return TelemetryHistogram::GetHistogramById(name, cx, ret);
1038 }
1039 
1040 NS_IMETHODIMP
GetKeyedHistogramById(const nsACString & name,JSContext * cx,JS::MutableHandle<JS::Value> ret)1041 TelemetryImpl::GetKeyedHistogramById(const nsACString& name, JSContext* cx,
1042                                      JS::MutableHandle<JS::Value> ret) {
1043   return TelemetryHistogram::GetKeyedHistogramById(name, cx, ret);
1044 }
1045 
1046 /**
1047  * Indicates if Telemetry can record base data (FHR data). This is true if the
1048  * FHR data reporting service or self-support are enabled.
1049  *
1050  * In the unlikely event that adding a new base probe is needed, please check
1051  * the data collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection
1052  * and talk to the Telemetry team.
1053  */
1054 NS_IMETHODIMP
GetCanRecordBase(bool * ret)1055 TelemetryImpl::GetCanRecordBase(bool* ret) {
1056   *ret = mCanRecordBase;
1057   return NS_OK;
1058 }
1059 
1060 NS_IMETHODIMP
SetCanRecordBase(bool canRecord)1061 TelemetryImpl::SetCanRecordBase(bool canRecord) {
1062 #ifndef FUZZING
1063   if (canRecord != mCanRecordBase) {
1064     TelemetryHistogram::SetCanRecordBase(canRecord);
1065     TelemetryScalar::SetCanRecordBase(canRecord);
1066     TelemetryEvent::SetCanRecordBase(canRecord);
1067     mCanRecordBase = canRecord;
1068   }
1069 #endif
1070   return NS_OK;
1071 }
1072 
1073 /**
1074  * Indicates if Telemetry is allowed to record extended data. Returns false if
1075  * the user hasn't opted into "extended Telemetry" on the Release channel, when
1076  * the user has explicitly opted out of Telemetry on Nightly/Aurora/Beta or if
1077  * manually set to false during tests. If the returned value is false, gathering
1078  * of extended telemetry statistics is disabled.
1079  */
1080 NS_IMETHODIMP
GetCanRecordExtended(bool * ret)1081 TelemetryImpl::GetCanRecordExtended(bool* ret) {
1082   *ret = mCanRecordExtended;
1083   return NS_OK;
1084 }
1085 
1086 NS_IMETHODIMP
SetCanRecordExtended(bool canRecord)1087 TelemetryImpl::SetCanRecordExtended(bool canRecord) {
1088 #ifndef FUZZING
1089   if (canRecord != mCanRecordExtended) {
1090     TelemetryHistogram::SetCanRecordExtended(canRecord);
1091     TelemetryScalar::SetCanRecordExtended(canRecord);
1092     TelemetryEvent::SetCanRecordExtended(canRecord);
1093     mCanRecordExtended = canRecord;
1094   }
1095 #endif
1096   return NS_OK;
1097 }
1098 
1099 NS_IMETHODIMP
GetCanRecordReleaseData(bool * ret)1100 TelemetryImpl::GetCanRecordReleaseData(bool* ret) {
1101   *ret = mCanRecordBase;
1102   return NS_OK;
1103 }
1104 
1105 NS_IMETHODIMP
GetCanRecordPrereleaseData(bool * ret)1106 TelemetryImpl::GetCanRecordPrereleaseData(bool* ret) {
1107   *ret = mCanRecordExtended;
1108   return NS_OK;
1109 }
1110 
1111 NS_IMETHODIMP
GetIsOfficialTelemetry(bool * ret)1112 TelemetryImpl::GetIsOfficialTelemetry(bool* ret) {
1113 #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) && \
1114     !defined(DEBUG)
1115   *ret = true;
1116 #else
1117   *ret = false;
1118 #endif
1119   return NS_OK;
1120 }
1121 
CreateTelemetryInstance()1122 already_AddRefed<nsITelemetry> TelemetryImpl::CreateTelemetryInstance() {
1123   {
1124     auto lock = sTelemetry.Lock();
1125     MOZ_ASSERT(
1126         *lock == nullptr,
1127         "CreateTelemetryInstance may only be called once, via GetService()");
1128   }
1129 
1130   bool useTelemetry = false;
1131 #ifndef FUZZING
1132   if (XRE_IsParentProcess() || XRE_IsContentProcess() || XRE_IsGPUProcess() ||
1133       XRE_IsSocketProcess()) {
1134     useTelemetry = true;
1135   }
1136 #endif
1137 
1138   // First, initialize the TelemetryHistogram and TelemetryScalar global states.
1139   TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry);
1140   TelemetryScalar::InitializeGlobalState(useTelemetry, useTelemetry);
1141 
1142   // Only record events from the parent process.
1143   TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(),
1144                                         XRE_IsParentProcess());
1145   TelemetryOrigin::InitializeGlobalState();
1146   // Currently, only UserInteractions from the parent process are recorded.
1147   TelemetryUserInteraction::InitializeGlobalState(useTelemetry, useTelemetry);
1148 
1149   // Now, create and initialize the Telemetry global state.
1150   TelemetryImpl* telemetry = new TelemetryImpl();
1151   {
1152     auto lock = sTelemetry.Lock();
1153     *lock = telemetry;
1154     // AddRef for the local reference before releasing the lock.
1155     NS_ADDREF(telemetry);
1156   }
1157 
1158   // AddRef for the caller
1159   nsCOMPtr<nsITelemetry> ret = telemetry;
1160 
1161   telemetry->mCanRecordBase = useTelemetry;
1162   telemetry->mCanRecordExtended = useTelemetry;
1163 
1164   telemetry->InitMemoryReporter();
1165   InitHistogramRecordingEnabled();  // requires sTelemetry to exist
1166 
1167   return ret.forget();
1168 }
1169 
ShutdownTelemetry()1170 void TelemetryImpl::ShutdownTelemetry() {
1171   // No point in collecting IO beyond this point
1172   ClearIOReporting();
1173   {
1174     auto lock = sTelemetry.Lock();
1175     NS_IF_RELEASE(lock.ref());
1176   }
1177 
1178   // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global
1179   // states, so as to release any heap storage that would otherwise be kept
1180   // alive by it.
1181   TelemetryHistogram::DeInitializeGlobalState();
1182   TelemetryScalar::DeInitializeGlobalState();
1183   TelemetryEvent::DeInitializeGlobalState();
1184   TelemetryOrigin::DeInitializeGlobalState();
1185   TelemetryUserInteraction::DeInitializeGlobalState();
1186   TelemetryIPCAccumulator::DeInitializeGlobalState();
1187 }
1188 
StoreSlowSQL(const nsACString & sql,uint32_t delay,SanitizedState state)1189 void TelemetryImpl::StoreSlowSQL(const nsACString& sql, uint32_t delay,
1190                                  SanitizedState state) {
1191   auto lock = sTelemetry.Lock();
1192   auto telemetry = lock.ref();
1193   AutoHashtable<SlowSQLEntryType>* slowSQLMap = nullptr;
1194   if (state == Sanitized)
1195     slowSQLMap = &(telemetry->mSanitizedSQL);
1196   else
1197     slowSQLMap = &(telemetry->mPrivateSQL);
1198 
1199   MutexAutoLock hashMutex(telemetry->mHashMutex);
1200 
1201   SlowSQLEntryType* entry = slowSQLMap->GetEntry(sql);
1202   if (!entry) {
1203     entry = slowSQLMap->PutEntry(sql);
1204     if (MOZ_UNLIKELY(!entry)) return;
1205     entry->GetModifiableData()->mainThread.hitCount = 0;
1206     entry->GetModifiableData()->mainThread.totalTime = 0;
1207     entry->GetModifiableData()->otherThreads.hitCount = 0;
1208     entry->GetModifiableData()->otherThreads.totalTime = 0;
1209   }
1210 
1211   if (NS_IsMainThread()) {
1212     entry->GetModifiableData()->mainThread.hitCount++;
1213     entry->GetModifiableData()->mainThread.totalTime += delay;
1214   } else {
1215     entry->GetModifiableData()->otherThreads.hitCount++;
1216     entry->GetModifiableData()->otherThreads.totalTime += delay;
1217   }
1218 }
1219 
1220 /**
1221  * This method replaces string literals in SQL strings with the word :private
1222  *
1223  * States used in this state machine:
1224  *
1225  * NORMAL:
1226  *  - This is the active state when not iterating over a string literal or
1227  *  comment
1228  *
1229  * SINGLE_QUOTE:
1230  *  - Defined here: http://www.sqlite.org/lang_expr.html
1231  *  - This state represents iterating over a string literal opened with
1232  *  a single quote.
1233  *  - A single quote within the string can be encoded by putting 2 single quotes
1234  *  in a row, e.g. 'This literal contains an escaped quote '''
1235  *  - Any double quotes found within a single-quoted literal are ignored
1236  *  - This state covers BLOB literals, e.g. X'ABC123'
1237  *  - The string literal and the enclosing quotes will be replaced with
1238  *  the text :private
1239  *
1240  * DOUBLE_QUOTE:
1241  *  - Same rules as the SINGLE_QUOTE state.
1242  *  - According to http://www.sqlite.org/lang_keywords.html,
1243  *  SQLite interprets text in double quotes as an identifier unless it's used in
1244  *  a context where it cannot be resolved to an identifier and a string literal
1245  *  is allowed. This method removes text in double-quotes for safety.
1246  *
1247  *  DASH_COMMENT:
1248  *  - http://www.sqlite.org/lang_comment.html
1249  *  - A dash comment starts with two dashes in a row,
1250  *  e.g. DROP TABLE foo -- a comment
1251  *  - Any text following two dashes in a row is interpreted as a comment until
1252  *  end of input or a newline character
1253  *  - Any quotes found within the comment are ignored and no replacements made
1254  *
1255  *  C_STYLE_COMMENT:
1256  *  - http://www.sqlite.org/lang_comment.html
1257  *  - A C-style comment starts with a forward slash and an asterisk, and ends
1258  *  with an asterisk and a forward slash
1259  *  - Any text following comment start is interpreted as a comment up to end of
1260  *  input or comment end
1261  *  - Any quotes found within the comment are ignored and no replacements made
1262  */
SanitizeSQL(const nsACString & sql)1263 nsCString TelemetryImpl::SanitizeSQL(const nsACString& sql) {
1264   nsCString output;
1265   int length = sql.Length();
1266 
1267   typedef enum {
1268     NORMAL,
1269     SINGLE_QUOTE,
1270     DOUBLE_QUOTE,
1271     DASH_COMMENT,
1272     C_STYLE_COMMENT,
1273   } State;
1274 
1275   State state = NORMAL;
1276   int fragmentStart = 0;
1277   for (int i = 0; i < length; i++) {
1278     char character = sql[i];
1279     char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0';
1280 
1281     switch (character) {
1282       case '\'':
1283       case '"':
1284         if (state == NORMAL) {
1285           state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE;
1286           output +=
1287               nsDependentCSubstring(sql, fragmentStart, i - fragmentStart);
1288           output += ":private";
1289           fragmentStart = -1;
1290         } else if ((state == SINGLE_QUOTE && character == '\'') ||
1291                    (state == DOUBLE_QUOTE && character == '"')) {
1292           if (nextCharacter == character) {
1293             // Two consecutive quotes within a string literal are a single
1294             // escaped quote
1295             i++;
1296           } else {
1297             state = NORMAL;
1298             fragmentStart = i + 1;
1299           }
1300         }
1301         break;
1302       case '-':
1303         if (state == NORMAL) {
1304           if (nextCharacter == '-') {
1305             state = DASH_COMMENT;
1306             i++;
1307           }
1308         }
1309         break;
1310       case '\n':
1311         if (state == DASH_COMMENT) {
1312           state = NORMAL;
1313         }
1314         break;
1315       case '/':
1316         if (state == NORMAL) {
1317           if (nextCharacter == '*') {
1318             state = C_STYLE_COMMENT;
1319             i++;
1320           }
1321         }
1322         break;
1323       case '*':
1324         if (state == C_STYLE_COMMENT) {
1325           if (nextCharacter == '/') {
1326             state = NORMAL;
1327           }
1328         }
1329         break;
1330       default:
1331         continue;
1332     }
1333   }
1334 
1335   if ((fragmentStart >= 0) && fragmentStart < length)
1336     output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart);
1337 
1338   return output;
1339 }
1340 
1341 // An allowlist mechanism to prevent Telemetry reporting on Addon & Thunderbird
1342 // DBs.
1343 struct TrackedDBEntry {
1344   const char* mName;
1345   const uint32_t mNameLength;
1346 
1347   // This struct isn't meant to be used beyond the static arrays below.
TrackedDBEntry__anone6a533cc0111::TrackedDBEntry1348   constexpr TrackedDBEntry(const char* aName, uint32_t aNameLength)
1349       : mName(aName), mNameLength(aNameLength) {}
1350 
1351   TrackedDBEntry() = delete;
1352   TrackedDBEntry(TrackedDBEntry&) = delete;
1353 };
1354 
1355 #define TRACKEDDB_ENTRY(_name) \
1356   { _name, (sizeof(_name) - 1) }
1357 
1358 // An allowlist of database names. If the database name exactly matches one of
1359 // these then its SQL statements will always be recorded.
1360 static constexpr TrackedDBEntry kTrackedDBs[] = {
1361     // IndexedDB for about:home, see aboutHome.js
1362     TRACKEDDB_ENTRY("818200132aebmoouht.sqlite"),
1363     TRACKEDDB_ENTRY("addons.sqlite"),
1364     TRACKEDDB_ENTRY("content-prefs.sqlite"),
1365     TRACKEDDB_ENTRY("cookies.sqlite"),
1366     TRACKEDDB_ENTRY("extensions.sqlite"),
1367     TRACKEDDB_ENTRY("favicons.sqlite"),
1368     TRACKEDDB_ENTRY("formhistory.sqlite"),
1369     TRACKEDDB_ENTRY("index.sqlite"),
1370     TRACKEDDB_ENTRY("netpredictions.sqlite"),
1371     TRACKEDDB_ENTRY("permissions.sqlite"),
1372     TRACKEDDB_ENTRY("places.sqlite"),
1373     TRACKEDDB_ENTRY("reading-list.sqlite"),
1374     TRACKEDDB_ENTRY("search.sqlite"),
1375     TRACKEDDB_ENTRY("signons.sqlite"),
1376     TRACKEDDB_ENTRY("urlclassifier3.sqlite"),
1377     TRACKEDDB_ENTRY("webappsstore.sqlite")};
1378 
1379 // An allowlist of database name prefixes. If the database name begins with
1380 // one of these prefixes then its SQL statements will always be recorded.
1381 static const TrackedDBEntry kTrackedDBPrefixes[] = {
1382     TRACKEDDB_ENTRY("indexedDB-")};
1383 
1384 #undef TRACKEDDB_ENTRY
1385 
1386 // Slow SQL statements will be automatically
1387 // trimmed to kMaxSlowStatementLength characters.
1388 // This limit doesn't include the ellipsis and DB name,
1389 // that are appended at the end of the stored statement.
1390 const uint32_t kMaxSlowStatementLength = 1000;
1391 
RecordSlowStatement(const nsACString & sql,const nsACString & dbName,uint32_t delay)1392 void TelemetryImpl::RecordSlowStatement(const nsACString& sql,
1393                                         const nsACString& dbName,
1394                                         uint32_t delay) {
1395   MOZ_ASSERT(!sql.IsEmpty());
1396   MOZ_ASSERT(!dbName.IsEmpty());
1397 
1398   {
1399     auto lock = sTelemetry.Lock();
1400     if (!lock.ref() || !TelemetryHistogram::CanRecordExtended()) {
1401       return;
1402     }
1403   }
1404 
1405   bool recordStatement = false;
1406 
1407   for (const TrackedDBEntry& nameEntry : kTrackedDBs) {
1408     MOZ_ASSERT(nameEntry.mNameLength);
1409     const nsDependentCString name(nameEntry.mName, nameEntry.mNameLength);
1410     if (dbName == name) {
1411       recordStatement = true;
1412       break;
1413     }
1414   }
1415 
1416   if (!recordStatement) {
1417     for (const TrackedDBEntry& prefixEntry : kTrackedDBPrefixes) {
1418       MOZ_ASSERT(prefixEntry.mNameLength);
1419       const nsDependentCString prefix(prefixEntry.mName,
1420                                       prefixEntry.mNameLength);
1421       if (StringBeginsWith(dbName, prefix)) {
1422         recordStatement = true;
1423         break;
1424       }
1425     }
1426   }
1427 
1428   if (recordStatement) {
1429     nsAutoCString sanitizedSQL(SanitizeSQL(sql));
1430     if (sanitizedSQL.Length() > kMaxSlowStatementLength) {
1431       sanitizedSQL.SetLength(kMaxSlowStatementLength);
1432       sanitizedSQL += "...";
1433     }
1434     sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get());
1435     StoreSlowSQL(sanitizedSQL, delay, Sanitized);
1436   } else {
1437     // Report aggregate DB-level statistics for addon DBs
1438     nsAutoCString aggregate;
1439     aggregate.AppendPrintf("Untracked SQL for %s",
1440                            nsPromiseFlatCString(dbName).get());
1441     StoreSlowSQL(aggregate, delay, Sanitized);
1442   }
1443 
1444   nsAutoCString fullSQL;
1445   fullSQL.AppendPrintf("%s /* %s */", nsPromiseFlatCString(sql).get(),
1446                        nsPromiseFlatCString(dbName).get());
1447   StoreSlowSQL(fullSQL, delay, Unsanitized);
1448 }
1449 
CanRecordBase()1450 bool TelemetryImpl::CanRecordBase() {
1451   auto lock = sTelemetry.Lock();
1452   auto telemetry = lock.ref();
1453   if (!telemetry) {
1454     return false;
1455   }
1456   bool canRecordBase;
1457   nsresult rv = telemetry->GetCanRecordBase(&canRecordBase);
1458   return NS_SUCCEEDED(rv) && canRecordBase;
1459 }
1460 
CanRecordExtended()1461 bool TelemetryImpl::CanRecordExtended() {
1462   auto lock = sTelemetry.Lock();
1463   auto telemetry = lock.ref();
1464   if (!telemetry) {
1465     return false;
1466   }
1467   bool canRecordExtended;
1468   nsresult rv = telemetry->GetCanRecordExtended(&canRecordExtended);
1469   return NS_SUCCEEDED(rv) && canRecordExtended;
1470 }
1471 
CanRecordReleaseData()1472 bool TelemetryImpl::CanRecordReleaseData() { return CanRecordBase(); }
1473 
CanRecordPrereleaseData()1474 bool TelemetryImpl::CanRecordPrereleaseData() { return CanRecordExtended(); }
1475 
NS_IMPL_ISUPPORTS(TelemetryImpl,nsITelemetry,nsIMemoryReporter)1476 NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter)
1477 
1478 NS_IMETHODIMP
1479 TelemetryImpl::GetFileIOReports(JSContext* cx, JS::MutableHandleValue ret) {
1480   if (sTelemetryIOObserver) {
1481     JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
1482     if (!obj) {
1483       return NS_ERROR_FAILURE;
1484     }
1485 
1486     if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) {
1487       return NS_ERROR_FAILURE;
1488     }
1489     ret.setObject(*obj);
1490     return NS_OK;
1491   }
1492   ret.setNull();
1493   return NS_OK;
1494 }
1495 
1496 NS_IMETHODIMP
MsSinceProcessStart(double * aResult)1497 TelemetryImpl::MsSinceProcessStart(double* aResult) {
1498   return Telemetry::Common::MsSinceProcessStart(aResult);
1499 }
1500 
1501 NS_IMETHODIMP
MsSinceProcessStartIncludingSuspend(double * aResult)1502 TelemetryImpl::MsSinceProcessStartIncludingSuspend(double* aResult) {
1503   return Telemetry::Common::MsSinceProcessStartIncludingSuspend(aResult);
1504 }
1505 
1506 NS_IMETHODIMP
MsSinceProcessStartExcludingSuspend(double * aResult)1507 TelemetryImpl::MsSinceProcessStartExcludingSuspend(double* aResult) {
1508   return Telemetry::Common::MsSinceProcessStartExcludingSuspend(aResult);
1509 }
1510 
1511 NS_IMETHODIMP
MsSystemNow(double * aResult)1512 TelemetryImpl::MsSystemNow(double* aResult) {
1513 #if defined(XP_UNIX) && !defined(XP_DARWIN)
1514   timespec ts;
1515   clock_gettime(CLOCK_REALTIME, &ts);
1516   *aResult = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
1517 #else
1518   using namespace std::chrono;
1519   milliseconds ms =
1520       duration_cast<milliseconds>(system_clock::now().time_since_epoch());
1521   *aResult = static_cast<double>(ms.count());
1522 #endif  // XP_UNIX && !XP_DARWIN
1523 
1524   return NS_OK;
1525 }
1526 
1527 // Telemetry Scalars IDL Implementation
1528 
1529 NS_IMETHODIMP
ScalarAdd(const nsACString & aName,JS::HandleValue aVal,JSContext * aCx)1530 TelemetryImpl::ScalarAdd(const nsACString& aName, JS::HandleValue aVal,
1531                          JSContext* aCx) {
1532   return TelemetryScalar::Add(aName, aVal, aCx);
1533 }
1534 
1535 NS_IMETHODIMP
ScalarSet(const nsACString & aName,JS::HandleValue aVal,JSContext * aCx)1536 TelemetryImpl::ScalarSet(const nsACString& aName, JS::HandleValue aVal,
1537                          JSContext* aCx) {
1538   return TelemetryScalar::Set(aName, aVal, aCx);
1539 }
1540 
1541 NS_IMETHODIMP
ScalarSetMaximum(const nsACString & aName,JS::HandleValue aVal,JSContext * aCx)1542 TelemetryImpl::ScalarSetMaximum(const nsACString& aName, JS::HandleValue aVal,
1543                                 JSContext* aCx) {
1544   return TelemetryScalar::SetMaximum(aName, aVal, aCx);
1545 }
1546 
1547 NS_IMETHODIMP
KeyedScalarAdd(const nsACString & aName,const nsAString & aKey,JS::HandleValue aVal,JSContext * aCx)1548 TelemetryImpl::KeyedScalarAdd(const nsACString& aName, const nsAString& aKey,
1549                               JS::HandleValue aVal, JSContext* aCx) {
1550   return TelemetryScalar::Add(aName, aKey, aVal, aCx);
1551 }
1552 
1553 NS_IMETHODIMP
KeyedScalarSet(const nsACString & aName,const nsAString & aKey,JS::HandleValue aVal,JSContext * aCx)1554 TelemetryImpl::KeyedScalarSet(const nsACString& aName, const nsAString& aKey,
1555                               JS::HandleValue aVal, JSContext* aCx) {
1556   return TelemetryScalar::Set(aName, aKey, aVal, aCx);
1557 }
1558 
1559 NS_IMETHODIMP
KeyedScalarSetMaximum(const nsACString & aName,const nsAString & aKey,JS::HandleValue aVal,JSContext * aCx)1560 TelemetryImpl::KeyedScalarSetMaximum(const nsACString& aName,
1561                                      const nsAString& aKey,
1562                                      JS::HandleValue aVal, JSContext* aCx) {
1563   return TelemetryScalar::SetMaximum(aName, aKey, aVal, aCx);
1564 }
1565 
1566 NS_IMETHODIMP
RegisterScalars(const nsACString & aCategoryName,JS::Handle<JS::Value> aScalarData,JSContext * cx)1567 TelemetryImpl::RegisterScalars(const nsACString& aCategoryName,
1568                                JS::Handle<JS::Value> aScalarData,
1569                                JSContext* cx) {
1570   return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, false,
1571                                           cx);
1572 }
1573 
1574 NS_IMETHODIMP
RegisterBuiltinScalars(const nsACString & aCategoryName,JS::Handle<JS::Value> aScalarData,JSContext * cx)1575 TelemetryImpl::RegisterBuiltinScalars(const nsACString& aCategoryName,
1576                                       JS::Handle<JS::Value> aScalarData,
1577                                       JSContext* cx) {
1578   return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, true, cx);
1579 }
1580 
1581 NS_IMETHODIMP
ClearScalars()1582 TelemetryImpl::ClearScalars() {
1583   TelemetryScalar::ClearScalars();
1584   return NS_OK;
1585 }
1586 
1587 // Telemetry Event IDL implementation.
1588 
1589 NS_IMETHODIMP
RecordEvent(const nsACString & aCategory,const nsACString & aMethod,const nsACString & aObject,JS::HandleValue aValue,JS::HandleValue aExtra,JSContext * aCx,uint8_t optional_argc)1590 TelemetryImpl::RecordEvent(const nsACString& aCategory,
1591                            const nsACString& aMethod, const nsACString& aObject,
1592                            JS::HandleValue aValue, JS::HandleValue aExtra,
1593                            JSContext* aCx, uint8_t optional_argc) {
1594   return TelemetryEvent::RecordEvent(aCategory, aMethod, aObject, aValue,
1595                                      aExtra, aCx, optional_argc);
1596 }
1597 
1598 NS_IMETHODIMP
SnapshotEvents(uint32_t aDataset,bool aClear,uint32_t aEventLimit,JSContext * aCx,uint8_t optional_argc,JS::MutableHandleValue aResult)1599 TelemetryImpl::SnapshotEvents(uint32_t aDataset, bool aClear,
1600                               uint32_t aEventLimit, JSContext* aCx,
1601                               uint8_t optional_argc,
1602                               JS::MutableHandleValue aResult) {
1603   return TelemetryEvent::CreateSnapshots(aDataset, aClear, aEventLimit, aCx,
1604                                          optional_argc, aResult);
1605 }
1606 
1607 NS_IMETHODIMP
RegisterEvents(const nsACString & aCategory,JS::Handle<JS::Value> aEventData,JSContext * cx)1608 TelemetryImpl::RegisterEvents(const nsACString& aCategory,
1609                               JS::Handle<JS::Value> aEventData, JSContext* cx) {
1610   return TelemetryEvent::RegisterEvents(aCategory, aEventData, false, cx);
1611 }
1612 
1613 NS_IMETHODIMP
RegisterBuiltinEvents(const nsACString & aCategory,JS::Handle<JS::Value> aEventData,JSContext * cx)1614 TelemetryImpl::RegisterBuiltinEvents(const nsACString& aCategory,
1615                                      JS::Handle<JS::Value> aEventData,
1616                                      JSContext* cx) {
1617   return TelemetryEvent::RegisterEvents(aCategory, aEventData, true, cx);
1618 }
1619 
1620 NS_IMETHODIMP
ClearEvents()1621 TelemetryImpl::ClearEvents() {
1622   TelemetryEvent::ClearEvents();
1623   return NS_OK;
1624 }
1625 
1626 NS_IMETHODIMP
SetEventRecordingEnabled(const nsACString & aCategory,bool aEnabled)1627 TelemetryImpl::SetEventRecordingEnabled(const nsACString& aCategory,
1628                                         bool aEnabled) {
1629   TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
1630   return NS_OK;
1631 }
1632 
1633 NS_IMETHODIMP
GetOriginSnapshot(bool aClear,JSContext * aCx,JS::MutableHandleValue aResult)1634 TelemetryImpl::GetOriginSnapshot(bool aClear, JSContext* aCx,
1635                                  JS::MutableHandleValue aResult) {
1636   return TelemetryOrigin::GetOriginSnapshot(aClear, aCx, aResult);
1637 }
1638 
1639 NS_IMETHODIMP
GetEncodedOriginSnapshot(bool aClear,JSContext * aCx,Promise ** aResult)1640 TelemetryImpl::GetEncodedOriginSnapshot(bool aClear, JSContext* aCx,
1641                                         Promise** aResult) {
1642   if (!XRE_IsParentProcess()) {
1643     return NS_ERROR_FAILURE;
1644   }
1645   NS_ENSURE_ARG_POINTER(aResult);
1646   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
1647   if (NS_WARN_IF(!global)) {
1648     return NS_ERROR_FAILURE;
1649   }
1650   ErrorResult erv;
1651   RefPtr<Promise> promise = Promise::Create(global, erv);
1652   if (NS_WARN_IF(erv.Failed())) {
1653     return erv.StealNSResult();
1654   }
1655 
1656   // TODO: Put this all on a Worker Thread
1657 
1658   JS::RootedValue snapshot(aCx);
1659   nsresult rv;
1660   rv = TelemetryOrigin::GetEncodedOriginSnapshot(aClear, aCx, &snapshot);
1661   if (NS_WARN_IF(NS_FAILED(rv))) {
1662     return rv;
1663   }
1664   promise->MaybeResolve(snapshot);
1665   promise.forget(aResult);
1666   return NS_OK;
1667 }
1668 
1669 NS_IMETHODIMP
ClearOrigins()1670 TelemetryImpl::ClearOrigins() {
1671   TelemetryOrigin::ClearOrigins();
1672   return NS_OK;
1673 }
1674 
1675 NS_IMETHODIMP
FlushBatchedChildTelemetry()1676 TelemetryImpl::FlushBatchedChildTelemetry() {
1677   TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
1678   return NS_OK;
1679 }
1680 
1681 NS_IMETHODIMP
EarlyInit()1682 TelemetryImpl::EarlyInit() {
1683   Unused << MemoryTelemetry::Get();
1684 
1685   return NS_OK;
1686 }
1687 
1688 NS_IMETHODIMP
DelayedInit()1689 TelemetryImpl::DelayedInit() {
1690   MemoryTelemetry::Get().DelayedInit();
1691   return NS_OK;
1692 }
1693 
1694 NS_IMETHODIMP
Shutdown()1695 TelemetryImpl::Shutdown() {
1696   MemoryTelemetry::Get().Shutdown();
1697   return NS_OK;
1698 }
1699 
1700 NS_IMETHODIMP
GatherMemory(JSContext * aCx,Promise ** aResult)1701 TelemetryImpl::GatherMemory(JSContext* aCx, Promise** aResult) {
1702   ErrorResult rv;
1703   RefPtr<Promise> promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
1704   if (rv.Failed()) {
1705     return rv.StealNSResult();
1706   }
1707 
1708   MemoryTelemetry::Get().GatherReports(
1709       [promise]() { promise->MaybeResolve(JS::UndefinedHandleValue); });
1710 
1711   promise.forget(aResult);
1712   return NS_OK;
1713 }
1714 
1715 NS_IMETHODIMP
GetAllStores(JSContext * aCx,JS::MutableHandleValue aResult)1716 TelemetryImpl::GetAllStores(JSContext* aCx, JS::MutableHandleValue aResult) {
1717   StringHashSet stores;
1718   nsresult rv;
1719 
1720   rv = TelemetryHistogram::GetAllStores(stores);
1721   if (NS_FAILED(rv)) {
1722     return rv;
1723   }
1724   rv = TelemetryScalar::GetAllStores(stores);
1725   if (NS_FAILED(rv)) {
1726     return rv;
1727   }
1728 
1729   JS::RootedVector<JS::Value> allStores(aCx);
1730   if (!allStores.reserve(stores.Count())) {
1731     return NS_ERROR_FAILURE;
1732   }
1733 
1734   for (const auto& value : stores) {
1735     JS::RootedValue store(aCx);
1736 
1737     store.setString(ToJSString(aCx, value));
1738     if (!allStores.append(store)) {
1739       return NS_ERROR_FAILURE;
1740     }
1741   }
1742 
1743   JS::Rooted<JSObject*> rarray(aCx, JS::NewArrayObject(aCx, allStores));
1744   if (rarray == nullptr) {
1745     return NS_ERROR_FAILURE;
1746   }
1747   aResult.setObject(*rarray);
1748 
1749   return NS_OK;
1750 }
1751 
1752 }  // namespace
1753 
1754 ////////////////////////////////////////////////////////////////////////
1755 ////////////////////////////////////////////////////////////////////////
1756 //
1757 // EXTERNALLY VISIBLE FUNCTIONS in no name space
1758 // These are NOT listed in Telemetry.h
1759 
1760 /**
1761  * The XRE_TelemetryAdd function is to be used by embedding applications
1762  * that can't use mozilla::Telemetry::Accumulate() directly.
1763  */
XRE_TelemetryAccumulate(int aID,uint32_t aSample)1764 void XRE_TelemetryAccumulate(int aID, uint32_t aSample) {
1765   mozilla::Telemetry::Accumulate((mozilla::Telemetry::HistogramID)aID, aSample);
1766 }
1767 
1768 ////////////////////////////////////////////////////////////////////////
1769 ////////////////////////////////////////////////////////////////////////
1770 //
1771 // EXTERNALLY VISIBLE FUNCTIONS in mozilla::
1772 // These are NOT listed in Telemetry.h
1773 
1774 namespace mozilla {
1775 
RecordShutdownStartTimeStamp()1776 void RecordShutdownStartTimeStamp() {
1777 #ifdef DEBUG
1778   // FIXME: this function should only be called once, since it should be called
1779   // at the earliest point we *know* we are shutting down. Unfortunately
1780   // this assert has been firing. Given that if we are called multiple times
1781   // we just keep the last timestamp, the assert is commented for now.
1782   static bool recorded = false;
1783   //  MOZ_ASSERT(!recorded);
1784   (void)
1785       recorded;  // Silence unused-var warnings (remove when assert re-enabled)
1786   recorded = true;
1787 #endif
1788 
1789   if (!Telemetry::CanRecordExtended()) return;
1790 
1791   gRecordedShutdownStartTime = TimeStamp::Now();
1792 
1793   GetShutdownTimeFileName();
1794 }
1795 
RecordShutdownEndTimeStamp()1796 void RecordShutdownEndTimeStamp() {
1797   if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName)
1798     return;
1799 
1800   PathString name(gRecordedShutdownTimeFileName);
1801   free(const_cast<PathChar*>(gRecordedShutdownTimeFileName));
1802   gRecordedShutdownTimeFileName = nullptr;
1803   gAlreadyFreedShutdownTimeFileName = true;
1804 
1805   if (gRecordedShutdownStartTime.IsNull()) {
1806     // If |CanRecordExtended()| is true before |AsyncFetchTelemetryData| is
1807     // called and then disabled before shutdown, |RecordShutdownStartTimeStamp|
1808     // will bail out and we will end up with a null |gRecordedShutdownStartTime|
1809     // here. This can happen during tests.
1810     return;
1811   }
1812 
1813   nsTAutoString<PathChar> tmpName(name);
1814   tmpName.AppendLiteral(".tmp");
1815   RefPtr<nsLocalFile> tmpFile = new nsLocalFile(tmpName);
1816   FILE* f;
1817   if (NS_FAILED(tmpFile->OpenANSIFileDesc("w", &f)) || !f) return;
1818   // On a normal release build this should be called just before
1819   // calling _exit, but on a debug build or when the user forces a full
1820   // shutdown this is called as late as possible, so we have to
1821   // allow this write as write poisoning will be enabled.
1822   MozillaRegisterDebugFILE(f);
1823 
1824   TimeStamp now = TimeStamp::Now();
1825   MOZ_ASSERT(now >= gRecordedShutdownStartTime);
1826   TimeDuration diff = now - gRecordedShutdownStartTime;
1827   uint32_t diff2 = diff.ToMilliseconds();
1828   int written = fprintf(f, "%d\n", diff2);
1829   MozillaUnRegisterDebugFILE(f);
1830   int rv = fclose(f);
1831   if (written < 0 || rv != 0) {
1832     tmpFile->Remove(false);
1833     return;
1834   }
1835   RefPtr<nsLocalFile> file = new nsLocalFile(name);
1836   nsAutoString leafName;
1837   file->GetLeafName(leafName);
1838   tmpFile->RenameTo(nullptr, leafName);
1839 }
1840 
1841 }  // namespace mozilla
1842 
1843 ////////////////////////////////////////////////////////////////////////
1844 ////////////////////////////////////////////////////////////////////////
1845 //
1846 // EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry::
1847 // These are listed in Telemetry.h
1848 
1849 namespace mozilla::Telemetry {
1850 
1851 // The external API for controlling recording state
SetHistogramRecordingEnabled(HistogramID aID,bool aEnabled)1852 void SetHistogramRecordingEnabled(HistogramID aID, bool aEnabled) {
1853   TelemetryHistogram::SetHistogramRecordingEnabled(aID, aEnabled);
1854 }
1855 
Accumulate(HistogramID aHistogram,uint32_t aSample)1856 void Accumulate(HistogramID aHistogram, uint32_t aSample) {
1857   TelemetryHistogram::Accumulate(aHistogram, aSample);
1858 }
1859 
Accumulate(HistogramID aHistogram,const nsTArray<uint32_t> & aSamples)1860 void Accumulate(HistogramID aHistogram, const nsTArray<uint32_t>& aSamples) {
1861   TelemetryHistogram::Accumulate(aHistogram, aSamples);
1862 }
1863 
Accumulate(HistogramID aID,const nsCString & aKey,uint32_t aSample)1864 void Accumulate(HistogramID aID, const nsCString& aKey, uint32_t aSample) {
1865   TelemetryHistogram::Accumulate(aID, aKey, aSample);
1866 }
1867 
Accumulate(HistogramID aID,const nsCString & aKey,const nsTArray<uint32_t> & aSamples)1868 void Accumulate(HistogramID aID, const nsCString& aKey,
1869                 const nsTArray<uint32_t>& aSamples) {
1870   TelemetryHistogram::Accumulate(aID, aKey, aSamples);
1871 }
1872 
Accumulate(const char * name,uint32_t sample)1873 void Accumulate(const char* name, uint32_t sample) {
1874   TelemetryHistogram::Accumulate(name, sample);
1875 }
1876 
Accumulate(const char * name,const nsCString & key,uint32_t sample)1877 void Accumulate(const char* name, const nsCString& key, uint32_t sample) {
1878   TelemetryHistogram::Accumulate(name, key, sample);
1879 }
1880 
AccumulateCategorical(HistogramID id,const nsCString & label)1881 void AccumulateCategorical(HistogramID id, const nsCString& label) {
1882   TelemetryHistogram::AccumulateCategorical(id, label);
1883 }
1884 
AccumulateCategorical(HistogramID id,const nsTArray<nsCString> & labels)1885 void AccumulateCategorical(HistogramID id, const nsTArray<nsCString>& labels) {
1886   TelemetryHistogram::AccumulateCategorical(id, labels);
1887 }
1888 
AccumulateTimeDelta(HistogramID aHistogram,TimeStamp start,TimeStamp end)1889 void AccumulateTimeDelta(HistogramID aHistogram, TimeStamp start,
1890                          TimeStamp end) {
1891   if (start > end) {
1892     Accumulate(aHistogram, 0);
1893     return;
1894   }
1895   Accumulate(aHistogram, static_cast<uint32_t>((end - start).ToMilliseconds()));
1896 }
1897 
AccumulateTimeDelta(HistogramID aHistogram,const nsCString & key,TimeStamp start,TimeStamp end)1898 void AccumulateTimeDelta(HistogramID aHistogram, const nsCString& key,
1899                          TimeStamp start, TimeStamp end) {
1900   if (start > end) {
1901     Accumulate(aHistogram, key, 0);
1902     return;
1903   }
1904   Accumulate(aHistogram, key,
1905              static_cast<uint32_t>((end - start).ToMilliseconds()));
1906 }
GetHistogramName(HistogramID id)1907 const char* GetHistogramName(HistogramID id) {
1908   return TelemetryHistogram::GetHistogramName(id);
1909 }
1910 
CanRecordBase()1911 bool CanRecordBase() { return TelemetryImpl::CanRecordBase(); }
1912 
CanRecordExtended()1913 bool CanRecordExtended() { return TelemetryImpl::CanRecordExtended(); }
1914 
CanRecordReleaseData()1915 bool CanRecordReleaseData() { return TelemetryImpl::CanRecordReleaseData(); }
1916 
CanRecordPrereleaseData()1917 bool CanRecordPrereleaseData() {
1918   return TelemetryImpl::CanRecordPrereleaseData();
1919 }
1920 
RecordSlowSQLStatement(const nsACString & statement,const nsACString & dbName,uint32_t delay)1921 void RecordSlowSQLStatement(const nsACString& statement,
1922                             const nsACString& dbName, uint32_t delay) {
1923   TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
1924 }
1925 
Init()1926 void Init() {
1927   // Make the service manager hold a long-lived reference to the service
1928   nsCOMPtr<nsITelemetry> telemetryService =
1929       do_GetService("@mozilla.org/base/telemetry;1");
1930   MOZ_ASSERT(telemetryService);
1931 }
1932 
WriteFailedProfileLock(nsIFile * aProfileDir)1933 void WriteFailedProfileLock(nsIFile* aProfileDir) {
1934   nsCOMPtr<nsIFile> file;
1935   nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir);
1936   NS_ENSURE_SUCCESS_VOID(rv);
1937   int64_t fileSize = 0;
1938   rv = file->GetFileSize(&fileSize);
1939   // It's expected that the file might not exist yet
1940   if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
1941     return;
1942   }
1943   nsCOMPtr<nsIFileStream> fileStream;
1944   rv = NS_NewLocalFileStream(getter_AddRefs(fileStream), file,
1945                              PR_RDWR | PR_CREATE_FILE, 0640);
1946   NS_ENSURE_SUCCESS_VOID(rv);
1947   NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize);
1948   unsigned int failedLockCount = 0;
1949   if (fileSize > 0) {
1950     nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(fileStream);
1951     NS_ENSURE_TRUE_VOID(inStream);
1952     if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) {
1953       failedLockCount = 0;
1954     }
1955   }
1956   ++failedLockCount;
1957   nsAutoCString bufStr;
1958   bufStr.AppendInt(static_cast<int>(failedLockCount));
1959   nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(fileStream);
1960   NS_ENSURE_TRUE_VOID(seekStream);
1961   // If we read in an existing failed lock count, we need to reset the file ptr
1962   if (fileSize > 0) {
1963     rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
1964     NS_ENSURE_SUCCESS_VOID(rv);
1965   }
1966   nsCOMPtr<nsIOutputStream> outStream = do_QueryInterface(fileStream);
1967   uint32_t bytesLeft = bufStr.Length();
1968   const char* bytes = bufStr.get();
1969   do {
1970     uint32_t written = 0;
1971     rv = outStream->Write(bytes, bytesLeft, &written);
1972     if (NS_FAILED(rv)) {
1973       break;
1974     }
1975     bytes += written;
1976     bytesLeft -= written;
1977   } while (bytesLeft > 0);
1978   seekStream->SetEOF();
1979 }
1980 
InitIOReporting(nsIFile * aXreDir)1981 void InitIOReporting(nsIFile* aXreDir) {
1982   // Never initialize twice
1983   if (sTelemetryIOObserver) {
1984     return;
1985   }
1986 
1987   sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir);
1988   IOInterposer::Register(IOInterposeObserver::OpAllWithStaging,
1989                          sTelemetryIOObserver);
1990 }
1991 
SetProfileDir(nsIFile * aProfD)1992 void SetProfileDir(nsIFile* aProfD) {
1993   if (!sTelemetryIOObserver || !aProfD) {
1994     return;
1995   }
1996   nsAutoString profDirPath;
1997   nsresult rv = aProfD->GetPath(profDirPath);
1998   if (NS_FAILED(rv)) {
1999     return;
2000   }
2001   sTelemetryIOObserver->AddPath(profDirPath, u"{profile}"_ns);
2002 }
2003 
2004 // Scalar API C++ Endpoints
2005 
ScalarAdd(mozilla::Telemetry::ScalarID aId,uint32_t aVal)2006 void ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aVal) {
2007   TelemetryScalar::Add(aId, aVal);
2008 }
2009 
ScalarSet(mozilla::Telemetry::ScalarID aId,uint32_t aVal)2010 void ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aVal) {
2011   TelemetryScalar::Set(aId, aVal);
2012 }
2013 
ScalarSet(mozilla::Telemetry::ScalarID aId,bool aVal)2014 void ScalarSet(mozilla::Telemetry::ScalarID aId, bool aVal) {
2015   TelemetryScalar::Set(aId, aVal);
2016 }
2017 
ScalarSet(mozilla::Telemetry::ScalarID aId,const nsAString & aVal)2018 void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aVal) {
2019   TelemetryScalar::Set(aId, aVal);
2020 }
2021 
ScalarSetMaximum(mozilla::Telemetry::ScalarID aId,uint32_t aVal)2022 void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aVal) {
2023   TelemetryScalar::SetMaximum(aId, aVal);
2024 }
2025 
ScalarAdd(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,uint32_t aVal)2026 void ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2027                uint32_t aVal) {
2028   TelemetryScalar::Add(aId, aKey, aVal);
2029 }
2030 
ScalarSet(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,uint32_t aVal)2031 void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2032                uint32_t aVal) {
2033   TelemetryScalar::Set(aId, aKey, aVal);
2034 }
2035 
ScalarSet(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,bool aVal)2036 void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2037                bool aVal) {
2038   TelemetryScalar::Set(aId, aKey, aVal);
2039 }
2040 
ScalarSetMaximum(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,uint32_t aVal)2041 void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2042                       uint32_t aVal) {
2043   TelemetryScalar::SetMaximum(aId, aKey, aVal);
2044 }
2045 
RecordEvent(mozilla::Telemetry::EventID aId,const mozilla::Maybe<nsCString> & aValue,const mozilla::Maybe<CopyableTArray<EventExtraEntry>> & aExtra)2046 void RecordEvent(
2047     mozilla::Telemetry::EventID aId, const mozilla::Maybe<nsCString>& aValue,
2048     const mozilla::Maybe<CopyableTArray<EventExtraEntry>>& aExtra) {
2049   TelemetryEvent::RecordEventNative(aId, aValue, aExtra);
2050 }
2051 
SetEventRecordingEnabled(const nsACString & aCategory,bool aEnabled)2052 void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled) {
2053   TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
2054 }
2055 
RecordOrigin(mozilla::Telemetry::OriginMetricID aId,const nsACString & aOrigin)2056 void RecordOrigin(mozilla::Telemetry::OriginMetricID aId,
2057                   const nsACString& aOrigin) {
2058   TelemetryOrigin::RecordOrigin(aId, aOrigin);
2059 }
2060 
ShutdownTelemetry()2061 void ShutdownTelemetry() { TelemetryImpl::ShutdownTelemetry(); }
2062 
2063 }  // namespace mozilla::Telemetry
2064 
NS_IMPL_COMPONENT_FACTORY(nsITelemetry)2065 NS_IMPL_COMPONENT_FACTORY(nsITelemetry) {
2066   return TelemetryImpl::CreateTelemetryInstance().downcast<nsISupports>();
2067 }
2068