summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/core/Telemetry.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/core/Telemetry.cpp')
-rw-r--r--toolkit/components/telemetry/core/Telemetry.cpp2035
1 files changed, 2035 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/core/Telemetry.cpp b/toolkit/components/telemetry/core/Telemetry.cpp
new file mode 100644
index 0000000000..a0effb02bb
--- /dev/null
+++ b/toolkit/components/telemetry/core/Telemetry.cpp
@@ -0,0 +1,2035 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Telemetry.h"
+
+#include <algorithm>
+#include <prio.h>
+#include <prproces.h>
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
+# include <time.h>
+#else
+# include <chrono>
+#endif
+#include "base/pickle.h"
+#include "base/process_util.h"
+#if defined(MOZ_TELEMETRY_GECKOVIEW)
+# include "geckoview/TelemetryGeckoViewPersistence.h"
+#endif
+#include "ipc/TelemetryIPCAccumulator.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/GCAPI.h"
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+#include "mozilla/Components.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FStream.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/MemoryTelemetry.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StartupTimeline.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#if defined(XP_WIN)
+# include "mozilla/WinDllServices.h"
+#endif
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsBaseHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFileStreams.h"
+#include "nsIMemoryReporter.h"
+#include "nsISeekableStream.h"
+#include "nsITelemetry.h"
+#if defined(XP_WIN)
+# include "other/UntrustedModules.h"
+#endif
+#include "nsJSUtils.h"
+#include "nsLocalFile.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#if defined(XP_WIN)
+# include "nsUnicharUtils.h"
+#endif
+#include "nsVersionComparator.h"
+#include "nsXPCOMCIDInternal.h"
+#include "other/CombinedStacks.h"
+#include "other/TelemetryIOInterposeObserver.h"
+#include "TelemetryCommon.h"
+#include "TelemetryEvent.h"
+#include "TelemetryHistogram.h"
+#include "TelemetryScalar.h"
+#include "TelemetryUserInteraction.h"
+
+namespace {
+
+using namespace mozilla;
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::Promise;
+using mozilla::Telemetry::CombinedStacks;
+using mozilla::Telemetry::EventExtraEntry;
+using mozilla::Telemetry::TelemetryIOInterposeObserver;
+using Telemetry::Common::AutoHashtable;
+using Telemetry::Common::GetCurrentProduct;
+using Telemetry::Common::StringHashSet;
+using Telemetry::Common::SupportedProduct;
+using Telemetry::Common::ToJSString;
+
+// This is not a member of TelemetryImpl because we want to record I/O during
+// startup.
+StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver;
+
+void ClearIOReporting() {
+ if (!sTelemetryIOObserver) {
+ return;
+ }
+ IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
+ sTelemetryIOObserver);
+ sTelemetryIOObserver = nullptr;
+}
+
+class TelemetryImpl final : public nsITelemetry, public nsIMemoryReporter {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITELEMETRY
+ NS_DECL_NSIMEMORYREPORTER
+
+ public:
+ void InitMemoryReporter();
+
+ static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
+ static void ShutdownTelemetry();
+ static void RecordSlowStatement(const nsACString& sql,
+ const nsACString& dbName, uint32_t delay);
+ struct Stat {
+ uint32_t hitCount;
+ uint32_t totalTime;
+ };
+ struct StmtStats {
+ struct Stat mainThread;
+ struct Stat otherThreads;
+ };
+ typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
+
+ static void RecordIceCandidates(const uint32_t iceCandidateBitmask,
+ const bool success);
+ static bool CanRecordBase();
+ static bool CanRecordExtended();
+ static bool CanRecordReleaseData();
+ static bool CanRecordPrereleaseData();
+
+ private:
+ TelemetryImpl();
+ ~TelemetryImpl();
+
+ static nsCString SanitizeSQL(const nsACString& sql);
+
+ enum SanitizedState { Sanitized, Unsanitized };
+
+ static void StoreSlowSQL(const nsACString& offender, uint32_t delay,
+ SanitizedState state);
+
+ static bool ReflectMainThreadSQL(SlowSQLEntryType* entry, JSContext* cx,
+ JS::Handle<JSObject*> obj);
+ static bool ReflectOtherThreadsSQL(SlowSQLEntryType* entry, JSContext* cx,
+ JS::Handle<JSObject*> obj);
+ static bool ReflectSQL(const SlowSQLEntryType* entry, const Stat* stat,
+ JSContext* cx, JS::Handle<JSObject*> obj);
+
+ bool AddSQLInfo(JSContext* cx, JS::Handle<JSObject*> rootObj, bool mainThread,
+ bool privateSQL);
+ bool GetSQLStats(JSContext* cx, JS::MutableHandle<JS::Value> ret,
+ bool includePrivateSql);
+
+ void ReadLateWritesStacks(nsIFile* aProfileDir);
+
+ static StaticDataMutex<TelemetryImpl*> sTelemetry;
+ AutoHashtable<SlowSQLEntryType> mPrivateSQL;
+ AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
+ Mutex mHashMutex MOZ_UNANNOTATED;
+ Atomic<bool, SequentiallyConsistent> mCanRecordBase;
+ Atomic<bool, SequentiallyConsistent> mCanRecordExtended;
+
+ CombinedStacks
+ mLateWritesStacks; // This is collected out of the main thread.
+ bool mCachedTelemetryData;
+ uint32_t mLastShutdownTime;
+ uint32_t mFailedLockCount;
+ nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks;
+ friend class nsFetchTelemetryData;
+};
+
+StaticDataMutex<TelemetryImpl*> TelemetryImpl::sTelemetry(nullptr, nullptr);
+
+MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf)
+
+NS_IMETHODIMP
+TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ mozilla::MallocSizeOf aMallocSizeOf = TelemetryMallocSizeOf;
+
+#define COLLECT_REPORT(name, size, desc) \
+ MOZ_COLLECT_REPORT(name, KIND_HEAP, UNITS_BYTES, size, desc)
+
+ COLLECT_REPORT("explicit/telemetry/impl", aMallocSizeOf(this),
+ "Memory used by the Telemetry core implemenation");
+
+ COLLECT_REPORT(
+ "explicit/telemetry/scalar/shallow",
+ TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf),
+ "Memory used by the Telemetry Scalar implemenation");
+
+ { // Scope for mHashMutex lock
+ MutexAutoLock lock(mHashMutex);
+ COLLECT_REPORT("explicit/telemetry/PrivateSQL",
+ mPrivateSQL.SizeOfExcludingThis(aMallocSizeOf),
+ "Memory used by the PrivateSQL Telemetry");
+
+ COLLECT_REPORT("explicit/telemetry/SanitizedSQL",
+ mSanitizedSQL.SizeOfExcludingThis(aMallocSizeOf),
+ "Memory used by the SanitizedSQL Telemetry");
+ }
+
+ if (sTelemetryIOObserver) {
+ COLLECT_REPORT("explicit/telemetry/IOObserver",
+ sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf),
+ "Memory used by the Telemetry IO Observer");
+ }
+
+ COLLECT_REPORT("explicit/telemetry/LateWritesStacks",
+ mLateWritesStacks.SizeOfExcludingThis(),
+ "Memory used by the Telemetry LateWrites Stack capturer");
+
+ COLLECT_REPORT("explicit/telemetry/Callbacks",
+ mCallbacks.ShallowSizeOfExcludingThis(aMallocSizeOf),
+ "Memory used by the Telemetry Callbacks array (shallow)");
+
+ COLLECT_REPORT(
+ "explicit/telemetry/histogram/data",
+ TelemetryHistogram::GetHistogramSizesOfIncludingThis(aMallocSizeOf),
+ "Memory used by Telemetry Histogram data");
+
+ COLLECT_REPORT("explicit/telemetry/scalar/data",
+ TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf),
+ "Memory used by Telemetry Scalar data");
+
+ COLLECT_REPORT("explicit/telemetry/event/data",
+ TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf),
+ "Memory used by Telemetry Event data");
+
+#undef COLLECT_REPORT
+
+ return NS_OK;
+}
+
+void InitHistogramRecordingEnabled() {
+ TelemetryHistogram::InitHistogramRecordingEnabled();
+}
+
+using PathChar = filesystem::Path::value_type;
+using PathCharPtr = const PathChar*;
+
+static uint32_t ReadLastShutdownDuration(PathCharPtr filename) {
+ RefPtr<nsLocalFile> file =
+ new nsLocalFile(nsTDependentString<PathChar>(filename));
+ FILE* f;
+ if (NS_FAILED(file->OpenANSIFileDesc("r", &f)) || !f) {
+ return 0;
+ }
+
+ int shutdownTime;
+ int r = fscanf(f, "%d\n", &shutdownTime);
+ fclose(f);
+ if (r != 1) {
+ return 0;
+ }
+
+ return shutdownTime;
+}
+
+const int32_t kMaxFailedProfileLockFileSize = 10;
+
+bool GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount,
+ unsigned int& result) {
+ nsAutoCString bufStr;
+ nsresult rv;
+ rv = NS_ReadInputStreamToString(inStream, bufStr, aCount);
+ NS_ENSURE_SUCCESS(rv, false);
+ result = bufStr.ToInteger(&rv);
+ return NS_SUCCEEDED(rv) && result > 0;
+}
+
+nsresult GetFailedProfileLockFile(nsIFile** aFile, nsIFile* aProfileDir) {
+ NS_ENSURE_ARG_POINTER(aProfileDir);
+
+ nsresult rv = aProfileDir->Clone(aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (*aFile)->AppendNative("Telemetry.FailedProfileLocks.txt"_ns);
+ return NS_OK;
+}
+
+class nsFetchTelemetryData : public Runnable {
+ public:
+ nsFetchTelemetryData(PathCharPtr aShutdownTimeFilename,
+ nsIFile* aFailedProfileLockFile, nsIFile* aProfileDir)
+ : mozilla::Runnable("nsFetchTelemetryData"),
+ mShutdownTimeFilename(aShutdownTimeFilename),
+ mFailedProfileLockFile(aFailedProfileLockFile),
+ mProfileDir(aProfileDir) {}
+
+ private:
+ PathCharPtr mShutdownTimeFilename;
+ nsCOMPtr<nsIFile> mFailedProfileLockFile;
+ nsCOMPtr<nsIFile> mProfileDir;
+
+ public:
+ void MainThread() {
+ auto lock = TelemetryImpl::sTelemetry.Lock();
+ auto telemetry = lock.ref();
+ telemetry->mCachedTelemetryData = true;
+ for (unsigned int i = 0, n = telemetry->mCallbacks.Count(); i < n; ++i) {
+ telemetry->mCallbacks[i]->Complete();
+ }
+ telemetry->mCallbacks.Clear();
+ }
+
+ NS_IMETHOD Run() override {
+ uint32_t failedLockCount = 0;
+ uint32_t lastShutdownDuration = 0;
+ LoadFailedLockCount(failedLockCount);
+ lastShutdownDuration = ReadLastShutdownDuration(mShutdownTimeFilename);
+ {
+ auto lock = TelemetryImpl::sTelemetry.Lock();
+ auto telemetry = lock.ref();
+ telemetry->mFailedLockCount = failedLockCount;
+ telemetry->mLastShutdownTime = lastShutdownDuration;
+ telemetry->ReadLateWritesStacks(mProfileDir);
+ }
+
+ TelemetryScalar::Set(Telemetry::ScalarID::BROWSER_TIMINGS_LAST_SHUTDOWN,
+ lastShutdownDuration);
+
+ nsCOMPtr<nsIRunnable> e =
+ NewRunnableMethod("nsFetchTelemetryData::MainThread", this,
+ &nsFetchTelemetryData::MainThread);
+ NS_ENSURE_STATE(e);
+ NS_DispatchToMainThread(e);
+ return NS_OK;
+ }
+
+ private:
+ nsresult LoadFailedLockCount(uint32_t& failedLockCount) {
+ failedLockCount = 0;
+ int64_t fileSize = 0;
+ nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize,
+ NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream),
+ mFailedProfileLockFile, PR_RDONLY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount),
+ NS_ERROR_UNEXPECTED);
+ inStream->Close();
+
+ mFailedProfileLockFile->Remove(false);
+ return NS_OK;
+ }
+};
+
+static TimeStamp gRecordedShutdownStartTime;
+static bool gAlreadyFreedShutdownTimeFileName = false;
+static PathCharPtr gRecordedShutdownTimeFileName = nullptr;
+
+static PathCharPtr GetShutdownTimeFileName() {
+ if (gAlreadyFreedShutdownTimeFileName) {
+ return nullptr;
+ }
+
+ if (!gRecordedShutdownTimeFileName) {
+ nsCOMPtr<nsIFile> mozFile;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
+ if (!mozFile) return nullptr;
+
+ mozFile->AppendNative("Telemetry.ShutdownTime.txt"_ns);
+
+ gRecordedShutdownTimeFileName = NS_xstrdup(mozFile->NativePath().get());
+ }
+
+ return gRecordedShutdownTimeFileName;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetLastShutdownDuration(uint32_t* aResult) {
+ // The user must call AsyncFetchTelemetryData first. We return zero instead of
+ // reporting a failure so that the rest of telemetry can uniformly handle
+ // the read not being available yet.
+ if (!mCachedTelemetryData) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ *aResult = mLastShutdownTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult) {
+ // The user must call AsyncFetchTelemetryData first. We return zero instead of
+ // reporting a failure so that the rest of telemetry can uniformly handle
+ // the read not being available yet.
+ if (!mCachedTelemetryData) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ *aResult = mFailedLockCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::AsyncFetchTelemetryData(
+ nsIFetchTelemetryDataCallback* aCallback) {
+ // We have finished reading the data already, just call the callback.
+ if (mCachedTelemetryData) {
+ aCallback->Complete();
+ return NS_OK;
+ }
+
+ // We already have a read request running, just remember the callback.
+ if (mCallbacks.Count() != 0) {
+ mCallbacks.AppendObject(aCallback);
+ return NS_OK;
+ }
+
+ // We make this check so that GetShutdownTimeFileName() doesn't get
+ // called; calling that function without telemetry enabled violates
+ // assumptions that the write-the-shutdown-timestamp machinery makes.
+ if (!Telemetry::CanRecordExtended()) {
+ mCachedTelemetryData = true;
+ aCallback->Complete();
+ return NS_OK;
+ }
+
+ // Send the read to a background thread provided by the stream transport
+ // service to avoid a read in the main thread.
+ nsCOMPtr<nsIEventTarget> targetThread =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!targetThread) {
+ mCachedTelemetryData = true;
+ aCallback->Complete();
+ return NS_OK;
+ }
+
+ // We have to get the filename from the main thread.
+ PathCharPtr shutdownTimeFilename = GetShutdownTimeFileName();
+ if (!shutdownTimeFilename) {
+ mCachedTelemetryData = true;
+ aCallback->Complete();
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ if (NS_FAILED(rv)) {
+ mCachedTelemetryData = true;
+ aCallback->Complete();
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> failedProfileLockFile;
+ rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile),
+ profileDir);
+ if (NS_FAILED(rv)) {
+ mCachedTelemetryData = true;
+ aCallback->Complete();
+ return NS_OK;
+ }
+
+ mCallbacks.AppendObject(aCallback);
+
+ nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(
+ shutdownTimeFilename, failedProfileLockFile, profileDir);
+
+ targetThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+TelemetryImpl::TelemetryImpl()
+ : mHashMutex("Telemetry::mHashMutex"),
+ mCanRecordBase(false),
+ mCanRecordExtended(false),
+ mCachedTelemetryData(false),
+ mLastShutdownTime(0),
+ mFailedLockCount(0) {
+ // We expect TelemetryHistogram::InitializeGlobalState() to have been
+ // called before we get to this point.
+ MOZ_ASSERT(TelemetryHistogram::GlobalStateHasBeenInitialized());
+}
+
+TelemetryImpl::~TelemetryImpl() {
+ UnregisterWeakMemoryReporter(this);
+
+ // This is still racey as access to these collections is guarded using
+ // sTelemetry. We will fix this in bug 1367344.
+ MutexAutoLock hashLock(mHashMutex);
+}
+
+void TelemetryImpl::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
+
+bool TelemetryImpl::ReflectSQL(const SlowSQLEntryType* entry, const Stat* stat,
+ JSContext* cx, JS::Handle<JSObject*> obj) {
+ if (stat->hitCount == 0) return true;
+
+ const nsACString& sql = entry->GetKey();
+
+ JS::Rooted<JSObject*> arrayObj(cx, JS::NewArrayObject(cx, 0));
+ if (!arrayObj) {
+ return false;
+ }
+ return (
+ JS_DefineElement(cx, arrayObj, 0, stat->hitCount, JSPROP_ENUMERATE) &&
+ JS_DefineElement(cx, arrayObj, 1, stat->totalTime, JSPROP_ENUMERATE) &&
+ JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj,
+ JSPROP_ENUMERATE));
+}
+
+bool TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType* entry, JSContext* cx,
+ JS::Handle<JSObject*> obj) {
+ return ReflectSQL(entry, &entry->GetModifiableData()->mainThread, cx, obj);
+}
+
+bool TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType* entry,
+ JSContext* cx,
+ JS::Handle<JSObject*> obj) {
+ return ReflectSQL(entry, &entry->GetModifiableData()->otherThreads, cx, obj);
+}
+
+bool TelemetryImpl::AddSQLInfo(JSContext* cx, JS::Handle<JSObject*> rootObj,
+ bool mainThread, bool privateSQL) {
+ JS::Rooted<JSObject*> statsObj(cx, JS_NewPlainObject(cx));
+ if (!statsObj) return false;
+
+ AutoHashtable<SlowSQLEntryType>& sqlMap =
+ (privateSQL ? mPrivateSQL : mSanitizedSQL);
+ AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction =
+ (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL);
+ if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) {
+ return false;
+ }
+
+ return JS_DefineProperty(cx, rootObj,
+ mainThread ? "mainThread" : "otherThreads", statsObj,
+ JSPROP_ENUMERATE);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::SetHistogramRecordingEnabled(const nsACString& id,
+ bool aEnabled) {
+ return TelemetryHistogram::SetHistogramRecordingEnabled(id, aEnabled);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetSnapshotForHistograms(const nsACString& aStoreName,
+ bool aClearStore, bool aFilterTest,
+ JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ constexpr auto defaultStore = "main"_ns;
+ unsigned int dataset = mCanRecordExtended
+ ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
+ : nsITelemetry::DATASET_ALL_CHANNELS;
+ return TelemetryHistogram::CreateHistogramSnapshots(
+ aCx, aResult, aStoreName.IsVoid() ? defaultStore : aStoreName, dataset,
+ aClearStore, aFilterTest);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetSnapshotForKeyedHistograms(
+ const nsACString& aStoreName, bool aClearStore, bool aFilterTest,
+ JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
+ constexpr auto defaultStore = "main"_ns;
+ unsigned int dataset = mCanRecordExtended
+ ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
+ : nsITelemetry::DATASET_ALL_CHANNELS;
+ return TelemetryHistogram::GetKeyedHistogramSnapshots(
+ aCx, aResult, aStoreName.IsVoid() ? defaultStore : aStoreName, dataset,
+ aClearStore, aFilterTest);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetCategoricalLabels(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ return TelemetryHistogram::GetCategoricalHistogramLabels(aCx, aResult);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetSnapshotForScalars(const nsACString& aStoreName,
+ bool aClearStore, bool aFilterTest,
+ JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ constexpr auto defaultStore = "main"_ns;
+ unsigned int dataset = mCanRecordExtended
+ ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
+ : nsITelemetry::DATASET_ALL_CHANNELS;
+ return TelemetryScalar::CreateSnapshots(
+ dataset, aClearStore, aCx, 1, aResult, aFilterTest,
+ aStoreName.IsVoid() ? defaultStore : aStoreName);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetSnapshotForKeyedScalars(
+ const nsACString& aStoreName, bool aClearStore, bool aFilterTest,
+ JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
+ constexpr auto defaultStore = "main"_ns;
+ unsigned int dataset = mCanRecordExtended
+ ? nsITelemetry::DATASET_PRERELEASE_CHANNELS
+ : nsITelemetry::DATASET_ALL_CHANNELS;
+ return TelemetryScalar::CreateKeyedSnapshots(
+ dataset, aClearStore, aCx, 1, aResult, aFilterTest,
+ aStoreName.IsVoid() ? defaultStore : aStoreName);
+}
+
+bool TelemetryImpl::GetSQLStats(JSContext* cx, JS::MutableHandle<JS::Value> ret,
+ bool includePrivateSql) {
+ JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
+ if (!root_obj) return false;
+ ret.setObject(*root_obj);
+
+ MutexAutoLock hashMutex(mHashMutex);
+ // Add info about slow SQL queries on the main thread
+ if (!AddSQLInfo(cx, root_obj, true, includePrivateSql)) return false;
+ // Add info about slow SQL queries on other threads
+ if (!AddSQLInfo(cx, root_obj, false, includePrivateSql)) return false;
+
+ return true;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetSlowSQL(JSContext* cx, JS::MutableHandle<JS::Value> ret) {
+ if (GetSQLStats(cx, ret, false)) return NS_OK;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetDebugSlowSQL(JSContext* cx,
+ JS::MutableHandle<JS::Value> ret) {
+ bool revealPrivateSql =
+ Preferences::GetBool("toolkit.telemetry.debugSlowSql", false);
+ if (GetSQLStats(cx, ret, revealPrivateSql)) return NS_OK;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetUntrustedModuleLoadEvents(uint32_t aFlags, JSContext* cx,
+ Promise** aPromise) {
+#if defined(XP_WIN)
+ return Telemetry::GetUntrustedModuleLoadEvents(aFlags, cx, aPromise);
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetAreUntrustedModuleLoadEventsReady(bool* ret) {
+#if defined(XP_WIN)
+ *ret = DllServices::Get()->IsReadyForBackgroundProcessing();
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+#if defined(MOZ_GECKO_PROFILER)
+class GetLoadedModulesResultRunnable final : public Runnable {
+ nsMainThreadPtrHandle<Promise> mPromise;
+ SharedLibraryInfo mRawModules;
+ nsCOMPtr<nsIThread> mWorkerThread;
+# if defined(XP_WIN)
+ nsTHashMap<nsStringHashKey, nsString> mCertSubjects;
+# endif // defined(XP_WIN)
+
+ public:
+ GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle<Promise>& aPromise,
+ const SharedLibraryInfo& rawModules)
+ : mozilla::Runnable("GetLoadedModulesResultRunnable"),
+ mPromise(aPromise),
+ mRawModules(rawModules),
+ mWorkerThread(do_GetCurrentThread()) {
+ MOZ_ASSERT(!NS_IsMainThread());
+# if defined(XP_WIN)
+ ObtainCertSubjects();
+# endif // defined(XP_WIN)
+ }
+
+ NS_IMETHOD
+ Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mWorkerThread->Shutdown();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mPromise->GetGlobalObject()))) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> moduleArray(cx, JS::NewArrayObject(cx, 0));
+ if (!moduleArray) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) {
+ const SharedLibrary& info = mRawModules.GetEntry(i);
+
+ JS::Rooted<JSObject*> moduleObj(cx, JS_NewPlainObject(cx));
+ if (!moduleObj) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ // Module name.
+ JS::Rooted<JSString*> moduleName(
+ cx, JS_NewUCStringCopyZ(cx, info.GetModuleName().get()));
+ if (!moduleName || !JS_DefineProperty(cx, moduleObj, "name", moduleName,
+ JSPROP_ENUMERATE)) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ // Module debug name.
+ JS::Rooted<JS::Value> moduleDebugName(cx);
+
+ if (!info.GetDebugName().IsEmpty()) {
+ JS::Rooted<JSString*> str_moduleDebugName(
+ cx, JS_NewUCStringCopyZ(cx, info.GetDebugName().get()));
+ if (!str_moduleDebugName) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+ moduleDebugName.setString(str_moduleDebugName);
+ } else {
+ moduleDebugName.setNull();
+ }
+
+ if (!JS_DefineProperty(cx, moduleObj, "debugName", moduleDebugName,
+ JSPROP_ENUMERATE)) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ // Module Breakpad identifier.
+ JS::Rooted<JS::Value> id(cx);
+
+ if (!info.GetBreakpadId().IsEmpty()) {
+ JS::Rooted<JSString*> str_id(
+ cx, JS_NewStringCopyZ(cx, info.GetBreakpadId().get()));
+ if (!str_id) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+ id.setString(str_id);
+ } else {
+ id.setNull();
+ }
+
+ if (!JS_DefineProperty(cx, moduleObj, "debugID", id, JSPROP_ENUMERATE)) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ // Module version.
+ JS::Rooted<JS::Value> version(cx);
+
+ if (!info.GetVersion().IsEmpty()) {
+ JS::Rooted<JSString*> v(
+ cx, JS_NewStringCopyZ(cx, info.GetVersion().BeginReading()));
+ if (!v) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+ version.setString(v);
+ } else {
+ version.setNull();
+ }
+
+ if (!JS_DefineProperty(cx, moduleObj, "version", version,
+ JSPROP_ENUMERATE)) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+# if defined(XP_WIN)
+ // Cert Subject.
+ if (auto subject = mCertSubjects.Lookup(info.GetModulePath())) {
+ JS::Rooted<JSString*> jsOrg(cx, ToJSString(cx, *subject));
+ if (!jsOrg) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ JS::Rooted<JS::Value> certSubject(cx);
+ certSubject.setString(jsOrg);
+
+ if (!JS_DefineProperty(cx, moduleObj, "certSubject", certSubject,
+ JSPROP_ENUMERATE)) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+ }
+# endif // defined(XP_WIN)
+
+ if (!JS_DefineElement(cx, moduleArray, i, moduleObj, JSPROP_ENUMERATE)) {
+ mPromise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+ }
+
+ mPromise->MaybeResolve(moduleArray);
+ return NS_OK;
+ }
+
+ private:
+# if defined(XP_WIN)
+ void ObtainCertSubjects() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // NB: Currently we cannot lower this down to the profiler layer due to
+ // differing startup dependencies between the profiler and DllServices.
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+
+ for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) {
+ const SharedLibrary& info = mRawModules.GetEntry(i);
+
+ auto orgName = dllSvc->GetBinaryOrgName(info.GetModulePath().get());
+ if (orgName) {
+ mCertSubjects.InsertOrUpdate(info.GetModulePath(),
+ nsDependentString(orgName.get()));
+ }
+ }
+ }
+# endif // defined(XP_WIN)
+};
+
+class GetLoadedModulesRunnable final : public Runnable {
+ nsMainThreadPtrHandle<Promise> mPromise;
+
+ public:
+ explicit GetLoadedModulesRunnable(
+ const nsMainThreadPtrHandle<Promise>& aPromise)
+ : mozilla::Runnable("GetLoadedModulesRunnable"), mPromise(aPromise) {}
+
+ NS_IMETHOD
+ Run() override {
+ nsCOMPtr<nsIRunnable> resultRunnable = new GetLoadedModulesResultRunnable(
+ mPromise, SharedLibraryInfo::GetInfoForSelf());
+ return NS_DispatchToMainThread(resultRunnable);
+ }
+};
+#endif // MOZ_GECKO_PROFILER
+
+NS_IMETHODIMP
+TelemetryImpl::GetLoadedModules(JSContext* cx, Promise** aPromise) {
+#if defined(MOZ_GECKO_PROFILER)
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx);
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(global, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIThread> getModulesThread;
+ nsresult rv =
+ NS_NewNamedThread("TelemetryModule", getter_AddRefs(getModulesThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ nsMainThreadPtrHandle<Promise> mainThreadPromise(
+ new nsMainThreadPtrHolder<Promise>(
+ "TelemetryImpl::GetLoadedModules::Promise", promise));
+ nsCOMPtr<nsIRunnable> runnable =
+ new GetLoadedModulesRunnable(mainThreadPromise);
+ promise.forget(aPromise);
+
+ return getModulesThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+#else // MOZ_GECKO_PROFILER
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif // MOZ_GECKO_PROFILER
+}
+
+static bool IsValidBreakpadId(const std::string& breakpadId) {
+ if (breakpadId.size() < 33) {
+ return false;
+ }
+ for (char c : breakpadId) {
+ if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Read a stack from the given file name. In case of any error, aStack is
+// unchanged.
+static void ReadStack(PathCharPtr aFileName,
+ Telemetry::ProcessedStack& aStack) {
+ IFStream file(aFileName);
+
+ size_t numModules;
+ file >> numModules;
+ if (file.fail()) {
+ return;
+ }
+
+ char newline = file.get();
+ if (file.fail() || newline != '\n') {
+ return;
+ }
+
+ Telemetry::ProcessedStack stack;
+ for (size_t i = 0; i < numModules; ++i) {
+ std::string breakpadId;
+ file >> breakpadId;
+ if (file.fail() || !IsValidBreakpadId(breakpadId)) {
+ return;
+ }
+
+ char space = file.get();
+ if (file.fail() || space != ' ') {
+ return;
+ }
+
+ std::string moduleName;
+ getline(file, moduleName);
+ if (file.fail() || moduleName[0] == ' ') {
+ return;
+ }
+
+ Telemetry::ProcessedStack::Module module = {
+ NS_ConvertUTF8toUTF16(moduleName.c_str()),
+ nsCString(breakpadId.c_str(), breakpadId.size()),
+ };
+ stack.AddModule(module);
+ }
+
+ size_t numFrames;
+ file >> numFrames;
+ if (file.fail()) {
+ return;
+ }
+
+ newline = file.get();
+ if (file.fail() || newline != '\n') {
+ return;
+ }
+
+ for (size_t i = 0; i < numFrames; ++i) {
+ uint16_t index;
+ file >> index;
+ uintptr_t offset;
+ file >> std::hex >> offset >> std::dec;
+ if (file.fail()) {
+ return;
+ }
+
+ Telemetry::ProcessedStack::Frame frame = {offset, index};
+ stack.AddFrame(frame);
+ }
+
+ aStack = stack;
+}
+
+void TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) {
+ nsCOMPtr<nsIDirectoryEnumerator> files;
+ if (NS_FAILED(aProfileDir->GetDirectoryEntries(getter_AddRefs(files)))) {
+ return;
+ }
+
+ constexpr auto prefix = u"Telemetry.LateWriteFinal-"_ns;
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
+ nsAutoString leafName;
+ if (NS_FAILED(file->GetLeafName(leafName)) ||
+ !StringBeginsWith(leafName, prefix)) {
+ continue;
+ }
+
+ Telemetry::ProcessedStack stack;
+ ReadStack(file->NativePath().get(), stack);
+ if (stack.GetStackSize() != 0) {
+ mLateWritesStacks.AddStack(stack);
+ }
+ // Delete the file so that we don't report it again on the next run.
+ file->Remove(false);
+ }
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetLateWrites(JSContext* cx, JS::MutableHandle<JS::Value> ret) {
+ // The user must call AsyncReadTelemetryData first. We return an empty list
+ // instead of reporting a failure so that the rest of telemetry can uniformly
+ // handle the read not being available yet.
+
+ // FIXME: we allocate the js object again and again in the getter. We should
+ // figure out a way to cache it. In order to do that we have to call
+ // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl
+ // constructor, but it is not clear how to get a JSContext in there.
+ // Another option would be to call it in here when we first call
+ // CreateJSStackObject, but we would still need to figure out where to call
+ // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot
+ // and just set the pointer to nullptr is the telemetry destructor?
+
+ JSObject* report;
+ if (!mCachedTelemetryData) {
+ CombinedStacks empty;
+ report = CreateJSStackObject(cx, empty);
+ } else {
+ report = CreateJSStackObject(cx, mLateWritesStacks);
+ }
+
+ if (report == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ret.setObject(*report);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetHistogramById(const nsACString& name, JSContext* cx,
+ JS::MutableHandle<JS::Value> ret) {
+ return TelemetryHistogram::GetHistogramById(name, cx, ret);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetKeyedHistogramById(const nsACString& name, JSContext* cx,
+ JS::MutableHandle<JS::Value> ret) {
+ return TelemetryHistogram::GetKeyedHistogramById(name, cx, ret);
+}
+
+/**
+ * Indicates if Telemetry can record base data (FHR data). This is true if the
+ * FHR data reporting service or self-support are enabled.
+ *
+ * In the unlikely event that adding a new base probe is needed, please check
+ * the data collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection
+ * and talk to the Telemetry team.
+ */
+NS_IMETHODIMP
+TelemetryImpl::GetCanRecordBase(bool* ret) {
+ *ret = mCanRecordBase;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::SetCanRecordBase(bool canRecord) {
+#ifndef FUZZING
+ if (canRecord != mCanRecordBase) {
+ TelemetryHistogram::SetCanRecordBase(canRecord);
+ TelemetryScalar::SetCanRecordBase(canRecord);
+ TelemetryEvent::SetCanRecordBase(canRecord);
+ mCanRecordBase = canRecord;
+ }
+#endif
+ return NS_OK;
+}
+
+/**
+ * Indicates if Telemetry is allowed to record extended data. Returns false if
+ * the user hasn't opted into "extended Telemetry" on the Release channel, when
+ * the user has explicitly opted out of Telemetry on Nightly/Aurora/Beta or if
+ * manually set to false during tests. If the returned value is false, gathering
+ * of extended telemetry statistics is disabled.
+ */
+NS_IMETHODIMP
+TelemetryImpl::GetCanRecordExtended(bool* ret) {
+ *ret = mCanRecordExtended;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::SetCanRecordExtended(bool canRecord) {
+#ifndef FUZZING
+ if (canRecord != mCanRecordExtended) {
+ TelemetryHistogram::SetCanRecordExtended(canRecord);
+ TelemetryScalar::SetCanRecordExtended(canRecord);
+ TelemetryEvent::SetCanRecordExtended(canRecord);
+ mCanRecordExtended = canRecord;
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetCanRecordReleaseData(bool* ret) {
+ *ret = mCanRecordBase;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetCanRecordPrereleaseData(bool* ret) {
+ *ret = mCanRecordExtended;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetIsOfficialTelemetry(bool* ret) {
+#if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) && \
+ !defined(DEBUG)
+ *ret = true;
+#else
+ *ret = false;
+#endif
+ return NS_OK;
+}
+
+already_AddRefed<nsITelemetry> TelemetryImpl::CreateTelemetryInstance() {
+ {
+ auto lock = sTelemetry.Lock();
+ MOZ_ASSERT(
+ *lock == nullptr,
+ "CreateTelemetryInstance may only be called once, via GetService()");
+ }
+
+ bool useTelemetry = false;
+#ifndef FUZZING
+ if (XRE_IsParentProcess() || XRE_IsContentProcess() || XRE_IsGPUProcess() ||
+ XRE_IsRDDProcess() || XRE_IsSocketProcess() || XRE_IsUtilityProcess()) {
+ useTelemetry = true;
+ }
+#endif
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // Background tasks collect per-task metrics with Glean.
+ useTelemetry = false;
+ }
+#endif
+
+ // First, initialize the TelemetryHistogram and TelemetryScalar global states.
+ TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry);
+ TelemetryScalar::InitializeGlobalState(useTelemetry, useTelemetry);
+
+ // Only record events from the parent process.
+ TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(),
+ XRE_IsParentProcess());
+
+ // Currently, only UserInteractions from the parent process are recorded.
+ TelemetryUserInteraction::InitializeGlobalState(useTelemetry, useTelemetry);
+
+ // Now, create and initialize the Telemetry global state.
+ TelemetryImpl* telemetry = new TelemetryImpl();
+ {
+ auto lock = sTelemetry.Lock();
+ *lock = telemetry;
+ // AddRef for the local reference before releasing the lock.
+ NS_ADDREF(telemetry);
+ }
+
+ // AddRef for the caller
+ nsCOMPtr<nsITelemetry> ret = telemetry;
+
+ telemetry->mCanRecordBase = useTelemetry;
+ telemetry->mCanRecordExtended = useTelemetry;
+
+ telemetry->InitMemoryReporter();
+ InitHistogramRecordingEnabled(); // requires sTelemetry to exist
+
+ return ret.forget();
+}
+
+void TelemetryImpl::ShutdownTelemetry() {
+ // No point in collecting IO beyond this point
+ ClearIOReporting();
+ {
+ auto lock = sTelemetry.Lock();
+ NS_IF_RELEASE(lock.ref());
+ }
+
+ // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global
+ // states, so as to release any heap storage that would otherwise be kept
+ // alive by it.
+ TelemetryHistogram::DeInitializeGlobalState();
+ TelemetryScalar::DeInitializeGlobalState();
+ TelemetryEvent::DeInitializeGlobalState();
+
+ TelemetryUserInteraction::DeInitializeGlobalState();
+ TelemetryIPCAccumulator::DeInitializeGlobalState();
+}
+
+void TelemetryImpl::StoreSlowSQL(const nsACString& sql, uint32_t delay,
+ SanitizedState state) {
+ auto lock = sTelemetry.Lock();
+ auto telemetry = lock.ref();
+ AutoHashtable<SlowSQLEntryType>* slowSQLMap = nullptr;
+ if (state == Sanitized)
+ slowSQLMap = &(telemetry->mSanitizedSQL);
+ else
+ slowSQLMap = &(telemetry->mPrivateSQL);
+
+ MutexAutoLock hashMutex(telemetry->mHashMutex);
+
+ SlowSQLEntryType* entry = slowSQLMap->GetEntry(sql);
+ if (!entry) {
+ entry = slowSQLMap->PutEntry(sql);
+ if (MOZ_UNLIKELY(!entry)) return;
+ entry->GetModifiableData()->mainThread.hitCount = 0;
+ entry->GetModifiableData()->mainThread.totalTime = 0;
+ entry->GetModifiableData()->otherThreads.hitCount = 0;
+ entry->GetModifiableData()->otherThreads.totalTime = 0;
+ }
+
+ if (NS_IsMainThread()) {
+ entry->GetModifiableData()->mainThread.hitCount++;
+ entry->GetModifiableData()->mainThread.totalTime += delay;
+ } else {
+ entry->GetModifiableData()->otherThreads.hitCount++;
+ entry->GetModifiableData()->otherThreads.totalTime += delay;
+ }
+}
+
+/**
+ * This method replaces string literals in SQL strings with the word :private
+ *
+ * States used in this state machine:
+ *
+ * NORMAL:
+ * - This is the active state when not iterating over a string literal or
+ * comment
+ *
+ * SINGLE_QUOTE:
+ * - Defined here: http://www.sqlite.org/lang_expr.html
+ * - This state represents iterating over a string literal opened with
+ * a single quote.
+ * - A single quote within the string can be encoded by putting 2 single quotes
+ * in a row, e.g. 'This literal contains an escaped quote '''
+ * - Any double quotes found within a single-quoted literal are ignored
+ * - This state covers BLOB literals, e.g. X'ABC123'
+ * - The string literal and the enclosing quotes will be replaced with
+ * the text :private
+ *
+ * DOUBLE_QUOTE:
+ * - Same rules as the SINGLE_QUOTE state.
+ * - According to http://www.sqlite.org/lang_keywords.html,
+ * SQLite interprets text in double quotes as an identifier unless it's used in
+ * a context where it cannot be resolved to an identifier and a string literal
+ * is allowed. This method removes text in double-quotes for safety.
+ *
+ * DASH_COMMENT:
+ * - http://www.sqlite.org/lang_comment.html
+ * - A dash comment starts with two dashes in a row,
+ * e.g. DROP TABLE foo -- a comment
+ * - Any text following two dashes in a row is interpreted as a comment until
+ * end of input or a newline character
+ * - Any quotes found within the comment are ignored and no replacements made
+ *
+ * C_STYLE_COMMENT:
+ * - http://www.sqlite.org/lang_comment.html
+ * - A C-style comment starts with a forward slash and an asterisk, and ends
+ * with an asterisk and a forward slash
+ * - Any text following comment start is interpreted as a comment up to end of
+ * input or comment end
+ * - Any quotes found within the comment are ignored and no replacements made
+ */
+nsCString TelemetryImpl::SanitizeSQL(const nsACString& sql) {
+ nsCString output;
+ int length = sql.Length();
+
+ typedef enum {
+ NORMAL,
+ SINGLE_QUOTE,
+ DOUBLE_QUOTE,
+ DASH_COMMENT,
+ C_STYLE_COMMENT,
+ } State;
+
+ State state = NORMAL;
+ int fragmentStart = 0;
+ for (int i = 0; i < length; i++) {
+ char character = sql[i];
+ char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0';
+
+ switch (character) {
+ case '\'':
+ case '"':
+ if (state == NORMAL) {
+ state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE;
+ output +=
+ nsDependentCSubstring(sql, fragmentStart, i - fragmentStart);
+ output += ":private";
+ fragmentStart = -1;
+ } else if ((state == SINGLE_QUOTE && character == '\'') ||
+ (state == DOUBLE_QUOTE && character == '"')) {
+ if (nextCharacter == character) {
+ // Two consecutive quotes within a string literal are a single
+ // escaped quote
+ i++;
+ } else {
+ state = NORMAL;
+ fragmentStart = i + 1;
+ }
+ }
+ break;
+ case '-':
+ if (state == NORMAL) {
+ if (nextCharacter == '-') {
+ state = DASH_COMMENT;
+ i++;
+ }
+ }
+ break;
+ case '\n':
+ if (state == DASH_COMMENT) {
+ state = NORMAL;
+ }
+ break;
+ case '/':
+ if (state == NORMAL) {
+ if (nextCharacter == '*') {
+ state = C_STYLE_COMMENT;
+ i++;
+ }
+ }
+ break;
+ case '*':
+ if (state == C_STYLE_COMMENT) {
+ if (nextCharacter == '/') {
+ state = NORMAL;
+ }
+ }
+ break;
+ default:
+ continue;
+ }
+ }
+
+ if ((fragmentStart >= 0) && fragmentStart < length)
+ output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart);
+
+ return output;
+}
+
+// An allowlist mechanism to prevent Telemetry reporting on Addon & Thunderbird
+// DBs.
+struct TrackedDBEntry {
+ const char* mName;
+ const uint32_t mNameLength;
+
+ // This struct isn't meant to be used beyond the static arrays below.
+ constexpr TrackedDBEntry(const char* aName, uint32_t aNameLength)
+ : mName(aName), mNameLength(aNameLength) {}
+
+ TrackedDBEntry() = delete;
+ TrackedDBEntry(TrackedDBEntry&) = delete;
+};
+
+#define TRACKEDDB_ENTRY(_name) \
+ { _name, (sizeof(_name) - 1) }
+
+// An allowlist of database names. If the database name exactly matches one of
+// these then its SQL statements will always be recorded.
+static constexpr TrackedDBEntry kTrackedDBs[] = {
+ // IndexedDB for about:home, see aboutHome.js
+ TRACKEDDB_ENTRY("818200132aebmoouht.sqlite"),
+ TRACKEDDB_ENTRY("addons.sqlite"),
+ TRACKEDDB_ENTRY("content-prefs.sqlite"),
+ TRACKEDDB_ENTRY("cookies.sqlite"),
+ TRACKEDDB_ENTRY("extensions.sqlite"),
+ TRACKEDDB_ENTRY("favicons.sqlite"),
+ TRACKEDDB_ENTRY("formhistory.sqlite"),
+ TRACKEDDB_ENTRY("index.sqlite"),
+ TRACKEDDB_ENTRY("netpredictions.sqlite"),
+ TRACKEDDB_ENTRY("permissions.sqlite"),
+ TRACKEDDB_ENTRY("places.sqlite"),
+ TRACKEDDB_ENTRY("reading-list.sqlite"),
+ TRACKEDDB_ENTRY("search.sqlite"),
+ TRACKEDDB_ENTRY("urlclassifier3.sqlite"),
+ TRACKEDDB_ENTRY("webappsstore.sqlite")};
+
+// An allowlist of database name prefixes. If the database name begins with
+// one of these prefixes then its SQL statements will always be recorded.
+static const TrackedDBEntry kTrackedDBPrefixes[] = {
+ TRACKEDDB_ENTRY("indexedDB-")};
+
+#undef TRACKEDDB_ENTRY
+
+// Slow SQL statements will be automatically
+// trimmed to kMaxSlowStatementLength characters.
+// This limit doesn't include the ellipsis and DB name,
+// that are appended at the end of the stored statement.
+const uint32_t kMaxSlowStatementLength = 1000;
+
+void TelemetryImpl::RecordSlowStatement(const nsACString& sql,
+ const nsACString& dbName,
+ uint32_t delay) {
+ MOZ_ASSERT(!sql.IsEmpty());
+ MOZ_ASSERT(!dbName.IsEmpty());
+
+ {
+ auto lock = sTelemetry.Lock();
+ if (!lock.ref() || !TelemetryHistogram::CanRecordExtended()) {
+ return;
+ }
+ }
+
+ bool recordStatement = false;
+
+ for (const TrackedDBEntry& nameEntry : kTrackedDBs) {
+ MOZ_ASSERT(nameEntry.mNameLength);
+ const nsDependentCString name(nameEntry.mName, nameEntry.mNameLength);
+ if (dbName == name) {
+ recordStatement = true;
+ break;
+ }
+ }
+
+ if (!recordStatement) {
+ for (const TrackedDBEntry& prefixEntry : kTrackedDBPrefixes) {
+ MOZ_ASSERT(prefixEntry.mNameLength);
+ const nsDependentCString prefix(prefixEntry.mName,
+ prefixEntry.mNameLength);
+ if (StringBeginsWith(dbName, prefix)) {
+ recordStatement = true;
+ break;
+ }
+ }
+ }
+
+ if (recordStatement) {
+ nsAutoCString sanitizedSQL(SanitizeSQL(sql));
+ if (sanitizedSQL.Length() > kMaxSlowStatementLength) {
+ sanitizedSQL.SetLength(kMaxSlowStatementLength);
+ sanitizedSQL += "...";
+ }
+ sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get());
+ StoreSlowSQL(sanitizedSQL, delay, Sanitized);
+ } else {
+ // Report aggregate DB-level statistics for addon DBs
+ nsAutoCString aggregate;
+ aggregate.AppendPrintf("Untracked SQL for %s",
+ nsPromiseFlatCString(dbName).get());
+ StoreSlowSQL(aggregate, delay, Sanitized);
+ }
+
+ nsAutoCString fullSQL;
+ fullSQL.AppendPrintf("%s /* %s */", nsPromiseFlatCString(sql).get(),
+ nsPromiseFlatCString(dbName).get());
+ StoreSlowSQL(fullSQL, delay, Unsanitized);
+}
+
+bool TelemetryImpl::CanRecordBase() {
+ auto lock = sTelemetry.Lock();
+ auto telemetry = lock.ref();
+ if (!telemetry) {
+ return false;
+ }
+ bool canRecordBase;
+ nsresult rv = telemetry->GetCanRecordBase(&canRecordBase);
+ return NS_SUCCEEDED(rv) && canRecordBase;
+}
+
+bool TelemetryImpl::CanRecordExtended() {
+ auto lock = sTelemetry.Lock();
+ auto telemetry = lock.ref();
+ if (!telemetry) {
+ return false;
+ }
+ bool canRecordExtended;
+ nsresult rv = telemetry->GetCanRecordExtended(&canRecordExtended);
+ return NS_SUCCEEDED(rv) && canRecordExtended;
+}
+
+bool TelemetryImpl::CanRecordReleaseData() { return CanRecordBase(); }
+
+bool TelemetryImpl::CanRecordPrereleaseData() { return CanRecordExtended(); }
+
+NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter)
+
+NS_IMETHODIMP
+TelemetryImpl::GetFileIOReports(JSContext* cx,
+ JS::MutableHandle<JS::Value> ret) {
+ if (sTelemetryIOObserver) {
+ JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
+ if (!obj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) {
+ return NS_ERROR_FAILURE;
+ }
+ ret.setObject(*obj);
+ return NS_OK;
+ }
+ ret.setNull();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::MsSinceProcessStart(double* aResult) {
+ return Telemetry::Common::MsSinceProcessStart(aResult);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::MsSinceProcessStartIncludingSuspend(double* aResult) {
+ return Telemetry::Common::MsSinceProcessStartIncludingSuspend(aResult);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::MsSinceProcessStartExcludingSuspend(double* aResult) {
+ return Telemetry::Common::MsSinceProcessStartExcludingSuspend(aResult);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::MsSystemNow(double* aResult) {
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
+ timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ *aResult = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+#else
+ using namespace std::chrono;
+ milliseconds ms =
+ duration_cast<milliseconds>(system_clock::now().time_since_epoch());
+ *aResult = static_cast<double>(ms.count());
+#endif // XP_UNIX && !XP_DARWIN
+
+ return NS_OK;
+}
+
+// Telemetry Scalars IDL Implementation
+
+NS_IMETHODIMP
+TelemetryImpl::ScalarAdd(const nsACString& aName, JS::Handle<JS::Value> aVal,
+ JSContext* aCx) {
+ return TelemetryScalar::Add(aName, aVal, aCx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::ScalarSet(const nsACString& aName, JS::Handle<JS::Value> aVal,
+ JSContext* aCx) {
+ return TelemetryScalar::Set(aName, aVal, aCx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::ScalarSetMaximum(const nsACString& aName,
+ JS::Handle<JS::Value> aVal, JSContext* aCx) {
+ return TelemetryScalar::SetMaximum(aName, aVal, aCx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::KeyedScalarAdd(const nsACString& aName, const nsAString& aKey,
+ JS::Handle<JS::Value> aVal, JSContext* aCx) {
+ return TelemetryScalar::Add(aName, aKey, aVal, aCx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::KeyedScalarSet(const nsACString& aName, const nsAString& aKey,
+ JS::Handle<JS::Value> aVal, JSContext* aCx) {
+ return TelemetryScalar::Set(aName, aKey, aVal, aCx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::KeyedScalarSetMaximum(const nsACString& aName,
+ const nsAString& aKey,
+ JS::Handle<JS::Value> aVal,
+ JSContext* aCx) {
+ return TelemetryScalar::SetMaximum(aName, aKey, aVal, aCx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::RegisterScalars(const nsACString& aCategoryName,
+ JS::Handle<JS::Value> aScalarData,
+ JSContext* cx) {
+ return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, false,
+ cx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::RegisterBuiltinScalars(const nsACString& aCategoryName,
+ JS::Handle<JS::Value> aScalarData,
+ JSContext* cx) {
+ return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, true, cx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::ClearScalars() {
+ TelemetryScalar::ClearScalars();
+ return NS_OK;
+}
+
+// Telemetry Event IDL implementation.
+
+NS_IMETHODIMP
+TelemetryImpl::RecordEvent(const nsACString& aCategory,
+ const nsACString& aMethod, const nsACString& aObject,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aExtra, JSContext* aCx,
+ uint8_t optional_argc) {
+ return TelemetryEvent::RecordEvent(aCategory, aMethod, aObject, aValue,
+ aExtra, aCx, optional_argc);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::SnapshotEvents(uint32_t aDataset, bool aClear,
+ uint32_t aEventLimit, JSContext* aCx,
+ uint8_t optional_argc,
+ JS::MutableHandle<JS::Value> aResult) {
+ return TelemetryEvent::CreateSnapshots(aDataset, aClear, aEventLimit, aCx,
+ optional_argc, aResult);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::RegisterEvents(const nsACString& aCategory,
+ JS::Handle<JS::Value> aEventData, JSContext* cx) {
+ return TelemetryEvent::RegisterEvents(aCategory, aEventData, false, cx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::RegisterBuiltinEvents(const nsACString& aCategory,
+ JS::Handle<JS::Value> aEventData,
+ JSContext* cx) {
+ return TelemetryEvent::RegisterEvents(aCategory, aEventData, true, cx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::ClearEvents() {
+ TelemetryEvent::ClearEvents();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::SetEventRecordingEnabled(const nsACString& aCategory,
+ bool aEnabled) {
+ TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::FlushBatchedChildTelemetry() {
+ TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::EarlyInit() {
+ Unused << MemoryTelemetry::Get();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::DelayedInit() {
+ MemoryTelemetry::Get().DelayedInit();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::Shutdown() {
+ MemoryTelemetry::Get().Shutdown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GatherMemory(JSContext* aCx, Promise** aResult) {
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ MemoryTelemetry::Get().GatherReports(
+ [promise]() { promise->MaybeResolve(JS::UndefinedHandleValue); });
+
+ promise.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetAllStores(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ StringHashSet stores;
+ nsresult rv;
+
+ rv = TelemetryHistogram::GetAllStores(stores);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = TelemetryScalar::GetAllStores(stores);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ JS::RootedVector<JS::Value> allStores(aCx);
+ if (!allStores.reserve(stores.Count())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& value : stores) {
+ JS::Rooted<JS::Value> store(aCx);
+
+ store.setString(ToJSString(aCx, value));
+ if (!allStores.append(store)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ JS::Rooted<JSObject*> rarray(aCx, JS::NewArrayObject(aCx, allStores));
+ if (rarray == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+ aResult.setObject(*rarray);
+
+ return NS_OK;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// EXTERNALLY VISIBLE FUNCTIONS in no name space
+// These are NOT listed in Telemetry.h
+
+/**
+ * The XRE_TelemetryAdd function is to be used by embedding applications
+ * that can't use mozilla::Telemetry::Accumulate() directly.
+ */
+void XRE_TelemetryAccumulate(int aID, uint32_t aSample) {
+ mozilla::Telemetry::Accumulate((mozilla::Telemetry::HistogramID)aID, aSample);
+}
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// EXTERNALLY VISIBLE FUNCTIONS in mozilla::
+// These are NOT listed in Telemetry.h
+
+namespace mozilla {
+
+void RecordShutdownStartTimeStamp() {
+#ifdef DEBUG
+ // FIXME: this function should only be called once, since it should be called
+ // at the earliest point we *know* we are shutting down. Unfortunately
+ // this assert has been firing. Given that if we are called multiple times
+ // we just keep the last timestamp, the assert is commented for now.
+ static bool recorded = false;
+ // MOZ_ASSERT(!recorded);
+ (void)
+ recorded; // Silence unused-var warnings (remove when assert re-enabled)
+ recorded = true;
+#endif
+
+ if (!Telemetry::CanRecordExtended()) return;
+
+ gRecordedShutdownStartTime = TimeStamp::Now();
+
+ GetShutdownTimeFileName();
+}
+
+void RecordShutdownEndTimeStamp() {
+ if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName)
+ return;
+
+ PathString name(gRecordedShutdownTimeFileName);
+ free(const_cast<PathChar*>(gRecordedShutdownTimeFileName));
+ gRecordedShutdownTimeFileName = nullptr;
+ gAlreadyFreedShutdownTimeFileName = true;
+
+ if (gRecordedShutdownStartTime.IsNull()) {
+ // If |CanRecordExtended()| is true before |AsyncFetchTelemetryData| is
+ // called and then disabled before shutdown, |RecordShutdownStartTimeStamp|
+ // will bail out and we will end up with a null |gRecordedShutdownStartTime|
+ // here. This can happen during tests.
+ return;
+ }
+
+ nsTAutoString<PathChar> tmpName(name);
+ tmpName.AppendLiteral(".tmp");
+ RefPtr<nsLocalFile> tmpFile = new nsLocalFile(tmpName);
+ FILE* f;
+ if (NS_FAILED(tmpFile->OpenANSIFileDesc("w", &f)) || !f) return;
+ // On a normal release build this should be called just before
+ // calling _exit, but on a debug build or when the user forces a full
+ // shutdown this is called as late as possible, so we have to
+ // allow this write as write poisoning will be enabled.
+ MozillaRegisterDebugFILE(f);
+
+ TimeStamp now = TimeStamp::Now();
+ MOZ_ASSERT(now >= gRecordedShutdownStartTime);
+ TimeDuration diff = now - gRecordedShutdownStartTime;
+ uint32_t diff2 = diff.ToMilliseconds();
+ int written = fprintf(f, "%d\n", diff2);
+ MozillaUnRegisterDebugFILE(f);
+ int rv = fclose(f);
+ if (written < 0 || rv != 0) {
+ tmpFile->Remove(false);
+ return;
+ }
+ RefPtr<nsLocalFile> file = new nsLocalFile(name);
+ nsAutoString leafName;
+ file->GetLeafName(leafName);
+ tmpFile->RenameTo(nullptr, leafName);
+}
+
+} // namespace mozilla
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry::
+// These are listed in Telemetry.h
+
+namespace mozilla::Telemetry {
+
+// The external API for controlling recording state
+void SetHistogramRecordingEnabled(HistogramID aID, bool aEnabled) {
+ TelemetryHistogram::SetHistogramRecordingEnabled(aID, aEnabled);
+}
+
+void Accumulate(HistogramID aHistogram, uint32_t aSample) {
+ TelemetryHistogram::Accumulate(aHistogram, aSample);
+}
+
+void Accumulate(HistogramID aHistogram, const nsTArray<uint32_t>& aSamples) {
+ TelemetryHistogram::Accumulate(aHistogram, aSamples);
+}
+
+void Accumulate(HistogramID aID, const nsCString& aKey, uint32_t aSample) {
+ TelemetryHistogram::Accumulate(aID, aKey, aSample);
+}
+
+void Accumulate(HistogramID aID, const nsCString& aKey,
+ const nsTArray<uint32_t>& aSamples) {
+ TelemetryHistogram::Accumulate(aID, aKey, aSamples);
+}
+
+void Accumulate(const char* name, uint32_t sample) {
+ TelemetryHistogram::Accumulate(name, sample);
+}
+
+void Accumulate(const char* name, const nsCString& key, uint32_t sample) {
+ TelemetryHistogram::Accumulate(name, key, sample);
+}
+
+void AccumulateCategorical(HistogramID id, const nsCString& label) {
+ TelemetryHistogram::AccumulateCategorical(id, label);
+}
+
+void AccumulateCategorical(HistogramID id, const nsTArray<nsCString>& labels) {
+ TelemetryHistogram::AccumulateCategorical(id, labels);
+}
+
+void AccumulateTimeDelta(HistogramID aHistogram, TimeStamp start,
+ TimeStamp end) {
+ if (start > end) {
+ Accumulate(aHistogram, 0);
+ return;
+ }
+ Accumulate(aHistogram, static_cast<uint32_t>((end - start).ToMilliseconds()));
+}
+
+void AccumulateTimeDelta(HistogramID aHistogram, const nsCString& key,
+ TimeStamp start, TimeStamp end) {
+ if (start > end) {
+ Accumulate(aHistogram, key, 0);
+ return;
+ }
+ Accumulate(aHistogram, key,
+ static_cast<uint32_t>((end - start).ToMilliseconds()));
+}
+const char* GetHistogramName(HistogramID id) {
+ return TelemetryHistogram::GetHistogramName(id);
+}
+
+bool CanRecordBase() { return TelemetryImpl::CanRecordBase(); }
+
+bool CanRecordExtended() { return TelemetryImpl::CanRecordExtended(); }
+
+bool CanRecordReleaseData() { return TelemetryImpl::CanRecordReleaseData(); }
+
+bool CanRecordPrereleaseData() {
+ return TelemetryImpl::CanRecordPrereleaseData();
+}
+
+void RecordSlowSQLStatement(const nsACString& statement,
+ const nsACString& dbName, uint32_t delay) {
+ TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
+}
+
+void Init() {
+ // Make the service manager hold a long-lived reference to the service
+ nsCOMPtr<nsITelemetry> telemetryService =
+ do_GetService("@mozilla.org/base/telemetry;1");
+ MOZ_ASSERT(telemetryService);
+}
+
+void WriteFailedProfileLock(nsIFile* aProfileDir) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ int64_t fileSize = 0;
+ rv = file->GetFileSize(&fileSize);
+ // It's expected that the file might not exist yet
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ nsCOMPtr<nsIRandomAccessStream> fileRandomAccessStream;
+ rv = NS_NewLocalFileRandomAccessStream(getter_AddRefs(fileRandomAccessStream),
+ file, PR_RDWR | PR_CREATE_FILE, 0640);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize);
+ unsigned int failedLockCount = 0;
+ if (fileSize > 0) {
+ nsCOMPtr<nsIInputStream> inStream =
+ do_QueryInterface(fileRandomAccessStream);
+ NS_ENSURE_TRUE_VOID(inStream);
+ if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) {
+ failedLockCount = 0;
+ }
+ }
+ ++failedLockCount;
+ nsAutoCString bufStr;
+ bufStr.AppendInt(static_cast<int>(failedLockCount));
+ // If we read in an existing failed lock count, we need to reset the file ptr
+ if (fileSize > 0) {
+ rv = fileRandomAccessStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ nsCOMPtr<nsIOutputStream> outStream =
+ do_QueryInterface(fileRandomAccessStream);
+ uint32_t bytesLeft = bufStr.Length();
+ const char* bytes = bufStr.get();
+ do {
+ uint32_t written = 0;
+ rv = outStream->Write(bytes, bytesLeft, &written);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ bytes += written;
+ bytesLeft -= written;
+ } while (bytesLeft > 0);
+ fileRandomAccessStream->SetEOF();
+}
+
+void InitIOReporting(nsIFile* aXreDir) {
+ // Never initialize twice
+ if (sTelemetryIOObserver) {
+ return;
+ }
+
+ sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir);
+ IOInterposer::Register(IOInterposeObserver::OpAllWithStaging,
+ sTelemetryIOObserver);
+}
+
+void SetProfileDir(nsIFile* aProfD) {
+ if (!sTelemetryIOObserver || !aProfD) {
+ return;
+ }
+ nsAutoString profDirPath;
+ nsresult rv = aProfD->GetPath(profDirPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ sTelemetryIOObserver->AddPath(profDirPath, u"{profile}"_ns);
+}
+
+// Scalar API C++ Endpoints
+
+void ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aVal) {
+ TelemetryScalar::Add(aId, aVal);
+}
+
+void ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aVal) {
+ TelemetryScalar::Set(aId, aVal);
+}
+
+void ScalarSet(mozilla::Telemetry::ScalarID aId, bool aVal) {
+ TelemetryScalar::Set(aId, aVal);
+}
+
+void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aVal) {
+ TelemetryScalar::Set(aId, aVal);
+}
+
+void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aVal) {
+ TelemetryScalar::SetMaximum(aId, aVal);
+}
+
+void ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
+ uint32_t aVal) {
+ TelemetryScalar::Add(aId, aKey, aVal);
+}
+
+void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
+ uint32_t aVal) {
+ TelemetryScalar::Set(aId, aKey, aVal);
+}
+
+void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
+ bool aVal) {
+ TelemetryScalar::Set(aId, aKey, aVal);
+}
+
+void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
+ uint32_t aVal) {
+ TelemetryScalar::SetMaximum(aId, aKey, aVal);
+}
+
+void RecordEvent(
+ mozilla::Telemetry::EventID aId, const mozilla::Maybe<nsCString>& aValue,
+ const mozilla::Maybe<CopyableTArray<EventExtraEntry>>& aExtra) {
+ TelemetryEvent::RecordEventNative(aId, aValue, aExtra);
+}
+
+void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled) {
+ TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
+}
+
+void ShutdownTelemetry() { TelemetryImpl::ShutdownTelemetry(); }
+
+} // namespace mozilla::Telemetry
+
+NS_IMPL_COMPONENT_FACTORY(nsITelemetry) {
+ return TelemetryImpl::CreateTelemetryInstance().downcast<nsISupports>();
+}