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