1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "TelemetryIOInterposeObserver.h"
8 #include "core/TelemetryCommon.h"
9 #include "js/Array.h"  // JS::NewArrayObject
10 #include "nsIFile.h"
11 
12 namespace mozilla::Telemetry {
13 
TelemetryIOInterposeObserver(nsIFile * aXreDir)14 TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir)
15     : mCurStage(STAGE_STARTUP) {
16   nsAutoString xreDirPath;
17   nsresult rv = aXreDir->GetPath(xreDirPath);
18   if (NS_SUCCEEDED(rv)) {
19     AddPath(xreDirPath, u"{xre}"_ns);
20   }
21 }
22 
AddPath(const nsAString & aPath,const nsAString & aSubstName)23 void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath,
24                                            const nsAString& aSubstName) {
25   mSafeDirs.AppendElement(SafeDir(aPath, aSubstName));
26 }
27 
28 // Threshold for reporting slow main-thread I/O (50 milliseconds).
29 const TimeDuration kTelemetryReportThreshold =
30     TimeDuration::FromMilliseconds(50);
31 
Observe(Observation & aOb)32 void TelemetryIOInterposeObserver::Observe(Observation& aOb) {
33   // We only report main-thread I/O
34   if (!IsMainThread()) {
35     return;
36   }
37 
38   if (aOb.ObservedOperation() == OpNextStage) {
39     mCurStage = NextStage(mCurStage);
40     MOZ_ASSERT(mCurStage < NUM_STAGES);
41     return;
42   }
43 
44   if (aOb.Duration() < kTelemetryReportThreshold) {
45     return;
46   }
47 
48   // Get the filename
49   nsAutoString filename;
50   aOb.Filename(filename);
51 
52   // Discard observations without filename
53   if (filename.IsEmpty()) {
54     return;
55   }
56 
57 #if defined(XP_WIN)
58   auto comparator = nsCaseInsensitiveStringComparator;
59 #else
60   auto comparator = nsTDefaultStringComparator<char16_t>;
61 #endif
62   nsAutoString processedName;
63   uint32_t safeDirsLen = mSafeDirs.Length();
64   for (uint32_t i = 0; i < safeDirsLen; ++i) {
65     if (StringBeginsWith(filename, mSafeDirs[i].mPath, comparator)) {
66       processedName = mSafeDirs[i].mSubstName;
67       processedName += Substring(filename, mSafeDirs[i].mPath.Length());
68       break;
69     }
70   }
71 
72   if (processedName.IsEmpty()) {
73     return;
74   }
75 
76   // Create a new entry or retrieve the existing one
77   FileIOEntryType* entry = mFileStats.PutEntry(processedName);
78   if (entry) {
79     FileStats& stats = entry->GetModifiableData()->mStats[mCurStage];
80     // Update the statistics
81     stats.totalTime += (double)aOb.Duration().ToMilliseconds();
82     switch (aOb.ObservedOperation()) {
83       case OpCreateOrOpen:
84         stats.creates++;
85         break;
86       case OpRead:
87         stats.reads++;
88         break;
89       case OpWrite:
90         stats.writes++;
91         break;
92       case OpFSync:
93         stats.fsyncs++;
94         break;
95       case OpStat:
96         stats.stats++;
97         break;
98       default:
99         break;
100     }
101   }
102 }
103 
ReflectFileStats(FileIOEntryType * entry,JSContext * cx,JS::Handle<JSObject * > obj)104 bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry,
105                                                     JSContext* cx,
106                                                     JS::Handle<JSObject*> obj) {
107   JS::RootedValueArray<NUM_STAGES> stages(cx);
108 
109   FileStatsByStage& statsByStage = *entry->GetModifiableData();
110   for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) {
111     FileStats& fileStats = statsByStage.mStats[s];
112 
113     if (fileStats.totalTime == 0 && fileStats.creates == 0 &&
114         fileStats.reads == 0 && fileStats.writes == 0 &&
115         fileStats.fsyncs == 0 && fileStats.stats == 0) {
116       // Don't add an array that contains no information
117       stages[s].setNull();
118       continue;
119     }
120 
121     // Array we want to report
122     JS::RootedValueArray<6> stats(cx);
123     stats[0].setNumber(fileStats.totalTime);
124     stats[1].setNumber(fileStats.creates);
125     stats[2].setNumber(fileStats.reads);
126     stats[3].setNumber(fileStats.writes);
127     stats[4].setNumber(fileStats.fsyncs);
128     stats[5].setNumber(fileStats.stats);
129 
130     // Create jsStats as array of elements above
131     JS::RootedObject jsStats(cx, JS::NewArrayObject(cx, stats));
132     if (!jsStats) {
133       continue;
134     }
135 
136     stages[s].setObject(*jsStats);
137   }
138 
139   JS::Rooted<JSObject*> jsEntry(cx, JS::NewArrayObject(cx, stages));
140   if (!jsEntry) {
141     return false;
142   }
143 
144   // Add jsEntry to top-level dictionary
145   const nsAString& key = entry->GetKey();
146   return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), jsEntry,
147                              JSPROP_ENUMERATE | JSPROP_READONLY);
148 }
149 
ReflectIntoJS(JSContext * cx,JS::Handle<JSObject * > rootObj)150 bool TelemetryIOInterposeObserver::ReflectIntoJS(
151     JSContext* cx, JS::Handle<JSObject*> rootObj) {
152   return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj);
153 }
154 
155 /**
156  * Get size of hash table with file stats
157  */
158 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const159 size_t TelemetryIOInterposeObserver::SizeOfIncludingThis(
160     mozilla::MallocSizeOf aMallocSizeOf) const {
161   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
162 }
163 
SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const164 size_t TelemetryIOInterposeObserver::SizeOfExcludingThis(
165     mozilla::MallocSizeOf aMallocSizeOf) const {
166   size_t size = 0;
167   size += mFileStats.ShallowSizeOfExcludingThis(aMallocSizeOf);
168   for (auto iter = mFileStats.ConstIter(); !iter.Done(); iter.Next()) {
169     size += iter.Get()->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
170   }
171   size += mSafeDirs.ShallowSizeOfExcludingThis(aMallocSizeOf);
172   uint32_t safeDirsLen = mSafeDirs.Length();
173   for (uint32_t i = 0; i < safeDirsLen; ++i) {
174     size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf);
175   }
176   return size;
177 }
178 
179 }  // namespace mozilla::Telemetry
180