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