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/. */
12 #if defined(XP_UNIX) && !defined(XP_DARWIN)
17 #include "base/pickle.h"
18 #include "base/process_util.h"
19 #include "ipc/TelemetryIPCAccumulator.h"
21 #include "jsfriendapi.h"
22 #include "js/Array.h" // JS::NewArrayObject
24 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
25 #include "mozilla/dom/ToJSValue.h"
26 #include "mozilla/dom/Promise.h"
27 #include "mozilla/glean/TelemetryMetrics.h"
28 #include "mozilla/Atomics.h"
29 #include "mozilla/Attributes.h"
30 #include "mozilla/BackgroundHangMonitor.h"
31 #ifdef MOZ_BACKGROUNDTASKS
32 # include "mozilla/BackgroundTasks.h"
34 #include "mozilla/Components.h"
35 #include "mozilla/DataMutex.h"
36 #include "mozilla/DebugOnly.h"
37 #include "mozilla/FStream.h"
38 #include "mozilla/IOInterposer.h"
39 #include "mozilla/Likely.h"
40 #include "mozilla/MathAlgorithms.h"
41 #include "mozilla/MemoryReporting.h"
42 #include "mozilla/MemoryTelemetry.h"
43 #include "mozilla/ModuleUtils.h"
44 #include "mozilla/Mutex.h"
45 #include "mozilla/PoisonIOInterposer.h"
46 #include "mozilla/Preferences.h"
47 #include "mozilla/StartupTimeline.h"
48 #include "mozilla/StaticPtr.h"
49 #include "mozilla/Unused.h"
51 # include "mozilla/WinDllServices.h"
53 #include "nsAppDirectoryServiceDefs.h"
54 #include "nsBaseHashtable.h"
55 #include "nsClassHashtable.h"
56 #include "nsCOMArray.h"
58 #include "nsTHashMap.h"
59 #include "nsHashKeys.h"
60 #include "nsIDirectoryEnumerator.h"
61 #include "nsDirectoryServiceDefs.h"
62 #include "nsIFileStreams.h"
63 #include "nsIMemoryReporter.h"
64 #include "nsISeekableStream.h"
65 #include "nsITelemetry.h"
67 # include "other/UntrustedModules.h"
69 #include "nsJSUtils.h"
70 #include "nsNativeCharsetUtils.h"
72 #include "nsNetUtil.h"
73 #include "nsProxyRelease.h"
74 #include "nsReadableUtils.h"
76 #include "nsTHashtable.h"
77 #include "nsThreadUtils.h"
79 # include "nsUnicharUtils.h"
81 #include "nsVersionComparator.h"
82 #include "nsXPCOMCIDInternal.h"
83 #include "other/CombinedStacks.h"
84 #include "other/TelemetryIOInterposeObserver.h"
85 #include "TelemetryCommon.h"
86 #include "TelemetryEvent.h"
87 #include "TelemetryHistogram.h"
88 #include "TelemetryScalar.h"
89 #include "TelemetryUserInteraction.h"
93 using namespace mozilla
;
94 using mozilla::dom::AutoJSAPI
;
95 using mozilla::dom::Promise
;
96 using mozilla::Telemetry::CombinedStacks
;
97 using mozilla::Telemetry::EventExtraEntry
;
98 using mozilla::Telemetry::TelemetryIOInterposeObserver
;
99 using Telemetry::Common::AutoHashtable
;
100 using Telemetry::Common::GetCurrentProduct
;
101 using Telemetry::Common::StringHashSet
;
102 using Telemetry::Common::SupportedProduct
;
103 using Telemetry::Common::ToJSString
;
105 // This is not a member of TelemetryImpl because we want to record I/O during
107 StaticAutoPtr
<TelemetryIOInterposeObserver
> sTelemetryIOObserver
;
109 void ClearIOReporting() {
110 if (!sTelemetryIOObserver
) {
113 IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging
,
114 sTelemetryIOObserver
);
115 sTelemetryIOObserver
= nullptr;
118 class TelemetryImpl final
: public nsITelemetry
, public nsIMemoryReporter
{
119 NS_DECL_THREADSAFE_ISUPPORTS
121 NS_DECL_NSIMEMORYREPORTER
124 void InitMemoryReporter();
126 static already_AddRefed
<nsITelemetry
> CreateTelemetryInstance();
127 static void ShutdownTelemetry();
128 static void RecordSlowStatement(const nsACString
& sql
,
129 const nsACString
& dbName
, uint32_t delay
);
135 struct Stat mainThread
;
136 struct Stat otherThreads
;
138 typedef nsBaseHashtableET
<nsCStringHashKey
, StmtStats
> SlowSQLEntryType
;
140 static void RecordIceCandidates(const uint32_t iceCandidateBitmask
,
142 static bool CanRecordBase();
143 static bool CanRecordExtended();
144 static bool CanRecordReleaseData();
145 static bool CanRecordPrereleaseData();
151 static nsCString
SanitizeSQL(const nsACString
& sql
);
153 enum SanitizedState
{ Sanitized
, Unsanitized
};
155 static void StoreSlowSQL(const nsACString
& offender
, uint32_t delay
,
156 SanitizedState state
);
158 static bool ReflectMainThreadSQL(SlowSQLEntryType
* entry
, JSContext
* cx
,
159 JS::Handle
<JSObject
*> obj
);
160 static bool ReflectOtherThreadsSQL(SlowSQLEntryType
* entry
, JSContext
* cx
,
161 JS::Handle
<JSObject
*> obj
);
162 static bool ReflectSQL(const SlowSQLEntryType
* entry
, const Stat
* stat
,
163 JSContext
* cx
, JS::Handle
<JSObject
*> obj
);
165 bool AddSQLInfo(JSContext
* cx
, JS::Handle
<JSObject
*> rootObj
, bool mainThread
,
167 bool GetSQLStats(JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
,
168 bool includePrivateSql
);
170 void ReadLateWritesStacks(nsIFile
* aProfileDir
);
172 static StaticDataMutex
<TelemetryImpl
*> sTelemetry
;
173 AutoHashtable
<SlowSQLEntryType
> mPrivateSQL
;
174 AutoHashtable
<SlowSQLEntryType
> mSanitizedSQL
;
175 Mutex mHashMutex MOZ_UNANNOTATED
;
176 Atomic
<bool, SequentiallyConsistent
> mCanRecordBase
;
177 Atomic
<bool, SequentiallyConsistent
> mCanRecordExtended
;
180 mLateWritesStacks
; // This is collected out of the main thread.
181 bool mCachedTelemetryData
;
182 uint32_t mLastShutdownTime
;
183 uint32_t mFailedLockCount
;
184 nsCOMArray
<nsIFetchTelemetryDataCallback
> mCallbacks
;
185 friend class nsFetchTelemetryData
;
188 MOZ_RUNINIT StaticDataMutex
<TelemetryImpl
*> TelemetryImpl::sTelemetry(nullptr,
191 MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf
)
194 TelemetryImpl::CollectReports(nsIHandleReportCallback
* aHandleReport
,
195 nsISupports
* aData
, bool aAnonymize
) {
196 mozilla::MallocSizeOf aMallocSizeOf
= TelemetryMallocSizeOf
;
198 #define COLLECT_REPORT(name, size, desc) \
199 MOZ_COLLECT_REPORT(name, KIND_HEAP, UNITS_BYTES, size, desc)
201 COLLECT_REPORT("explicit/telemetry/impl", aMallocSizeOf(this),
202 "Memory used by the Telemetry core implemenation");
205 "explicit/telemetry/scalar/shallow",
206 TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf
),
207 "Memory used by the Telemetry Scalar implemenation");
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");
215 COLLECT_REPORT("explicit/telemetry/SanitizedSQL",
216 mSanitizedSQL
.SizeOfExcludingThis(aMallocSizeOf
),
217 "Memory used by the SanitizedSQL Telemetry");
220 if (sTelemetryIOObserver
) {
221 COLLECT_REPORT("explicit/telemetry/IOObserver",
222 sTelemetryIOObserver
->SizeOfIncludingThis(aMallocSizeOf
),
223 "Memory used by the Telemetry IO Observer");
226 COLLECT_REPORT("explicit/telemetry/LateWritesStacks",
227 mLateWritesStacks
.SizeOfExcludingThis(),
228 "Memory used by the Telemetry LateWrites Stack capturer");
230 COLLECT_REPORT("explicit/telemetry/Callbacks",
231 mCallbacks
.ShallowSizeOfExcludingThis(aMallocSizeOf
),
232 "Memory used by the Telemetry Callbacks array (shallow)");
235 "explicit/telemetry/histogram/data",
236 TelemetryHistogram::GetHistogramSizesOfIncludingThis(aMallocSizeOf
),
237 "Memory used by Telemetry Histogram data");
239 COLLECT_REPORT("explicit/telemetry/scalar/data",
240 TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf
),
241 "Memory used by Telemetry Scalar data");
243 COLLECT_REPORT("explicit/telemetry/event/data",
244 TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf
),
245 "Memory used by Telemetry Event data");
247 #undef COLLECT_REPORT
252 void InitHistogramRecordingEnabled() {
253 TelemetryHistogram::InitHistogramRecordingEnabled();
256 using PathChar
= filesystem::Path::value_type
;
257 using PathCharPtr
= const PathChar
*;
259 static uint32_t ReadLastShutdownDuration(PathCharPtr filename
) {
260 nsCOMPtr
<nsIFile
> file
;
261 if (NS_FAILED(NS_NewPathStringLocalFile(DependentPathString(filename
),
262 getter_AddRefs(file
)))) {
266 if (NS_FAILED(file
->OpenANSIFileDesc("r", &f
)) || !f
) {
271 int r
= fscanf(f
, "%d\n", &shutdownTime
);
280 const int32_t kMaxFailedProfileLockFileSize
= 10;
282 bool GetFailedLockCount(nsIInputStream
* inStream
, uint32_t aCount
,
283 unsigned int& result
) {
284 nsAutoCString bufStr
;
286 rv
= NS_ReadInputStreamToString(inStream
, bufStr
, aCount
);
287 NS_ENSURE_SUCCESS(rv
, false);
288 result
= bufStr
.ToInteger(&rv
);
289 return NS_SUCCEEDED(rv
) && result
> 0;
292 nsresult
GetFailedProfileLockFile(nsIFile
** aFile
, nsIFile
* aProfileDir
) {
293 NS_ENSURE_ARG_POINTER(aProfileDir
);
295 nsresult rv
= aProfileDir
->Clone(aFile
);
296 NS_ENSURE_SUCCESS(rv
, rv
);
298 (*aFile
)->AppendNative("Telemetry.FailedProfileLocks.txt"_ns
);
302 class nsFetchTelemetryData
: public Runnable
{
304 nsFetchTelemetryData(PathCharPtr aShutdownTimeFilename
,
305 nsIFile
* aFailedProfileLockFile
, nsIFile
* aProfileDir
)
306 : mozilla::Runnable("nsFetchTelemetryData"),
307 mShutdownTimeFilename(aShutdownTimeFilename
),
308 mFailedProfileLockFile(aFailedProfileLockFile
),
309 mProfileDir(aProfileDir
) {}
312 PathCharPtr mShutdownTimeFilename
;
313 nsCOMPtr
<nsIFile
> mFailedProfileLockFile
;
314 nsCOMPtr
<nsIFile
> mProfileDir
;
318 auto lock
= TelemetryImpl::sTelemetry
.Lock();
319 auto telemetry
= lock
.ref();
320 telemetry
->mCachedTelemetryData
= true;
321 for (unsigned int i
= 0, n
= telemetry
->mCallbacks
.Count(); i
< n
; ++i
) {
322 telemetry
->mCallbacks
[i
]->Complete();
324 telemetry
->mCallbacks
.Clear();
327 NS_IMETHOD
Run() override
{
328 uint32_t failedLockCount
= 0;
329 uint32_t lastShutdownDuration
= 0;
330 LoadFailedLockCount(failedLockCount
);
331 lastShutdownDuration
= ReadLastShutdownDuration(mShutdownTimeFilename
);
333 auto lock
= TelemetryImpl::sTelemetry
.Lock();
334 auto telemetry
= lock
.ref();
335 telemetry
->mFailedLockCount
= failedLockCount
;
336 telemetry
->mLastShutdownTime
= lastShutdownDuration
;
337 telemetry
->ReadLateWritesStacks(mProfileDir
);
340 glean::browser_timings::last_shutdown
.Set(lastShutdownDuration
);
342 nsCOMPtr
<nsIRunnable
> e
=
343 NewRunnableMethod("nsFetchTelemetryData::MainThread", this,
344 &nsFetchTelemetryData::MainThread
);
346 NS_DispatchToMainThread(e
);
351 nsresult
LoadFailedLockCount(uint32_t& failedLockCount
) {
353 int64_t fileSize
= 0;
354 nsresult rv
= mFailedProfileLockFile
->GetFileSize(&fileSize
);
358 NS_ENSURE_TRUE(fileSize
<= kMaxFailedProfileLockFileSize
,
359 NS_ERROR_UNEXPECTED
);
360 nsCOMPtr
<nsIInputStream
> inStream
;
361 rv
= NS_NewLocalFileInputStream(getter_AddRefs(inStream
),
362 mFailedProfileLockFile
, PR_RDONLY
);
363 NS_ENSURE_SUCCESS(rv
, rv
);
364 NS_ENSURE_TRUE(GetFailedLockCount(inStream
, fileSize
, failedLockCount
),
365 NS_ERROR_UNEXPECTED
);
368 mFailedProfileLockFile
->Remove(false);
373 static TimeStamp gRecordedShutdownStartTime
;
374 static bool gAlreadyFreedShutdownTimeFileName
= false;
375 static PathCharPtr gRecordedShutdownTimeFileName
= nullptr;
377 static PathCharPtr
GetShutdownTimeFileName() {
378 if (gAlreadyFreedShutdownTimeFileName
) {
382 if (!gRecordedShutdownTimeFileName
) {
383 nsCOMPtr
<nsIFile
> mozFile
;
384 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(mozFile
));
385 if (!mozFile
) return nullptr;
387 mozFile
->AppendNative("Telemetry.ShutdownTime.txt"_ns
);
389 gRecordedShutdownTimeFileName
= NS_xstrdup(mozFile
->NativePath().get());
392 return gRecordedShutdownTimeFileName
;
396 TelemetryImpl::GetLastShutdownDuration(uint32_t* aResult
) {
397 // The user must call AsyncFetchTelemetryData first. We return zero instead of
398 // reporting a failure so that the rest of telemetry can uniformly handle
399 // the read not being available yet.
400 if (!mCachedTelemetryData
) {
405 *aResult
= mLastShutdownTime
;
410 TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult
) {
411 // The user must call AsyncFetchTelemetryData first. We return zero instead of
412 // reporting a failure so that the rest of telemetry can uniformly handle
413 // the read not being available yet.
414 if (!mCachedTelemetryData
) {
419 *aResult
= mFailedLockCount
;
424 TelemetryImpl::AsyncFetchTelemetryData(
425 nsIFetchTelemetryDataCallback
* aCallback
) {
426 // We have finished reading the data already, just call the callback.
427 if (mCachedTelemetryData
) {
428 aCallback
->Complete();
432 // We already have a read request running, just remember the callback.
433 if (mCallbacks
.Count() != 0) {
434 mCallbacks
.AppendObject(aCallback
);
438 // We make this check so that GetShutdownTimeFileName() doesn't get
439 // called; calling that function without telemetry enabled violates
440 // assumptions that the write-the-shutdown-timestamp machinery makes.
441 if (!Telemetry::CanRecordExtended()) {
442 mCachedTelemetryData
= true;
443 aCallback
->Complete();
447 // Send the read to a background thread provided by the stream transport
448 // service to avoid a read in the main thread.
449 nsCOMPtr
<nsIEventTarget
> targetThread
=
450 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
452 mCachedTelemetryData
= true;
453 aCallback
->Complete();
457 // We have to get the filename from the main thread.
458 PathCharPtr shutdownTimeFilename
= GetShutdownTimeFileName();
459 if (!shutdownTimeFilename
) {
460 mCachedTelemetryData
= true;
461 aCallback
->Complete();
465 nsCOMPtr
<nsIFile
> profileDir
;
466 nsresult rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
467 getter_AddRefs(profileDir
));
469 mCachedTelemetryData
= true;
470 aCallback
->Complete();
474 nsCOMPtr
<nsIFile
> failedProfileLockFile
;
475 rv
= GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile
),
478 mCachedTelemetryData
= true;
479 aCallback
->Complete();
483 mCallbacks
.AppendObject(aCallback
);
485 nsCOMPtr
<nsIRunnable
> event
= new nsFetchTelemetryData(
486 shutdownTimeFilename
, failedProfileLockFile
, profileDir
);
488 targetThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
492 TelemetryImpl::TelemetryImpl()
493 : mHashMutex("Telemetry::mHashMutex"),
494 mCanRecordBase(false),
495 mCanRecordExtended(false),
496 mCachedTelemetryData(false),
497 mLastShutdownTime(0),
498 mFailedLockCount(0) {
499 // We expect TelemetryHistogram::InitializeGlobalState() to have been
500 // called before we get to this point.
501 MOZ_ASSERT(TelemetryHistogram::GlobalStateHasBeenInitialized());
504 TelemetryImpl::~TelemetryImpl() {
505 UnregisterWeakMemoryReporter(this);
507 // This is still racey as access to these collections is guarded using
508 // sTelemetry. We will fix this in bug 1367344.
509 MutexAutoLock
hashLock(mHashMutex
);
512 void TelemetryImpl::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
514 bool TelemetryImpl::ReflectSQL(const SlowSQLEntryType
* entry
, const Stat
* stat
,
515 JSContext
* cx
, JS::Handle
<JSObject
*> obj
) {
516 if (stat
->hitCount
== 0) return true;
518 const nsACString
& sql
= entry
->GetKey();
520 JS::Rooted
<JSObject
*> arrayObj(cx
, JS::NewArrayObject(cx
, 0));
525 JS_DefineElement(cx
, arrayObj
, 0, stat
->hitCount
, JSPROP_ENUMERATE
) &&
526 JS_DefineElement(cx
, arrayObj
, 1, stat
->totalTime
, JSPROP_ENUMERATE
) &&
527 JS_DefineProperty(cx
, obj
, sql
.BeginReading(), arrayObj
,
531 bool TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType
* entry
, JSContext
* cx
,
532 JS::Handle
<JSObject
*> obj
) {
533 return ReflectSQL(entry
, &entry
->GetModifiableData()->mainThread
, cx
, obj
);
536 bool TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType
* entry
,
538 JS::Handle
<JSObject
*> obj
) {
539 return ReflectSQL(entry
, &entry
->GetModifiableData()->otherThreads
, cx
, obj
);
542 bool TelemetryImpl::AddSQLInfo(JSContext
* cx
, JS::Handle
<JSObject
*> rootObj
,
543 bool mainThread
, bool privateSQL
) {
544 JS::Rooted
<JSObject
*> statsObj(cx
, JS_NewPlainObject(cx
));
545 if (!statsObj
) return false;
547 AutoHashtable
<SlowSQLEntryType
>& sqlMap
=
548 (privateSQL
? mPrivateSQL
: mSanitizedSQL
);
549 AutoHashtable
<SlowSQLEntryType
>::ReflectEntryFunc reflectFunction
=
550 (mainThread
? ReflectMainThreadSQL
: ReflectOtherThreadsSQL
);
551 if (!sqlMap
.ReflectIntoJS(reflectFunction
, cx
, statsObj
)) {
555 return JS_DefineProperty(cx
, rootObj
,
556 mainThread
? "mainThread" : "otherThreads", statsObj
,
561 TelemetryImpl::GetSnapshotForHistograms(const nsACString
& aStoreName
,
562 bool aClearStore
, bool aFilterTest
,
564 JS::MutableHandle
<JS::Value
> aResult
) {
565 constexpr auto defaultStore
= "main"_ns
;
566 unsigned int dataset
= mCanRecordExtended
567 ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
568 : nsITelemetry::DATASET_ALL_CHANNELS
;
569 return TelemetryHistogram::CreateHistogramSnapshots(
570 aCx
, aResult
, aStoreName
.IsVoid() ? defaultStore
: aStoreName
, dataset
,
571 aClearStore
, aFilterTest
);
575 TelemetryImpl::GetSnapshotForKeyedHistograms(
576 const nsACString
& aStoreName
, bool aClearStore
, bool aFilterTest
,
577 JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
) {
578 constexpr auto defaultStore
= "main"_ns
;
579 unsigned int dataset
= mCanRecordExtended
580 ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
581 : nsITelemetry::DATASET_ALL_CHANNELS
;
582 return TelemetryHistogram::GetKeyedHistogramSnapshots(
583 aCx
, aResult
, aStoreName
.IsVoid() ? defaultStore
: aStoreName
, dataset
,
584 aClearStore
, aFilterTest
);
588 TelemetryImpl::GetCategoricalLabels(JSContext
* aCx
,
589 JS::MutableHandle
<JS::Value
> aResult
) {
590 return TelemetryHistogram::GetCategoricalHistogramLabels(aCx
, aResult
);
594 TelemetryImpl::GetSnapshotForScalars(const nsACString
& aStoreName
,
595 bool aClearStore
, bool aFilterTest
,
597 JS::MutableHandle
<JS::Value
> aResult
) {
598 constexpr auto defaultStore
= "main"_ns
;
599 unsigned int dataset
= mCanRecordExtended
600 ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
601 : nsITelemetry::DATASET_ALL_CHANNELS
;
602 return TelemetryScalar::CreateSnapshots(
603 dataset
, aClearStore
, aCx
, 1, aResult
, aFilterTest
,
604 aStoreName
.IsVoid() ? defaultStore
: aStoreName
);
608 TelemetryImpl::GetSnapshotForKeyedScalars(
609 const nsACString
& aStoreName
, bool aClearStore
, bool aFilterTest
,
610 JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
) {
611 constexpr auto defaultStore
= "main"_ns
;
612 unsigned int dataset
= mCanRecordExtended
613 ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
614 : nsITelemetry::DATASET_ALL_CHANNELS
;
615 return TelemetryScalar::CreateKeyedSnapshots(
616 dataset
, aClearStore
, aCx
, 1, aResult
, aFilterTest
,
617 aStoreName
.IsVoid() ? defaultStore
: aStoreName
);
620 bool TelemetryImpl::GetSQLStats(JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
,
621 bool includePrivateSql
) {
622 JS::Rooted
<JSObject
*> root_obj(cx
, JS_NewPlainObject(cx
));
623 if (!root_obj
) return false;
624 ret
.setObject(*root_obj
);
626 MutexAutoLock
hashMutex(mHashMutex
);
627 // Add info about slow SQL queries on the main thread
628 if (!AddSQLInfo(cx
, root_obj
, true, includePrivateSql
)) return false;
629 // Add info about slow SQL queries on other threads
630 if (!AddSQLInfo(cx
, root_obj
, false, includePrivateSql
)) return false;
636 TelemetryImpl::GetSlowSQL(JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
) {
637 if (GetSQLStats(cx
, ret
, false)) return NS_OK
;
638 return NS_ERROR_FAILURE
;
642 TelemetryImpl::GetDebugSlowSQL(JSContext
* cx
,
643 JS::MutableHandle
<JS::Value
> ret
) {
644 bool revealPrivateSql
=
645 Preferences::GetBool("toolkit.telemetry.debugSlowSql", false);
646 if (GetSQLStats(cx
, ret
, revealPrivateSql
)) return NS_OK
;
647 return NS_ERROR_FAILURE
;
651 TelemetryImpl::GetUntrustedModuleLoadEvents(uint32_t aFlags
, JSContext
* cx
,
652 Promise
** aPromise
) {
654 return Telemetry::GetUntrustedModuleLoadEvents(aFlags
, cx
, aPromise
);
656 return NS_ERROR_NOT_IMPLEMENTED
;
661 TelemetryImpl::GetAreUntrustedModuleLoadEventsReady(bool* ret
) {
663 *ret
= DllServices::Get()->IsReadyForBackgroundProcessing();
666 return NS_ERROR_NOT_IMPLEMENTED
;
670 #if defined(MOZ_GECKO_PROFILER)
671 class GetLoadedModulesResultRunnable final
: public Runnable
{
672 nsMainThreadPtrHandle
<Promise
> mPromise
;
673 SharedLibraryInfo mRawModules
;
674 nsCOMPtr
<nsIThread
> mWorkerThread
;
676 nsTHashMap
<nsStringHashKey
, nsString
> mCertSubjects
;
677 # endif // defined(XP_WIN)
680 GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle
<Promise
>& aPromise
,
681 const SharedLibraryInfo
& rawModules
)
682 : mozilla::Runnable("GetLoadedModulesResultRunnable"),
684 mRawModules(rawModules
),
685 mWorkerThread(do_GetCurrentThread()) {
686 MOZ_ASSERT(!NS_IsMainThread());
688 ObtainCertSubjects();
689 # endif // defined(XP_WIN)
694 MOZ_ASSERT(NS_IsMainThread());
696 mWorkerThread
->Shutdown();
699 if (NS_WARN_IF(!jsapi
.Init(mPromise
->GetGlobalObject()))) {
700 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
704 JSContext
* cx
= jsapi
.cx();
706 JS::Rooted
<JSObject
*> moduleArray(cx
, JS::NewArrayObject(cx
, 0));
708 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
712 for (unsigned int i
= 0, n
= mRawModules
.GetSize(); i
!= n
; i
++) {
713 const SharedLibrary
& info
= mRawModules
.GetEntry(i
);
715 JS::Rooted
<JSObject
*> moduleObj(cx
, JS_NewPlainObject(cx
));
717 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
722 JS::Rooted
<JSString
*> moduleName(
725 cx
, NS_ConvertUTF8toUTF16(info
.GetModuleName().c_str()).get()));
726 if (!moduleName
|| !JS_DefineProperty(cx
, moduleObj
, "name", moduleName
,
728 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
732 // Module debug name.
733 JS::Rooted
<JS::Value
> moduleDebugName(cx
);
735 if (!info
.GetDebugName().empty()) {
736 JS::Rooted
<JSString
*> str_moduleDebugName(
739 cx
, NS_ConvertUTF8toUTF16(info
.GetDebugName().c_str()).get()));
740 if (!str_moduleDebugName
) {
741 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
744 moduleDebugName
.setString(str_moduleDebugName
);
746 moduleDebugName
.setNull();
749 if (!JS_DefineProperty(cx
, moduleObj
, "debugName", moduleDebugName
,
751 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
755 // Module Breakpad identifier.
756 JS::Rooted
<JS::Value
> id(cx
);
758 if (!info
.GetBreakpadId().empty()) {
759 JS::Rooted
<JSString
*> str_id(
760 cx
, JS_NewStringCopyZ(cx
, info
.GetBreakpadId().c_str()));
762 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
765 id
.setString(str_id
);
770 if (!JS_DefineProperty(cx
, moduleObj
, "debugID", id
, JSPROP_ENUMERATE
)) {
771 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
776 JS::Rooted
<JS::Value
> version(cx
);
778 if (!info
.GetVersion().empty()) {
779 JS::Rooted
<JSString
*> v(
780 cx
, JS_NewStringCopyZ(cx
, info
.GetVersion().c_str()));
782 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
785 version
.setString(v
);
790 if (!JS_DefineProperty(cx
, moduleObj
, "version", version
,
792 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
798 if (auto subject
= mCertSubjects
.Lookup(
799 NS_ConvertUTF8toUTF16(info
.GetModulePath().c_str()))) {
800 JS::Rooted
<JSString
*> jsOrg(cx
, ToJSString(cx
, *subject
));
802 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
806 JS::Rooted
<JS::Value
> certSubject(cx
);
807 certSubject
.setString(jsOrg
);
809 if (!JS_DefineProperty(cx
, moduleObj
, "certSubject", certSubject
,
811 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
815 # endif // defined(XP_WIN)
817 if (!JS_DefineElement(cx
, moduleArray
, i
, moduleObj
, JSPROP_ENUMERATE
)) {
818 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
823 mPromise
->MaybeResolve(moduleArray
);
829 void ObtainCertSubjects() {
830 MOZ_ASSERT(!NS_IsMainThread());
832 // NB: Currently we cannot lower this down to the profiler layer due to
833 // differing startup dependencies between the profiler and DllServices.
834 RefPtr
<DllServices
> dllSvc(DllServices::Get());
836 for (unsigned int i
= 0, n
= mRawModules
.GetSize(); i
!= n
; i
++) {
837 const SharedLibrary
& info
= mRawModules
.GetEntry(i
);
839 auto orgName
= dllSvc
->GetBinaryOrgName(
840 NS_ConvertUTF8toUTF16(info
.GetModulePath().c_str()).get());
842 mCertSubjects
.InsertOrUpdate(
843 NS_ConvertUTF8toUTF16(info
.GetModulePath().c_str()),
844 nsDependentString(orgName
.get()));
848 # endif // defined(XP_WIN)
851 class GetLoadedModulesRunnable final
: public Runnable
{
852 nsMainThreadPtrHandle
<Promise
> mPromise
;
855 explicit GetLoadedModulesRunnable(
856 const nsMainThreadPtrHandle
<Promise
>& aPromise
)
857 : mozilla::Runnable("GetLoadedModulesRunnable"), mPromise(aPromise
) {}
861 nsCOMPtr
<nsIRunnable
> resultRunnable
= new GetLoadedModulesResultRunnable(
862 mPromise
, SharedLibraryInfo::GetInfoForSelf());
863 return NS_DispatchToMainThread(resultRunnable
);
866 #endif // MOZ_GECKO_PROFILER
869 TelemetryImpl::GetLoadedModules(JSContext
* cx
, Promise
** aPromise
) {
870 #if defined(MOZ_GECKO_PROFILER)
871 nsIGlobalObject
* global
= xpc::CurrentNativeGlobal(cx
);
872 if (NS_WARN_IF(!global
)) {
873 return NS_ERROR_FAILURE
;
877 RefPtr
<Promise
> promise
= Promise::Create(global
, result
);
878 if (NS_WARN_IF(result
.Failed())) {
879 return result
.StealNSResult();
882 nsCOMPtr
<nsIThread
> getModulesThread
;
884 NS_NewNamedThread("TelemetryModule", getter_AddRefs(getModulesThread
));
885 if (NS_WARN_IF(NS_FAILED(rv
))) {
886 promise
->MaybeReject(NS_ERROR_FAILURE
);
890 nsMainThreadPtrHandle
<Promise
> mainThreadPromise(
891 new nsMainThreadPtrHolder
<Promise
>(
892 "TelemetryImpl::GetLoadedModules::Promise", promise
));
893 nsCOMPtr
<nsIRunnable
> runnable
=
894 new GetLoadedModulesRunnable(mainThreadPromise
);
895 promise
.forget(aPromise
);
897 return getModulesThread
->Dispatch(runnable
, nsIEventTarget::DISPATCH_NORMAL
);
898 #else // MOZ_GECKO_PROFILER
899 return NS_ERROR_NOT_IMPLEMENTED
;
900 #endif // MOZ_GECKO_PROFILER
903 static bool IsValidBreakpadId(const std::string
& breakpadId
) {
904 if (breakpadId
.size() < 33) {
907 for (char c
: breakpadId
) {
908 if ((c
< '0' || c
> '9') && (c
< 'A' || c
> 'F')) {
915 // Read a stack from the given file name. In case of any error, aStack is
917 static void ReadStack(PathCharPtr aFileName
,
918 Telemetry::ProcessedStack
& aStack
) {
919 IFStream
file(aFileName
);
927 char newline
= file
.get();
928 if (file
.fail() || newline
!= '\n') {
932 Telemetry::ProcessedStack stack
;
933 for (size_t i
= 0; i
< numModules
; ++i
) {
934 std::string breakpadId
;
936 if (file
.fail() || !IsValidBreakpadId(breakpadId
)) {
940 char space
= file
.get();
941 if (file
.fail() || space
!= ' ') {
945 std::string moduleName
;
946 getline(file
, moduleName
);
947 if (file
.fail() || moduleName
[0] == ' ') {
951 Telemetry::ProcessedStack::Module module
= {
952 NS_ConvertUTF8toUTF16(moduleName
.c_str()),
953 nsCString(breakpadId
.c_str(), breakpadId
.size()),
955 stack
.AddModule(module
);
964 newline
= file
.get();
965 if (file
.fail() || newline
!= '\n') {
969 for (size_t i
= 0; i
< numFrames
; ++i
) {
973 file
>> std::hex
>> offset
>> std::dec
;
978 Telemetry::ProcessedStack::Frame frame
= {offset
, index
};
979 stack
.AddFrame(frame
);
985 void TelemetryImpl::ReadLateWritesStacks(nsIFile
* aProfileDir
) {
986 nsCOMPtr
<nsIDirectoryEnumerator
> files
;
987 if (NS_FAILED(aProfileDir
->GetDirectoryEntries(getter_AddRefs(files
)))) {
991 constexpr auto prefix
= u
"Telemetry.LateWriteFinal-"_ns
;
992 nsCOMPtr
<nsIFile
> file
;
993 while (NS_SUCCEEDED(files
->GetNextFile(getter_AddRefs(file
))) && file
) {
994 nsAutoString leafName
;
995 if (NS_FAILED(file
->GetLeafName(leafName
)) ||
996 !StringBeginsWith(leafName
, prefix
)) {
1000 Telemetry::ProcessedStack stack
;
1001 ReadStack(file
->NativePath().get(), stack
);
1002 if (stack
.GetStackSize() != 0) {
1003 mLateWritesStacks
.AddStack(stack
);
1005 // Delete the file so that we don't report it again on the next run.
1006 file
->Remove(false);
1011 TelemetryImpl::GetLateWrites(JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
) {
1012 // The user must call AsyncReadTelemetryData first. We return an empty list
1013 // instead of reporting a failure so that the rest of telemetry can uniformly
1014 // handle the read not being available yet.
1016 // FIXME: we allocate the js object again and again in the getter. We should
1017 // figure out a way to cache it. In order to do that we have to call
1018 // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl
1019 // constructor, but it is not clear how to get a JSContext in there.
1020 // Another option would be to call it in here when we first call
1021 // CreateJSStackObject, but we would still need to figure out where to call
1022 // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot
1023 // and just set the pointer to nullptr is the telemetry destructor?
1026 if (!mCachedTelemetryData
) {
1027 CombinedStacks empty
;
1028 report
= CreateJSStackObject(cx
, empty
);
1030 report
= CreateJSStackObject(cx
, mLateWritesStacks
);
1033 if (report
== nullptr) {
1034 return NS_ERROR_FAILURE
;
1037 ret
.setObject(*report
);
1042 TelemetryImpl::GetHistogramById(const nsACString
& name
, JSContext
* cx
,
1043 JS::MutableHandle
<JS::Value
> ret
) {
1044 return TelemetryHistogram::GetHistogramById(name
, cx
, ret
);
1048 TelemetryImpl::GetKeyedHistogramById(const nsACString
& name
, JSContext
* cx
,
1049 JS::MutableHandle
<JS::Value
> ret
) {
1050 return TelemetryHistogram::GetKeyedHistogramById(name
, cx
, ret
);
1054 * Indicates if Telemetry can record base data (FHR data). This is true if the
1055 * FHR data reporting service or self-support are enabled.
1057 * In the unlikely event that adding a new base probe is needed, please check
1058 * the data collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection
1059 * and talk to the Telemetry team.
1062 TelemetryImpl::GetCanRecordBase(bool* ret
) {
1063 *ret
= mCanRecordBase
;
1068 TelemetryImpl::SetCanRecordBase(bool canRecord
) {
1070 if (canRecord
!= mCanRecordBase
) {
1071 TelemetryHistogram::SetCanRecordBase(canRecord
);
1072 TelemetryScalar::SetCanRecordBase(canRecord
);
1073 TelemetryEvent::SetCanRecordBase(canRecord
);
1074 mCanRecordBase
= canRecord
;
1081 * Indicates if Telemetry is allowed to record extended data. Returns false if
1082 * the user hasn't opted into "extended Telemetry" on the Release channel, when
1083 * the user has explicitly opted out of Telemetry on Nightly/Aurora/Beta or if
1084 * manually set to false during tests. If the returned value is false, gathering
1085 * of extended telemetry statistics is disabled.
1088 TelemetryImpl::GetCanRecordExtended(bool* ret
) {
1089 *ret
= mCanRecordExtended
;
1094 TelemetryImpl::SetCanRecordExtended(bool canRecord
) {
1096 if (canRecord
!= mCanRecordExtended
) {
1097 TelemetryHistogram::SetCanRecordExtended(canRecord
);
1098 TelemetryScalar::SetCanRecordExtended(canRecord
);
1099 TelemetryEvent::SetCanRecordExtended(canRecord
);
1100 mCanRecordExtended
= canRecord
;
1107 TelemetryImpl::GetCanRecordReleaseData(bool* ret
) {
1108 *ret
= mCanRecordBase
;
1113 TelemetryImpl::GetCanRecordPrereleaseData(bool* ret
) {
1114 *ret
= mCanRecordExtended
;
1119 TelemetryImpl::GetIsOfficialTelemetry(bool* ret
) {
1120 #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) && \
1129 already_AddRefed
<nsITelemetry
> TelemetryImpl::CreateTelemetryInstance() {
1131 auto lock
= sTelemetry
.Lock();
1134 "CreateTelemetryInstance may only be called once, via GetService()");
1137 bool useTelemetry
= false;
1139 if (XRE_IsParentProcess() || XRE_IsContentProcess() || XRE_IsGPUProcess() ||
1140 XRE_IsRDDProcess() || XRE_IsSocketProcess() || XRE_IsUtilityProcess()) {
1141 useTelemetry
= true;
1144 #ifdef MOZ_BACKGROUNDTASKS
1145 if (BackgroundTasks::IsBackgroundTaskMode()) {
1146 // Background tasks collect per-task metrics with Glean.
1147 useTelemetry
= false;
1151 // First, initialize the TelemetryHistogram and TelemetryScalar global states.
1152 TelemetryHistogram::InitializeGlobalState(useTelemetry
, useTelemetry
);
1153 TelemetryScalar::InitializeGlobalState(useTelemetry
, useTelemetry
);
1155 // Only record events from the parent process.
1156 TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(),
1157 XRE_IsParentProcess());
1159 // Currently, only UserInteractions from the parent process are recorded.
1160 TelemetryUserInteraction::InitializeGlobalState(useTelemetry
);
1162 // Now, create and initialize the Telemetry global state.
1163 TelemetryImpl
* telemetry
= new TelemetryImpl();
1165 auto lock
= sTelemetry
.Lock();
1167 // AddRef for the local reference before releasing the lock.
1168 NS_ADDREF(telemetry
);
1171 // AddRef for the caller
1172 nsCOMPtr
<nsITelemetry
> ret
= telemetry
;
1174 telemetry
->mCanRecordBase
= useTelemetry
;
1175 telemetry
->mCanRecordExtended
= useTelemetry
;
1177 telemetry
->InitMemoryReporter();
1178 InitHistogramRecordingEnabled(); // requires sTelemetry to exist
1180 return ret
.forget();
1183 void TelemetryImpl::ShutdownTelemetry() {
1184 // No point in collecting IO beyond this point
1187 auto lock
= sTelemetry
.Lock();
1188 NS_IF_RELEASE(lock
.ref());
1191 // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global
1192 // states, so as to release any heap storage that would otherwise be kept
1194 TelemetryHistogram::DeInitializeGlobalState();
1195 TelemetryScalar::DeInitializeGlobalState();
1196 TelemetryEvent::DeInitializeGlobalState();
1198 TelemetryUserInteraction::DeInitializeGlobalState();
1199 TelemetryIPCAccumulator::DeInitializeGlobalState();
1202 void TelemetryImpl::StoreSlowSQL(const nsACString
& sql
, uint32_t delay
,
1203 SanitizedState state
) {
1204 auto lock
= sTelemetry
.Lock();
1205 auto telemetry
= lock
.ref();
1206 AutoHashtable
<SlowSQLEntryType
>* slowSQLMap
= nullptr;
1207 if (state
== Sanitized
)
1208 slowSQLMap
= &(telemetry
->mSanitizedSQL
);
1210 slowSQLMap
= &(telemetry
->mPrivateSQL
);
1212 MutexAutoLock
hashMutex(telemetry
->mHashMutex
);
1214 SlowSQLEntryType
* entry
= slowSQLMap
->GetEntry(sql
);
1216 entry
= slowSQLMap
->PutEntry(sql
);
1217 if (MOZ_UNLIKELY(!entry
)) return;
1218 entry
->GetModifiableData()->mainThread
.hitCount
= 0;
1219 entry
->GetModifiableData()->mainThread
.totalTime
= 0;
1220 entry
->GetModifiableData()->otherThreads
.hitCount
= 0;
1221 entry
->GetModifiableData()->otherThreads
.totalTime
= 0;
1224 if (NS_IsMainThread()) {
1225 entry
->GetModifiableData()->mainThread
.hitCount
++;
1226 entry
->GetModifiableData()->mainThread
.totalTime
+= delay
;
1228 entry
->GetModifiableData()->otherThreads
.hitCount
++;
1229 entry
->GetModifiableData()->otherThreads
.totalTime
+= delay
;
1234 * This method replaces string literals in SQL strings with the word :private
1236 * States used in this state machine:
1239 * - This is the active state when not iterating over a string literal or
1243 * - Defined here: http://www.sqlite.org/lang_expr.html
1244 * - This state represents iterating over a string literal opened with
1246 * - A single quote within the string can be encoded by putting 2 single quotes
1247 * in a row, e.g. 'This literal contains an escaped quote '''
1248 * - Any double quotes found within a single-quoted literal are ignored
1249 * - This state covers BLOB literals, e.g. X'ABC123'
1250 * - The string literal and the enclosing quotes will be replaced with
1254 * - Same rules as the SINGLE_QUOTE state.
1255 * - According to http://www.sqlite.org/lang_keywords.html,
1256 * SQLite interprets text in double quotes as an identifier unless it's used in
1257 * a context where it cannot be resolved to an identifier and a string literal
1258 * is allowed. This method removes text in double-quotes for safety.
1261 * - http://www.sqlite.org/lang_comment.html
1262 * - A dash comment starts with two dashes in a row,
1263 * e.g. DROP TABLE foo -- a comment
1264 * - Any text following two dashes in a row is interpreted as a comment until
1265 * end of input or a newline character
1266 * - Any quotes found within the comment are ignored and no replacements made
1269 * - http://www.sqlite.org/lang_comment.html
1270 * - A C-style comment starts with a forward slash and an asterisk, and ends
1271 * with an asterisk and a forward slash
1272 * - Any text following comment start is interpreted as a comment up to end of
1273 * input or comment end
1274 * - Any quotes found within the comment are ignored and no replacements made
1276 nsCString
TelemetryImpl::SanitizeSQL(const nsACString
& sql
) {
1278 int length
= sql
.Length();
1288 State state
= NORMAL
;
1289 int fragmentStart
= 0;
1290 for (int i
= 0; i
< length
; i
++) {
1291 char character
= sql
[i
];
1292 char nextCharacter
= (i
+ 1 < length
) ? sql
[i
+ 1] : '\0';
1294 switch (character
) {
1297 if (state
== NORMAL
) {
1298 state
= (character
== '\'') ? SINGLE_QUOTE
: DOUBLE_QUOTE
;
1300 nsDependentCSubstring(sql
, fragmentStart
, i
- fragmentStart
);
1301 output
+= ":private";
1303 } else if ((state
== SINGLE_QUOTE
&& character
== '\'') ||
1304 (state
== DOUBLE_QUOTE
&& character
== '"')) {
1305 if (nextCharacter
== character
) {
1306 // Two consecutive quotes within a string literal are a single
1311 fragmentStart
= i
+ 1;
1316 if (state
== NORMAL
) {
1317 if (nextCharacter
== '-') {
1318 state
= DASH_COMMENT
;
1324 if (state
== DASH_COMMENT
) {
1329 if (state
== NORMAL
) {
1330 if (nextCharacter
== '*') {
1331 state
= C_STYLE_COMMENT
;
1337 if (state
== C_STYLE_COMMENT
) {
1338 if (nextCharacter
== '/') {
1348 if ((fragmentStart
>= 0) && fragmentStart
< length
)
1349 output
+= nsDependentCSubstring(sql
, fragmentStart
, length
- fragmentStart
);
1354 // An allowlist mechanism to prevent Telemetry reporting on Addon & Thunderbird
1356 struct TrackedDBEntry
{
1358 const uint32_t mNameLength
;
1360 // This struct isn't meant to be used beyond the static arrays below.
1361 constexpr TrackedDBEntry(const char* aName
, uint32_t aNameLength
)
1362 : mName(aName
), mNameLength(aNameLength
) {}
1364 TrackedDBEntry() = delete;
1365 TrackedDBEntry(TrackedDBEntry
&) = delete;
1368 #define TRACKEDDB_ENTRY(_name) {_name, (sizeof(_name) - 1)}
1370 // An allowlist of database names. If the database name exactly matches one of
1371 // these then its SQL statements will always be recorded.
1372 static constexpr TrackedDBEntry kTrackedDBs
[] = {
1373 // IndexedDB for about:home, see aboutHome.js
1374 TRACKEDDB_ENTRY("818200132aebmoouht.sqlite"),
1375 TRACKEDDB_ENTRY("addons.sqlite"),
1376 TRACKEDDB_ENTRY("content-prefs.sqlite"),
1377 TRACKEDDB_ENTRY("cookies.sqlite"),
1378 TRACKEDDB_ENTRY("extensions.sqlite"),
1379 TRACKEDDB_ENTRY("favicons.sqlite"),
1380 TRACKEDDB_ENTRY("formhistory.sqlite"),
1381 TRACKEDDB_ENTRY("index.sqlite"),
1382 TRACKEDDB_ENTRY("netpredictions.sqlite"),
1383 TRACKEDDB_ENTRY("permissions.sqlite"),
1384 TRACKEDDB_ENTRY("places.sqlite"),
1385 TRACKEDDB_ENTRY("reading-list.sqlite"),
1386 TRACKEDDB_ENTRY("search.sqlite"),
1387 TRACKEDDB_ENTRY("urlclassifier3.sqlite"),
1388 TRACKEDDB_ENTRY("webappsstore.sqlite")};
1390 // An allowlist of database name prefixes. If the database name begins with
1391 // one of these prefixes then its SQL statements will always be recorded.
1392 static const TrackedDBEntry kTrackedDBPrefixes
[] = {
1393 TRACKEDDB_ENTRY("indexedDB-")};
1395 #undef TRACKEDDB_ENTRY
1397 // Slow SQL statements will be automatically
1398 // trimmed to kMaxSlowStatementLength characters.
1399 // This limit doesn't include the ellipsis and DB name,
1400 // that are appended at the end of the stored statement.
1401 const uint32_t kMaxSlowStatementLength
= 1000;
1403 void TelemetryImpl::RecordSlowStatement(const nsACString
& sql
,
1404 const nsACString
& dbName
,
1406 MOZ_ASSERT(!sql
.IsEmpty());
1407 MOZ_ASSERT(!dbName
.IsEmpty());
1410 auto lock
= sTelemetry
.Lock();
1411 if (!lock
.ref() || !TelemetryHistogram::CanRecordExtended()) {
1416 bool recordStatement
= false;
1418 for (const TrackedDBEntry
& nameEntry
: kTrackedDBs
) {
1419 MOZ_ASSERT(nameEntry
.mNameLength
);
1420 const nsDependentCString
name(nameEntry
.mName
, nameEntry
.mNameLength
);
1421 if (dbName
== name
) {
1422 recordStatement
= true;
1427 if (!recordStatement
) {
1428 for (const TrackedDBEntry
& prefixEntry
: kTrackedDBPrefixes
) {
1429 MOZ_ASSERT(prefixEntry
.mNameLength
);
1430 const nsDependentCString
prefix(prefixEntry
.mName
,
1431 prefixEntry
.mNameLength
);
1432 if (StringBeginsWith(dbName
, prefix
)) {
1433 recordStatement
= true;
1439 if (recordStatement
) {
1440 nsAutoCString
sanitizedSQL(SanitizeSQL(sql
));
1441 if (sanitizedSQL
.Length() > kMaxSlowStatementLength
) {
1442 sanitizedSQL
.SetLength(kMaxSlowStatementLength
);
1443 sanitizedSQL
+= "...";
1445 sanitizedSQL
.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName
).get());
1446 StoreSlowSQL(sanitizedSQL
, delay
, Sanitized
);
1448 // Report aggregate DB-level statistics for addon DBs
1449 nsAutoCString aggregate
;
1450 aggregate
.AppendPrintf("Untracked SQL for %s",
1451 nsPromiseFlatCString(dbName
).get());
1452 StoreSlowSQL(aggregate
, delay
, Sanitized
);
1455 nsAutoCString fullSQL
;
1456 fullSQL
.AppendPrintf("%s /* %s */", nsPromiseFlatCString(sql
).get(),
1457 nsPromiseFlatCString(dbName
).get());
1458 StoreSlowSQL(fullSQL
, delay
, Unsanitized
);
1461 bool TelemetryImpl::CanRecordBase() {
1462 auto lock
= sTelemetry
.Lock();
1463 auto telemetry
= lock
.ref();
1468 nsresult rv
= telemetry
->GetCanRecordBase(&canRecordBase
);
1469 return NS_SUCCEEDED(rv
) && canRecordBase
;
1472 bool TelemetryImpl::CanRecordExtended() {
1473 auto lock
= sTelemetry
.Lock();
1474 auto telemetry
= lock
.ref();
1478 bool canRecordExtended
;
1479 nsresult rv
= telemetry
->GetCanRecordExtended(&canRecordExtended
);
1480 return NS_SUCCEEDED(rv
) && canRecordExtended
;
1483 bool TelemetryImpl::CanRecordReleaseData() { return CanRecordBase(); }
1485 bool TelemetryImpl::CanRecordPrereleaseData() { return CanRecordExtended(); }
1487 NS_IMPL_ISUPPORTS(TelemetryImpl
, nsITelemetry
, nsIMemoryReporter
)
1490 TelemetryImpl::GetFileIOReports(JSContext
* cx
,
1491 JS::MutableHandle
<JS::Value
> ret
) {
1492 if (sTelemetryIOObserver
) {
1493 JS::Rooted
<JSObject
*> obj(cx
, JS_NewPlainObject(cx
));
1495 return NS_ERROR_FAILURE
;
1498 if (!sTelemetryIOObserver
->ReflectIntoJS(cx
, obj
)) {
1499 return NS_ERROR_FAILURE
;
1501 ret
.setObject(*obj
);
1509 TelemetryImpl::MsSinceProcessStart(double* aResult
) {
1510 return Telemetry::Common::MsSinceProcessStart(aResult
);
1514 TelemetryImpl::MsSinceProcessStartIncludingSuspend(double* aResult
) {
1515 return Telemetry::Common::MsSinceProcessStartIncludingSuspend(aResult
);
1519 TelemetryImpl::MsSinceProcessStartExcludingSuspend(double* aResult
) {
1520 return Telemetry::Common::MsSinceProcessStartExcludingSuspend(aResult
);
1524 TelemetryImpl::MsSystemNow(double* aResult
) {
1525 #if defined(XP_UNIX) && !defined(XP_DARWIN)
1527 clock_gettime(CLOCK_REALTIME
, &ts
);
1528 *aResult
= ts
.tv_sec
* 1000 + ts
.tv_nsec
/ 1000000;
1530 using namespace std::chrono
;
1532 duration_cast
<milliseconds
>(system_clock::now().time_since_epoch());
1533 *aResult
= static_cast<double>(ms
.count());
1534 #endif // XP_UNIX && !XP_DARWIN
1539 // Telemetry Scalars IDL Implementation
1542 TelemetryImpl::RegisterBuiltinScalars(const nsACString
& aCategoryName
,
1543 JS::Handle
<JS::Value
> aScalarData
,
1545 return TelemetryScalar::RegisterScalars(aCategoryName
, aScalarData
, cx
);
1549 TelemetryImpl::ClearScalars() {
1550 TelemetryScalar::ClearScalars();
1554 // Telemetry Event IDL implementation.
1557 TelemetryImpl::SnapshotEvents(uint32_t aDataset
, bool aClear
,
1558 uint32_t aEventLimit
, JSContext
* aCx
,
1559 uint8_t optional_argc
,
1560 JS::MutableHandle
<JS::Value
> aResult
) {
1561 return TelemetryEvent::CreateSnapshots(aDataset
, aClear
, aEventLimit
, aCx
,
1562 optional_argc
, aResult
);
1566 TelemetryImpl::RegisterBuiltinEvents(const nsACString
& aCategory
,
1567 JS::Handle
<JS::Value
> aEventData
,
1569 return TelemetryEvent::RegisterBuiltinEvents(aCategory
, aEventData
, cx
);
1573 TelemetryImpl::ClearEvents() {
1574 TelemetryEvent::ClearEvents();
1579 TelemetryImpl::FlushBatchedChildTelemetry() {
1580 TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
1585 TelemetryImpl::EarlyInit() {
1586 Unused
<< MemoryTelemetry::Get();
1592 TelemetryImpl::DelayedInit() {
1593 MemoryTelemetry::Get().DelayedInit();
1598 TelemetryImpl::Shutdown() {
1599 MemoryTelemetry::Get().Shutdown();
1604 TelemetryImpl::GatherMemory(JSContext
* aCx
, Promise
** aResult
) {
1606 RefPtr
<Promise
> promise
= Promise::Create(xpc::CurrentNativeGlobal(aCx
), rv
);
1608 return rv
.StealNSResult();
1611 MemoryTelemetry::Get().GatherReports(
1612 [promise
]() { promise
->MaybeResolve(JS::UndefinedHandleValue
); });
1614 promise
.forget(aResult
);
1619 TelemetryImpl::GetAllStores(JSContext
* aCx
,
1620 JS::MutableHandle
<JS::Value
> aResult
) {
1621 StringHashSet stores
;
1624 rv
= TelemetryHistogram::GetAllStores(stores
);
1625 if (NS_FAILED(rv
)) {
1628 rv
= TelemetryScalar::GetAllStores(stores
);
1629 if (NS_FAILED(rv
)) {
1633 JS::RootedVector
<JS::Value
> allStores(aCx
);
1634 if (!allStores
.reserve(stores
.Count())) {
1635 return NS_ERROR_FAILURE
;
1638 for (const auto& value
: stores
) {
1639 JS::Rooted
<JS::Value
> store(aCx
);
1641 store
.setString(ToJSString(aCx
, value
));
1642 if (!allStores
.append(store
)) {
1643 return NS_ERROR_FAILURE
;
1647 JS::Rooted
<JSObject
*> rarray(aCx
, JS::NewArrayObject(aCx
, allStores
));
1648 if (rarray
== nullptr) {
1649 return NS_ERROR_FAILURE
;
1651 aResult
.setObject(*rarray
);
1658 ////////////////////////////////////////////////////////////////////////
1659 ////////////////////////////////////////////////////////////////////////
1661 // EXTERNALLY VISIBLE FUNCTIONS in no name space
1662 // These are NOT listed in Telemetry.h
1665 * The XRE_TelemetryAdd function is to be used by embedding applications
1666 * that can't use mozilla::Telemetry::Accumulate() directly.
1668 void XRE_TelemetryAccumulate(int aID
, uint32_t aSample
) {
1669 mozilla::Telemetry::Accumulate((mozilla::Telemetry::HistogramID
)aID
, aSample
);
1672 ////////////////////////////////////////////////////////////////////////
1673 ////////////////////////////////////////////////////////////////////////
1675 // EXTERNALLY VISIBLE FUNCTIONS in mozilla::
1676 // These are NOT listed in Telemetry.h
1680 void RecordShutdownStartTimeStamp() {
1682 // FIXME: this function should only be called once, since it should be called
1683 // at the earliest point we *know* we are shutting down. Unfortunately
1684 // this assert has been firing. Given that if we are called multiple times
1685 // we just keep the last timestamp, the assert is commented for now.
1686 static bool recorded
= false;
1687 // MOZ_ASSERT(!recorded);
1689 recorded
; // Silence unused-var warnings (remove when assert re-enabled)
1693 if (!Telemetry::CanRecordExtended()) return;
1695 gRecordedShutdownStartTime
= TimeStamp::Now();
1697 GetShutdownTimeFileName();
1700 void RecordShutdownEndTimeStamp() {
1701 if (!gRecordedShutdownTimeFileName
|| gAlreadyFreedShutdownTimeFileName
)
1704 PathString
name(gRecordedShutdownTimeFileName
);
1705 free(const_cast<PathChar
*>(gRecordedShutdownTimeFileName
));
1706 gRecordedShutdownTimeFileName
= nullptr;
1707 gAlreadyFreedShutdownTimeFileName
= true;
1709 if (gRecordedShutdownStartTime
.IsNull()) {
1710 // If |CanRecordExtended()| is true before |AsyncFetchTelemetryData| is
1711 // called and then disabled before shutdown, |RecordShutdownStartTimeStamp|
1712 // will bail out and we will end up with a null |gRecordedShutdownStartTime|
1713 // here. This can happen during tests.
1717 AutoPathString
tmpName(name
);
1718 tmpName
.AppendLiteral(".tmp");
1719 nsCOMPtr
<nsIFile
> tmpFile
;
1720 if (NS_FAILED(NS_NewPathStringLocalFile(tmpName
, getter_AddRefs(tmpFile
)))) {
1724 if (NS_FAILED(tmpFile
->OpenANSIFileDesc("w", &f
)) || !f
) return;
1725 // On a normal release build this should be called just before
1726 // calling _exit, but on a debug build or when the user forces a full
1727 // shutdown this is called as late as possible, so we have to
1728 // allow this write as write poisoning will be enabled.
1729 MozillaRegisterDebugFILE(f
);
1731 TimeStamp now
= TimeStamp::Now();
1732 MOZ_ASSERT(now
>= gRecordedShutdownStartTime
);
1733 TimeDuration diff
= now
- gRecordedShutdownStartTime
;
1734 uint32_t diff2
= diff
.ToMilliseconds();
1735 int written
= fprintf(f
, "%d\n", diff2
);
1736 MozillaUnRegisterDebugFILE(f
);
1738 if (written
< 0 || rv
!= 0) {
1739 tmpFile
->Remove(false);
1742 nsCOMPtr
<nsIFile
> file
;
1743 if (NS_FAILED(NS_NewPathStringLocalFile(name
, getter_AddRefs(file
)))) {
1746 nsAutoString leafName
;
1747 if (NS_SUCCEEDED(file
->GetLeafName(leafName
))) {
1748 tmpFile
->RenameTo(nullptr, leafName
);
1752 } // namespace mozilla
1754 ////////////////////////////////////////////////////////////////////////
1755 ////////////////////////////////////////////////////////////////////////
1757 // EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry::
1758 // These are listed in Telemetry.h
1760 namespace mozilla::Telemetry
{
1762 void Accumulate(HistogramID aHistogram
, uint32_t aSample
) {
1763 TelemetryHistogram::Accumulate(aHistogram
, aSample
);
1766 void Accumulate(HistogramID aHistogram
, const nsTArray
<uint32_t>& aSamples
) {
1767 TelemetryHistogram::Accumulate(aHistogram
, aSamples
);
1770 void Accumulate(HistogramID aID
, const nsCString
& aKey
, uint32_t aSample
) {
1771 TelemetryHistogram::Accumulate(aID
, aKey
, aSample
);
1774 void Accumulate(HistogramID aID
, const nsCString
& aKey
,
1775 const nsTArray
<uint32_t>& aSamples
) {
1776 TelemetryHistogram::Accumulate(aID
, aKey
, aSamples
);
1779 void Accumulate(const char* name
, uint32_t sample
) {
1780 TelemetryHistogram::Accumulate(name
, sample
);
1783 void Accumulate(const char* name
, const nsCString
& key
, uint32_t sample
) {
1784 TelemetryHistogram::Accumulate(name
, key
, sample
);
1787 void AccumulateCategorical(HistogramID id
, const nsCString
& label
) {
1788 TelemetryHistogram::AccumulateCategorical(id
, label
);
1791 void AccumulateCategorical(HistogramID id
, const nsTArray
<nsCString
>& labels
) {
1792 TelemetryHistogram::AccumulateCategorical(id
, labels
);
1795 void AccumulateTimeDelta(HistogramID aHistogram
, TimeStamp start
,
1797 Accumulate(aHistogram
, static_cast<uint32_t>((end
- start
).ToMilliseconds()));
1800 void AccumulateTimeDelta(HistogramID aHistogram
, const nsCString
& key
,
1801 TimeStamp start
, TimeStamp end
) {
1802 Accumulate(aHistogram
, key
,
1803 static_cast<uint32_t>((end
- start
).ToMilliseconds()));
1805 const char* GetHistogramName(HistogramID id
) {
1806 return TelemetryHistogram::GetHistogramName(id
);
1809 bool CanRecordBase() { return TelemetryImpl::CanRecordBase(); }
1811 bool CanRecordExtended() { return TelemetryImpl::CanRecordExtended(); }
1813 bool CanRecordReleaseData() { return TelemetryImpl::CanRecordReleaseData(); }
1815 bool CanRecordPrereleaseData() {
1816 return TelemetryImpl::CanRecordPrereleaseData();
1819 void RecordSlowSQLStatement(const nsACString
& statement
,
1820 const nsACString
& dbName
, uint32_t delay
) {
1821 TelemetryImpl::RecordSlowStatement(statement
, dbName
, delay
);
1825 // Make the service manager hold a long-lived reference to the service
1826 nsCOMPtr
<nsITelemetry
> telemetryService
=
1827 do_GetService("@mozilla.org/base/telemetry;1");
1828 MOZ_ASSERT(telemetryService
);
1831 void WriteFailedProfileLock(nsIFile
* aProfileDir
) {
1832 nsCOMPtr
<nsIFile
> file
;
1833 nsresult rv
= GetFailedProfileLockFile(getter_AddRefs(file
), aProfileDir
);
1834 NS_ENSURE_SUCCESS_VOID(rv
);
1835 int64_t fileSize
= 0;
1836 rv
= file
->GetFileSize(&fileSize
);
1837 // It's expected that the file might not exist yet
1838 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_NOT_FOUND
) {
1841 nsCOMPtr
<nsIRandomAccessStream
> fileRandomAccessStream
;
1842 rv
= NS_NewLocalFileRandomAccessStream(getter_AddRefs(fileRandomAccessStream
),
1843 file
, PR_RDWR
| PR_CREATE_FILE
, 0640);
1844 NS_ENSURE_SUCCESS_VOID(rv
);
1845 NS_ENSURE_TRUE_VOID(fileSize
<= kMaxFailedProfileLockFileSize
);
1846 unsigned int failedLockCount
= 0;
1848 nsCOMPtr
<nsIInputStream
> inStream
=
1849 do_QueryInterface(fileRandomAccessStream
);
1850 NS_ENSURE_TRUE_VOID(inStream
);
1851 if (!GetFailedLockCount(inStream
, fileSize
, failedLockCount
)) {
1852 failedLockCount
= 0;
1856 nsAutoCString bufStr
;
1857 bufStr
.AppendInt(static_cast<int>(failedLockCount
));
1858 // If we read in an existing failed lock count, we need to reset the file ptr
1860 rv
= fileRandomAccessStream
->Seek(nsISeekableStream::NS_SEEK_SET
, 0);
1861 NS_ENSURE_SUCCESS_VOID(rv
);
1863 nsCOMPtr
<nsIOutputStream
> outStream
=
1864 do_QueryInterface(fileRandomAccessStream
);
1865 uint32_t bytesLeft
= bufStr
.Length();
1866 const char* bytes
= bufStr
.get();
1868 uint32_t written
= 0;
1869 rv
= outStream
->Write(bytes
, bytesLeft
, &written
);
1870 if (NS_FAILED(rv
)) {
1874 bytesLeft
-= written
;
1875 } while (bytesLeft
> 0);
1876 fileRandomAccessStream
->SetEOF();
1879 void InitIOReporting(nsIFile
* aXreDir
) {
1880 // Never initialize twice
1881 if (sTelemetryIOObserver
) {
1885 sTelemetryIOObserver
= new TelemetryIOInterposeObserver(aXreDir
);
1886 IOInterposer::Register(IOInterposeObserver::OpAllWithStaging
,
1887 sTelemetryIOObserver
);
1890 void SetProfileDir(nsIFile
* aProfD
) {
1891 if (!sTelemetryIOObserver
|| !aProfD
) {
1894 nsAutoString profDirPath
;
1895 nsresult rv
= aProfD
->GetPath(profDirPath
);
1896 if (NS_FAILED(rv
)) {
1899 sTelemetryIOObserver
->AddPath(profDirPath
, u
"{profile}"_ns
);
1902 void ShutdownTelemetry() { TelemetryImpl::ShutdownTelemetry(); }
1904 } // namespace mozilla::Telemetry
1906 NS_IMPL_COMPONENT_FACTORY(nsITelemetry
) {
1907 return TelemetryImpl::CreateTelemetryInstance().downcast
<nsISupports
>();