summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp')
-rw-r--r--toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp182
1 files changed, 182 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp b/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp
new file mode 100644
index 0000000000..c0792bb48c
--- /dev/null
+++ b/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 "TelemetryIOInterposeObserver.h"
+#include "core/TelemetryCommon.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineUCProperty
+#include "js/PropertyDescriptor.h" // JSPROP_ENUMERATE, JSPROP_READONLY
+#include "js/ValueArray.h"
+#include "nsIFile.h"
+
+namespace mozilla::Telemetry {
+
+TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir)
+ : mCurStage(STAGE_STARTUP) {
+ nsAutoString xreDirPath;
+ nsresult rv = aXreDir->GetPath(xreDirPath);
+ if (NS_SUCCEEDED(rv)) {
+ AddPath(xreDirPath, u"{xre}"_ns);
+ }
+}
+
+void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath,
+ const nsAString& aSubstName) {
+ mSafeDirs.AppendElement(SafeDir(aPath, aSubstName));
+}
+
+// Threshold for reporting slow main-thread I/O (50 milliseconds).
+const TimeDuration kTelemetryReportThreshold =
+ TimeDuration::FromMilliseconds(50);
+
+void TelemetryIOInterposeObserver::Observe(Observation& aOb) {
+ // We only report main-thread I/O
+ if (!IsMainThread()) {
+ return;
+ }
+
+ if (aOb.ObservedOperation() == OpNextStage) {
+ mCurStage = NextStage(mCurStage);
+ MOZ_ASSERT(mCurStage < NUM_STAGES);
+ return;
+ }
+
+ if (aOb.Duration() < kTelemetryReportThreshold) {
+ return;
+ }
+
+ // Get the filename
+ nsAutoString filename;
+ aOb.Filename(filename);
+
+ // Discard observations without filename
+ if (filename.IsEmpty()) {
+ return;
+ }
+
+#if defined(XP_WIN)
+ auto comparator = nsCaseInsensitiveStringComparator;
+#else
+ auto comparator = nsTDefaultStringComparator<char16_t>;
+#endif
+ nsAutoString processedName;
+ uint32_t safeDirsLen = mSafeDirs.Length();
+ for (uint32_t i = 0; i < safeDirsLen; ++i) {
+ if (StringBeginsWith(filename, mSafeDirs[i].mPath, comparator)) {
+ processedName = mSafeDirs[i].mSubstName;
+ processedName += Substring(filename, mSafeDirs[i].mPath.Length());
+ break;
+ }
+ }
+
+ if (processedName.IsEmpty()) {
+ return;
+ }
+
+ // Create a new entry or retrieve the existing one
+ FileIOEntryType* entry = mFileStats.PutEntry(processedName);
+ if (entry) {
+ FileStats& stats = entry->GetModifiableData()->mStats[mCurStage];
+ // Update the statistics
+ stats.totalTime += (double)aOb.Duration().ToMilliseconds();
+ switch (aOb.ObservedOperation()) {
+ case OpCreateOrOpen:
+ stats.creates++;
+ break;
+ case OpRead:
+ stats.reads++;
+ break;
+ case OpWrite:
+ stats.writes++;
+ break;
+ case OpFSync:
+ stats.fsyncs++;
+ break;
+ case OpStat:
+ stats.stats++;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry,
+ JSContext* cx,
+ JS::Handle<JSObject*> obj) {
+ JS::RootedValueArray<NUM_STAGES> stages(cx);
+
+ FileStatsByStage& statsByStage = *entry->GetModifiableData();
+ for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) {
+ FileStats& fileStats = statsByStage.mStats[s];
+
+ if (fileStats.totalTime == 0 && fileStats.creates == 0 &&
+ fileStats.reads == 0 && fileStats.writes == 0 &&
+ fileStats.fsyncs == 0 && fileStats.stats == 0) {
+ // Don't add an array that contains no information
+ stages[s].setNull();
+ continue;
+ }
+
+ // Array we want to report
+ JS::RootedValueArray<6> stats(cx);
+ stats[0].setNumber(fileStats.totalTime);
+ stats[1].setNumber(fileStats.creates);
+ stats[2].setNumber(fileStats.reads);
+ stats[3].setNumber(fileStats.writes);
+ stats[4].setNumber(fileStats.fsyncs);
+ stats[5].setNumber(fileStats.stats);
+
+ // Create jsStats as array of elements above
+ JS::Rooted<JSObject*> jsStats(cx, JS::NewArrayObject(cx, stats));
+ if (!jsStats) {
+ continue;
+ }
+
+ stages[s].setObject(*jsStats);
+ }
+
+ JS::Rooted<JSObject*> jsEntry(cx, JS::NewArrayObject(cx, stages));
+ if (!jsEntry) {
+ return false;
+ }
+
+ // Add jsEntry to top-level dictionary
+ const nsAString& key = entry->GetKey();
+ return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), jsEntry,
+ JSPROP_ENUMERATE | JSPROP_READONLY);
+}
+
+bool TelemetryIOInterposeObserver::ReflectIntoJS(
+ JSContext* cx, JS::Handle<JSObject*> rootObj) {
+ return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj);
+}
+
+/**
+ * Get size of hash table with file stats
+ */
+
+size_t TelemetryIOInterposeObserver::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t TelemetryIOInterposeObserver::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = 0;
+ size += mFileStats.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mFileStats.ConstIter(); !iter.Done(); iter.Next()) {
+ size += iter.Get()->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ size += mSafeDirs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ uint32_t safeDirsLen = mSafeDirs.Length();
+ for (uint32_t i = 0; i < safeDirsLen; ++i) {
+ size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return size;
+}
+
+} // namespace mozilla::Telemetry