|
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 <algorithm> |
|
8 |
|
9 #include <fstream> |
|
10 |
|
11 #include <prio.h> |
|
12 |
|
13 #include "mozilla/Attributes.h" |
|
14 #include "mozilla/DebugOnly.h" |
|
15 #include "mozilla/Likely.h" |
|
16 #include "mozilla/MathAlgorithms.h" |
|
17 |
|
18 #include "base/histogram.h" |
|
19 #include "base/pickle.h" |
|
20 #include "nsIComponentManager.h" |
|
21 #include "nsIServiceManager.h" |
|
22 #include "nsThreadManager.h" |
|
23 #include "nsCOMArray.h" |
|
24 #include "nsCOMPtr.h" |
|
25 #include "nsXPCOMPrivate.h" |
|
26 #include "nsIXULAppInfo.h" |
|
27 #include "nsVersionComparator.h" |
|
28 #include "mozilla/MemoryReporting.h" |
|
29 #include "mozilla/ModuleUtils.h" |
|
30 #include "nsIXPConnect.h" |
|
31 #include "mozilla/Services.h" |
|
32 #include "jsapi.h" |
|
33 #include "jsfriendapi.h" |
|
34 #include "js/GCAPI.h" |
|
35 #include "nsString.h" |
|
36 #include "nsITelemetry.h" |
|
37 #include "nsIFile.h" |
|
38 #include "nsIFileStreams.h" |
|
39 #include "nsIMemoryReporter.h" |
|
40 #include "nsISeekableStream.h" |
|
41 #include "Telemetry.h" |
|
42 #include "nsTHashtable.h" |
|
43 #include "nsHashKeys.h" |
|
44 #include "nsBaseHashtable.h" |
|
45 #include "nsXULAppAPI.h" |
|
46 #include "nsReadableUtils.h" |
|
47 #include "nsThreadUtils.h" |
|
48 #if defined(XP_WIN) |
|
49 #include "nsUnicharUtils.h" |
|
50 #endif |
|
51 #include "nsNetCID.h" |
|
52 #include "nsNetUtil.h" |
|
53 #include "plstr.h" |
|
54 #include "nsAppDirectoryServiceDefs.h" |
|
55 #include "mozilla/BackgroundHangMonitor.h" |
|
56 #include "mozilla/ThreadHangStats.h" |
|
57 #include "mozilla/ProcessedStack.h" |
|
58 #include "mozilla/Mutex.h" |
|
59 #include "mozilla/FileUtils.h" |
|
60 #include "mozilla/Preferences.h" |
|
61 #include "mozilla/StaticPtr.h" |
|
62 #include "mozilla/IOInterposer.h" |
|
63 #include "mozilla/PoisonIOInterposer.h" |
|
64 #include "mozilla/StartupTimeline.h" |
|
65 #if defined(MOZ_ENABLE_PROFILER_SPS) |
|
66 #include "shared-libraries.h" |
|
67 #endif |
|
68 |
|
69 #define EXPIRED_ID "__expired__" |
|
70 |
|
71 namespace { |
|
72 |
|
73 using namespace base; |
|
74 using namespace mozilla; |
|
75 |
|
76 template<class EntryType> |
|
77 class AutoHashtable : public nsTHashtable<EntryType> |
|
78 { |
|
79 public: |
|
80 AutoHashtable(uint32_t initSize = PL_DHASH_MIN_SIZE); |
|
81 typedef bool (*ReflectEntryFunc)(EntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj); |
|
82 bool ReflectIntoJS(ReflectEntryFunc entryFunc, JSContext *cx, JS::Handle<JSObject*> obj); |
|
83 private: |
|
84 struct EnumeratorArgs { |
|
85 JSContext *cx; |
|
86 JS::Handle<JSObject*> obj; |
|
87 ReflectEntryFunc entryFunc; |
|
88 }; |
|
89 static PLDHashOperator ReflectEntryStub(EntryType *entry, void *arg); |
|
90 }; |
|
91 |
|
92 template<class EntryType> |
|
93 AutoHashtable<EntryType>::AutoHashtable(uint32_t initSize) |
|
94 : nsTHashtable<EntryType>(initSize) |
|
95 { |
|
96 } |
|
97 |
|
98 template<typename EntryType> |
|
99 PLDHashOperator |
|
100 AutoHashtable<EntryType>::ReflectEntryStub(EntryType *entry, void *arg) |
|
101 { |
|
102 EnumeratorArgs *args = static_cast<EnumeratorArgs *>(arg); |
|
103 if (!args->entryFunc(entry, args->cx, args->obj)) { |
|
104 return PL_DHASH_STOP; |
|
105 } |
|
106 return PL_DHASH_NEXT; |
|
107 } |
|
108 |
|
109 /** |
|
110 * Reflect the individual entries of table into JS, usually by defining |
|
111 * some property and value of obj. entryFunc is called for each entry. |
|
112 */ |
|
113 template<typename EntryType> |
|
114 bool |
|
115 AutoHashtable<EntryType>::ReflectIntoJS(ReflectEntryFunc entryFunc, |
|
116 JSContext *cx, JS::Handle<JSObject*> obj) |
|
117 { |
|
118 EnumeratorArgs args = { cx, obj, entryFunc }; |
|
119 uint32_t num = this->EnumerateEntries(ReflectEntryStub, static_cast<void*>(&args)); |
|
120 return num == this->Count(); |
|
121 } |
|
122 |
|
123 // This class is conceptually a list of ProcessedStack objects, but it represents them |
|
124 // more efficiently by keeping a single global list of modules. |
|
125 class CombinedStacks { |
|
126 public: |
|
127 typedef std::vector<Telemetry::ProcessedStack::Frame> Stack; |
|
128 const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const; |
|
129 size_t GetModuleCount() const; |
|
130 const Stack& GetStack(unsigned aIndex) const; |
|
131 void AddStack(const Telemetry::ProcessedStack& aStack); |
|
132 size_t GetStackCount() const; |
|
133 size_t SizeOfExcludingThis() const; |
|
134 private: |
|
135 std::vector<Telemetry::ProcessedStack::Module> mModules; |
|
136 std::vector<Stack> mStacks; |
|
137 }; |
|
138 |
|
139 static JSObject * |
|
140 CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks); |
|
141 |
|
142 size_t |
|
143 CombinedStacks::GetModuleCount() const { |
|
144 return mModules.size(); |
|
145 } |
|
146 |
|
147 const Telemetry::ProcessedStack::Module& |
|
148 CombinedStacks::GetModule(unsigned aIndex) const { |
|
149 return mModules[aIndex]; |
|
150 } |
|
151 |
|
152 void |
|
153 CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) { |
|
154 mStacks.resize(mStacks.size() + 1); |
|
155 CombinedStacks::Stack& adjustedStack = mStacks.back(); |
|
156 |
|
157 size_t stackSize = aStack.GetStackSize(); |
|
158 for (size_t i = 0; i < stackSize; ++i) { |
|
159 const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i); |
|
160 uint16_t modIndex; |
|
161 if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) { |
|
162 modIndex = frame.mModIndex; |
|
163 } else { |
|
164 const Telemetry::ProcessedStack::Module& module = |
|
165 aStack.GetModule(frame.mModIndex); |
|
166 std::vector<Telemetry::ProcessedStack::Module>::iterator modIterator = |
|
167 std::find(mModules.begin(), mModules.end(), module); |
|
168 if (modIterator == mModules.end()) { |
|
169 mModules.push_back(module); |
|
170 modIndex = mModules.size() - 1; |
|
171 } else { |
|
172 modIndex = modIterator - mModules.begin(); |
|
173 } |
|
174 } |
|
175 Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex }; |
|
176 adjustedStack.push_back(adjustedFrame); |
|
177 } |
|
178 } |
|
179 |
|
180 const CombinedStacks::Stack& |
|
181 CombinedStacks::GetStack(unsigned aIndex) const { |
|
182 return mStacks[aIndex]; |
|
183 } |
|
184 |
|
185 size_t |
|
186 CombinedStacks::GetStackCount() const { |
|
187 return mStacks.size(); |
|
188 } |
|
189 |
|
190 size_t |
|
191 CombinedStacks::SizeOfExcludingThis() const { |
|
192 // This is a crude approximation. We would like to do something like |
|
193 // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call |
|
194 // malloc_usable_size which is only safe on the pointers returned by malloc. |
|
195 // While it works on current libstdc++, it is better to be safe and not assume |
|
196 // that &vec[0] points to one. We could use a custom allocator, but |
|
197 // it doesn't seem worth it. |
|
198 size_t n = 0; |
|
199 n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module); |
|
200 n += mStacks.capacity() * sizeof(Stack); |
|
201 for (std::vector<Stack>::const_iterator i = mStacks.begin(), |
|
202 e = mStacks.end(); i != e; ++i) { |
|
203 const Stack& s = *i; |
|
204 n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame); |
|
205 } |
|
206 return n; |
|
207 } |
|
208 |
|
209 class HangReports { |
|
210 public: |
|
211 size_t SizeOfExcludingThis() const; |
|
212 void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration, |
|
213 int32_t aSystemUptime, int32_t aFirefoxUptime); |
|
214 uint32_t GetDuration(unsigned aIndex) const; |
|
215 int32_t GetSystemUptime(unsigned aIndex) const; |
|
216 int32_t GetFirefoxUptime(unsigned aIndex) const; |
|
217 const CombinedStacks& GetStacks() const; |
|
218 private: |
|
219 struct HangInfo { |
|
220 // Hang duration (in seconds) |
|
221 uint32_t mDuration; |
|
222 // System uptime (in minutes) at the time of the hang |
|
223 int32_t mSystemUptime; |
|
224 // Firefox uptime (in minutes) at the time of the hang |
|
225 int32_t mFirefoxUptime; |
|
226 }; |
|
227 std::vector<HangInfo> mHangInfo; |
|
228 CombinedStacks mStacks; |
|
229 }; |
|
230 |
|
231 void |
|
232 HangReports::AddHang(const Telemetry::ProcessedStack& aStack, |
|
233 uint32_t aDuration, |
|
234 int32_t aSystemUptime, |
|
235 int32_t aFirefoxUptime) { |
|
236 HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime }; |
|
237 mHangInfo.push_back(info); |
|
238 mStacks.AddStack(aStack); |
|
239 } |
|
240 |
|
241 size_t |
|
242 HangReports::SizeOfExcludingThis() const { |
|
243 size_t n = 0; |
|
244 n += mStacks.SizeOfExcludingThis(); |
|
245 // This is a crude approximation. See comment on |
|
246 // CombinedStacks::SizeOfExcludingThis. |
|
247 n += mHangInfo.capacity() * sizeof(HangInfo); |
|
248 return n; |
|
249 } |
|
250 |
|
251 const CombinedStacks& |
|
252 HangReports::GetStacks() const { |
|
253 return mStacks; |
|
254 } |
|
255 |
|
256 uint32_t |
|
257 HangReports::GetDuration(unsigned aIndex) const { |
|
258 return mHangInfo[aIndex].mDuration; |
|
259 } |
|
260 |
|
261 int32_t |
|
262 HangReports::GetSystemUptime(unsigned aIndex) const { |
|
263 return mHangInfo[aIndex].mSystemUptime; |
|
264 } |
|
265 |
|
266 int32_t |
|
267 HangReports::GetFirefoxUptime(unsigned aIndex) const { |
|
268 return mHangInfo[aIndex].mFirefoxUptime; |
|
269 } |
|
270 |
|
271 /** |
|
272 * IOInterposeObserver recording statistics of main-thread I/O during execution, |
|
273 * aimed at consumption by TelemetryImpl |
|
274 */ |
|
275 class TelemetryIOInterposeObserver : public IOInterposeObserver |
|
276 { |
|
277 /** File-level statistics structure */ |
|
278 struct FileStats { |
|
279 FileStats() |
|
280 : creates(0) |
|
281 , reads(0) |
|
282 , writes(0) |
|
283 , fsyncs(0) |
|
284 , stats(0) |
|
285 , totalTime(0) |
|
286 {} |
|
287 uint32_t creates; /** Number of create/open operations */ |
|
288 uint32_t reads; /** Number of read operations */ |
|
289 uint32_t writes; /** Number of write operations */ |
|
290 uint32_t fsyncs; /** Number of fsync operations */ |
|
291 uint32_t stats; /** Number of stat operations */ |
|
292 double totalTime; /** Accumulated duration of all operations */ |
|
293 }; |
|
294 |
|
295 struct SafeDir { |
|
296 SafeDir(const nsAString& aPath, const nsAString& aSubstName) |
|
297 : mPath(aPath) |
|
298 , mSubstName(aSubstName) |
|
299 {} |
|
300 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { |
|
301 return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + |
|
302 mSubstName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); |
|
303 } |
|
304 nsString mPath; /** Path to the directory */ |
|
305 nsString mSubstName; /** Name to substitute with */ |
|
306 }; |
|
307 |
|
308 public: |
|
309 TelemetryIOInterposeObserver(nsIFile* aXreDir); |
|
310 |
|
311 /** |
|
312 * An implementation of Observe that records statistics of all |
|
313 * file IO operations. |
|
314 */ |
|
315 void Observe(Observation& aOb); |
|
316 |
|
317 /** |
|
318 * Reflect recorded file IO statistics into Javascript |
|
319 */ |
|
320 bool ReflectIntoJS(JSContext *cx, JS::Handle<JSObject*> rootObj); |
|
321 |
|
322 /** |
|
323 * Adds a path for inclusion in main thread I/O report. |
|
324 * @param aPath Directory path |
|
325 * @param aSubstName Name to substitute for aPath for privacy reasons |
|
326 */ |
|
327 void AddPath(const nsAString& aPath, const nsAString& aSubstName); |
|
328 |
|
329 /** |
|
330 * Get size of hash table with file stats |
|
331 */ |
|
332 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { |
|
333 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
334 } |
|
335 |
|
336 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { |
|
337 size_t size; |
|
338 size = mFileStats.SizeOfExcludingThis(SizeOfFileIOEntryTypeExcludingThis, |
|
339 aMallocSizeOf) + |
|
340 mSafeDirs.SizeOfExcludingThis(aMallocSizeOf); |
|
341 uint32_t safeDirsLen = mSafeDirs.Length(); |
|
342 for (uint32_t i = 0; i < safeDirsLen; ++i) { |
|
343 size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf); |
|
344 } |
|
345 return size; |
|
346 } |
|
347 |
|
348 private: |
|
349 enum Stage |
|
350 { |
|
351 STAGE_STARTUP = 0, |
|
352 STAGE_NORMAL, |
|
353 STAGE_SHUTDOWN, |
|
354 NUM_STAGES |
|
355 }; |
|
356 static inline Stage NextStage(Stage aStage) |
|
357 { |
|
358 switch (aStage) { |
|
359 case STAGE_STARTUP: |
|
360 return STAGE_NORMAL; |
|
361 case STAGE_NORMAL: |
|
362 return STAGE_SHUTDOWN; |
|
363 case STAGE_SHUTDOWN: |
|
364 return STAGE_SHUTDOWN; |
|
365 default: |
|
366 return NUM_STAGES; |
|
367 } |
|
368 } |
|
369 |
|
370 struct FileStatsByStage |
|
371 { |
|
372 FileStats mStats[NUM_STAGES]; |
|
373 }; |
|
374 typedef nsBaseHashtableET<nsStringHashKey, FileStatsByStage> FileIOEntryType; |
|
375 |
|
376 // Statistics for each filename |
|
377 AutoHashtable<FileIOEntryType> mFileStats; |
|
378 // Container for whitelisted directories |
|
379 nsTArray<SafeDir> mSafeDirs; |
|
380 Stage mCurStage; |
|
381 |
|
382 /** |
|
383 * Reflect a FileIOEntryType object to a Javascript property on obj with |
|
384 * filename as key containing array: |
|
385 * [totalTime, creates, reads, writes, fsyncs, stats] |
|
386 */ |
|
387 static bool ReflectFileStats(FileIOEntryType* entry, JSContext *cx, |
|
388 JS::Handle<JSObject*> obj); |
|
389 |
|
390 static size_t SizeOfFileIOEntryTypeExcludingThis(FileIOEntryType* aEntry, |
|
391 mozilla::MallocSizeOf mallocSizeOf, |
|
392 void*) |
|
393 { |
|
394 return aEntry->GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf); |
|
395 } |
|
396 }; |
|
397 |
|
398 TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir) |
|
399 : mCurStage(STAGE_STARTUP) |
|
400 { |
|
401 nsAutoString xreDirPath; |
|
402 nsresult rv = aXreDir->GetPath(xreDirPath); |
|
403 if (NS_SUCCEEDED(rv)) { |
|
404 AddPath(xreDirPath, NS_LITERAL_STRING("{xre}")); |
|
405 } |
|
406 } |
|
407 |
|
408 void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath, |
|
409 const nsAString& aSubstName) |
|
410 { |
|
411 mSafeDirs.AppendElement(SafeDir(aPath, aSubstName)); |
|
412 } |
|
413 |
|
414 void TelemetryIOInterposeObserver::Observe(Observation& aOb) |
|
415 { |
|
416 // We only report main-thread I/O |
|
417 if (!IsMainThread()) { |
|
418 return; |
|
419 } |
|
420 |
|
421 if (aOb.ObservedOperation() == OpNextStage) { |
|
422 mCurStage = NextStage(mCurStage); |
|
423 MOZ_ASSERT(mCurStage < NUM_STAGES); |
|
424 return; |
|
425 } |
|
426 |
|
427 // Get the filename |
|
428 const char16_t* filename = aOb.Filename(); |
|
429 |
|
430 // Discard observations without filename |
|
431 if (!filename) { |
|
432 return; |
|
433 } |
|
434 |
|
435 #if defined(XP_WIN) |
|
436 nsCaseInsensitiveStringComparator comparator; |
|
437 #else |
|
438 nsDefaultStringComparator comparator; |
|
439 #endif |
|
440 nsAutoString processedName; |
|
441 nsDependentString filenameStr(filename); |
|
442 uint32_t safeDirsLen = mSafeDirs.Length(); |
|
443 for (uint32_t i = 0; i < safeDirsLen; ++i) { |
|
444 if (StringBeginsWith(filenameStr, mSafeDirs[i].mPath, comparator)) { |
|
445 processedName = mSafeDirs[i].mSubstName; |
|
446 processedName += Substring(filenameStr, mSafeDirs[i].mPath.Length()); |
|
447 break; |
|
448 } |
|
449 } |
|
450 |
|
451 if (processedName.IsEmpty()) { |
|
452 return; |
|
453 } |
|
454 |
|
455 // Create a new entry or retrieve the existing one |
|
456 FileIOEntryType* entry = mFileStats.PutEntry(processedName); |
|
457 if (entry) { |
|
458 FileStats& stats = entry->mData.mStats[mCurStage]; |
|
459 // Update the statistics |
|
460 stats.totalTime += (double) aOb.Duration().ToMilliseconds(); |
|
461 switch (aOb.ObservedOperation()) { |
|
462 case OpCreateOrOpen: |
|
463 stats.creates++; |
|
464 break; |
|
465 case OpRead: |
|
466 stats.reads++; |
|
467 break; |
|
468 case OpWrite: |
|
469 stats.writes++; |
|
470 break; |
|
471 case OpFSync: |
|
472 stats.fsyncs++; |
|
473 break; |
|
474 case OpStat: |
|
475 stats.stats++; |
|
476 break; |
|
477 default: |
|
478 break; |
|
479 } |
|
480 } |
|
481 } |
|
482 |
|
483 bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry, |
|
484 JSContext *cx, |
|
485 JS::Handle<JSObject*> obj) |
|
486 { |
|
487 JS::AutoValueArray<NUM_STAGES> stages(cx); |
|
488 |
|
489 FileStatsByStage& statsByStage = entry->mData; |
|
490 for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) { |
|
491 FileStats& fileStats = statsByStage.mStats[s]; |
|
492 |
|
493 if (fileStats.totalTime == 0 && fileStats.creates == 0 && |
|
494 fileStats.reads == 0 && fileStats.writes == 0 && |
|
495 fileStats.fsyncs == 0 && fileStats.stats == 0) { |
|
496 // Don't add an array that contains no information |
|
497 stages[s].setNull(); |
|
498 continue; |
|
499 } |
|
500 |
|
501 // Array we want to report |
|
502 JS::AutoValueArray<6> stats(cx); |
|
503 stats[0].setNumber(fileStats.totalTime); |
|
504 stats[1].setNumber(fileStats.creates); |
|
505 stats[2].setNumber(fileStats.reads); |
|
506 stats[3].setNumber(fileStats.writes); |
|
507 stats[4].setNumber(fileStats.fsyncs); |
|
508 stats[5].setNumber(fileStats.stats); |
|
509 |
|
510 // Create jsStats as array of elements above |
|
511 JS::RootedObject jsStats(cx, JS_NewArrayObject(cx, stats)); |
|
512 if (!jsStats) { |
|
513 continue; |
|
514 } |
|
515 |
|
516 stages[s].setObject(*jsStats); |
|
517 } |
|
518 |
|
519 JS::RootedObject jsEntry(cx, JS_NewArrayObject(cx, stages)); |
|
520 if (!jsEntry) { |
|
521 return false; |
|
522 } |
|
523 |
|
524 // Add jsEntry to top-level dictionary |
|
525 const nsAString& key = entry->GetKey(); |
|
526 return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), |
|
527 OBJECT_TO_JSVAL(jsEntry), nullptr, nullptr, |
|
528 JSPROP_ENUMERATE | JSPROP_READONLY); |
|
529 } |
|
530 |
|
531 bool TelemetryIOInterposeObserver::ReflectIntoJS(JSContext *cx, |
|
532 JS::Handle<JSObject*> rootObj) |
|
533 { |
|
534 return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj); |
|
535 } |
|
536 |
|
537 // This is not a member of TelemetryImpl because we want to record I/O during |
|
538 // startup. |
|
539 StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver; |
|
540 |
|
541 void |
|
542 ClearIOReporting() |
|
543 { |
|
544 if (!sTelemetryIOObserver) { |
|
545 return; |
|
546 } |
|
547 IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging, |
|
548 sTelemetryIOObserver); |
|
549 sTelemetryIOObserver = nullptr; |
|
550 } |
|
551 |
|
552 class TelemetryImpl MOZ_FINAL |
|
553 : public nsITelemetry |
|
554 , public nsIMemoryReporter |
|
555 { |
|
556 NS_DECL_THREADSAFE_ISUPPORTS |
|
557 NS_DECL_NSITELEMETRY |
|
558 NS_DECL_NSIMEMORYREPORTER |
|
559 |
|
560 public: |
|
561 ~TelemetryImpl(); |
|
562 |
|
563 void InitMemoryReporter(); |
|
564 |
|
565 static bool CanRecord(); |
|
566 static already_AddRefed<nsITelemetry> CreateTelemetryInstance(); |
|
567 static void ShutdownTelemetry(); |
|
568 static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName, |
|
569 uint32_t delay); |
|
570 #if defined(MOZ_ENABLE_PROFILER_SPS) |
|
571 static void RecordChromeHang(uint32_t aDuration, |
|
572 Telemetry::ProcessedStack &aStack, |
|
573 int32_t aSystemUptime, |
|
574 int32_t aFirefoxUptime); |
|
575 #endif |
|
576 static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats); |
|
577 static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id); |
|
578 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); |
|
579 struct Stat { |
|
580 uint32_t hitCount; |
|
581 uint32_t totalTime; |
|
582 }; |
|
583 struct StmtStats { |
|
584 struct Stat mainThread; |
|
585 struct Stat otherThreads; |
|
586 }; |
|
587 typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType; |
|
588 |
|
589 private: |
|
590 TelemetryImpl(); |
|
591 |
|
592 static nsCString SanitizeSQL(const nsACString& sql); |
|
593 |
|
594 enum SanitizedState { Sanitized, Unsanitized }; |
|
595 |
|
596 static void StoreSlowSQL(const nsACString &offender, uint32_t delay, |
|
597 SanitizedState state); |
|
598 |
|
599 static bool ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx, |
|
600 JS::Handle<JSObject*> obj); |
|
601 static bool ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx, |
|
602 JS::Handle<JSObject*> obj); |
|
603 static bool ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat, |
|
604 JSContext *cx, JS::Handle<JSObject*> obj); |
|
605 |
|
606 bool AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread, |
|
607 bool privateSQL); |
|
608 bool GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, |
|
609 bool includePrivateSql); |
|
610 |
|
611 // Like GetHistogramById, but returns the underlying C++ object, not the JS one. |
|
612 nsresult GetHistogramByName(const nsACString &name, Histogram **ret); |
|
613 bool ShouldReflectHistogram(Histogram *h); |
|
614 void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs); |
|
615 typedef StatisticsRecorder::Histograms::iterator HistogramIterator; |
|
616 |
|
617 struct AddonHistogramInfo { |
|
618 uint32_t min; |
|
619 uint32_t max; |
|
620 uint32_t bucketCount; |
|
621 uint32_t histogramType; |
|
622 Histogram *h; |
|
623 }; |
|
624 typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo> AddonHistogramEntryType; |
|
625 typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType; |
|
626 typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *> AddonEntryType; |
|
627 typedef AutoHashtable<AddonEntryType> AddonMapType; |
|
628 static bool AddonHistogramReflector(AddonHistogramEntryType *entry, |
|
629 JSContext *cx, JS::Handle<JSObject*> obj); |
|
630 static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj); |
|
631 static bool CreateHistogramForAddon(const nsACString &name, |
|
632 AddonHistogramInfo &info); |
|
633 void ReadLateWritesStacks(nsIFile* aProfileDir); |
|
634 AddonMapType mAddonMap; |
|
635 |
|
636 // This is used for speedy string->Telemetry::ID conversions |
|
637 typedef nsBaseHashtableET<nsCharPtrHashKey, Telemetry::ID> CharPtrEntryType; |
|
638 typedef AutoHashtable<CharPtrEntryType> HistogramMapType; |
|
639 HistogramMapType mHistogramMap; |
|
640 bool mCanRecord; |
|
641 static TelemetryImpl *sTelemetry; |
|
642 AutoHashtable<SlowSQLEntryType> mPrivateSQL; |
|
643 AutoHashtable<SlowSQLEntryType> mSanitizedSQL; |
|
644 // This gets marked immutable in debug builds, so we can't use |
|
645 // AutoHashtable here. |
|
646 nsTHashtable<nsCStringHashKey> mTrackedDBs; |
|
647 Mutex mHashMutex; |
|
648 HangReports mHangReports; |
|
649 Mutex mHangReportsMutex; |
|
650 // mThreadHangStats stores recorded, inactive thread hang stats |
|
651 Vector<Telemetry::ThreadHangStats> mThreadHangStats; |
|
652 Mutex mThreadHangStatsMutex; |
|
653 |
|
654 CombinedStacks mLateWritesStacks; // This is collected out of the main thread. |
|
655 bool mCachedTelemetryData; |
|
656 uint32_t mLastShutdownTime; |
|
657 uint32_t mFailedLockCount; |
|
658 nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks; |
|
659 friend class nsFetchTelemetryData; |
|
660 }; |
|
661 |
|
662 TelemetryImpl* TelemetryImpl::sTelemetry = nullptr; |
|
663 |
|
664 MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf) |
|
665 |
|
666 NS_IMETHODIMP |
|
667 TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport, |
|
668 nsISupports* aData) |
|
669 { |
|
670 return MOZ_COLLECT_REPORT( |
|
671 "explicit/telemetry", KIND_HEAP, UNITS_BYTES, |
|
672 SizeOfIncludingThis(TelemetryMallocSizeOf), |
|
673 "Memory used by the telemetry system."); |
|
674 } |
|
675 |
|
676 // A initializer to initialize histogram collection |
|
677 StatisticsRecorder gStatisticsRecorder; |
|
678 |
|
679 // Hardcoded probes |
|
680 struct TelemetryHistogram { |
|
681 uint32_t min; |
|
682 uint32_t max; |
|
683 uint32_t bucketCount; |
|
684 uint32_t histogramType; |
|
685 uint32_t id_offset; |
|
686 uint32_t expiration_offset; |
|
687 bool extendedStatisticsOK; |
|
688 |
|
689 const char *id() const; |
|
690 const char *expiration() const; |
|
691 }; |
|
692 |
|
693 #include "TelemetryHistogramData.inc" |
|
694 bool gCorruptHistograms[Telemetry::HistogramCount]; |
|
695 |
|
696 const char * |
|
697 TelemetryHistogram::id() const |
|
698 { |
|
699 return &gHistogramStringTable[this->id_offset]; |
|
700 } |
|
701 |
|
702 const char * |
|
703 TelemetryHistogram::expiration() const |
|
704 { |
|
705 return &gHistogramStringTable[this->expiration_offset]; |
|
706 } |
|
707 |
|
708 bool |
|
709 IsExpired(const char *expiration){ |
|
710 static Version current_version = Version(MOZ_APP_VERSION); |
|
711 MOZ_ASSERT(expiration); |
|
712 return strcmp(expiration, "never") && (mozilla::Version(expiration) <= current_version); |
|
713 } |
|
714 |
|
715 bool |
|
716 IsExpired(const Histogram *histogram){ |
|
717 return histogram->histogram_name() == EXPIRED_ID; |
|
718 } |
|
719 |
|
720 nsresult |
|
721 HistogramGet(const char *name, const char *expiration, uint32_t min, uint32_t max, |
|
722 uint32_t bucketCount, uint32_t histogramType, Histogram **result) |
|
723 { |
|
724 if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN |
|
725 && histogramType != nsITelemetry::HISTOGRAM_FLAG) { |
|
726 // Sanity checks for histogram parameters. |
|
727 if (min >= max) |
|
728 return NS_ERROR_ILLEGAL_VALUE; |
|
729 |
|
730 if (bucketCount <= 2) |
|
731 return NS_ERROR_ILLEGAL_VALUE; |
|
732 |
|
733 if (min < 1) |
|
734 return NS_ERROR_ILLEGAL_VALUE; |
|
735 } |
|
736 |
|
737 if (IsExpired(expiration)) { |
|
738 name = EXPIRED_ID; |
|
739 min = 1; |
|
740 max = 2; |
|
741 bucketCount = 3; |
|
742 histogramType = nsITelemetry::HISTOGRAM_LINEAR; |
|
743 } |
|
744 |
|
745 switch (histogramType) { |
|
746 case nsITelemetry::HISTOGRAM_EXPONENTIAL: |
|
747 *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); |
|
748 break; |
|
749 case nsITelemetry::HISTOGRAM_LINEAR: |
|
750 *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); |
|
751 break; |
|
752 case nsITelemetry::HISTOGRAM_BOOLEAN: |
|
753 *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); |
|
754 break; |
|
755 case nsITelemetry::HISTOGRAM_FLAG: |
|
756 *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); |
|
757 break; |
|
758 default: |
|
759 return NS_ERROR_INVALID_ARG; |
|
760 } |
|
761 return NS_OK; |
|
762 } |
|
763 |
|
764 // O(1) histogram lookup by numeric id |
|
765 nsresult |
|
766 GetHistogramByEnumId(Telemetry::ID id, Histogram **ret) |
|
767 { |
|
768 static Histogram* knownHistograms[Telemetry::HistogramCount] = {0}; |
|
769 Histogram *h = knownHistograms[id]; |
|
770 if (h) { |
|
771 *ret = h; |
|
772 return NS_OK; |
|
773 } |
|
774 |
|
775 const TelemetryHistogram &p = gHistograms[id]; |
|
776 nsresult rv = HistogramGet(p.id(), p.expiration(), p.min, p.max, p.bucketCount, p.histogramType, &h); |
|
777 if (NS_FAILED(rv)) |
|
778 return rv; |
|
779 |
|
780 #ifdef DEBUG |
|
781 // Check that the C++ Histogram code computes the same ranges as the |
|
782 // Python histogram code. |
|
783 if (!IsExpired(p.expiration())) { |
|
784 const struct bounds &b = gBucketLowerBoundIndex[id]; |
|
785 if (b.length != 0) { |
|
786 MOZ_ASSERT(size_t(b.length) == h->bucket_count(), |
|
787 "C++/Python bucket # mismatch"); |
|
788 for (int i = 0; i < b.length; ++i) { |
|
789 MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i), |
|
790 "C++/Python bucket mismatch"); |
|
791 } |
|
792 } |
|
793 } |
|
794 #endif |
|
795 |
|
796 if (p.extendedStatisticsOK) { |
|
797 h->SetFlags(Histogram::kExtendedStatisticsFlag); |
|
798 } |
|
799 *ret = knownHistograms[id] = h; |
|
800 return NS_OK; |
|
801 } |
|
802 |
|
803 bool |
|
804 FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h) |
|
805 { |
|
806 JS::Rooted<JS::Value> range(cx); |
|
807 for (size_t i = 0; i < h->bucket_count(); i++) { |
|
808 range = INT_TO_JSVAL(h->ranges(i)); |
|
809 if (!JS_DefineElement(cx, array, i, range, nullptr, nullptr, JSPROP_ENUMERATE)) |
|
810 return false; |
|
811 } |
|
812 return true; |
|
813 } |
|
814 |
|
815 enum reflectStatus { |
|
816 REFLECT_OK, |
|
817 REFLECT_CORRUPT, |
|
818 REFLECT_FAILURE |
|
819 }; |
|
820 |
|
821 enum reflectStatus |
|
822 ReflectHistogramAndSamples(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h, |
|
823 const Histogram::SampleSet &ss) |
|
824 { |
|
825 // We don't want to reflect corrupt histograms. |
|
826 if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) { |
|
827 return REFLECT_CORRUPT; |
|
828 } |
|
829 |
|
830 if (!(JS_DefineProperty(cx, obj, "min", h->declared_min(), JSPROP_ENUMERATE) |
|
831 && JS_DefineProperty(cx, obj, "max", h->declared_max(), JSPROP_ENUMERATE) |
|
832 && JS_DefineProperty(cx, obj, "histogram_type", h->histogram_type(), JSPROP_ENUMERATE) |
|
833 && JS_DefineProperty(cx, obj, "sum", double(ss.sum()), JSPROP_ENUMERATE))) { |
|
834 return REFLECT_FAILURE; |
|
835 } |
|
836 |
|
837 if (h->histogram_type() == Histogram::HISTOGRAM) { |
|
838 if (!(JS_DefineProperty(cx, obj, "log_sum", ss.log_sum(), JSPROP_ENUMERATE) |
|
839 && JS_DefineProperty(cx, obj, "log_sum_squares", ss.log_sum_squares(), JSPROP_ENUMERATE))) { |
|
840 return REFLECT_FAILURE; |
|
841 } |
|
842 } else { |
|
843 // Export |sum_squares| as two separate 32-bit properties so that we |
|
844 // can accurately reconstruct it on the analysis side. |
|
845 uint64_t sum_squares = ss.sum_squares(); |
|
846 // Cast to avoid implicit truncation warnings. |
|
847 uint32_t lo = static_cast<uint32_t>(sum_squares); |
|
848 uint32_t hi = static_cast<uint32_t>(sum_squares >> 32); |
|
849 if (!(JS_DefineProperty(cx, obj, "sum_squares_lo", lo, JSPROP_ENUMERATE) |
|
850 && JS_DefineProperty(cx, obj, "sum_squares_hi", hi, JSPROP_ENUMERATE))) { |
|
851 return REFLECT_FAILURE; |
|
852 } |
|
853 } |
|
854 |
|
855 const size_t count = h->bucket_count(); |
|
856 JS::Rooted<JSObject*> rarray(cx, JS_NewArrayObject(cx, count)); |
|
857 if (!rarray) { |
|
858 return REFLECT_FAILURE; |
|
859 } |
|
860 if (!(FillRanges(cx, rarray, h) |
|
861 && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) { |
|
862 return REFLECT_FAILURE; |
|
863 } |
|
864 |
|
865 JS::Rooted<JSObject*> counts_array(cx, JS_NewArrayObject(cx, count)); |
|
866 if (!counts_array) { |
|
867 return REFLECT_FAILURE; |
|
868 } |
|
869 if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) { |
|
870 return REFLECT_FAILURE; |
|
871 } |
|
872 for (size_t i = 0; i < count; i++) { |
|
873 if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)), |
|
874 nullptr, nullptr, JSPROP_ENUMERATE)) { |
|
875 return REFLECT_FAILURE; |
|
876 } |
|
877 } |
|
878 |
|
879 return REFLECT_OK; |
|
880 } |
|
881 |
|
882 enum reflectStatus |
|
883 ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h) |
|
884 { |
|
885 Histogram::SampleSet ss; |
|
886 h->SnapshotSample(&ss); |
|
887 return ReflectHistogramAndSamples(cx, obj, h, ss); |
|
888 } |
|
889 |
|
890 bool |
|
891 IsEmpty(const Histogram *h) |
|
892 { |
|
893 Histogram::SampleSet ss; |
|
894 h->SnapshotSample(&ss); |
|
895 |
|
896 return ss.counts(0) == 0 && ss.sum() == 0; |
|
897 } |
|
898 |
|
899 bool |
|
900 JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) |
|
901 { |
|
902 JS::CallArgs args = CallArgsFromVp(argc, vp); |
|
903 if (!args.length()) { |
|
904 JS_ReportError(cx, "Expected one argument"); |
|
905 return false; |
|
906 } |
|
907 |
|
908 if (!(args[0].isNumber() || args[0].isBoolean())) { |
|
909 JS_ReportError(cx, "Not a number"); |
|
910 return false; |
|
911 } |
|
912 |
|
913 int32_t value; |
|
914 if (!JS::ToInt32(cx, args[0], &value)) { |
|
915 return false; |
|
916 } |
|
917 |
|
918 if (TelemetryImpl::CanRecord()) { |
|
919 JSObject *obj = JS_THIS_OBJECT(cx, vp); |
|
920 if (!obj) { |
|
921 return false; |
|
922 } |
|
923 |
|
924 Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj)); |
|
925 h->Add(value); |
|
926 } |
|
927 return true; |
|
928 |
|
929 } |
|
930 |
|
931 bool |
|
932 JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) |
|
933 { |
|
934 JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
|
935 JSObject *obj = JS_THIS_OBJECT(cx, vp); |
|
936 if (!obj) { |
|
937 return false; |
|
938 } |
|
939 |
|
940 Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj)); |
|
941 JS::Rooted<JSObject*> snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
942 if (!snapshot) |
|
943 return false; |
|
944 |
|
945 switch (ReflectHistogramSnapshot(cx, snapshot, h)) { |
|
946 case REFLECT_FAILURE: |
|
947 return false; |
|
948 case REFLECT_CORRUPT: |
|
949 JS_ReportError(cx, "Histogram is corrupt"); |
|
950 return false; |
|
951 case REFLECT_OK: |
|
952 args.rval().setObject(*snapshot); |
|
953 return true; |
|
954 default: |
|
955 MOZ_CRASH("unhandled reflection status"); |
|
956 } |
|
957 } |
|
958 |
|
959 bool |
|
960 JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) |
|
961 { |
|
962 JSObject *obj = JS_THIS_OBJECT(cx, vp); |
|
963 if (!obj) { |
|
964 return false; |
|
965 } |
|
966 |
|
967 Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj)); |
|
968 h->Clear(); |
|
969 return true; |
|
970 } |
|
971 |
|
972 nsresult |
|
973 WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
974 { |
|
975 static const JSClass JSHistogram_class = { |
|
976 "JSHistogram", /* name */ |
|
977 JSCLASS_HAS_PRIVATE, /* flags */ |
|
978 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
979 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub |
|
980 }; |
|
981 |
|
982 JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class, JS::NullPtr(), JS::NullPtr())); |
|
983 if (!obj) |
|
984 return NS_ERROR_FAILURE; |
|
985 if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0) |
|
986 && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0) |
|
987 && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0))) { |
|
988 return NS_ERROR_FAILURE; |
|
989 } |
|
990 JS_SetPrivate(obj, h); |
|
991 ret.setObject(*obj); |
|
992 return NS_OK; |
|
993 } |
|
994 |
|
995 static uint32_t |
|
996 ReadLastShutdownDuration(const char *filename) { |
|
997 FILE *f = fopen(filename, "r"); |
|
998 if (!f) { |
|
999 return 0; |
|
1000 } |
|
1001 |
|
1002 int shutdownTime; |
|
1003 int r = fscanf(f, "%d\n", &shutdownTime); |
|
1004 fclose(f); |
|
1005 if (r != 1) { |
|
1006 return 0; |
|
1007 } |
|
1008 |
|
1009 return shutdownTime; |
|
1010 } |
|
1011 |
|
1012 const int32_t kMaxFailedProfileLockFileSize = 10; |
|
1013 |
|
1014 bool |
|
1015 GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount, |
|
1016 unsigned int& result) |
|
1017 { |
|
1018 nsAutoCString bufStr; |
|
1019 nsresult rv; |
|
1020 rv = NS_ReadInputStreamToString(inStream, bufStr, aCount); |
|
1021 NS_ENSURE_SUCCESS(rv, false); |
|
1022 result = bufStr.ToInteger(&rv); |
|
1023 return NS_SUCCEEDED(rv) && result > 0; |
|
1024 } |
|
1025 |
|
1026 nsresult |
|
1027 GetFailedProfileLockFile(nsIFile* *aFile, nsIFile* aProfileDir) |
|
1028 { |
|
1029 NS_ENSURE_ARG_POINTER(aProfileDir); |
|
1030 |
|
1031 nsresult rv = aProfileDir->Clone(aFile); |
|
1032 NS_ENSURE_SUCCESS(rv, rv); |
|
1033 |
|
1034 (*aFile)->AppendNative(NS_LITERAL_CSTRING("Telemetry.FailedProfileLocks.txt")); |
|
1035 return NS_OK; |
|
1036 } |
|
1037 |
|
1038 class nsFetchTelemetryData : public nsRunnable |
|
1039 { |
|
1040 public: |
|
1041 nsFetchTelemetryData(const char* aShutdownTimeFilename, |
|
1042 nsIFile* aFailedProfileLockFile, |
|
1043 nsIFile* aProfileDir) |
|
1044 : mShutdownTimeFilename(aShutdownTimeFilename), |
|
1045 mFailedProfileLockFile(aFailedProfileLockFile), |
|
1046 mTelemetry(TelemetryImpl::sTelemetry), |
|
1047 mProfileDir(aProfileDir) |
|
1048 { |
|
1049 } |
|
1050 |
|
1051 private: |
|
1052 const char* mShutdownTimeFilename; |
|
1053 nsCOMPtr<nsIFile> mFailedProfileLockFile; |
|
1054 nsRefPtr<TelemetryImpl> mTelemetry; |
|
1055 nsCOMPtr<nsIFile> mProfileDir; |
|
1056 |
|
1057 public: |
|
1058 void MainThread() { |
|
1059 mTelemetry->mCachedTelemetryData = true; |
|
1060 for (unsigned int i = 0, n = mTelemetry->mCallbacks.Count(); i < n; ++i) { |
|
1061 mTelemetry->mCallbacks[i]->Complete(); |
|
1062 } |
|
1063 mTelemetry->mCallbacks.Clear(); |
|
1064 } |
|
1065 |
|
1066 NS_IMETHOD Run() { |
|
1067 LoadFailedLockCount(mTelemetry->mFailedLockCount); |
|
1068 mTelemetry->mLastShutdownTime = |
|
1069 ReadLastShutdownDuration(mShutdownTimeFilename); |
|
1070 mTelemetry->ReadLateWritesStacks(mProfileDir); |
|
1071 nsCOMPtr<nsIRunnable> e = |
|
1072 NS_NewRunnableMethod(this, &nsFetchTelemetryData::MainThread); |
|
1073 NS_ENSURE_STATE(e); |
|
1074 NS_DispatchToMainThread(e, NS_DISPATCH_NORMAL); |
|
1075 return NS_OK; |
|
1076 } |
|
1077 |
|
1078 private: |
|
1079 nsresult |
|
1080 LoadFailedLockCount(uint32_t& failedLockCount) |
|
1081 { |
|
1082 failedLockCount = 0; |
|
1083 int64_t fileSize = 0; |
|
1084 nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize); |
|
1085 if (NS_FAILED(rv)) { |
|
1086 return rv; |
|
1087 } |
|
1088 NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize, |
|
1089 NS_ERROR_UNEXPECTED); |
|
1090 nsCOMPtr<nsIInputStream> inStream; |
|
1091 rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), |
|
1092 mFailedProfileLockFile, |
|
1093 PR_RDONLY); |
|
1094 NS_ENSURE_SUCCESS(rv, rv); |
|
1095 NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount), |
|
1096 NS_ERROR_UNEXPECTED); |
|
1097 inStream->Close(); |
|
1098 |
|
1099 mFailedProfileLockFile->Remove(false); |
|
1100 return NS_OK; |
|
1101 } |
|
1102 }; |
|
1103 |
|
1104 static TimeStamp gRecordedShutdownStartTime; |
|
1105 static bool gAlreadyFreedShutdownTimeFileName = false; |
|
1106 static char *gRecordedShutdownTimeFileName = nullptr; |
|
1107 |
|
1108 static char * |
|
1109 GetShutdownTimeFileName() |
|
1110 { |
|
1111 if (gAlreadyFreedShutdownTimeFileName) { |
|
1112 return nullptr; |
|
1113 } |
|
1114 |
|
1115 if (!gRecordedShutdownTimeFileName) { |
|
1116 nsCOMPtr<nsIFile> mozFile; |
|
1117 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); |
|
1118 if (!mozFile) |
|
1119 return nullptr; |
|
1120 |
|
1121 mozFile->AppendNative(NS_LITERAL_CSTRING("Telemetry.ShutdownTime.txt")); |
|
1122 nsAutoCString nativePath; |
|
1123 nsresult rv = mozFile->GetNativePath(nativePath); |
|
1124 if (!NS_SUCCEEDED(rv)) |
|
1125 return nullptr; |
|
1126 |
|
1127 gRecordedShutdownTimeFileName = PL_strdup(nativePath.get()); |
|
1128 } |
|
1129 |
|
1130 return gRecordedShutdownTimeFileName; |
|
1131 } |
|
1132 |
|
1133 NS_IMETHODIMP |
|
1134 TelemetryImpl::GetLastShutdownDuration(uint32_t *aResult) |
|
1135 { |
|
1136 // The user must call AsyncFetchTelemetryData first. We return zero instead of |
|
1137 // reporting a failure so that the rest of telemetry can uniformly handle |
|
1138 // the read not being available yet. |
|
1139 if (!mCachedTelemetryData) { |
|
1140 *aResult = 0; |
|
1141 return NS_OK; |
|
1142 } |
|
1143 |
|
1144 *aResult = mLastShutdownTime; |
|
1145 return NS_OK; |
|
1146 } |
|
1147 |
|
1148 NS_IMETHODIMP |
|
1149 TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult) |
|
1150 { |
|
1151 // The user must call AsyncFetchTelemetryData first. We return zero instead of |
|
1152 // reporting a failure so that the rest of telemetry can uniformly handle |
|
1153 // the read not being available yet. |
|
1154 if (!mCachedTelemetryData) { |
|
1155 *aResult = 0; |
|
1156 return NS_OK; |
|
1157 } |
|
1158 |
|
1159 *aResult = mFailedLockCount; |
|
1160 return NS_OK; |
|
1161 } |
|
1162 |
|
1163 NS_IMETHODIMP |
|
1164 TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback) |
|
1165 { |
|
1166 // We have finished reading the data already, just call the callback. |
|
1167 if (mCachedTelemetryData) { |
|
1168 aCallback->Complete(); |
|
1169 return NS_OK; |
|
1170 } |
|
1171 |
|
1172 // We already have a read request running, just remember the callback. |
|
1173 if (mCallbacks.Count() != 0) { |
|
1174 mCallbacks.AppendObject(aCallback); |
|
1175 return NS_OK; |
|
1176 } |
|
1177 |
|
1178 // We make this check so that GetShutdownTimeFileName() doesn't get |
|
1179 // called; calling that function without telemetry enabled violates |
|
1180 // assumptions that the write-the-shutdown-timestamp machinery makes. |
|
1181 if (!Telemetry::CanRecord()) { |
|
1182 mCachedTelemetryData = true; |
|
1183 aCallback->Complete(); |
|
1184 return NS_OK; |
|
1185 } |
|
1186 |
|
1187 // Send the read to a background thread provided by the stream transport |
|
1188 // service to avoid a read in the main thread. |
|
1189 nsCOMPtr<nsIEventTarget> targetThread = |
|
1190 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); |
|
1191 if (!targetThread) { |
|
1192 mCachedTelemetryData = true; |
|
1193 aCallback->Complete(); |
|
1194 return NS_OK; |
|
1195 } |
|
1196 |
|
1197 // We have to get the filename from the main thread. |
|
1198 const char *shutdownTimeFilename = GetShutdownTimeFileName(); |
|
1199 if (!shutdownTimeFilename) { |
|
1200 mCachedTelemetryData = true; |
|
1201 aCallback->Complete(); |
|
1202 return NS_OK; |
|
1203 } |
|
1204 |
|
1205 nsCOMPtr<nsIFile> profileDir; |
|
1206 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, |
|
1207 getter_AddRefs(profileDir)); |
|
1208 if (NS_FAILED(rv)) { |
|
1209 mCachedTelemetryData = true; |
|
1210 aCallback->Complete(); |
|
1211 return NS_OK; |
|
1212 } |
|
1213 |
|
1214 nsCOMPtr<nsIFile> failedProfileLockFile; |
|
1215 rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile), |
|
1216 profileDir); |
|
1217 if (NS_FAILED(rv)) { |
|
1218 mCachedTelemetryData = true; |
|
1219 aCallback->Complete(); |
|
1220 return NS_OK; |
|
1221 } |
|
1222 |
|
1223 mCallbacks.AppendObject(aCallback); |
|
1224 |
|
1225 nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(shutdownTimeFilename, |
|
1226 failedProfileLockFile, |
|
1227 profileDir); |
|
1228 |
|
1229 targetThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
1230 return NS_OK; |
|
1231 } |
|
1232 |
|
1233 TelemetryImpl::TelemetryImpl(): |
|
1234 mHistogramMap(Telemetry::HistogramCount), |
|
1235 mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default), |
|
1236 mHashMutex("Telemetry::mHashMutex"), |
|
1237 mHangReportsMutex("Telemetry::mHangReportsMutex"), |
|
1238 mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex"), |
|
1239 mCachedTelemetryData(false), |
|
1240 mLastShutdownTime(0), |
|
1241 mFailedLockCount(0) |
|
1242 { |
|
1243 // A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs |
|
1244 const char *trackedDBs[] = { |
|
1245 "addons.sqlite", "content-prefs.sqlite", "cookies.sqlite", |
|
1246 "downloads.sqlite", "extensions.sqlite", "formhistory.sqlite", |
|
1247 "index.sqlite", "healthreport.sqlite", "netpredictions.sqlite", |
|
1248 "permissions.sqlite", "places.sqlite", "search.sqlite", "signons.sqlite", |
|
1249 "urlclassifier3.sqlite", "webappsstore.sqlite" |
|
1250 }; |
|
1251 |
|
1252 for (size_t i = 0; i < ArrayLength(trackedDBs); i++) |
|
1253 mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i])); |
|
1254 |
|
1255 #ifdef DEBUG |
|
1256 // Mark immutable to prevent asserts on simultaneous access from multiple threads |
|
1257 mTrackedDBs.MarkImmutable(); |
|
1258 #endif |
|
1259 } |
|
1260 |
|
1261 TelemetryImpl::~TelemetryImpl() { |
|
1262 UnregisterWeakMemoryReporter(this); |
|
1263 } |
|
1264 |
|
1265 void |
|
1266 TelemetryImpl::InitMemoryReporter() { |
|
1267 RegisterWeakMemoryReporter(this); |
|
1268 } |
|
1269 |
|
1270 NS_IMETHODIMP |
|
1271 TelemetryImpl::NewHistogram(const nsACString &name, const nsACString &expiration, uint32_t min, |
|
1272 uint32_t max, uint32_t bucketCount, uint32_t histogramType, |
|
1273 JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1274 { |
|
1275 Histogram *h; |
|
1276 nsresult rv = HistogramGet(PromiseFlatCString(name).get(), PromiseFlatCString(expiration).get(), |
|
1277 min, max, bucketCount, histogramType, &h); |
|
1278 if (NS_FAILED(rv)) |
|
1279 return rv; |
|
1280 h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); |
|
1281 h->SetFlags(Histogram::kExtendedStatisticsFlag); |
|
1282 return WrapAndReturnHistogram(h, cx, ret); |
|
1283 } |
|
1284 |
|
1285 bool |
|
1286 TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry, |
|
1287 const Stat *stat, |
|
1288 JSContext *cx, |
|
1289 JS::Handle<JSObject*> obj) |
|
1290 { |
|
1291 if (stat->hitCount == 0) |
|
1292 return true; |
|
1293 |
|
1294 const nsACString &sql = entry->GetKey(); |
|
1295 |
|
1296 JS::Rooted<JSObject*> arrayObj(cx, JS_NewArrayObject(cx, 0)); |
|
1297 if (!arrayObj) { |
|
1298 return false; |
|
1299 } |
|
1300 return (JS_SetElement(cx, arrayObj, 0, stat->hitCount) |
|
1301 && JS_SetElement(cx, arrayObj, 1, stat->totalTime) |
|
1302 && JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj, |
|
1303 JSPROP_ENUMERATE)); |
|
1304 } |
|
1305 |
|
1306 bool |
|
1307 TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx, |
|
1308 JS::Handle<JSObject*> obj) |
|
1309 { |
|
1310 return ReflectSQL(entry, &entry->mData.mainThread, cx, obj); |
|
1311 } |
|
1312 |
|
1313 bool |
|
1314 TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx, |
|
1315 JS::Handle<JSObject*> obj) |
|
1316 { |
|
1317 return ReflectSQL(entry, &entry->mData.otherThreads, cx, obj); |
|
1318 } |
|
1319 |
|
1320 bool |
|
1321 TelemetryImpl::AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread, |
|
1322 bool privateSQL) |
|
1323 { |
|
1324 JS::Rooted<JSObject*> statsObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1325 if (!statsObj) |
|
1326 return false; |
|
1327 |
|
1328 AutoHashtable<SlowSQLEntryType> &sqlMap = |
|
1329 (privateSQL ? mPrivateSQL : mSanitizedSQL); |
|
1330 AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction = |
|
1331 (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL); |
|
1332 if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) { |
|
1333 return false; |
|
1334 } |
|
1335 |
|
1336 return JS_DefineProperty(cx, rootObj, |
|
1337 mainThread ? "mainThread" : "otherThreads", |
|
1338 statsObj, JSPROP_ENUMERATE); |
|
1339 } |
|
1340 |
|
1341 nsresult |
|
1342 TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id) |
|
1343 { |
|
1344 if (!sTelemetry) { |
|
1345 return NS_ERROR_FAILURE; |
|
1346 } |
|
1347 |
|
1348 // Cache names |
|
1349 // Note the histogram names are statically allocated |
|
1350 TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap; |
|
1351 if (!map->Count()) { |
|
1352 for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) { |
|
1353 CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id()); |
|
1354 if (MOZ_UNLIKELY(!entry)) { |
|
1355 map->Clear(); |
|
1356 return NS_ERROR_OUT_OF_MEMORY; |
|
1357 } |
|
1358 entry->mData = (Telemetry::ID) i; |
|
1359 } |
|
1360 } |
|
1361 |
|
1362 CharPtrEntryType *entry = map->GetEntry(name); |
|
1363 if (!entry) { |
|
1364 return NS_ERROR_INVALID_ARG; |
|
1365 } |
|
1366 *id = entry->mData; |
|
1367 return NS_OK; |
|
1368 } |
|
1369 |
|
1370 nsresult |
|
1371 TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret) |
|
1372 { |
|
1373 Telemetry::ID id; |
|
1374 nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id); |
|
1375 if (NS_FAILED(rv)) { |
|
1376 return rv; |
|
1377 } |
|
1378 |
|
1379 rv = GetHistogramByEnumId(id, ret); |
|
1380 if (NS_FAILED(rv)) |
|
1381 return rv; |
|
1382 |
|
1383 return NS_OK; |
|
1384 } |
|
1385 |
|
1386 NS_IMETHODIMP |
|
1387 TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name, |
|
1388 JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1389 { |
|
1390 Telemetry::ID id; |
|
1391 nsresult rv = GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id); |
|
1392 if (NS_FAILED(rv)) { |
|
1393 return rv; |
|
1394 } |
|
1395 const TelemetryHistogram &p = gHistograms[id]; |
|
1396 |
|
1397 Histogram *existing; |
|
1398 rv = GetHistogramByEnumId(id, &existing); |
|
1399 if (NS_FAILED(rv)) { |
|
1400 return rv; |
|
1401 } |
|
1402 |
|
1403 Histogram *clone; |
|
1404 rv = HistogramGet(PromiseFlatCString(name).get(), p.expiration(), |
|
1405 existing->declared_min(), existing->declared_max(), |
|
1406 existing->bucket_count(), p.histogramType, &clone); |
|
1407 if (NS_FAILED(rv)) |
|
1408 return rv; |
|
1409 |
|
1410 Histogram::SampleSet ss; |
|
1411 existing->SnapshotSample(&ss); |
|
1412 clone->AddSampleSet(ss); |
|
1413 return WrapAndReturnHistogram(clone, cx, ret); |
|
1414 } |
|
1415 |
|
1416 void |
|
1417 TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs) |
|
1418 { |
|
1419 for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { |
|
1420 Histogram *h = *it; |
|
1421 |
|
1422 Telemetry::ID id; |
|
1423 nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id); |
|
1424 // This histogram isn't a static histogram, just ignore it. |
|
1425 if (NS_FAILED(rv)) { |
|
1426 continue; |
|
1427 } |
|
1428 |
|
1429 if (gCorruptHistograms[id]) { |
|
1430 continue; |
|
1431 } |
|
1432 |
|
1433 Histogram::SampleSet ss; |
|
1434 h->SnapshotSample(&ss); |
|
1435 Histogram::Inconsistencies check = h->FindCorruption(ss); |
|
1436 bool corrupt = (check != Histogram::NO_INCONSISTENCIES); |
|
1437 |
|
1438 if (corrupt) { |
|
1439 Telemetry::ID corruptID = Telemetry::HistogramCount; |
|
1440 if (check & Histogram::RANGE_CHECKSUM_ERROR) { |
|
1441 corruptID = Telemetry::RANGE_CHECKSUM_ERRORS; |
|
1442 } else if (check & Histogram::BUCKET_ORDER_ERROR) { |
|
1443 corruptID = Telemetry::BUCKET_ORDER_ERRORS; |
|
1444 } else if (check & Histogram::COUNT_HIGH_ERROR) { |
|
1445 corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS; |
|
1446 } else if (check & Histogram::COUNT_LOW_ERROR) { |
|
1447 corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS; |
|
1448 } |
|
1449 Telemetry::Accumulate(corruptID, 1); |
|
1450 } |
|
1451 |
|
1452 gCorruptHistograms[id] = corrupt; |
|
1453 } |
|
1454 } |
|
1455 |
|
1456 bool |
|
1457 TelemetryImpl::ShouldReflectHistogram(Histogram *h) |
|
1458 { |
|
1459 const char *name = h->histogram_name().c_str(); |
|
1460 Telemetry::ID id; |
|
1461 nsresult rv = GetHistogramEnumId(name, &id); |
|
1462 if (NS_FAILED(rv)) { |
|
1463 // GetHistogramEnumId generally should not fail. But a lookup |
|
1464 // failure shouldn't prevent us from reflecting histograms into JS. |
|
1465 // |
|
1466 // However, these two histograms are created by Histogram itself for |
|
1467 // tracking corruption. We have our own histograms for that, so |
|
1468 // ignore these two. |
|
1469 if (strcmp(name, "Histogram.InconsistentCountHigh") == 0 |
|
1470 || strcmp(name, "Histogram.InconsistentCountLow") == 0) { |
|
1471 return false; |
|
1472 } |
|
1473 return true; |
|
1474 } else { |
|
1475 return !gCorruptHistograms[id]; |
|
1476 } |
|
1477 } |
|
1478 |
|
1479 // Compute the name to pass into Histogram for the addon histogram |
|
1480 // 'name' from the addon 'id'. We can't use 'name' directly because it |
|
1481 // might conflict with other histograms in other addons or even with our |
|
1482 // own. |
|
1483 void |
|
1484 AddonHistogramName(const nsACString &id, const nsACString &name, |
|
1485 nsACString &ret) |
|
1486 { |
|
1487 ret.Append(id); |
|
1488 ret.Append(NS_LITERAL_CSTRING(":")); |
|
1489 ret.Append(name); |
|
1490 } |
|
1491 |
|
1492 NS_IMETHODIMP |
|
1493 TelemetryImpl::RegisterAddonHistogram(const nsACString &id, |
|
1494 const nsACString &name, |
|
1495 uint32_t min, uint32_t max, |
|
1496 uint32_t bucketCount, |
|
1497 uint32_t histogramType) |
|
1498 { |
|
1499 AddonEntryType *addonEntry = mAddonMap.GetEntry(id); |
|
1500 if (!addonEntry) { |
|
1501 addonEntry = mAddonMap.PutEntry(id); |
|
1502 if (MOZ_UNLIKELY(!addonEntry)) { |
|
1503 return NS_ERROR_OUT_OF_MEMORY; |
|
1504 } |
|
1505 addonEntry->mData = new AddonHistogramMapType(); |
|
1506 } |
|
1507 |
|
1508 AddonHistogramMapType *histogramMap = addonEntry->mData; |
|
1509 AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); |
|
1510 // Can't re-register the same histogram. |
|
1511 if (histogramEntry) { |
|
1512 return NS_ERROR_FAILURE; |
|
1513 } |
|
1514 |
|
1515 histogramEntry = histogramMap->PutEntry(name); |
|
1516 if (MOZ_UNLIKELY(!histogramEntry)) { |
|
1517 return NS_ERROR_OUT_OF_MEMORY; |
|
1518 } |
|
1519 |
|
1520 AddonHistogramInfo &info = histogramEntry->mData; |
|
1521 info.min = min; |
|
1522 info.max = max; |
|
1523 info.bucketCount = bucketCount; |
|
1524 info.histogramType = histogramType; |
|
1525 |
|
1526 return NS_OK; |
|
1527 } |
|
1528 |
|
1529 NS_IMETHODIMP |
|
1530 TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name, |
|
1531 JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1532 { |
|
1533 AddonEntryType *addonEntry = mAddonMap.GetEntry(id); |
|
1534 // The given id has not been registered. |
|
1535 if (!addonEntry) { |
|
1536 return NS_ERROR_INVALID_ARG; |
|
1537 } |
|
1538 |
|
1539 AddonHistogramMapType *histogramMap = addonEntry->mData; |
|
1540 AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); |
|
1541 // The given histogram name has not been registered. |
|
1542 if (!histogramEntry) { |
|
1543 return NS_ERROR_INVALID_ARG; |
|
1544 } |
|
1545 |
|
1546 AddonHistogramInfo &info = histogramEntry->mData; |
|
1547 if (!info.h) { |
|
1548 nsAutoCString actualName; |
|
1549 AddonHistogramName(id, name, actualName); |
|
1550 if (!CreateHistogramForAddon(actualName, info)) { |
|
1551 return NS_ERROR_FAILURE; |
|
1552 } |
|
1553 } |
|
1554 return WrapAndReturnHistogram(info.h, cx, ret); |
|
1555 } |
|
1556 |
|
1557 NS_IMETHODIMP |
|
1558 TelemetryImpl::UnregisterAddonHistograms(const nsACString &id) |
|
1559 { |
|
1560 AddonEntryType *addonEntry = mAddonMap.GetEntry(id); |
|
1561 if (addonEntry) { |
|
1562 // Histogram's destructor is private, so this is the best we can do. |
|
1563 // The histograms the addon created *will* stick around, but they |
|
1564 // will be deleted if and when the addon registers histograms with |
|
1565 // the same names. |
|
1566 delete addonEntry->mData; |
|
1567 mAddonMap.RemoveEntry(id); |
|
1568 } |
|
1569 |
|
1570 return NS_OK; |
|
1571 } |
|
1572 |
|
1573 NS_IMETHODIMP |
|
1574 TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1575 { |
|
1576 JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1577 if (!root_obj) |
|
1578 return NS_ERROR_FAILURE; |
|
1579 ret.setObject(*root_obj); |
|
1580 |
|
1581 // Ensure that all the HISTOGRAM_FLAG histograms have been created, so |
|
1582 // that their values are snapshotted. |
|
1583 for (size_t i = 0; i < Telemetry::HistogramCount; ++i) { |
|
1584 if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) { |
|
1585 Histogram *h; |
|
1586 DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h); |
|
1587 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
1588 } |
|
1589 }; |
|
1590 |
|
1591 StatisticsRecorder::Histograms hs; |
|
1592 StatisticsRecorder::GetHistograms(&hs); |
|
1593 |
|
1594 // We identify corrupt histograms first, rather than interspersing it |
|
1595 // in the loop below, to ensure that our corruption statistics don't |
|
1596 // depend on histogram enumeration order. |
|
1597 // |
|
1598 // Of course, we hope that all of these corruption-statistics |
|
1599 // histograms are not themselves corrupt... |
|
1600 IdentifyCorruptHistograms(hs); |
|
1601 |
|
1602 // OK, now we can actually reflect things. |
|
1603 JS::Rooted<JSObject*> hobj(cx); |
|
1604 for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { |
|
1605 Histogram *h = *it; |
|
1606 if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) { |
|
1607 continue; |
|
1608 } |
|
1609 |
|
1610 hobj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()); |
|
1611 if (!hobj) { |
|
1612 return NS_ERROR_FAILURE; |
|
1613 } |
|
1614 switch (ReflectHistogramSnapshot(cx, hobj, h)) { |
|
1615 case REFLECT_CORRUPT: |
|
1616 // We can still hit this case even if ShouldReflectHistograms |
|
1617 // returns true. The histogram lies outside of our control |
|
1618 // somehow; just skip it. |
|
1619 continue; |
|
1620 case REFLECT_FAILURE: |
|
1621 return NS_ERROR_FAILURE; |
|
1622 case REFLECT_OK: |
|
1623 if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), hobj, |
|
1624 JSPROP_ENUMERATE)) { |
|
1625 return NS_ERROR_FAILURE; |
|
1626 } |
|
1627 } |
|
1628 } |
|
1629 return NS_OK; |
|
1630 } |
|
1631 |
|
1632 bool |
|
1633 TelemetryImpl::CreateHistogramForAddon(const nsACString &name, |
|
1634 AddonHistogramInfo &info) |
|
1635 { |
|
1636 Histogram *h; |
|
1637 nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never", |
|
1638 info.min, info.max, info.bucketCount, |
|
1639 info.histogramType, &h); |
|
1640 if (NS_FAILED(rv)) { |
|
1641 return false; |
|
1642 } |
|
1643 // Don't let this histogram be reported via the normal means |
|
1644 // (e.g. Telemetry.registeredHistograms); we'll make it available in |
|
1645 // other ways. |
|
1646 h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); |
|
1647 info.h = h; |
|
1648 return true; |
|
1649 } |
|
1650 |
|
1651 bool |
|
1652 TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry, |
|
1653 JSContext *cx, JS::Handle<JSObject*> obj) |
|
1654 { |
|
1655 AddonHistogramInfo &info = entry->mData; |
|
1656 |
|
1657 // Never even accessed the histogram. |
|
1658 if (!info.h) { |
|
1659 // Have to force creation of HISTOGRAM_FLAG histograms. |
|
1660 if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) |
|
1661 return true; |
|
1662 |
|
1663 if (!CreateHistogramForAddon(entry->GetKey(), info)) { |
|
1664 return false; |
|
1665 } |
|
1666 } |
|
1667 |
|
1668 if (IsEmpty(info.h)) { |
|
1669 return true; |
|
1670 } |
|
1671 |
|
1672 JS::Rooted<JSObject*> snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1673 if (!snapshot) { |
|
1674 // Just consider this to be skippable. |
|
1675 return true; |
|
1676 } |
|
1677 switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) { |
|
1678 case REFLECT_FAILURE: |
|
1679 case REFLECT_CORRUPT: |
|
1680 return false; |
|
1681 case REFLECT_OK: |
|
1682 const nsACString &histogramName = entry->GetKey(); |
|
1683 if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), |
|
1684 snapshot, JSPROP_ENUMERATE)) { |
|
1685 return false; |
|
1686 } |
|
1687 break; |
|
1688 } |
|
1689 return true; |
|
1690 } |
|
1691 |
|
1692 bool |
|
1693 TelemetryImpl::AddonReflector(AddonEntryType *entry, |
|
1694 JSContext *cx, JS::Handle<JSObject*> obj) |
|
1695 { |
|
1696 const nsACString &addonId = entry->GetKey(); |
|
1697 JS::Rooted<JSObject*> subobj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1698 if (!subobj) { |
|
1699 return false; |
|
1700 } |
|
1701 |
|
1702 AddonHistogramMapType *map = entry->mData; |
|
1703 if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj) |
|
1704 && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), |
|
1705 subobj, JSPROP_ENUMERATE))) { |
|
1706 return false; |
|
1707 } |
|
1708 return true; |
|
1709 } |
|
1710 |
|
1711 NS_IMETHODIMP |
|
1712 TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1713 { |
|
1714 JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1715 if (!obj) { |
|
1716 return NS_ERROR_FAILURE; |
|
1717 } |
|
1718 |
|
1719 if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) { |
|
1720 return NS_ERROR_FAILURE; |
|
1721 } |
|
1722 ret.setObject(*obj); |
|
1723 return NS_OK; |
|
1724 } |
|
1725 |
|
1726 bool |
|
1727 TelemetryImpl::GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, bool includePrivateSql) |
|
1728 { |
|
1729 JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1730 if (!root_obj) |
|
1731 return false; |
|
1732 ret.setObject(*root_obj); |
|
1733 |
|
1734 MutexAutoLock hashMutex(mHashMutex); |
|
1735 // Add info about slow SQL queries on the main thread |
|
1736 if (!AddSQLInfo(cx, root_obj, true, includePrivateSql)) |
|
1737 return false; |
|
1738 // Add info about slow SQL queries on other threads |
|
1739 if (!AddSQLInfo(cx, root_obj, false, includePrivateSql)) |
|
1740 return false; |
|
1741 |
|
1742 return true; |
|
1743 } |
|
1744 |
|
1745 NS_IMETHODIMP |
|
1746 TelemetryImpl::GetSlowSQL(JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1747 { |
|
1748 if (GetSQLStats(cx, ret, false)) |
|
1749 return NS_OK; |
|
1750 return NS_ERROR_FAILURE; |
|
1751 } |
|
1752 |
|
1753 NS_IMETHODIMP |
|
1754 TelemetryImpl::GetDebugSlowSQL(JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1755 { |
|
1756 bool revealPrivateSql = |
|
1757 Preferences::GetBool("toolkit.telemetry.debugSlowSql", false); |
|
1758 if (GetSQLStats(cx, ret, revealPrivateSql)) |
|
1759 return NS_OK; |
|
1760 return NS_ERROR_FAILURE; |
|
1761 } |
|
1762 |
|
1763 NS_IMETHODIMP |
|
1764 TelemetryImpl::GetMaximalNumberOfConcurrentThreads(uint32_t *ret) |
|
1765 { |
|
1766 *ret = nsThreadManager::get()->GetHighestNumberOfThreads(); |
|
1767 return NS_OK; |
|
1768 } |
|
1769 |
|
1770 NS_IMETHODIMP |
|
1771 TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
1772 { |
|
1773 MutexAutoLock hangReportMutex(mHangReportsMutex); |
|
1774 |
|
1775 const CombinedStacks& stacks = mHangReports.GetStacks(); |
|
1776 JS::Rooted<JSObject*> fullReportObj(cx, CreateJSStackObject(cx, stacks)); |
|
1777 if (!fullReportObj) { |
|
1778 return NS_ERROR_FAILURE; |
|
1779 } |
|
1780 |
|
1781 ret.setObject(*fullReportObj); |
|
1782 |
|
1783 JS::Rooted<JSObject*> durationArray(cx, JS_NewArrayObject(cx, 0)); |
|
1784 JS::Rooted<JSObject*> systemUptimeArray(cx, JS_NewArrayObject(cx, 0)); |
|
1785 JS::Rooted<JSObject*> firefoxUptimeArray(cx, JS_NewArrayObject(cx, 0)); |
|
1786 if (!durationArray || !systemUptimeArray || !firefoxUptimeArray) { |
|
1787 return NS_ERROR_FAILURE; |
|
1788 } |
|
1789 |
|
1790 bool ok = JS_DefineProperty(cx, fullReportObj, "durations", |
|
1791 durationArray, JSPROP_ENUMERATE); |
|
1792 if (!ok) { |
|
1793 return NS_ERROR_FAILURE; |
|
1794 } |
|
1795 |
|
1796 ok = JS_DefineProperty(cx, fullReportObj, "systemUptime", |
|
1797 systemUptimeArray, JSPROP_ENUMERATE); |
|
1798 if (!ok) { |
|
1799 return NS_ERROR_FAILURE; |
|
1800 } |
|
1801 |
|
1802 ok = JS_DefineProperty(cx, fullReportObj, "firefoxUptime", |
|
1803 firefoxUptimeArray, JSPROP_ENUMERATE); |
|
1804 if (!ok) { |
|
1805 return NS_ERROR_FAILURE; |
|
1806 } |
|
1807 |
|
1808 const size_t length = stacks.GetStackCount(); |
|
1809 for (size_t i = 0; i < length; ++i) { |
|
1810 if (!JS_SetElement(cx, durationArray, i, mHangReports.GetDuration(i))) { |
|
1811 return NS_ERROR_FAILURE; |
|
1812 } |
|
1813 if (!JS_SetElement(cx, systemUptimeArray, i, mHangReports.GetSystemUptime(i))) { |
|
1814 return NS_ERROR_FAILURE; |
|
1815 } |
|
1816 if (!JS_SetElement(cx, firefoxUptimeArray, i, mHangReports.GetFirefoxUptime(i))) { |
|
1817 return NS_ERROR_FAILURE; |
|
1818 } |
|
1819 } |
|
1820 |
|
1821 return NS_OK; |
|
1822 } |
|
1823 |
|
1824 static JSObject * |
|
1825 CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks) { |
|
1826 JS::Rooted<JSObject*> ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
1827 if (!ret) { |
|
1828 return nullptr; |
|
1829 } |
|
1830 |
|
1831 JS::Rooted<JSObject*> moduleArray(cx, JS_NewArrayObject(cx, 0)); |
|
1832 if (!moduleArray) { |
|
1833 return nullptr; |
|
1834 } |
|
1835 bool ok = JS_DefineProperty(cx, ret, "memoryMap", moduleArray, |
|
1836 JSPROP_ENUMERATE); |
|
1837 if (!ok) { |
|
1838 return nullptr; |
|
1839 } |
|
1840 |
|
1841 const size_t moduleCount = stacks.GetModuleCount(); |
|
1842 for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) { |
|
1843 // Current module |
|
1844 const Telemetry::ProcessedStack::Module& module = |
|
1845 stacks.GetModule(moduleIndex); |
|
1846 |
|
1847 JS::Rooted<JSObject*> moduleInfoArray(cx, JS_NewArrayObject(cx, 0)); |
|
1848 if (!moduleInfoArray) { |
|
1849 return nullptr; |
|
1850 } |
|
1851 if (!JS_SetElement(cx, moduleArray, moduleIndex, moduleInfoArray)) { |
|
1852 return nullptr; |
|
1853 } |
|
1854 |
|
1855 unsigned index = 0; |
|
1856 |
|
1857 // Module name |
|
1858 JS::Rooted<JSString*> str(cx, JS_NewStringCopyZ(cx, module.mName.c_str())); |
|
1859 if (!str) { |
|
1860 return nullptr; |
|
1861 } |
|
1862 if (!JS_SetElement(cx, moduleInfoArray, index++, str)) { |
|
1863 return nullptr; |
|
1864 } |
|
1865 |
|
1866 // Module breakpad identifier |
|
1867 JS::Rooted<JSString*> id(cx, JS_NewStringCopyZ(cx, module.mBreakpadId.c_str())); |
|
1868 if (!id) { |
|
1869 return nullptr; |
|
1870 } |
|
1871 if (!JS_SetElement(cx, moduleInfoArray, index++, id)) { |
|
1872 return nullptr; |
|
1873 } |
|
1874 } |
|
1875 |
|
1876 JS::Rooted<JSObject*> reportArray(cx, JS_NewArrayObject(cx, 0)); |
|
1877 if (!reportArray) { |
|
1878 return nullptr; |
|
1879 } |
|
1880 ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE); |
|
1881 if (!ok) { |
|
1882 return nullptr; |
|
1883 } |
|
1884 |
|
1885 const size_t length = stacks.GetStackCount(); |
|
1886 for (size_t i = 0; i < length; ++i) { |
|
1887 // Represent call stack PCs as (module index, offset) pairs. |
|
1888 JS::Rooted<JSObject*> pcArray(cx, JS_NewArrayObject(cx, 0)); |
|
1889 if (!pcArray) { |
|
1890 return nullptr; |
|
1891 } |
|
1892 |
|
1893 if (!JS_SetElement(cx, reportArray, i, pcArray)) { |
|
1894 return nullptr; |
|
1895 } |
|
1896 |
|
1897 const CombinedStacks::Stack& stack = stacks.GetStack(i); |
|
1898 const uint32_t pcCount = stack.size(); |
|
1899 for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) { |
|
1900 const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex]; |
|
1901 JS::Rooted<JSObject*> framePair(cx, JS_NewArrayObject(cx, 0)); |
|
1902 if (!framePair) { |
|
1903 return nullptr; |
|
1904 } |
|
1905 int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex) ? |
|
1906 -1 : frame.mModIndex; |
|
1907 if (!JS_SetElement(cx, framePair, 0, modIndex)) { |
|
1908 return nullptr; |
|
1909 } |
|
1910 if (!JS_SetElement(cx, framePair, 1, static_cast<double>(frame.mOffset))) { |
|
1911 return nullptr; |
|
1912 } |
|
1913 if (!JS_SetElement(cx, pcArray, pcIndex, framePair)) { |
|
1914 return nullptr; |
|
1915 } |
|
1916 } |
|
1917 } |
|
1918 |
|
1919 return ret; |
|
1920 } |
|
1921 |
|
1922 static bool |
|
1923 IsValidBreakpadId(const std::string &breakpadId) { |
|
1924 if (breakpadId.size() < 33) { |
|
1925 return false; |
|
1926 } |
|
1927 for (unsigned i = 0, n = breakpadId.size(); i < n; ++i) { |
|
1928 char c = breakpadId[i]; |
|
1929 if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { |
|
1930 return false; |
|
1931 } |
|
1932 } |
|
1933 return true; |
|
1934 } |
|
1935 |
|
1936 // Read a stack from the given file name. In case of any error, aStack is |
|
1937 // unchanged. |
|
1938 static void |
|
1939 ReadStack(const char *aFileName, Telemetry::ProcessedStack &aStack) |
|
1940 { |
|
1941 std::ifstream file(aFileName); |
|
1942 |
|
1943 size_t numModules; |
|
1944 file >> numModules; |
|
1945 if (file.fail()) { |
|
1946 return; |
|
1947 } |
|
1948 |
|
1949 char newline = file.get(); |
|
1950 if (file.fail() || newline != '\n') { |
|
1951 return; |
|
1952 } |
|
1953 |
|
1954 Telemetry::ProcessedStack stack; |
|
1955 for (size_t i = 0; i < numModules; ++i) { |
|
1956 std::string breakpadId; |
|
1957 file >> breakpadId; |
|
1958 if (file.fail() || !IsValidBreakpadId(breakpadId)) { |
|
1959 return; |
|
1960 } |
|
1961 |
|
1962 char space = file.get(); |
|
1963 if (file.fail() || space != ' ') { |
|
1964 return; |
|
1965 } |
|
1966 |
|
1967 std::string moduleName; |
|
1968 getline(file, moduleName); |
|
1969 if (file.fail() || moduleName[0] == ' ') { |
|
1970 return; |
|
1971 } |
|
1972 |
|
1973 Telemetry::ProcessedStack::Module module = { |
|
1974 moduleName, |
|
1975 breakpadId |
|
1976 }; |
|
1977 stack.AddModule(module); |
|
1978 } |
|
1979 |
|
1980 size_t numFrames; |
|
1981 file >> numFrames; |
|
1982 if (file.fail()) { |
|
1983 return; |
|
1984 } |
|
1985 |
|
1986 newline = file.get(); |
|
1987 if (file.fail() || newline != '\n') { |
|
1988 return; |
|
1989 } |
|
1990 |
|
1991 for (size_t i = 0; i < numFrames; ++i) { |
|
1992 uint16_t index; |
|
1993 file >> index; |
|
1994 uintptr_t offset; |
|
1995 file >> std::hex >> offset >> std::dec; |
|
1996 if (file.fail()) { |
|
1997 return; |
|
1998 } |
|
1999 |
|
2000 Telemetry::ProcessedStack::Frame frame = { |
|
2001 offset, |
|
2002 index |
|
2003 }; |
|
2004 stack.AddFrame(frame); |
|
2005 } |
|
2006 |
|
2007 aStack = stack; |
|
2008 } |
|
2009 |
|
2010 static JSObject* |
|
2011 CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time) |
|
2012 { |
|
2013 /* Create JS representation of TimeHistogram, |
|
2014 in the format of Chromium-style histograms. */ |
|
2015 JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
2016 if (!ret) { |
|
2017 return nullptr; |
|
2018 } |
|
2019 |
|
2020 if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0), |
|
2021 JSPROP_ENUMERATE) || |
|
2022 !JS_DefineProperty(cx, ret, "max", |
|
2023 time.GetBucketMax(ArrayLength(time) - 1), |
|
2024 JSPROP_ENUMERATE) || |
|
2025 !JS_DefineProperty(cx, ret, "histogram_type", |
|
2026 nsITelemetry::HISTOGRAM_EXPONENTIAL, |
|
2027 JSPROP_ENUMERATE)) { |
|
2028 return nullptr; |
|
2029 } |
|
2030 // TODO: calculate "sum", "log_sum", and "log_sum_squares" |
|
2031 if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE) || |
|
2032 !JS_DefineProperty(cx, ret, "log_sum", 0.0, JSPROP_ENUMERATE) || |
|
2033 !JS_DefineProperty(cx, ret, "log_sum_squares", 0.0, JSPROP_ENUMERATE)) { |
|
2034 return nullptr; |
|
2035 } |
|
2036 |
|
2037 JS::RootedObject ranges( |
|
2038 cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); |
|
2039 JS::RootedObject counts( |
|
2040 cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); |
|
2041 if (!ranges || !counts) { |
|
2042 return nullptr; |
|
2043 } |
|
2044 /* In a Chromium-style histogram, the first bucket is an "under" bucket |
|
2045 that represents all values below the histogram's range. */ |
|
2046 if (!JS_SetElement(cx, ranges, 0, time.GetBucketMin(0)) || |
|
2047 !JS_SetElement(cx, counts, 0, 0)) { |
|
2048 return nullptr; |
|
2049 } |
|
2050 for (size_t i = 0; i < ArrayLength(time); i++) { |
|
2051 if (!JS_SetElement(cx, ranges, i + 1, time.GetBucketMax(i)) || |
|
2052 !JS_SetElement(cx, counts, i + 1, time[i])) { |
|
2053 return nullptr; |
|
2054 } |
|
2055 } |
|
2056 if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) || |
|
2057 !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) { |
|
2058 return nullptr; |
|
2059 } |
|
2060 return ret; |
|
2061 } |
|
2062 |
|
2063 static JSObject* |
|
2064 CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang) |
|
2065 { |
|
2066 JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
2067 if (!ret) { |
|
2068 return nullptr; |
|
2069 } |
|
2070 |
|
2071 const Telemetry::HangHistogram::Stack& hangStack = hang.GetStack(); |
|
2072 JS::RootedObject stack(cx, |
|
2073 JS_NewArrayObject(cx, hangStack.length())); |
|
2074 if (!ret) { |
|
2075 return nullptr; |
|
2076 } |
|
2077 for (size_t i = 0; i < hangStack.length(); i++) { |
|
2078 JS::RootedString string(cx, JS_NewStringCopyZ(cx, hangStack[i])); |
|
2079 if (!JS_SetElement(cx, stack, i, string)) { |
|
2080 return nullptr; |
|
2081 } |
|
2082 } |
|
2083 |
|
2084 JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang)); |
|
2085 if (!time || |
|
2086 !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) || |
|
2087 !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE)) { |
|
2088 return nullptr; |
|
2089 } |
|
2090 return ret; |
|
2091 } |
|
2092 |
|
2093 static JSObject* |
|
2094 CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread) |
|
2095 { |
|
2096 JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); |
|
2097 if (!ret) { |
|
2098 return nullptr; |
|
2099 } |
|
2100 JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName())); |
|
2101 if (!name || |
|
2102 !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) { |
|
2103 return nullptr; |
|
2104 } |
|
2105 |
|
2106 JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity)); |
|
2107 if (!activity || |
|
2108 !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) { |
|
2109 return nullptr; |
|
2110 } |
|
2111 |
|
2112 JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0)); |
|
2113 if (!hangs) { |
|
2114 return nullptr; |
|
2115 } |
|
2116 for (size_t i = 0; i < thread.mHangs.length(); i++) { |
|
2117 JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i])); |
|
2118 if (!JS_SetElement(cx, hangs, i, obj)) { |
|
2119 return nullptr; |
|
2120 } |
|
2121 } |
|
2122 if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) { |
|
2123 return nullptr; |
|
2124 } |
|
2125 return ret; |
|
2126 } |
|
2127 |
|
2128 NS_IMETHODIMP |
|
2129 TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::MutableHandle<JS::Value> ret) |
|
2130 { |
|
2131 JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0)); |
|
2132 if (!retObj) { |
|
2133 return NS_ERROR_FAILURE; |
|
2134 } |
|
2135 size_t threadIndex = 0; |
|
2136 |
|
2137 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR |
|
2138 /* First add active threads; we need to hold |iter| (and its lock) |
|
2139 throughout this method to avoid a race condition where a thread can |
|
2140 be recorded twice if the thread is destroyed while this method is |
|
2141 running */ |
|
2142 BackgroundHangMonitor::ThreadHangStatsIterator iter; |
|
2143 for (Telemetry::ThreadHangStats* histogram = iter.GetNext(); |
|
2144 histogram; histogram = iter.GetNext()) { |
|
2145 JS::RootedObject obj(cx, |
|
2146 CreateJSThreadHangStats(cx, *histogram)); |
|
2147 if (!JS_SetElement(cx, retObj, threadIndex++, obj)) { |
|
2148 return NS_ERROR_FAILURE; |
|
2149 } |
|
2150 } |
|
2151 #endif |
|
2152 |
|
2153 // Add saved threads next |
|
2154 MutexAutoLock autoLock(mThreadHangStatsMutex); |
|
2155 for (size_t i = 0; i < mThreadHangStats.length(); i++) { |
|
2156 JS::RootedObject obj(cx, |
|
2157 CreateJSThreadHangStats(cx, mThreadHangStats[i])); |
|
2158 if (!JS_SetElement(cx, retObj, threadIndex++, obj)) { |
|
2159 return NS_ERROR_FAILURE; |
|
2160 } |
|
2161 } |
|
2162 ret.setObject(*retObj); |
|
2163 return NS_OK; |
|
2164 } |
|
2165 |
|
2166 void |
|
2167 TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) |
|
2168 { |
|
2169 nsAutoCString nativePath; |
|
2170 nsresult rv = aProfileDir->GetNativePath(nativePath); |
|
2171 if (NS_FAILED(rv)) { |
|
2172 return; |
|
2173 } |
|
2174 |
|
2175 const char *name = nativePath.get(); |
|
2176 PRDir *dir = PR_OpenDir(name); |
|
2177 if (!dir) { |
|
2178 return; |
|
2179 } |
|
2180 |
|
2181 PRDirEntry *ent; |
|
2182 const char *prefix = "Telemetry.LateWriteFinal-"; |
|
2183 unsigned int prefixLen = strlen(prefix); |
|
2184 while ((ent = PR_ReadDir(dir, PR_SKIP_NONE))) { |
|
2185 if (strncmp(prefix, ent->name, prefixLen) != 0) { |
|
2186 continue; |
|
2187 } |
|
2188 |
|
2189 nsAutoCString stackNativePath = nativePath; |
|
2190 stackNativePath += XPCOM_FILE_PATH_SEPARATOR; |
|
2191 stackNativePath += nsDependentCString(ent->name); |
|
2192 |
|
2193 Telemetry::ProcessedStack stack; |
|
2194 ReadStack(stackNativePath.get(), stack); |
|
2195 if (stack.GetStackSize() != 0) { |
|
2196 mLateWritesStacks.AddStack(stack); |
|
2197 } |
|
2198 // Delete the file so that we don't report it again on the next run. |
|
2199 PR_Delete(stackNativePath.get()); |
|
2200 } |
|
2201 PR_CloseDir(dir); |
|
2202 } |
|
2203 |
|
2204 NS_IMETHODIMP |
|
2205 TelemetryImpl::GetLateWrites(JSContext *cx, JS::MutableHandle<JS::Value> ret) |
|
2206 { |
|
2207 // The user must call AsyncReadTelemetryData first. We return an empty list |
|
2208 // instead of reporting a failure so that the rest of telemetry can uniformly |
|
2209 // handle the read not being available yet. |
|
2210 |
|
2211 // FIXME: we allocate the js object again and again in the getter. We should |
|
2212 // figure out a way to cache it. In order to do that we have to call |
|
2213 // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl |
|
2214 // constructor, but it is not clear how to get a JSContext in there. |
|
2215 // Another option would be to call it in here when we first call |
|
2216 // CreateJSStackObject, but we would still need to figure out where to call |
|
2217 // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot |
|
2218 // and just set the pointer to nullptr is the telemetry destructor? |
|
2219 |
|
2220 JSObject *report; |
|
2221 if (!mCachedTelemetryData) { |
|
2222 CombinedStacks empty; |
|
2223 report = CreateJSStackObject(cx, empty); |
|
2224 } else { |
|
2225 report = CreateJSStackObject(cx, mLateWritesStacks); |
|
2226 } |
|
2227 |
|
2228 if (report == nullptr) { |
|
2229 return NS_ERROR_FAILURE; |
|
2230 } |
|
2231 |
|
2232 ret.setObject(*report); |
|
2233 return NS_OK; |
|
2234 } |
|
2235 |
|
2236 NS_IMETHODIMP |
|
2237 TelemetryImpl::RegisteredHistograms(uint32_t *aCount, char*** aHistograms) |
|
2238 { |
|
2239 size_t count = ArrayLength(gHistograms); |
|
2240 size_t offset = 0; |
|
2241 char** histograms = static_cast<char**>(nsMemory::Alloc(count * sizeof(char*))); |
|
2242 |
|
2243 for (size_t i = 0; i < count; ++i) { |
|
2244 if (IsExpired(gHistograms[i].expiration())) { |
|
2245 offset++; |
|
2246 continue; |
|
2247 } |
|
2248 |
|
2249 const char* h = gHistograms[i].id(); |
|
2250 size_t len = strlen(h); |
|
2251 histograms[i - offset] = static_cast<char*>(nsMemory::Clone(h, len+1)); |
|
2252 } |
|
2253 |
|
2254 *aCount = count - offset; |
|
2255 *aHistograms = histograms; |
|
2256 return NS_OK; |
|
2257 } |
|
2258 |
|
2259 NS_IMETHODIMP |
|
2260 TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, |
|
2261 JS::MutableHandle<JS::Value> ret) |
|
2262 { |
|
2263 Histogram *h; |
|
2264 nsresult rv = GetHistogramByName(name, &h); |
|
2265 if (NS_FAILED(rv)) |
|
2266 return rv; |
|
2267 |
|
2268 return WrapAndReturnHistogram(h, cx, ret); |
|
2269 } |
|
2270 |
|
2271 NS_IMETHODIMP |
|
2272 TelemetryImpl::GetCanRecord(bool *ret) { |
|
2273 *ret = mCanRecord; |
|
2274 return NS_OK; |
|
2275 } |
|
2276 |
|
2277 NS_IMETHODIMP |
|
2278 TelemetryImpl::SetCanRecord(bool canRecord) { |
|
2279 mCanRecord = !!canRecord; |
|
2280 return NS_OK; |
|
2281 } |
|
2282 |
|
2283 bool |
|
2284 TelemetryImpl::CanRecord() { |
|
2285 return !sTelemetry || sTelemetry->mCanRecord; |
|
2286 } |
|
2287 |
|
2288 NS_IMETHODIMP |
|
2289 TelemetryImpl::GetCanSend(bool *ret) { |
|
2290 #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) |
|
2291 *ret = true; |
|
2292 #else |
|
2293 *ret = false; |
|
2294 #endif |
|
2295 return NS_OK; |
|
2296 } |
|
2297 |
|
2298 already_AddRefed<nsITelemetry> |
|
2299 TelemetryImpl::CreateTelemetryInstance() |
|
2300 { |
|
2301 NS_ABORT_IF_FALSE(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()"); |
|
2302 sTelemetry = new TelemetryImpl(); |
|
2303 // AddRef for the local reference |
|
2304 NS_ADDREF(sTelemetry); |
|
2305 // AddRef for the caller |
|
2306 nsCOMPtr<nsITelemetry> ret = sTelemetry; |
|
2307 |
|
2308 sTelemetry->InitMemoryReporter(); |
|
2309 |
|
2310 return ret.forget(); |
|
2311 } |
|
2312 |
|
2313 void |
|
2314 TelemetryImpl::ShutdownTelemetry() |
|
2315 { |
|
2316 // No point in collecting IO beyond this point |
|
2317 ClearIOReporting(); |
|
2318 NS_IF_RELEASE(sTelemetry); |
|
2319 } |
|
2320 |
|
2321 void |
|
2322 TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay, |
|
2323 SanitizedState state) |
|
2324 { |
|
2325 AutoHashtable<SlowSQLEntryType> *slowSQLMap = nullptr; |
|
2326 if (state == Sanitized) |
|
2327 slowSQLMap = &(sTelemetry->mSanitizedSQL); |
|
2328 else |
|
2329 slowSQLMap = &(sTelemetry->mPrivateSQL); |
|
2330 |
|
2331 MutexAutoLock hashMutex(sTelemetry->mHashMutex); |
|
2332 |
|
2333 SlowSQLEntryType *entry = slowSQLMap->GetEntry(sql); |
|
2334 if (!entry) { |
|
2335 entry = slowSQLMap->PutEntry(sql); |
|
2336 if (MOZ_UNLIKELY(!entry)) |
|
2337 return; |
|
2338 entry->mData.mainThread.hitCount = 0; |
|
2339 entry->mData.mainThread.totalTime = 0; |
|
2340 entry->mData.otherThreads.hitCount = 0; |
|
2341 entry->mData.otherThreads.totalTime = 0; |
|
2342 } |
|
2343 |
|
2344 if (NS_IsMainThread()) { |
|
2345 entry->mData.mainThread.hitCount++; |
|
2346 entry->mData.mainThread.totalTime += delay; |
|
2347 } else { |
|
2348 entry->mData.otherThreads.hitCount++; |
|
2349 entry->mData.otherThreads.totalTime += delay; |
|
2350 } |
|
2351 } |
|
2352 |
|
2353 /** |
|
2354 * This method replaces string literals in SQL strings with the word :private |
|
2355 * |
|
2356 * States used in this state machine: |
|
2357 * |
|
2358 * NORMAL: |
|
2359 * - This is the active state when not iterating over a string literal or |
|
2360 * comment |
|
2361 * |
|
2362 * SINGLE_QUOTE: |
|
2363 * - Defined here: http://www.sqlite.org/lang_expr.html |
|
2364 * - This state represents iterating over a string literal opened with |
|
2365 * a single quote. |
|
2366 * - A single quote within the string can be encoded by putting 2 single quotes |
|
2367 * in a row, e.g. 'This literal contains an escaped quote ''' |
|
2368 * - Any double quotes found within a single-quoted literal are ignored |
|
2369 * - This state covers BLOB literals, e.g. X'ABC123' |
|
2370 * - The string literal and the enclosing quotes will be replaced with |
|
2371 * the text :private |
|
2372 * |
|
2373 * DOUBLE_QUOTE: |
|
2374 * - Same rules as the SINGLE_QUOTE state. |
|
2375 * - According to http://www.sqlite.org/lang_keywords.html, |
|
2376 * SQLite interprets text in double quotes as an identifier unless it's used in |
|
2377 * a context where it cannot be resolved to an identifier and a string literal |
|
2378 * is allowed. This method removes text in double-quotes for safety. |
|
2379 * |
|
2380 * DASH_COMMENT: |
|
2381 * - http://www.sqlite.org/lang_comment.html |
|
2382 * - A dash comment starts with two dashes in a row, |
|
2383 * e.g. DROP TABLE foo -- a comment |
|
2384 * - Any text following two dashes in a row is interpreted as a comment until |
|
2385 * end of input or a newline character |
|
2386 * - Any quotes found within the comment are ignored and no replacements made |
|
2387 * |
|
2388 * C_STYLE_COMMENT: |
|
2389 * - http://www.sqlite.org/lang_comment.html |
|
2390 * - A C-style comment starts with a forward slash and an asterisk, and ends |
|
2391 * with an asterisk and a forward slash |
|
2392 * - Any text following comment start is interpreted as a comment up to end of |
|
2393 * input or comment end |
|
2394 * - Any quotes found within the comment are ignored and no replacements made |
|
2395 */ |
|
2396 nsCString |
|
2397 TelemetryImpl::SanitizeSQL(const nsACString &sql) { |
|
2398 nsCString output; |
|
2399 int length = sql.Length(); |
|
2400 |
|
2401 typedef enum { |
|
2402 NORMAL, |
|
2403 SINGLE_QUOTE, |
|
2404 DOUBLE_QUOTE, |
|
2405 DASH_COMMENT, |
|
2406 C_STYLE_COMMENT, |
|
2407 } State; |
|
2408 |
|
2409 State state = NORMAL; |
|
2410 int fragmentStart = 0; |
|
2411 for (int i = 0; i < length; i++) { |
|
2412 char character = sql[i]; |
|
2413 char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0'; |
|
2414 |
|
2415 switch (character) { |
|
2416 case '\'': |
|
2417 case '"': |
|
2418 if (state == NORMAL) { |
|
2419 state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE; |
|
2420 output += nsDependentCSubstring(sql, fragmentStart, i - fragmentStart); |
|
2421 output += ":private"; |
|
2422 fragmentStart = -1; |
|
2423 } else if ((state == SINGLE_QUOTE && character == '\'') || |
|
2424 (state == DOUBLE_QUOTE && character == '"')) { |
|
2425 if (nextCharacter == character) { |
|
2426 // Two consecutive quotes within a string literal are a single escaped quote |
|
2427 i++; |
|
2428 } else { |
|
2429 state = NORMAL; |
|
2430 fragmentStart = i + 1; |
|
2431 } |
|
2432 } |
|
2433 break; |
|
2434 case '-': |
|
2435 if (state == NORMAL) { |
|
2436 if (nextCharacter == '-') { |
|
2437 state = DASH_COMMENT; |
|
2438 i++; |
|
2439 } |
|
2440 } |
|
2441 break; |
|
2442 case '\n': |
|
2443 if (state == DASH_COMMENT) { |
|
2444 state = NORMAL; |
|
2445 } |
|
2446 break; |
|
2447 case '/': |
|
2448 if (state == NORMAL) { |
|
2449 if (nextCharacter == '*') { |
|
2450 state = C_STYLE_COMMENT; |
|
2451 i++; |
|
2452 } |
|
2453 } |
|
2454 break; |
|
2455 case '*': |
|
2456 if (state == C_STYLE_COMMENT) { |
|
2457 if (nextCharacter == '/') { |
|
2458 state = NORMAL; |
|
2459 } |
|
2460 } |
|
2461 break; |
|
2462 default: |
|
2463 continue; |
|
2464 } |
|
2465 } |
|
2466 |
|
2467 if ((fragmentStart >= 0) && fragmentStart < length) |
|
2468 output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart); |
|
2469 |
|
2470 return output; |
|
2471 } |
|
2472 |
|
2473 // Slow SQL statements will be automatically |
|
2474 // trimmed to kMaxSlowStatementLength characters. |
|
2475 // This limit doesn't include the ellipsis and DB name, |
|
2476 // that are appended at the end of the stored statement. |
|
2477 const uint32_t kMaxSlowStatementLength = 1000; |
|
2478 |
|
2479 void |
|
2480 TelemetryImpl::RecordSlowStatement(const nsACString &sql, |
|
2481 const nsACString &dbName, |
|
2482 uint32_t delay) |
|
2483 { |
|
2484 if (!sTelemetry || !sTelemetry->mCanRecord) |
|
2485 return; |
|
2486 |
|
2487 bool isFirefoxDB = sTelemetry->mTrackedDBs.Contains(dbName); |
|
2488 if (isFirefoxDB) { |
|
2489 nsAutoCString sanitizedSQL(SanitizeSQL(sql)); |
|
2490 if (sanitizedSQL.Length() > kMaxSlowStatementLength) { |
|
2491 sanitizedSQL.SetLength(kMaxSlowStatementLength); |
|
2492 sanitizedSQL += "..."; |
|
2493 } |
|
2494 sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get()); |
|
2495 StoreSlowSQL(sanitizedSQL, delay, Sanitized); |
|
2496 } else { |
|
2497 // Report aggregate DB-level statistics for addon DBs |
|
2498 nsAutoCString aggregate; |
|
2499 aggregate.AppendPrintf("Untracked SQL for %s", |
|
2500 nsPromiseFlatCString(dbName).get()); |
|
2501 StoreSlowSQL(aggregate, delay, Sanitized); |
|
2502 } |
|
2503 |
|
2504 nsAutoCString fullSQL; |
|
2505 fullSQL.AppendPrintf("%s /* %s */", |
|
2506 nsPromiseFlatCString(sql).get(), |
|
2507 nsPromiseFlatCString(dbName).get()); |
|
2508 StoreSlowSQL(fullSQL, delay, Unsanitized); |
|
2509 } |
|
2510 |
|
2511 #if defined(MOZ_ENABLE_PROFILER_SPS) |
|
2512 void |
|
2513 TelemetryImpl::RecordChromeHang(uint32_t aDuration, |
|
2514 Telemetry::ProcessedStack &aStack, |
|
2515 int32_t aSystemUptime, |
|
2516 int32_t aFirefoxUptime) |
|
2517 { |
|
2518 if (!sTelemetry || !sTelemetry->mCanRecord) |
|
2519 return; |
|
2520 |
|
2521 MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex); |
|
2522 |
|
2523 sTelemetry->mHangReports.AddHang(aStack, aDuration, |
|
2524 aSystemUptime, aFirefoxUptime); |
|
2525 } |
|
2526 #endif |
|
2527 |
|
2528 void |
|
2529 TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats) |
|
2530 { |
|
2531 if (!sTelemetry || !sTelemetry->mCanRecord) |
|
2532 return; |
|
2533 |
|
2534 MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex); |
|
2535 |
|
2536 sTelemetry->mThreadHangStats.append(Move(aStats)); |
|
2537 } |
|
2538 |
|
2539 NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter) |
|
2540 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance) |
|
2541 |
|
2542 #define NS_TELEMETRY_CID \ |
|
2543 {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}} |
|
2544 NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID); |
|
2545 |
|
2546 const Module::CIDEntry kTelemetryCIDs[] = { |
|
2547 { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor }, |
|
2548 { nullptr } |
|
2549 }; |
|
2550 |
|
2551 const Module::ContractIDEntry kTelemetryContracts[] = { |
|
2552 { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID }, |
|
2553 { nullptr } |
|
2554 }; |
|
2555 |
|
2556 const Module kTelemetryModule = { |
|
2557 Module::kVersion, |
|
2558 kTelemetryCIDs, |
|
2559 kTelemetryContracts, |
|
2560 nullptr, |
|
2561 nullptr, |
|
2562 nullptr, |
|
2563 TelemetryImpl::ShutdownTelemetry |
|
2564 }; |
|
2565 |
|
2566 NS_IMETHODIMP |
|
2567 TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret) |
|
2568 { |
|
2569 if (sTelemetryIOObserver) { |
|
2570 JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), |
|
2571 JS::NullPtr())); |
|
2572 if (!obj) { |
|
2573 return NS_ERROR_FAILURE; |
|
2574 } |
|
2575 |
|
2576 if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) { |
|
2577 return NS_ERROR_FAILURE; |
|
2578 } |
|
2579 ret.setObject(*obj); |
|
2580 return NS_OK; |
|
2581 } |
|
2582 ret.setNull(); |
|
2583 return NS_OK; |
|
2584 } |
|
2585 |
|
2586 NS_IMETHODIMP |
|
2587 TelemetryImpl::MsSinceProcessStart(double* aResult) |
|
2588 { |
|
2589 bool error; |
|
2590 *aResult = (TimeStamp::NowLoRes() - |
|
2591 TimeStamp::ProcessCreation(error)).ToMilliseconds(); |
|
2592 if (error) { |
|
2593 return NS_ERROR_NOT_AVAILABLE; |
|
2594 } |
|
2595 return NS_OK; |
|
2596 } |
|
2597 |
|
2598 size_t |
|
2599 TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) |
|
2600 { |
|
2601 size_t n = aMallocSizeOf(this); |
|
2602 // Ignore the hashtables in mAddonMap; they are not significant. |
|
2603 n += mAddonMap.SizeOfExcludingThis(nullptr, aMallocSizeOf); |
|
2604 n += mHistogramMap.SizeOfExcludingThis(nullptr, aMallocSizeOf); |
|
2605 { // Scope for mHashMutex lock |
|
2606 MutexAutoLock lock(mHashMutex); |
|
2607 n += mPrivateSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf); |
|
2608 n += mSanitizedSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf); |
|
2609 } |
|
2610 n += mTrackedDBs.SizeOfExcludingThis(nullptr, aMallocSizeOf); |
|
2611 { // Scope for mHangReportsMutex lock |
|
2612 MutexAutoLock lock(mHangReportsMutex); |
|
2613 n += mHangReports.SizeOfExcludingThis(); |
|
2614 } |
|
2615 { // Scope for mThreadHangStatsMutex lock |
|
2616 MutexAutoLock lock(mThreadHangStatsMutex); |
|
2617 n += mThreadHangStats.sizeOfExcludingThis(aMallocSizeOf); |
|
2618 } |
|
2619 |
|
2620 // It's a bit gross that we measure this other stuff that lives outside of |
|
2621 // TelemetryImpl... oh well. |
|
2622 if (sTelemetryIOObserver) { |
|
2623 n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf); |
|
2624 } |
|
2625 StatisticsRecorder::Histograms hs; |
|
2626 StatisticsRecorder::GetHistograms(&hs); |
|
2627 for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { |
|
2628 Histogram *h = *it; |
|
2629 n += h->SizeOfIncludingThis(aMallocSizeOf); |
|
2630 } |
|
2631 |
|
2632 return n; |
|
2633 } |
|
2634 |
|
2635 } // anonymous namespace |
|
2636 |
|
2637 namespace mozilla { |
|
2638 void |
|
2639 RecordShutdownStartTimeStamp() { |
|
2640 #ifdef DEBUG |
|
2641 // FIXME: this function should only be called once, since it should be called |
|
2642 // at the earliest point we *know* we are shutting down. Unfortunately |
|
2643 // this assert has been firing. Given that if we are called multiple times |
|
2644 // we just keep the last timestamp, the assert is commented for now. |
|
2645 static bool recorded = false; |
|
2646 // MOZ_ASSERT(!recorded); |
|
2647 (void)recorded; // Silence unused-var warnings (remove when assert re-enabled) |
|
2648 recorded = true; |
|
2649 #endif |
|
2650 |
|
2651 if (!Telemetry::CanRecord()) |
|
2652 return; |
|
2653 |
|
2654 gRecordedShutdownStartTime = TimeStamp::Now(); |
|
2655 |
|
2656 GetShutdownTimeFileName(); |
|
2657 } |
|
2658 |
|
2659 void |
|
2660 RecordShutdownEndTimeStamp() { |
|
2661 if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName) |
|
2662 return; |
|
2663 |
|
2664 nsCString name(gRecordedShutdownTimeFileName); |
|
2665 PL_strfree(gRecordedShutdownTimeFileName); |
|
2666 gRecordedShutdownTimeFileName = nullptr; |
|
2667 gAlreadyFreedShutdownTimeFileName = true; |
|
2668 |
|
2669 nsCString tmpName = name; |
|
2670 tmpName += ".tmp"; |
|
2671 FILE *f = fopen(tmpName.get(), "w"); |
|
2672 if (!f) |
|
2673 return; |
|
2674 // On a normal release build this should be called just before |
|
2675 // calling _exit, but on a debug build or when the user forces a full |
|
2676 // shutdown this is called as late as possible, so we have to |
|
2677 // white list this write as write poisoning will be enabled. |
|
2678 MozillaRegisterDebugFILE(f); |
|
2679 |
|
2680 TimeStamp now = TimeStamp::Now(); |
|
2681 MOZ_ASSERT(now >= gRecordedShutdownStartTime); |
|
2682 TimeDuration diff = now - gRecordedShutdownStartTime; |
|
2683 uint32_t diff2 = diff.ToMilliseconds(); |
|
2684 int written = fprintf(f, "%d\n", diff2); |
|
2685 MozillaUnRegisterDebugFILE(f); |
|
2686 int rv = fclose(f); |
|
2687 if (written < 0 || rv != 0) { |
|
2688 PR_Delete(tmpName.get()); |
|
2689 return; |
|
2690 } |
|
2691 PR_Delete(name.get()); |
|
2692 PR_Rename(tmpName.get(), name.get()); |
|
2693 } |
|
2694 |
|
2695 namespace Telemetry { |
|
2696 |
|
2697 void |
|
2698 Accumulate(ID aHistogram, uint32_t aSample) |
|
2699 { |
|
2700 if (!TelemetryImpl::CanRecord()) { |
|
2701 return; |
|
2702 } |
|
2703 Histogram *h; |
|
2704 nsresult rv = GetHistogramByEnumId(aHistogram, &h); |
|
2705 if (NS_SUCCEEDED(rv)) |
|
2706 h->Add(aSample); |
|
2707 } |
|
2708 |
|
2709 void |
|
2710 Accumulate(const char* name, uint32_t sample) |
|
2711 { |
|
2712 if (!TelemetryImpl::CanRecord()) { |
|
2713 return; |
|
2714 } |
|
2715 ID id; |
|
2716 nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id); |
|
2717 if (NS_FAILED(rv)) { |
|
2718 return; |
|
2719 } |
|
2720 |
|
2721 Histogram *h; |
|
2722 rv = GetHistogramByEnumId(id, &h); |
|
2723 if (NS_SUCCEEDED(rv)) { |
|
2724 h->Add(sample); |
|
2725 } |
|
2726 } |
|
2727 |
|
2728 void |
|
2729 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end) |
|
2730 { |
|
2731 Accumulate(aHistogram, |
|
2732 static_cast<uint32_t>((end - start).ToMilliseconds())); |
|
2733 } |
|
2734 |
|
2735 bool |
|
2736 CanRecord() |
|
2737 { |
|
2738 return TelemetryImpl::CanRecord(); |
|
2739 } |
|
2740 |
|
2741 base::Histogram* |
|
2742 GetHistogramById(ID id) |
|
2743 { |
|
2744 Histogram *h = nullptr; |
|
2745 GetHistogramByEnumId(id, &h); |
|
2746 return h; |
|
2747 } |
|
2748 |
|
2749 void |
|
2750 RecordSlowSQLStatement(const nsACString &statement, |
|
2751 const nsACString &dbName, |
|
2752 uint32_t delay) |
|
2753 { |
|
2754 TelemetryImpl::RecordSlowStatement(statement, dbName, delay); |
|
2755 } |
|
2756 |
|
2757 void Init() |
|
2758 { |
|
2759 // Make the service manager hold a long-lived reference to the service |
|
2760 nsCOMPtr<nsITelemetry> telemetryService = |
|
2761 do_GetService("@mozilla.org/base/telemetry;1"); |
|
2762 MOZ_ASSERT(telemetryService); |
|
2763 } |
|
2764 |
|
2765 #if defined(MOZ_ENABLE_PROFILER_SPS) |
|
2766 void RecordChromeHang(uint32_t duration, |
|
2767 ProcessedStack &aStack, |
|
2768 int32_t aSystemUptime, |
|
2769 int32_t aFirefoxUptime) |
|
2770 { |
|
2771 TelemetryImpl::RecordChromeHang(duration, aStack, |
|
2772 aSystemUptime, aFirefoxUptime); |
|
2773 } |
|
2774 #endif |
|
2775 |
|
2776 void RecordThreadHangStats(ThreadHangStats& aStats) |
|
2777 { |
|
2778 TelemetryImpl::RecordThreadHangStats(aStats); |
|
2779 } |
|
2780 |
|
2781 ProcessedStack::ProcessedStack() |
|
2782 { |
|
2783 } |
|
2784 |
|
2785 size_t ProcessedStack::GetStackSize() const |
|
2786 { |
|
2787 return mStack.size(); |
|
2788 } |
|
2789 |
|
2790 const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const |
|
2791 { |
|
2792 MOZ_ASSERT(aIndex < mStack.size()); |
|
2793 return mStack[aIndex]; |
|
2794 } |
|
2795 |
|
2796 void ProcessedStack::AddFrame(const Frame &aFrame) |
|
2797 { |
|
2798 mStack.push_back(aFrame); |
|
2799 } |
|
2800 |
|
2801 size_t ProcessedStack::GetNumModules() const |
|
2802 { |
|
2803 return mModules.size(); |
|
2804 } |
|
2805 |
|
2806 const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const |
|
2807 { |
|
2808 MOZ_ASSERT(aIndex < mModules.size()); |
|
2809 return mModules[aIndex]; |
|
2810 } |
|
2811 |
|
2812 void ProcessedStack::AddModule(const Module &aModule) |
|
2813 { |
|
2814 mModules.push_back(aModule); |
|
2815 } |
|
2816 |
|
2817 void ProcessedStack::Clear() { |
|
2818 mModules.clear(); |
|
2819 mStack.clear(); |
|
2820 } |
|
2821 |
|
2822 bool ProcessedStack::Module::operator==(const Module& aOther) const { |
|
2823 return mName == aOther.mName && |
|
2824 mBreakpadId == aOther.mBreakpadId; |
|
2825 } |
|
2826 |
|
2827 struct StackFrame |
|
2828 { |
|
2829 uintptr_t mPC; // The program counter at this position in the call stack. |
|
2830 uint16_t mIndex; // The number of this frame in the call stack. |
|
2831 uint16_t mModIndex; // The index of module that has this program counter. |
|
2832 }; |
|
2833 |
|
2834 |
|
2835 #ifdef MOZ_ENABLE_PROFILER_SPS |
|
2836 static bool CompareByPC(const StackFrame &a, const StackFrame &b) |
|
2837 { |
|
2838 return a.mPC < b.mPC; |
|
2839 } |
|
2840 |
|
2841 static bool CompareByIndex(const StackFrame &a, const StackFrame &b) |
|
2842 { |
|
2843 return a.mIndex < b.mIndex; |
|
2844 } |
|
2845 #endif |
|
2846 |
|
2847 ProcessedStack |
|
2848 GetStackAndModules(const std::vector<uintptr_t>& aPCs) |
|
2849 { |
|
2850 std::vector<StackFrame> rawStack; |
|
2851 for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(), |
|
2852 e = aPCs.end(); i != e; ++i) { |
|
2853 uintptr_t aPC = *i; |
|
2854 StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()), |
|
2855 std::numeric_limits<uint16_t>::max()}; |
|
2856 rawStack.push_back(Frame); |
|
2857 } |
|
2858 |
|
2859 #ifdef MOZ_ENABLE_PROFILER_SPS |
|
2860 // Remove all modules not referenced by a PC on the stack |
|
2861 std::sort(rawStack.begin(), rawStack.end(), CompareByPC); |
|
2862 |
|
2863 size_t moduleIndex = 0; |
|
2864 size_t stackIndex = 0; |
|
2865 size_t stackSize = rawStack.size(); |
|
2866 |
|
2867 SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf(); |
|
2868 rawModules.SortByAddress(); |
|
2869 |
|
2870 while (moduleIndex < rawModules.GetSize()) { |
|
2871 const SharedLibrary& module = rawModules.GetEntry(moduleIndex); |
|
2872 uintptr_t moduleStart = module.GetStart(); |
|
2873 uintptr_t moduleEnd = module.GetEnd() - 1; |
|
2874 // the interval is [moduleStart, moduleEnd) |
|
2875 |
|
2876 bool moduleReferenced = false; |
|
2877 for (;stackIndex < stackSize; ++stackIndex) { |
|
2878 uintptr_t pc = rawStack[stackIndex].mPC; |
|
2879 if (pc >= moduleEnd) |
|
2880 break; |
|
2881 |
|
2882 if (pc >= moduleStart) { |
|
2883 // If the current PC is within the current module, mark |
|
2884 // module as used |
|
2885 moduleReferenced = true; |
|
2886 rawStack[stackIndex].mPC -= moduleStart; |
|
2887 rawStack[stackIndex].mModIndex = moduleIndex; |
|
2888 } else { |
|
2889 // PC does not belong to any module. It is probably from |
|
2890 // the JIT. Use a fixed mPC so that we don't get different |
|
2891 // stacks on different runs. |
|
2892 rawStack[stackIndex].mPC = |
|
2893 std::numeric_limits<uintptr_t>::max(); |
|
2894 } |
|
2895 } |
|
2896 |
|
2897 if (moduleReferenced) { |
|
2898 ++moduleIndex; |
|
2899 } else { |
|
2900 // Remove module if no PCs within its address range |
|
2901 rawModules.RemoveEntries(moduleIndex, moduleIndex + 1); |
|
2902 } |
|
2903 } |
|
2904 |
|
2905 for (;stackIndex < stackSize; ++stackIndex) { |
|
2906 // These PCs are past the last module. |
|
2907 rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max(); |
|
2908 } |
|
2909 |
|
2910 std::sort(rawStack.begin(), rawStack.end(), CompareByIndex); |
|
2911 #endif |
|
2912 |
|
2913 // Copy the information to the return value. |
|
2914 ProcessedStack Ret; |
|
2915 for (std::vector<StackFrame>::iterator i = rawStack.begin(), |
|
2916 e = rawStack.end(); i != e; ++i) { |
|
2917 const StackFrame &rawFrame = *i; |
|
2918 ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex }; |
|
2919 Ret.AddFrame(frame); |
|
2920 } |
|
2921 |
|
2922 #ifdef MOZ_ENABLE_PROFILER_SPS |
|
2923 for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) { |
|
2924 const SharedLibrary &info = rawModules.GetEntry(i); |
|
2925 const std::string &name = info.GetName(); |
|
2926 std::string basename = name; |
|
2927 #ifdef XP_MACOSX |
|
2928 // FIXME: We want to use just the basename as the libname, but the |
|
2929 // current profiler addon needs the full path name, so we compute the |
|
2930 // basename in here. |
|
2931 size_t pos = name.rfind('/'); |
|
2932 if (pos != std::string::npos) { |
|
2933 basename = name.substr(pos + 1); |
|
2934 } |
|
2935 #endif |
|
2936 ProcessedStack::Module module = { |
|
2937 basename, |
|
2938 info.GetBreakpadId() |
|
2939 }; |
|
2940 Ret.AddModule(module); |
|
2941 } |
|
2942 #endif |
|
2943 |
|
2944 return Ret; |
|
2945 } |
|
2946 |
|
2947 void |
|
2948 WriteFailedProfileLock(nsIFile* aProfileDir) |
|
2949 { |
|
2950 nsCOMPtr<nsIFile> file; |
|
2951 nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir); |
|
2952 NS_ENSURE_SUCCESS_VOID(rv); |
|
2953 int64_t fileSize = 0; |
|
2954 rv = file->GetFileSize(&fileSize); |
|
2955 // It's expected that the file might not exist yet |
|
2956 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { |
|
2957 return; |
|
2958 } |
|
2959 nsCOMPtr<nsIFileStream> fileStream; |
|
2960 rv = NS_NewLocalFileStream(getter_AddRefs(fileStream), file, |
|
2961 PR_RDWR | PR_CREATE_FILE, 0640); |
|
2962 NS_ENSURE_SUCCESS_VOID(rv); |
|
2963 NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize); |
|
2964 unsigned int failedLockCount = 0; |
|
2965 if (fileSize > 0) { |
|
2966 nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(fileStream); |
|
2967 NS_ENSURE_TRUE_VOID(inStream); |
|
2968 if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) { |
|
2969 failedLockCount = 0; |
|
2970 } |
|
2971 } |
|
2972 ++failedLockCount; |
|
2973 nsAutoCString bufStr; |
|
2974 bufStr.AppendInt(static_cast<int>(failedLockCount)); |
|
2975 nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(fileStream); |
|
2976 NS_ENSURE_TRUE_VOID(seekStream); |
|
2977 // If we read in an existing failed lock count, we need to reset the file ptr |
|
2978 if (fileSize > 0) { |
|
2979 rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); |
|
2980 NS_ENSURE_SUCCESS_VOID(rv); |
|
2981 } |
|
2982 nsCOMPtr<nsIOutputStream> outStream = do_QueryInterface(fileStream); |
|
2983 uint32_t bytesLeft = bufStr.Length(); |
|
2984 const char* bytes = bufStr.get(); |
|
2985 do { |
|
2986 uint32_t written = 0; |
|
2987 rv = outStream->Write(bytes, bytesLeft, &written); |
|
2988 if (NS_FAILED(rv)) { |
|
2989 break; |
|
2990 } |
|
2991 bytes += written; |
|
2992 bytesLeft -= written; |
|
2993 } while (bytesLeft > 0); |
|
2994 seekStream->SetEOF(); |
|
2995 } |
|
2996 |
|
2997 void |
|
2998 InitIOReporting(nsIFile* aXreDir) |
|
2999 { |
|
3000 // Never initialize twice |
|
3001 if (sTelemetryIOObserver) { |
|
3002 return; |
|
3003 } |
|
3004 |
|
3005 sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir); |
|
3006 IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, |
|
3007 sTelemetryIOObserver); |
|
3008 } |
|
3009 |
|
3010 void |
|
3011 SetProfileDir(nsIFile* aProfD) |
|
3012 { |
|
3013 if (!sTelemetryIOObserver || !aProfD) { |
|
3014 return; |
|
3015 } |
|
3016 nsAutoString profDirPath; |
|
3017 nsresult rv = aProfD->GetPath(profDirPath); |
|
3018 if (NS_FAILED(rv)) { |
|
3019 return; |
|
3020 } |
|
3021 sTelemetryIOObserver->AddPath(profDirPath, NS_LITERAL_STRING("{profile}")); |
|
3022 } |
|
3023 |
|
3024 void |
|
3025 TimeHistogram::Add(PRIntervalTime aTime) |
|
3026 { |
|
3027 uint32_t timeMs = PR_IntervalToMilliseconds(aTime); |
|
3028 size_t index = mozilla::FloorLog2(timeMs); |
|
3029 operator[](index)++; |
|
3030 } |
|
3031 |
|
3032 uint32_t |
|
3033 HangHistogram::GetHash(const Stack& aStack) |
|
3034 { |
|
3035 uint32_t hash = 0; |
|
3036 for (const char* const* label = aStack.begin(); |
|
3037 label != aStack.end(); label++) { |
|
3038 /* We only need to hash the pointer instead of the text content |
|
3039 because we are assuming constant pointers */ |
|
3040 hash = AddToHash(hash, *label); |
|
3041 } |
|
3042 return hash; |
|
3043 } |
|
3044 |
|
3045 bool |
|
3046 HangHistogram::operator==(const HangHistogram& aOther) const |
|
3047 { |
|
3048 if (mHash != aOther.mHash) { |
|
3049 return false; |
|
3050 } |
|
3051 if (mStack.length() != aOther.mStack.length()) { |
|
3052 return false; |
|
3053 } |
|
3054 return PodEqual(mStack.begin(), aOther.mStack.begin(), mStack.length()); |
|
3055 } |
|
3056 |
|
3057 |
|
3058 } // namespace Telemetry |
|
3059 } // namespace mozilla |
|
3060 |
|
3061 NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule; |
|
3062 |
|
3063 /** |
|
3064 * The XRE_TelemetryAdd function is to be used by embedding applications |
|
3065 * that can't use mozilla::Telemetry::Accumulate() directly. |
|
3066 */ |
|
3067 void |
|
3068 XRE_TelemetryAccumulate(int aID, uint32_t aSample) |
|
3069 { |
|
3070 mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample); |
|
3071 } |