diff options
Diffstat (limited to 'toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp')
-rw-r--r-- | toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp | 182 |
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 |