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