Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
7 #include <algorithm>
9 #include <fstream>
11 #include <prio.h>
13 #include "mozilla/Attributes.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/Likely.h"
16 #include "mozilla/MathAlgorithms.h"
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
69 #define EXPIRED_ID "__expired__"
71 namespace {
73 using namespace base;
74 using namespace mozilla;
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 };
92 template<class EntryType>
93 AutoHashtable<EntryType>::AutoHashtable(uint32_t initSize)
94 : nsTHashtable<EntryType>(initSize)
95 {
96 }
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 }
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 }
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 };
139 static JSObject *
140 CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks);
142 size_t
143 CombinedStacks::GetModuleCount() const {
144 return mModules.size();
145 }
147 const Telemetry::ProcessedStack::Module&
148 CombinedStacks::GetModule(unsigned aIndex) const {
149 return mModules[aIndex];
150 }
152 void
153 CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
154 mStacks.resize(mStacks.size() + 1);
155 CombinedStacks::Stack& adjustedStack = mStacks.back();
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 }
180 const CombinedStacks::Stack&
181 CombinedStacks::GetStack(unsigned aIndex) const {
182 return mStacks[aIndex];
183 }
185 size_t
186 CombinedStacks::GetStackCount() const {
187 return mStacks.size();
188 }
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 }
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 };
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 }
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 }
251 const CombinedStacks&
252 HangReports::GetStacks() const {
253 return mStacks;
254 }
256 uint32_t
257 HangReports::GetDuration(unsigned aIndex) const {
258 return mHangInfo[aIndex].mDuration;
259 }
261 int32_t
262 HangReports::GetSystemUptime(unsigned aIndex) const {
263 return mHangInfo[aIndex].mSystemUptime;
264 }
266 int32_t
267 HangReports::GetFirefoxUptime(unsigned aIndex) const {
268 return mHangInfo[aIndex].mFirefoxUptime;
269 }
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 };
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 };
308 public:
309 TelemetryIOInterposeObserver(nsIFile* aXreDir);
311 /**
312 * An implementation of Observe that records statistics of all
313 * file IO operations.
314 */
315 void Observe(Observation& aOb);
317 /**
318 * Reflect recorded file IO statistics into Javascript
319 */
320 bool ReflectIntoJS(JSContext *cx, JS::Handle<JSObject*> rootObj);
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);
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 }
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 }
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 }
370 struct FileStatsByStage
371 {
372 FileStats mStats[NUM_STAGES];
373 };
374 typedef nsBaseHashtableET<nsStringHashKey, FileStatsByStage> FileIOEntryType;
376 // Statistics for each filename
377 AutoHashtable<FileIOEntryType> mFileStats;
378 // Container for whitelisted directories
379 nsTArray<SafeDir> mSafeDirs;
380 Stage mCurStage;
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);
390 static size_t SizeOfFileIOEntryTypeExcludingThis(FileIOEntryType* aEntry,
391 mozilla::MallocSizeOf mallocSizeOf,
392 void*)
393 {
394 return aEntry->GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf);
395 }
396 };
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 }
408 void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath,
409 const nsAString& aSubstName)
410 {
411 mSafeDirs.AppendElement(SafeDir(aPath, aSubstName));
412 }
414 void TelemetryIOInterposeObserver::Observe(Observation& aOb)
415 {
416 // We only report main-thread I/O
417 if (!IsMainThread()) {
418 return;
419 }
421 if (aOb.ObservedOperation() == OpNextStage) {
422 mCurStage = NextStage(mCurStage);
423 MOZ_ASSERT(mCurStage < NUM_STAGES);
424 return;
425 }
427 // Get the filename
428 const char16_t* filename = aOb.Filename();
430 // Discard observations without filename
431 if (!filename) {
432 return;
433 }
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 }
451 if (processedName.IsEmpty()) {
452 return;
453 }
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 }
483 bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry,
484 JSContext *cx,
485 JS::Handle<JSObject*> obj)
486 {
487 JS::AutoValueArray<NUM_STAGES> stages(cx);
489 FileStatsByStage& statsByStage = entry->mData;
490 for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) {
491 FileStats& fileStats = statsByStage.mStats[s];
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 }
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);
510 // Create jsStats as array of elements above
511 JS::RootedObject jsStats(cx, JS_NewArrayObject(cx, stats));
512 if (!jsStats) {
513 continue;
514 }
516 stages[s].setObject(*jsStats);
517 }
519 JS::RootedObject jsEntry(cx, JS_NewArrayObject(cx, stages));
520 if (!jsEntry) {
521 return false;
522 }
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 }
531 bool TelemetryIOInterposeObserver::ReflectIntoJS(JSContext *cx,
532 JS::Handle<JSObject*> rootObj)
533 {
534 return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj);
535 }
537 // This is not a member of TelemetryImpl because we want to record I/O during
538 // startup.
539 StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver;
541 void
542 ClearIOReporting()
543 {
544 if (!sTelemetryIOObserver) {
545 return;
546 }
547 IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
548 sTelemetryIOObserver);
549 sTelemetryIOObserver = nullptr;
550 }
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
560 public:
561 ~TelemetryImpl();
563 void InitMemoryReporter();
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;
589 private:
590 TelemetryImpl();
592 static nsCString SanitizeSQL(const nsACString& sql);
594 enum SanitizedState { Sanitized, Unsanitized };
596 static void StoreSlowSQL(const nsACString &offender, uint32_t delay,
597 SanitizedState state);
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);
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);
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;
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;
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;
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 };
662 TelemetryImpl* TelemetryImpl::sTelemetry = nullptr;
664 MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf)
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 }
676 // A initializer to initialize histogram collection
677 StatisticsRecorder gStatisticsRecorder;
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;
689 const char *id() const;
690 const char *expiration() const;
691 };
693 #include "TelemetryHistogramData.inc"
694 bool gCorruptHistograms[Telemetry::HistogramCount];
696 const char *
697 TelemetryHistogram::id() const
698 {
699 return &gHistogramStringTable[this->id_offset];
700 }
702 const char *
703 TelemetryHistogram::expiration() const
704 {
705 return &gHistogramStringTable[this->expiration_offset];
706 }
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 }
715 bool
716 IsExpired(const Histogram *histogram){
717 return histogram->histogram_name() == EXPIRED_ID;
718 }
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;
730 if (bucketCount <= 2)
731 return NS_ERROR_ILLEGAL_VALUE;
733 if (min < 1)
734 return NS_ERROR_ILLEGAL_VALUE;
735 }
737 if (IsExpired(expiration)) {
738 name = EXPIRED_ID;
739 min = 1;
740 max = 2;
741 bucketCount = 3;
742 histogramType = nsITelemetry::HISTOGRAM_LINEAR;
743 }
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 }
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 }
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;
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
796 if (p.extendedStatisticsOK) {
797 h->SetFlags(Histogram::kExtendedStatisticsFlag);
798 }
799 *ret = knownHistograms[id] = h;
800 return NS_OK;
801 }
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 }
815 enum reflectStatus {
816 REFLECT_OK,
817 REFLECT_CORRUPT,
818 REFLECT_FAILURE
819 };
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 }
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 }
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 }
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 }
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 }
879 return REFLECT_OK;
880 }
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 }
890 bool
891 IsEmpty(const Histogram *h)
892 {
893 Histogram::SampleSet ss;
894 h->SnapshotSample(&ss);
896 return ss.counts(0) == 0 && ss.sum() == 0;
897 }
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 }
908 if (!(args[0].isNumber() || args[0].isBoolean())) {
909 JS_ReportError(cx, "Not a number");
910 return false;
911 }
913 int32_t value;
914 if (!JS::ToInt32(cx, args[0], &value)) {
915 return false;
916 }
918 if (TelemetryImpl::CanRecord()) {
919 JSObject *obj = JS_THIS_OBJECT(cx, vp);
920 if (!obj) {
921 return false;
922 }
924 Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
925 h->Add(value);
926 }
927 return true;
929 }
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 }
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;
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 }
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 }
967 Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
968 h->Clear();
969 return true;
970 }
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 };
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 }
995 static uint32_t
996 ReadLastShutdownDuration(const char *filename) {
997 FILE *f = fopen(filename, "r");
998 if (!f) {
999 return 0;
1000 }
1002 int shutdownTime;
1003 int r = fscanf(f, "%d\n", &shutdownTime);
1004 fclose(f);
1005 if (r != 1) {
1006 return 0;
1007 }
1009 return shutdownTime;
1010 }
1012 const int32_t kMaxFailedProfileLockFileSize = 10;
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 }
1026 nsresult
1027 GetFailedProfileLockFile(nsIFile* *aFile, nsIFile* aProfileDir)
1028 {
1029 NS_ENSURE_ARG_POINTER(aProfileDir);
1031 nsresult rv = aProfileDir->Clone(aFile);
1032 NS_ENSURE_SUCCESS(rv, rv);
1034 (*aFile)->AppendNative(NS_LITERAL_CSTRING("Telemetry.FailedProfileLocks.txt"));
1035 return NS_OK;
1036 }
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 }
1051 private:
1052 const char* mShutdownTimeFilename;
1053 nsCOMPtr<nsIFile> mFailedProfileLockFile;
1054 nsRefPtr<TelemetryImpl> mTelemetry;
1055 nsCOMPtr<nsIFile> mProfileDir;
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 }
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 }
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();
1099 mFailedProfileLockFile->Remove(false);
1100 return NS_OK;
1101 }
1102 };
1104 static TimeStamp gRecordedShutdownStartTime;
1105 static bool gAlreadyFreedShutdownTimeFileName = false;
1106 static char *gRecordedShutdownTimeFileName = nullptr;
1108 static char *
1109 GetShutdownTimeFileName()
1110 {
1111 if (gAlreadyFreedShutdownTimeFileName) {
1112 return nullptr;
1113 }
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;
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;
1127 gRecordedShutdownTimeFileName = PL_strdup(nativePath.get());
1128 }
1130 return gRecordedShutdownTimeFileName;
1131 }
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 }
1144 *aResult = mLastShutdownTime;
1145 return NS_OK;
1146 }
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 }
1159 *aResult = mFailedLockCount;
1160 return NS_OK;
1161 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
1223 mCallbacks.AppendObject(aCallback);
1225 nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(shutdownTimeFilename,
1226 failedProfileLockFile,
1227 profileDir);
1229 targetThread->Dispatch(event, NS_DISPATCH_NORMAL);
1230 return NS_OK;
1231 }
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 };
1252 for (size_t i = 0; i < ArrayLength(trackedDBs); i++)
1253 mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i]));
1255 #ifdef DEBUG
1256 // Mark immutable to prevent asserts on simultaneous access from multiple threads
1257 mTrackedDBs.MarkImmutable();
1258 #endif
1259 }
1261 TelemetryImpl::~TelemetryImpl() {
1262 UnregisterWeakMemoryReporter(this);
1263 }
1265 void
1266 TelemetryImpl::InitMemoryReporter() {
1267 RegisterWeakMemoryReporter(this);
1268 }
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 }
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;
1294 const nsACString &sql = entry->GetKey();
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 }
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 }
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 }
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;
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 }
1336 return JS_DefineProperty(cx, rootObj,
1337 mainThread ? "mainThread" : "otherThreads",
1338 statsObj, JSPROP_ENUMERATE);
1339 }
1341 nsresult
1342 TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id)
1343 {
1344 if (!sTelemetry) {
1345 return NS_ERROR_FAILURE;
1346 }
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 }
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 }
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 }
1379 rv = GetHistogramByEnumId(id, ret);
1380 if (NS_FAILED(rv))
1381 return rv;
1383 return NS_OK;
1384 }
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];
1397 Histogram *existing;
1398 rv = GetHistogramByEnumId(id, &existing);
1399 if (NS_FAILED(rv)) {
1400 return rv;
1401 }
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;
1410 Histogram::SampleSet ss;
1411 existing->SnapshotSample(&ss);
1412 clone->AddSampleSet(ss);
1413 return WrapAndReturnHistogram(clone, cx, ret);
1414 }
1416 void
1417 TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
1418 {
1419 for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
1420 Histogram *h = *it;
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 }
1429 if (gCorruptHistograms[id]) {
1430 continue;
1431 }
1433 Histogram::SampleSet ss;
1434 h->SnapshotSample(&ss);
1435 Histogram::Inconsistencies check = h->FindCorruption(ss);
1436 bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
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 }
1452 gCorruptHistograms[id] = corrupt;
1453 }
1454 }
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 }
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 }
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 }
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 }
1515 histogramEntry = histogramMap->PutEntry(name);
1516 if (MOZ_UNLIKELY(!histogramEntry)) {
1517 return NS_ERROR_OUT_OF_MEMORY;
1518 }
1520 AddonHistogramInfo &info = histogramEntry->mData;
1521 info.min = min;
1522 info.max = max;
1523 info.bucketCount = bucketCount;
1524 info.histogramType = histogramType;
1526 return NS_OK;
1527 }
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 }
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 }
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 }
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 }
1570 return NS_OK;
1571 }
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);
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 };
1591 StatisticsRecorder::Histograms hs;
1592 StatisticsRecorder::GetHistograms(&hs);
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);
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 }
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 }
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 }
1651 bool
1652 TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry,
1653 JSContext *cx, JS::Handle<JSObject*> obj)
1654 {
1655 AddonHistogramInfo &info = entry->mData;
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;
1663 if (!CreateHistogramForAddon(entry->GetKey(), info)) {
1664 return false;
1665 }
1666 }
1668 if (IsEmpty(info.h)) {
1669 return true;
1670 }
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 }
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 }
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 }
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 }
1719 if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) {
1720 return NS_ERROR_FAILURE;
1721 }
1722 ret.setObject(*obj);
1723 return NS_OK;
1724 }
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);
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;
1742 return true;
1743 }
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 }
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 }
1763 NS_IMETHODIMP
1764 TelemetryImpl::GetMaximalNumberOfConcurrentThreads(uint32_t *ret)
1765 {
1766 *ret = nsThreadManager::get()->GetHighestNumberOfThreads();
1767 return NS_OK;
1768 }
1770 NS_IMETHODIMP
1771 TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle<JS::Value> ret)
1772 {
1773 MutexAutoLock hangReportMutex(mHangReportsMutex);
1775 const CombinedStacks& stacks = mHangReports.GetStacks();
1776 JS::Rooted<JSObject*> fullReportObj(cx, CreateJSStackObject(cx, stacks));
1777 if (!fullReportObj) {
1778 return NS_ERROR_FAILURE;
1779 }
1781 ret.setObject(*fullReportObj);
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 }
1790 bool ok = JS_DefineProperty(cx, fullReportObj, "durations",
1791 durationArray, JSPROP_ENUMERATE);
1792 if (!ok) {
1793 return NS_ERROR_FAILURE;
1794 }
1796 ok = JS_DefineProperty(cx, fullReportObj, "systemUptime",
1797 systemUptimeArray, JSPROP_ENUMERATE);
1798 if (!ok) {
1799 return NS_ERROR_FAILURE;
1800 }
1802 ok = JS_DefineProperty(cx, fullReportObj, "firefoxUptime",
1803 firefoxUptimeArray, JSPROP_ENUMERATE);
1804 if (!ok) {
1805 return NS_ERROR_FAILURE;
1806 }
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 }
1821 return NS_OK;
1822 }
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 }
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 }
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);
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 }
1855 unsigned index = 0;
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 }
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 }
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 }
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 }
1893 if (!JS_SetElement(cx, reportArray, i, pcArray)) {
1894 return nullptr;
1895 }
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 }
1919 return ret;
1920 }
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 }
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);
1943 size_t numModules;
1944 file >> numModules;
1945 if (file.fail()) {
1946 return;
1947 }
1949 char newline = file.get();
1950 if (file.fail() || newline != '\n') {
1951 return;
1952 }
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 }
1962 char space = file.get();
1963 if (file.fail() || space != ' ') {
1964 return;
1965 }
1967 std::string moduleName;
1968 getline(file, moduleName);
1969 if (file.fail() || moduleName[0] == ' ') {
1970 return;
1971 }
1973 Telemetry::ProcessedStack::Module module = {
1974 moduleName,
1975 breakpadId
1976 };
1977 stack.AddModule(module);
1978 }
1980 size_t numFrames;
1981 file >> numFrames;
1982 if (file.fail()) {
1983 return;
1984 }
1986 newline = file.get();
1987 if (file.fail() || newline != '\n') {
1988 return;
1989 }
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 }
2000 Telemetry::ProcessedStack::Frame frame = {
2001 offset,
2002 index
2003 };
2004 stack.AddFrame(frame);
2005 }
2007 aStack = stack;
2008 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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;
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
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 }
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 }
2175 const char *name = nativePath.get();
2176 PRDir *dir = PR_OpenDir(name);
2177 if (!dir) {
2178 return;
2179 }
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 }
2189 nsAutoCString stackNativePath = nativePath;
2190 stackNativePath += XPCOM_FILE_PATH_SEPARATOR;
2191 stackNativePath += nsDependentCString(ent->name);
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 }
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.
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?
2220 JSObject *report;
2221 if (!mCachedTelemetryData) {
2222 CombinedStacks empty;
2223 report = CreateJSStackObject(cx, empty);
2224 } else {
2225 report = CreateJSStackObject(cx, mLateWritesStacks);
2226 }
2228 if (report == nullptr) {
2229 return NS_ERROR_FAILURE;
2230 }
2232 ret.setObject(*report);
2233 return NS_OK;
2234 }
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*)));
2243 for (size_t i = 0; i < count; ++i) {
2244 if (IsExpired(gHistograms[i].expiration())) {
2245 offset++;
2246 continue;
2247 }
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 }
2254 *aCount = count - offset;
2255 *aHistograms = histograms;
2256 return NS_OK;
2257 }
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;
2268 return WrapAndReturnHistogram(h, cx, ret);
2269 }
2271 NS_IMETHODIMP
2272 TelemetryImpl::GetCanRecord(bool *ret) {
2273 *ret = mCanRecord;
2274 return NS_OK;
2275 }
2277 NS_IMETHODIMP
2278 TelemetryImpl::SetCanRecord(bool canRecord) {
2279 mCanRecord = !!canRecord;
2280 return NS_OK;
2281 }
2283 bool
2284 TelemetryImpl::CanRecord() {
2285 return !sTelemetry || sTelemetry->mCanRecord;
2286 }
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 }
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;
2308 sTelemetry->InitMemoryReporter();
2310 return ret.forget();
2311 }
2313 void
2314 TelemetryImpl::ShutdownTelemetry()
2315 {
2316 // No point in collecting IO beyond this point
2317 ClearIOReporting();
2318 NS_IF_RELEASE(sTelemetry);
2319 }
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);
2331 MutexAutoLock hashMutex(sTelemetry->mHashMutex);
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 }
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 }
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();
2401 typedef enum {
2402 NORMAL,
2403 SINGLE_QUOTE,
2404 DOUBLE_QUOTE,
2405 DASH_COMMENT,
2406 C_STYLE_COMMENT,
2407 } State;
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';
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 }
2467 if ((fragmentStart >= 0) && fragmentStart < length)
2468 output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart);
2470 return output;
2471 }
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;
2479 void
2480 TelemetryImpl::RecordSlowStatement(const nsACString &sql,
2481 const nsACString &dbName,
2482 uint32_t delay)
2483 {
2484 if (!sTelemetry || !sTelemetry->mCanRecord)
2485 return;
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 }
2504 nsAutoCString fullSQL;
2505 fullSQL.AppendPrintf("%s /* %s */",
2506 nsPromiseFlatCString(sql).get(),
2507 nsPromiseFlatCString(dbName).get());
2508 StoreSlowSQL(fullSQL, delay, Unsanitized);
2509 }
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;
2521 MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
2523 sTelemetry->mHangReports.AddHang(aStack, aDuration,
2524 aSystemUptime, aFirefoxUptime);
2525 }
2526 #endif
2528 void
2529 TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
2530 {
2531 if (!sTelemetry || !sTelemetry->mCanRecord)
2532 return;
2534 MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex);
2536 sTelemetry->mThreadHangStats.append(Move(aStats));
2537 }
2539 NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter)
2540 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
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);
2546 const Module::CIDEntry kTelemetryCIDs[] = {
2547 { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor },
2548 { nullptr }
2549 };
2551 const Module::ContractIDEntry kTelemetryContracts[] = {
2552 { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID },
2553 { nullptr }
2554 };
2556 const Module kTelemetryModule = {
2557 Module::kVersion,
2558 kTelemetryCIDs,
2559 kTelemetryContracts,
2560 nullptr,
2561 nullptr,
2562 nullptr,
2563 TelemetryImpl::ShutdownTelemetry
2564 };
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 }
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 }
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 }
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 }
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 }
2632 return n;
2633 }
2635 } // anonymous namespace
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
2651 if (!Telemetry::CanRecord())
2652 return;
2654 gRecordedShutdownStartTime = TimeStamp::Now();
2656 GetShutdownTimeFileName();
2657 }
2659 void
2660 RecordShutdownEndTimeStamp() {
2661 if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName)
2662 return;
2664 nsCString name(gRecordedShutdownTimeFileName);
2665 PL_strfree(gRecordedShutdownTimeFileName);
2666 gRecordedShutdownTimeFileName = nullptr;
2667 gAlreadyFreedShutdownTimeFileName = true;
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);
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 }
2695 namespace Telemetry {
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 }
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 }
2721 Histogram *h;
2722 rv = GetHistogramByEnumId(id, &h);
2723 if (NS_SUCCEEDED(rv)) {
2724 h->Add(sample);
2725 }
2726 }
2728 void
2729 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
2730 {
2731 Accumulate(aHistogram,
2732 static_cast<uint32_t>((end - start).ToMilliseconds()));
2733 }
2735 bool
2736 CanRecord()
2737 {
2738 return TelemetryImpl::CanRecord();
2739 }
2741 base::Histogram*
2742 GetHistogramById(ID id)
2743 {
2744 Histogram *h = nullptr;
2745 GetHistogramByEnumId(id, &h);
2746 return h;
2747 }
2749 void
2750 RecordSlowSQLStatement(const nsACString &statement,
2751 const nsACString &dbName,
2752 uint32_t delay)
2753 {
2754 TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
2755 }
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 }
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
2776 void RecordThreadHangStats(ThreadHangStats& aStats)
2777 {
2778 TelemetryImpl::RecordThreadHangStats(aStats);
2779 }
2781 ProcessedStack::ProcessedStack()
2782 {
2783 }
2785 size_t ProcessedStack::GetStackSize() const
2786 {
2787 return mStack.size();
2788 }
2790 const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const
2791 {
2792 MOZ_ASSERT(aIndex < mStack.size());
2793 return mStack[aIndex];
2794 }
2796 void ProcessedStack::AddFrame(const Frame &aFrame)
2797 {
2798 mStack.push_back(aFrame);
2799 }
2801 size_t ProcessedStack::GetNumModules() const
2802 {
2803 return mModules.size();
2804 }
2806 const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
2807 {
2808 MOZ_ASSERT(aIndex < mModules.size());
2809 return mModules[aIndex];
2810 }
2812 void ProcessedStack::AddModule(const Module &aModule)
2813 {
2814 mModules.push_back(aModule);
2815 }
2817 void ProcessedStack::Clear() {
2818 mModules.clear();
2819 mStack.clear();
2820 }
2822 bool ProcessedStack::Module::operator==(const Module& aOther) const {
2823 return mName == aOther.mName &&
2824 mBreakpadId == aOther.mBreakpadId;
2825 }
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 };
2835 #ifdef MOZ_ENABLE_PROFILER_SPS
2836 static bool CompareByPC(const StackFrame &a, const StackFrame &b)
2837 {
2838 return a.mPC < b.mPC;
2839 }
2841 static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
2842 {
2843 return a.mIndex < b.mIndex;
2844 }
2845 #endif
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 }
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);
2863 size_t moduleIndex = 0;
2864 size_t stackIndex = 0;
2865 size_t stackSize = rawStack.size();
2867 SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
2868 rawModules.SortByAddress();
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)
2876 bool moduleReferenced = false;
2877 for (;stackIndex < stackSize; ++stackIndex) {
2878 uintptr_t pc = rawStack[stackIndex].mPC;
2879 if (pc >= moduleEnd)
2880 break;
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 }
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 }
2905 for (;stackIndex < stackSize; ++stackIndex) {
2906 // These PCs are past the last module.
2907 rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
2908 }
2910 std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
2911 #endif
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 }
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
2944 return Ret;
2945 }
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 }
2997 void
2998 InitIOReporting(nsIFile* aXreDir)
2999 {
3000 // Never initialize twice
3001 if (sTelemetryIOObserver) {
3002 return;
3003 }
3005 sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir);
3006 IOInterposer::Register(IOInterposeObserver::OpAllWithStaging,
3007 sTelemetryIOObserver);
3008 }
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 }
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 }
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 }
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 }
3058 } // namespace Telemetry
3059 } // namespace mozilla
3061 NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule;
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 }