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