1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/telemetry/Telemetry.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,3071 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <algorithm> 1.11 + 1.12 +#include <fstream> 1.13 + 1.14 +#include <prio.h> 1.15 + 1.16 +#include "mozilla/Attributes.h" 1.17 +#include "mozilla/DebugOnly.h" 1.18 +#include "mozilla/Likely.h" 1.19 +#include "mozilla/MathAlgorithms.h" 1.20 + 1.21 +#include "base/histogram.h" 1.22 +#include "base/pickle.h" 1.23 +#include "nsIComponentManager.h" 1.24 +#include "nsIServiceManager.h" 1.25 +#include "nsThreadManager.h" 1.26 +#include "nsCOMArray.h" 1.27 +#include "nsCOMPtr.h" 1.28 +#include "nsXPCOMPrivate.h" 1.29 +#include "nsIXULAppInfo.h" 1.30 +#include "nsVersionComparator.h" 1.31 +#include "mozilla/MemoryReporting.h" 1.32 +#include "mozilla/ModuleUtils.h" 1.33 +#include "nsIXPConnect.h" 1.34 +#include "mozilla/Services.h" 1.35 +#include "jsapi.h" 1.36 +#include "jsfriendapi.h" 1.37 +#include "js/GCAPI.h" 1.38 +#include "nsString.h" 1.39 +#include "nsITelemetry.h" 1.40 +#include "nsIFile.h" 1.41 +#include "nsIFileStreams.h" 1.42 +#include "nsIMemoryReporter.h" 1.43 +#include "nsISeekableStream.h" 1.44 +#include "Telemetry.h" 1.45 +#include "nsTHashtable.h" 1.46 +#include "nsHashKeys.h" 1.47 +#include "nsBaseHashtable.h" 1.48 +#include "nsXULAppAPI.h" 1.49 +#include "nsReadableUtils.h" 1.50 +#include "nsThreadUtils.h" 1.51 +#if defined(XP_WIN) 1.52 +#include "nsUnicharUtils.h" 1.53 +#endif 1.54 +#include "nsNetCID.h" 1.55 +#include "nsNetUtil.h" 1.56 +#include "plstr.h" 1.57 +#include "nsAppDirectoryServiceDefs.h" 1.58 +#include "mozilla/BackgroundHangMonitor.h" 1.59 +#include "mozilla/ThreadHangStats.h" 1.60 +#include "mozilla/ProcessedStack.h" 1.61 +#include "mozilla/Mutex.h" 1.62 +#include "mozilla/FileUtils.h" 1.63 +#include "mozilla/Preferences.h" 1.64 +#include "mozilla/StaticPtr.h" 1.65 +#include "mozilla/IOInterposer.h" 1.66 +#include "mozilla/PoisonIOInterposer.h" 1.67 +#include "mozilla/StartupTimeline.h" 1.68 +#if defined(MOZ_ENABLE_PROFILER_SPS) 1.69 +#include "shared-libraries.h" 1.70 +#endif 1.71 + 1.72 +#define EXPIRED_ID "__expired__" 1.73 + 1.74 +namespace { 1.75 + 1.76 +using namespace base; 1.77 +using namespace mozilla; 1.78 + 1.79 +template<class EntryType> 1.80 +class AutoHashtable : public nsTHashtable<EntryType> 1.81 +{ 1.82 +public: 1.83 + AutoHashtable(uint32_t initSize = PL_DHASH_MIN_SIZE); 1.84 + typedef bool (*ReflectEntryFunc)(EntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj); 1.85 + bool ReflectIntoJS(ReflectEntryFunc entryFunc, JSContext *cx, JS::Handle<JSObject*> obj); 1.86 +private: 1.87 + struct EnumeratorArgs { 1.88 + JSContext *cx; 1.89 + JS::Handle<JSObject*> obj; 1.90 + ReflectEntryFunc entryFunc; 1.91 + }; 1.92 + static PLDHashOperator ReflectEntryStub(EntryType *entry, void *arg); 1.93 +}; 1.94 + 1.95 +template<class EntryType> 1.96 +AutoHashtable<EntryType>::AutoHashtable(uint32_t initSize) 1.97 + : nsTHashtable<EntryType>(initSize) 1.98 +{ 1.99 +} 1.100 + 1.101 +template<typename EntryType> 1.102 +PLDHashOperator 1.103 +AutoHashtable<EntryType>::ReflectEntryStub(EntryType *entry, void *arg) 1.104 +{ 1.105 + EnumeratorArgs *args = static_cast<EnumeratorArgs *>(arg); 1.106 + if (!args->entryFunc(entry, args->cx, args->obj)) { 1.107 + return PL_DHASH_STOP; 1.108 + } 1.109 + return PL_DHASH_NEXT; 1.110 +} 1.111 + 1.112 +/** 1.113 + * Reflect the individual entries of table into JS, usually by defining 1.114 + * some property and value of obj. entryFunc is called for each entry. 1.115 + */ 1.116 +template<typename EntryType> 1.117 +bool 1.118 +AutoHashtable<EntryType>::ReflectIntoJS(ReflectEntryFunc entryFunc, 1.119 + JSContext *cx, JS::Handle<JSObject*> obj) 1.120 +{ 1.121 + EnumeratorArgs args = { cx, obj, entryFunc }; 1.122 + uint32_t num = this->EnumerateEntries(ReflectEntryStub, static_cast<void*>(&args)); 1.123 + return num == this->Count(); 1.124 +} 1.125 + 1.126 +// This class is conceptually a list of ProcessedStack objects, but it represents them 1.127 +// more efficiently by keeping a single global list of modules. 1.128 +class CombinedStacks { 1.129 +public: 1.130 + typedef std::vector<Telemetry::ProcessedStack::Frame> Stack; 1.131 + const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const; 1.132 + size_t GetModuleCount() const; 1.133 + const Stack& GetStack(unsigned aIndex) const; 1.134 + void AddStack(const Telemetry::ProcessedStack& aStack); 1.135 + size_t GetStackCount() const; 1.136 + size_t SizeOfExcludingThis() const; 1.137 +private: 1.138 + std::vector<Telemetry::ProcessedStack::Module> mModules; 1.139 + std::vector<Stack> mStacks; 1.140 +}; 1.141 + 1.142 +static JSObject * 1.143 +CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks); 1.144 + 1.145 +size_t 1.146 +CombinedStacks::GetModuleCount() const { 1.147 + return mModules.size(); 1.148 +} 1.149 + 1.150 +const Telemetry::ProcessedStack::Module& 1.151 +CombinedStacks::GetModule(unsigned aIndex) const { 1.152 + return mModules[aIndex]; 1.153 +} 1.154 + 1.155 +void 1.156 +CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) { 1.157 + mStacks.resize(mStacks.size() + 1); 1.158 + CombinedStacks::Stack& adjustedStack = mStacks.back(); 1.159 + 1.160 + size_t stackSize = aStack.GetStackSize(); 1.161 + for (size_t i = 0; i < stackSize; ++i) { 1.162 + const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i); 1.163 + uint16_t modIndex; 1.164 + if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) { 1.165 + modIndex = frame.mModIndex; 1.166 + } else { 1.167 + const Telemetry::ProcessedStack::Module& module = 1.168 + aStack.GetModule(frame.mModIndex); 1.169 + std::vector<Telemetry::ProcessedStack::Module>::iterator modIterator = 1.170 + std::find(mModules.begin(), mModules.end(), module); 1.171 + if (modIterator == mModules.end()) { 1.172 + mModules.push_back(module); 1.173 + modIndex = mModules.size() - 1; 1.174 + } else { 1.175 + modIndex = modIterator - mModules.begin(); 1.176 + } 1.177 + } 1.178 + Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex }; 1.179 + adjustedStack.push_back(adjustedFrame); 1.180 + } 1.181 +} 1.182 + 1.183 +const CombinedStacks::Stack& 1.184 +CombinedStacks::GetStack(unsigned aIndex) const { 1.185 + return mStacks[aIndex]; 1.186 +} 1.187 + 1.188 +size_t 1.189 +CombinedStacks::GetStackCount() const { 1.190 + return mStacks.size(); 1.191 +} 1.192 + 1.193 +size_t 1.194 +CombinedStacks::SizeOfExcludingThis() const { 1.195 + // This is a crude approximation. We would like to do something like 1.196 + // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call 1.197 + // malloc_usable_size which is only safe on the pointers returned by malloc. 1.198 + // While it works on current libstdc++, it is better to be safe and not assume 1.199 + // that &vec[0] points to one. We could use a custom allocator, but 1.200 + // it doesn't seem worth it. 1.201 + size_t n = 0; 1.202 + n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module); 1.203 + n += mStacks.capacity() * sizeof(Stack); 1.204 + for (std::vector<Stack>::const_iterator i = mStacks.begin(), 1.205 + e = mStacks.end(); i != e; ++i) { 1.206 + const Stack& s = *i; 1.207 + n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame); 1.208 + } 1.209 + return n; 1.210 +} 1.211 + 1.212 +class HangReports { 1.213 +public: 1.214 + size_t SizeOfExcludingThis() const; 1.215 + void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration, 1.216 + int32_t aSystemUptime, int32_t aFirefoxUptime); 1.217 + uint32_t GetDuration(unsigned aIndex) const; 1.218 + int32_t GetSystemUptime(unsigned aIndex) const; 1.219 + int32_t GetFirefoxUptime(unsigned aIndex) const; 1.220 + const CombinedStacks& GetStacks() const; 1.221 +private: 1.222 + struct HangInfo { 1.223 + // Hang duration (in seconds) 1.224 + uint32_t mDuration; 1.225 + // System uptime (in minutes) at the time of the hang 1.226 + int32_t mSystemUptime; 1.227 + // Firefox uptime (in minutes) at the time of the hang 1.228 + int32_t mFirefoxUptime; 1.229 + }; 1.230 + std::vector<HangInfo> mHangInfo; 1.231 + CombinedStacks mStacks; 1.232 +}; 1.233 + 1.234 +void 1.235 +HangReports::AddHang(const Telemetry::ProcessedStack& aStack, 1.236 + uint32_t aDuration, 1.237 + int32_t aSystemUptime, 1.238 + int32_t aFirefoxUptime) { 1.239 + HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime }; 1.240 + mHangInfo.push_back(info); 1.241 + mStacks.AddStack(aStack); 1.242 +} 1.243 + 1.244 +size_t 1.245 +HangReports::SizeOfExcludingThis() const { 1.246 + size_t n = 0; 1.247 + n += mStacks.SizeOfExcludingThis(); 1.248 + // This is a crude approximation. See comment on 1.249 + // CombinedStacks::SizeOfExcludingThis. 1.250 + n += mHangInfo.capacity() * sizeof(HangInfo); 1.251 + return n; 1.252 +} 1.253 + 1.254 +const CombinedStacks& 1.255 +HangReports::GetStacks() const { 1.256 + return mStacks; 1.257 +} 1.258 + 1.259 +uint32_t 1.260 +HangReports::GetDuration(unsigned aIndex) const { 1.261 + return mHangInfo[aIndex].mDuration; 1.262 +} 1.263 + 1.264 +int32_t 1.265 +HangReports::GetSystemUptime(unsigned aIndex) const { 1.266 + return mHangInfo[aIndex].mSystemUptime; 1.267 +} 1.268 + 1.269 +int32_t 1.270 +HangReports::GetFirefoxUptime(unsigned aIndex) const { 1.271 + return mHangInfo[aIndex].mFirefoxUptime; 1.272 +} 1.273 + 1.274 +/** 1.275 + * IOInterposeObserver recording statistics of main-thread I/O during execution, 1.276 + * aimed at consumption by TelemetryImpl 1.277 + */ 1.278 +class TelemetryIOInterposeObserver : public IOInterposeObserver 1.279 +{ 1.280 + /** File-level statistics structure */ 1.281 + struct FileStats { 1.282 + FileStats() 1.283 + : creates(0) 1.284 + , reads(0) 1.285 + , writes(0) 1.286 + , fsyncs(0) 1.287 + , stats(0) 1.288 + , totalTime(0) 1.289 + {} 1.290 + uint32_t creates; /** Number of create/open operations */ 1.291 + uint32_t reads; /** Number of read operations */ 1.292 + uint32_t writes; /** Number of write operations */ 1.293 + uint32_t fsyncs; /** Number of fsync operations */ 1.294 + uint32_t stats; /** Number of stat operations */ 1.295 + double totalTime; /** Accumulated duration of all operations */ 1.296 + }; 1.297 + 1.298 + struct SafeDir { 1.299 + SafeDir(const nsAString& aPath, const nsAString& aSubstName) 1.300 + : mPath(aPath) 1.301 + , mSubstName(aSubstName) 1.302 + {} 1.303 + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 1.304 + return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + 1.305 + mSubstName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); 1.306 + } 1.307 + nsString mPath; /** Path to the directory */ 1.308 + nsString mSubstName; /** Name to substitute with */ 1.309 + }; 1.310 + 1.311 +public: 1.312 + TelemetryIOInterposeObserver(nsIFile* aXreDir); 1.313 + 1.314 + /** 1.315 + * An implementation of Observe that records statistics of all 1.316 + * file IO operations. 1.317 + */ 1.318 + void Observe(Observation& aOb); 1.319 + 1.320 + /** 1.321 + * Reflect recorded file IO statistics into Javascript 1.322 + */ 1.323 + bool ReflectIntoJS(JSContext *cx, JS::Handle<JSObject*> rootObj); 1.324 + 1.325 + /** 1.326 + * Adds a path for inclusion in main thread I/O report. 1.327 + * @param aPath Directory path 1.328 + * @param aSubstName Name to substitute for aPath for privacy reasons 1.329 + */ 1.330 + void AddPath(const nsAString& aPath, const nsAString& aSubstName); 1.331 + 1.332 + /** 1.333 + * Get size of hash table with file stats 1.334 + */ 1.335 + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 1.336 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.337 + } 1.338 + 1.339 + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 1.340 + size_t size; 1.341 + size = mFileStats.SizeOfExcludingThis(SizeOfFileIOEntryTypeExcludingThis, 1.342 + aMallocSizeOf) + 1.343 + mSafeDirs.SizeOfExcludingThis(aMallocSizeOf); 1.344 + uint32_t safeDirsLen = mSafeDirs.Length(); 1.345 + for (uint32_t i = 0; i < safeDirsLen; ++i) { 1.346 + size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf); 1.347 + } 1.348 + return size; 1.349 + } 1.350 + 1.351 +private: 1.352 + enum Stage 1.353 + { 1.354 + STAGE_STARTUP = 0, 1.355 + STAGE_NORMAL, 1.356 + STAGE_SHUTDOWN, 1.357 + NUM_STAGES 1.358 + }; 1.359 + static inline Stage NextStage(Stage aStage) 1.360 + { 1.361 + switch (aStage) { 1.362 + case STAGE_STARTUP: 1.363 + return STAGE_NORMAL; 1.364 + case STAGE_NORMAL: 1.365 + return STAGE_SHUTDOWN; 1.366 + case STAGE_SHUTDOWN: 1.367 + return STAGE_SHUTDOWN; 1.368 + default: 1.369 + return NUM_STAGES; 1.370 + } 1.371 + } 1.372 + 1.373 + struct FileStatsByStage 1.374 + { 1.375 + FileStats mStats[NUM_STAGES]; 1.376 + }; 1.377 + typedef nsBaseHashtableET<nsStringHashKey, FileStatsByStage> FileIOEntryType; 1.378 + 1.379 + // Statistics for each filename 1.380 + AutoHashtable<FileIOEntryType> mFileStats; 1.381 + // Container for whitelisted directories 1.382 + nsTArray<SafeDir> mSafeDirs; 1.383 + Stage mCurStage; 1.384 + 1.385 + /** 1.386 + * Reflect a FileIOEntryType object to a Javascript property on obj with 1.387 + * filename as key containing array: 1.388 + * [totalTime, creates, reads, writes, fsyncs, stats] 1.389 + */ 1.390 + static bool ReflectFileStats(FileIOEntryType* entry, JSContext *cx, 1.391 + JS::Handle<JSObject*> obj); 1.392 + 1.393 + static size_t SizeOfFileIOEntryTypeExcludingThis(FileIOEntryType* aEntry, 1.394 + mozilla::MallocSizeOf mallocSizeOf, 1.395 + void*) 1.396 + { 1.397 + return aEntry->GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf); 1.398 + } 1.399 +}; 1.400 + 1.401 +TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir) 1.402 + : mCurStage(STAGE_STARTUP) 1.403 +{ 1.404 + nsAutoString xreDirPath; 1.405 + nsresult rv = aXreDir->GetPath(xreDirPath); 1.406 + if (NS_SUCCEEDED(rv)) { 1.407 + AddPath(xreDirPath, NS_LITERAL_STRING("{xre}")); 1.408 + } 1.409 +} 1.410 + 1.411 +void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath, 1.412 + const nsAString& aSubstName) 1.413 +{ 1.414 + mSafeDirs.AppendElement(SafeDir(aPath, aSubstName)); 1.415 +} 1.416 + 1.417 +void TelemetryIOInterposeObserver::Observe(Observation& aOb) 1.418 +{ 1.419 + // We only report main-thread I/O 1.420 + if (!IsMainThread()) { 1.421 + return; 1.422 + } 1.423 + 1.424 + if (aOb.ObservedOperation() == OpNextStage) { 1.425 + mCurStage = NextStage(mCurStage); 1.426 + MOZ_ASSERT(mCurStage < NUM_STAGES); 1.427 + return; 1.428 + } 1.429 + 1.430 + // Get the filename 1.431 + const char16_t* filename = aOb.Filename(); 1.432 + 1.433 + // Discard observations without filename 1.434 + if (!filename) { 1.435 + return; 1.436 + } 1.437 + 1.438 +#if defined(XP_WIN) 1.439 + nsCaseInsensitiveStringComparator comparator; 1.440 +#else 1.441 + nsDefaultStringComparator comparator; 1.442 +#endif 1.443 + nsAutoString processedName; 1.444 + nsDependentString filenameStr(filename); 1.445 + uint32_t safeDirsLen = mSafeDirs.Length(); 1.446 + for (uint32_t i = 0; i < safeDirsLen; ++i) { 1.447 + if (StringBeginsWith(filenameStr, mSafeDirs[i].mPath, comparator)) { 1.448 + processedName = mSafeDirs[i].mSubstName; 1.449 + processedName += Substring(filenameStr, mSafeDirs[i].mPath.Length()); 1.450 + break; 1.451 + } 1.452 + } 1.453 + 1.454 + if (processedName.IsEmpty()) { 1.455 + return; 1.456 + } 1.457 + 1.458 + // Create a new entry or retrieve the existing one 1.459 + FileIOEntryType* entry = mFileStats.PutEntry(processedName); 1.460 + if (entry) { 1.461 + FileStats& stats = entry->mData.mStats[mCurStage]; 1.462 + // Update the statistics 1.463 + stats.totalTime += (double) aOb.Duration().ToMilliseconds(); 1.464 + switch (aOb.ObservedOperation()) { 1.465 + case OpCreateOrOpen: 1.466 + stats.creates++; 1.467 + break; 1.468 + case OpRead: 1.469 + stats.reads++; 1.470 + break; 1.471 + case OpWrite: 1.472 + stats.writes++; 1.473 + break; 1.474 + case OpFSync: 1.475 + stats.fsyncs++; 1.476 + break; 1.477 + case OpStat: 1.478 + stats.stats++; 1.479 + break; 1.480 + default: 1.481 + break; 1.482 + } 1.483 + } 1.484 +} 1.485 + 1.486 +bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry, 1.487 + JSContext *cx, 1.488 + JS::Handle<JSObject*> obj) 1.489 +{ 1.490 + JS::AutoValueArray<NUM_STAGES> stages(cx); 1.491 + 1.492 + FileStatsByStage& statsByStage = entry->mData; 1.493 + for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) { 1.494 + FileStats& fileStats = statsByStage.mStats[s]; 1.495 + 1.496 + if (fileStats.totalTime == 0 && fileStats.creates == 0 && 1.497 + fileStats.reads == 0 && fileStats.writes == 0 && 1.498 + fileStats.fsyncs == 0 && fileStats.stats == 0) { 1.499 + // Don't add an array that contains no information 1.500 + stages[s].setNull(); 1.501 + continue; 1.502 + } 1.503 + 1.504 + // Array we want to report 1.505 + JS::AutoValueArray<6> stats(cx); 1.506 + stats[0].setNumber(fileStats.totalTime); 1.507 + stats[1].setNumber(fileStats.creates); 1.508 + stats[2].setNumber(fileStats.reads); 1.509 + stats[3].setNumber(fileStats.writes); 1.510 + stats[4].setNumber(fileStats.fsyncs); 1.511 + stats[5].setNumber(fileStats.stats); 1.512 + 1.513 + // Create jsStats as array of elements above 1.514 + JS::RootedObject jsStats(cx, JS_NewArrayObject(cx, stats)); 1.515 + if (!jsStats) { 1.516 + continue; 1.517 + } 1.518 + 1.519 + stages[s].setObject(*jsStats); 1.520 + } 1.521 + 1.522 + JS::RootedObject jsEntry(cx, JS_NewArrayObject(cx, stages)); 1.523 + if (!jsEntry) { 1.524 + return false; 1.525 + } 1.526 + 1.527 + // Add jsEntry to top-level dictionary 1.528 + const nsAString& key = entry->GetKey(); 1.529 + return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), 1.530 + OBJECT_TO_JSVAL(jsEntry), nullptr, nullptr, 1.531 + JSPROP_ENUMERATE | JSPROP_READONLY); 1.532 +} 1.533 + 1.534 +bool TelemetryIOInterposeObserver::ReflectIntoJS(JSContext *cx, 1.535 + JS::Handle<JSObject*> rootObj) 1.536 +{ 1.537 + return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj); 1.538 +} 1.539 + 1.540 +// This is not a member of TelemetryImpl because we want to record I/O during 1.541 +// startup. 1.542 +StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver; 1.543 + 1.544 +void 1.545 +ClearIOReporting() 1.546 +{ 1.547 + if (!sTelemetryIOObserver) { 1.548 + return; 1.549 + } 1.550 + IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging, 1.551 + sTelemetryIOObserver); 1.552 + sTelemetryIOObserver = nullptr; 1.553 +} 1.554 + 1.555 +class TelemetryImpl MOZ_FINAL 1.556 + : public nsITelemetry 1.557 + , public nsIMemoryReporter 1.558 +{ 1.559 + NS_DECL_THREADSAFE_ISUPPORTS 1.560 + NS_DECL_NSITELEMETRY 1.561 + NS_DECL_NSIMEMORYREPORTER 1.562 + 1.563 +public: 1.564 + ~TelemetryImpl(); 1.565 + 1.566 + void InitMemoryReporter(); 1.567 + 1.568 + static bool CanRecord(); 1.569 + static already_AddRefed<nsITelemetry> CreateTelemetryInstance(); 1.570 + static void ShutdownTelemetry(); 1.571 + static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName, 1.572 + uint32_t delay); 1.573 +#if defined(MOZ_ENABLE_PROFILER_SPS) 1.574 + static void RecordChromeHang(uint32_t aDuration, 1.575 + Telemetry::ProcessedStack &aStack, 1.576 + int32_t aSystemUptime, 1.577 + int32_t aFirefoxUptime); 1.578 +#endif 1.579 + static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats); 1.580 + static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id); 1.581 + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); 1.582 + struct Stat { 1.583 + uint32_t hitCount; 1.584 + uint32_t totalTime; 1.585 + }; 1.586 + struct StmtStats { 1.587 + struct Stat mainThread; 1.588 + struct Stat otherThreads; 1.589 + }; 1.590 + typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType; 1.591 + 1.592 +private: 1.593 + TelemetryImpl(); 1.594 + 1.595 + static nsCString SanitizeSQL(const nsACString& sql); 1.596 + 1.597 + enum SanitizedState { Sanitized, Unsanitized }; 1.598 + 1.599 + static void StoreSlowSQL(const nsACString &offender, uint32_t delay, 1.600 + SanitizedState state); 1.601 + 1.602 + static bool ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx, 1.603 + JS::Handle<JSObject*> obj); 1.604 + static bool ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx, 1.605 + JS::Handle<JSObject*> obj); 1.606 + static bool ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat, 1.607 + JSContext *cx, JS::Handle<JSObject*> obj); 1.608 + 1.609 + bool AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread, 1.610 + bool privateSQL); 1.611 + bool GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, 1.612 + bool includePrivateSql); 1.613 + 1.614 + // Like GetHistogramById, but returns the underlying C++ object, not the JS one. 1.615 + nsresult GetHistogramByName(const nsACString &name, Histogram **ret); 1.616 + bool ShouldReflectHistogram(Histogram *h); 1.617 + void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs); 1.618 + typedef StatisticsRecorder::Histograms::iterator HistogramIterator; 1.619 + 1.620 + struct AddonHistogramInfo { 1.621 + uint32_t min; 1.622 + uint32_t max; 1.623 + uint32_t bucketCount; 1.624 + uint32_t histogramType; 1.625 + Histogram *h; 1.626 + }; 1.627 + typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo> AddonHistogramEntryType; 1.628 + typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType; 1.629 + typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *> AddonEntryType; 1.630 + typedef AutoHashtable<AddonEntryType> AddonMapType; 1.631 + static bool AddonHistogramReflector(AddonHistogramEntryType *entry, 1.632 + JSContext *cx, JS::Handle<JSObject*> obj); 1.633 + static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj); 1.634 + static bool CreateHistogramForAddon(const nsACString &name, 1.635 + AddonHistogramInfo &info); 1.636 + void ReadLateWritesStacks(nsIFile* aProfileDir); 1.637 + AddonMapType mAddonMap; 1.638 + 1.639 + // This is used for speedy string->Telemetry::ID conversions 1.640 + typedef nsBaseHashtableET<nsCharPtrHashKey, Telemetry::ID> CharPtrEntryType; 1.641 + typedef AutoHashtable<CharPtrEntryType> HistogramMapType; 1.642 + HistogramMapType mHistogramMap; 1.643 + bool mCanRecord; 1.644 + static TelemetryImpl *sTelemetry; 1.645 + AutoHashtable<SlowSQLEntryType> mPrivateSQL; 1.646 + AutoHashtable<SlowSQLEntryType> mSanitizedSQL; 1.647 + // This gets marked immutable in debug builds, so we can't use 1.648 + // AutoHashtable here. 1.649 + nsTHashtable<nsCStringHashKey> mTrackedDBs; 1.650 + Mutex mHashMutex; 1.651 + HangReports mHangReports; 1.652 + Mutex mHangReportsMutex; 1.653 + // mThreadHangStats stores recorded, inactive thread hang stats 1.654 + Vector<Telemetry::ThreadHangStats> mThreadHangStats; 1.655 + Mutex mThreadHangStatsMutex; 1.656 + 1.657 + CombinedStacks mLateWritesStacks; // This is collected out of the main thread. 1.658 + bool mCachedTelemetryData; 1.659 + uint32_t mLastShutdownTime; 1.660 + uint32_t mFailedLockCount; 1.661 + nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks; 1.662 + friend class nsFetchTelemetryData; 1.663 +}; 1.664 + 1.665 +TelemetryImpl* TelemetryImpl::sTelemetry = nullptr; 1.666 + 1.667 +MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf) 1.668 + 1.669 +NS_IMETHODIMP 1.670 +TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport, 1.671 + nsISupports* aData) 1.672 +{ 1.673 + return MOZ_COLLECT_REPORT( 1.674 + "explicit/telemetry", KIND_HEAP, UNITS_BYTES, 1.675 + SizeOfIncludingThis(TelemetryMallocSizeOf), 1.676 + "Memory used by the telemetry system."); 1.677 +} 1.678 + 1.679 +// A initializer to initialize histogram collection 1.680 +StatisticsRecorder gStatisticsRecorder; 1.681 + 1.682 +// Hardcoded probes 1.683 +struct TelemetryHistogram { 1.684 + uint32_t min; 1.685 + uint32_t max; 1.686 + uint32_t bucketCount; 1.687 + uint32_t histogramType; 1.688 + uint32_t id_offset; 1.689 + uint32_t expiration_offset; 1.690 + bool extendedStatisticsOK; 1.691 + 1.692 + const char *id() const; 1.693 + const char *expiration() const; 1.694 +}; 1.695 + 1.696 +#include "TelemetryHistogramData.inc" 1.697 +bool gCorruptHistograms[Telemetry::HistogramCount]; 1.698 + 1.699 +const char * 1.700 +TelemetryHistogram::id() const 1.701 +{ 1.702 + return &gHistogramStringTable[this->id_offset]; 1.703 +} 1.704 + 1.705 +const char * 1.706 +TelemetryHistogram::expiration() const 1.707 +{ 1.708 + return &gHistogramStringTable[this->expiration_offset]; 1.709 +} 1.710 + 1.711 +bool 1.712 +IsExpired(const char *expiration){ 1.713 + static Version current_version = Version(MOZ_APP_VERSION); 1.714 + MOZ_ASSERT(expiration); 1.715 + return strcmp(expiration, "never") && (mozilla::Version(expiration) <= current_version); 1.716 +} 1.717 + 1.718 +bool 1.719 +IsExpired(const Histogram *histogram){ 1.720 + return histogram->histogram_name() == EXPIRED_ID; 1.721 +} 1.722 + 1.723 +nsresult 1.724 +HistogramGet(const char *name, const char *expiration, uint32_t min, uint32_t max, 1.725 + uint32_t bucketCount, uint32_t histogramType, Histogram **result) 1.726 +{ 1.727 + if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN 1.728 + && histogramType != nsITelemetry::HISTOGRAM_FLAG) { 1.729 + // Sanity checks for histogram parameters. 1.730 + if (min >= max) 1.731 + return NS_ERROR_ILLEGAL_VALUE; 1.732 + 1.733 + if (bucketCount <= 2) 1.734 + return NS_ERROR_ILLEGAL_VALUE; 1.735 + 1.736 + if (min < 1) 1.737 + return NS_ERROR_ILLEGAL_VALUE; 1.738 + } 1.739 + 1.740 + if (IsExpired(expiration)) { 1.741 + name = EXPIRED_ID; 1.742 + min = 1; 1.743 + max = 2; 1.744 + bucketCount = 3; 1.745 + histogramType = nsITelemetry::HISTOGRAM_LINEAR; 1.746 + } 1.747 + 1.748 + switch (histogramType) { 1.749 + case nsITelemetry::HISTOGRAM_EXPONENTIAL: 1.750 + *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); 1.751 + break; 1.752 + case nsITelemetry::HISTOGRAM_LINEAR: 1.753 + *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); 1.754 + break; 1.755 + case nsITelemetry::HISTOGRAM_BOOLEAN: 1.756 + *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); 1.757 + break; 1.758 + case nsITelemetry::HISTOGRAM_FLAG: 1.759 + *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); 1.760 + break; 1.761 + default: 1.762 + return NS_ERROR_INVALID_ARG; 1.763 + } 1.764 + return NS_OK; 1.765 +} 1.766 + 1.767 +// O(1) histogram lookup by numeric id 1.768 +nsresult 1.769 +GetHistogramByEnumId(Telemetry::ID id, Histogram **ret) 1.770 +{ 1.771 + static Histogram* knownHistograms[Telemetry::HistogramCount] = {0}; 1.772 + Histogram *h = knownHistograms[id]; 1.773 + if (h) { 1.774 + *ret = h; 1.775 + return NS_OK; 1.776 + } 1.777 + 1.778 + const TelemetryHistogram &p = gHistograms[id]; 1.779 + nsresult rv = HistogramGet(p.id(), p.expiration(), p.min, p.max, p.bucketCount, p.histogramType, &h); 1.780 + if (NS_FAILED(rv)) 1.781 + return rv; 1.782 + 1.783 +#ifdef DEBUG 1.784 + // Check that the C++ Histogram code computes the same ranges as the 1.785 + // Python histogram code. 1.786 + if (!IsExpired(p.expiration())) { 1.787 + const struct bounds &b = gBucketLowerBoundIndex[id]; 1.788 + if (b.length != 0) { 1.789 + MOZ_ASSERT(size_t(b.length) == h->bucket_count(), 1.790 + "C++/Python bucket # mismatch"); 1.791 + for (int i = 0; i < b.length; ++i) { 1.792 + MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i), 1.793 + "C++/Python bucket mismatch"); 1.794 + } 1.795 + } 1.796 + } 1.797 +#endif 1.798 + 1.799 + if (p.extendedStatisticsOK) { 1.800 + h->SetFlags(Histogram::kExtendedStatisticsFlag); 1.801 + } 1.802 + *ret = knownHistograms[id] = h; 1.803 + return NS_OK; 1.804 +} 1.805 + 1.806 +bool 1.807 +FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h) 1.808 +{ 1.809 + JS::Rooted<JS::Value> range(cx); 1.810 + for (size_t i = 0; i < h->bucket_count(); i++) { 1.811 + range = INT_TO_JSVAL(h->ranges(i)); 1.812 + if (!JS_DefineElement(cx, array, i, range, nullptr, nullptr, JSPROP_ENUMERATE)) 1.813 + return false; 1.814 + } 1.815 + return true; 1.816 +} 1.817 + 1.818 +enum reflectStatus { 1.819 + REFLECT_OK, 1.820 + REFLECT_CORRUPT, 1.821 + REFLECT_FAILURE 1.822 +}; 1.823 + 1.824 +enum reflectStatus 1.825 +ReflectHistogramAndSamples(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h, 1.826 + const Histogram::SampleSet &ss) 1.827 +{ 1.828 + // We don't want to reflect corrupt histograms. 1.829 + if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) { 1.830 + return REFLECT_CORRUPT; 1.831 + } 1.832 + 1.833 + if (!(JS_DefineProperty(cx, obj, "min", h->declared_min(), JSPROP_ENUMERATE) 1.834 + && JS_DefineProperty(cx, obj, "max", h->declared_max(), JSPROP_ENUMERATE) 1.835 + && JS_DefineProperty(cx, obj, "histogram_type", h->histogram_type(), JSPROP_ENUMERATE) 1.836 + && JS_DefineProperty(cx, obj, "sum", double(ss.sum()), JSPROP_ENUMERATE))) { 1.837 + return REFLECT_FAILURE; 1.838 + } 1.839 + 1.840 + if (h->histogram_type() == Histogram::HISTOGRAM) { 1.841 + if (!(JS_DefineProperty(cx, obj, "log_sum", ss.log_sum(), JSPROP_ENUMERATE) 1.842 + && JS_DefineProperty(cx, obj, "log_sum_squares", ss.log_sum_squares(), JSPROP_ENUMERATE))) { 1.843 + return REFLECT_FAILURE; 1.844 + } 1.845 + } else { 1.846 + // Export |sum_squares| as two separate 32-bit properties so that we 1.847 + // can accurately reconstruct it on the analysis side. 1.848 + uint64_t sum_squares = ss.sum_squares(); 1.849 + // Cast to avoid implicit truncation warnings. 1.850 + uint32_t lo = static_cast<uint32_t>(sum_squares); 1.851 + uint32_t hi = static_cast<uint32_t>(sum_squares >> 32); 1.852 + if (!(JS_DefineProperty(cx, obj, "sum_squares_lo", lo, JSPROP_ENUMERATE) 1.853 + && JS_DefineProperty(cx, obj, "sum_squares_hi", hi, JSPROP_ENUMERATE))) { 1.854 + return REFLECT_FAILURE; 1.855 + } 1.856 + } 1.857 + 1.858 + const size_t count = h->bucket_count(); 1.859 + JS::Rooted<JSObject*> rarray(cx, JS_NewArrayObject(cx, count)); 1.860 + if (!rarray) { 1.861 + return REFLECT_FAILURE; 1.862 + } 1.863 + if (!(FillRanges(cx, rarray, h) 1.864 + && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) { 1.865 + return REFLECT_FAILURE; 1.866 + } 1.867 + 1.868 + JS::Rooted<JSObject*> counts_array(cx, JS_NewArrayObject(cx, count)); 1.869 + if (!counts_array) { 1.870 + return REFLECT_FAILURE; 1.871 + } 1.872 + if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) { 1.873 + return REFLECT_FAILURE; 1.874 + } 1.875 + for (size_t i = 0; i < count; i++) { 1.876 + if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)), 1.877 + nullptr, nullptr, JSPROP_ENUMERATE)) { 1.878 + return REFLECT_FAILURE; 1.879 + } 1.880 + } 1.881 + 1.882 + return REFLECT_OK; 1.883 +} 1.884 + 1.885 +enum reflectStatus 1.886 +ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h) 1.887 +{ 1.888 + Histogram::SampleSet ss; 1.889 + h->SnapshotSample(&ss); 1.890 + return ReflectHistogramAndSamples(cx, obj, h, ss); 1.891 +} 1.892 + 1.893 +bool 1.894 +IsEmpty(const Histogram *h) 1.895 +{ 1.896 + Histogram::SampleSet ss; 1.897 + h->SnapshotSample(&ss); 1.898 + 1.899 + return ss.counts(0) == 0 && ss.sum() == 0; 1.900 +} 1.901 + 1.902 +bool 1.903 +JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) 1.904 +{ 1.905 + JS::CallArgs args = CallArgsFromVp(argc, vp); 1.906 + if (!args.length()) { 1.907 + JS_ReportError(cx, "Expected one argument"); 1.908 + return false; 1.909 + } 1.910 + 1.911 + if (!(args[0].isNumber() || args[0].isBoolean())) { 1.912 + JS_ReportError(cx, "Not a number"); 1.913 + return false; 1.914 + } 1.915 + 1.916 + int32_t value; 1.917 + if (!JS::ToInt32(cx, args[0], &value)) { 1.918 + return false; 1.919 + } 1.920 + 1.921 + if (TelemetryImpl::CanRecord()) { 1.922 + JSObject *obj = JS_THIS_OBJECT(cx, vp); 1.923 + if (!obj) { 1.924 + return false; 1.925 + } 1.926 + 1.927 + Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj)); 1.928 + h->Add(value); 1.929 + } 1.930 + return true; 1.931 + 1.932 +} 1.933 + 1.934 +bool 1.935 +JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) 1.936 +{ 1.937 + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 1.938 + JSObject *obj = JS_THIS_OBJECT(cx, vp); 1.939 + if (!obj) { 1.940 + return false; 1.941 + } 1.942 + 1.943 + Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj)); 1.944 + JS::Rooted<JSObject*> snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.945 + if (!snapshot) 1.946 + return false; 1.947 + 1.948 + switch (ReflectHistogramSnapshot(cx, snapshot, h)) { 1.949 + case REFLECT_FAILURE: 1.950 + return false; 1.951 + case REFLECT_CORRUPT: 1.952 + JS_ReportError(cx, "Histogram is corrupt"); 1.953 + return false; 1.954 + case REFLECT_OK: 1.955 + args.rval().setObject(*snapshot); 1.956 + return true; 1.957 + default: 1.958 + MOZ_CRASH("unhandled reflection status"); 1.959 + } 1.960 +} 1.961 + 1.962 +bool 1.963 +JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) 1.964 +{ 1.965 + JSObject *obj = JS_THIS_OBJECT(cx, vp); 1.966 + if (!obj) { 1.967 + return false; 1.968 + } 1.969 + 1.970 + Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj)); 1.971 + h->Clear(); 1.972 + return true; 1.973 +} 1.974 + 1.975 +nsresult 1.976 +WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.977 +{ 1.978 + static const JSClass JSHistogram_class = { 1.979 + "JSHistogram", /* name */ 1.980 + JSCLASS_HAS_PRIVATE, /* flags */ 1.981 + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, 1.982 + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub 1.983 + }; 1.984 + 1.985 + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class, JS::NullPtr(), JS::NullPtr())); 1.986 + if (!obj) 1.987 + return NS_ERROR_FAILURE; 1.988 + if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0) 1.989 + && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0) 1.990 + && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0))) { 1.991 + return NS_ERROR_FAILURE; 1.992 + } 1.993 + JS_SetPrivate(obj, h); 1.994 + ret.setObject(*obj); 1.995 + return NS_OK; 1.996 +} 1.997 + 1.998 +static uint32_t 1.999 +ReadLastShutdownDuration(const char *filename) { 1.1000 + FILE *f = fopen(filename, "r"); 1.1001 + if (!f) { 1.1002 + return 0; 1.1003 + } 1.1004 + 1.1005 + int shutdownTime; 1.1006 + int r = fscanf(f, "%d\n", &shutdownTime); 1.1007 + fclose(f); 1.1008 + if (r != 1) { 1.1009 + return 0; 1.1010 + } 1.1011 + 1.1012 + return shutdownTime; 1.1013 +} 1.1014 + 1.1015 +const int32_t kMaxFailedProfileLockFileSize = 10; 1.1016 + 1.1017 +bool 1.1018 +GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount, 1.1019 + unsigned int& result) 1.1020 +{ 1.1021 + nsAutoCString bufStr; 1.1022 + nsresult rv; 1.1023 + rv = NS_ReadInputStreamToString(inStream, bufStr, aCount); 1.1024 + NS_ENSURE_SUCCESS(rv, false); 1.1025 + result = bufStr.ToInteger(&rv); 1.1026 + return NS_SUCCEEDED(rv) && result > 0; 1.1027 +} 1.1028 + 1.1029 +nsresult 1.1030 +GetFailedProfileLockFile(nsIFile* *aFile, nsIFile* aProfileDir) 1.1031 +{ 1.1032 + NS_ENSURE_ARG_POINTER(aProfileDir); 1.1033 + 1.1034 + nsresult rv = aProfileDir->Clone(aFile); 1.1035 + NS_ENSURE_SUCCESS(rv, rv); 1.1036 + 1.1037 + (*aFile)->AppendNative(NS_LITERAL_CSTRING("Telemetry.FailedProfileLocks.txt")); 1.1038 + return NS_OK; 1.1039 +} 1.1040 + 1.1041 +class nsFetchTelemetryData : public nsRunnable 1.1042 +{ 1.1043 +public: 1.1044 + nsFetchTelemetryData(const char* aShutdownTimeFilename, 1.1045 + nsIFile* aFailedProfileLockFile, 1.1046 + nsIFile* aProfileDir) 1.1047 + : mShutdownTimeFilename(aShutdownTimeFilename), 1.1048 + mFailedProfileLockFile(aFailedProfileLockFile), 1.1049 + mTelemetry(TelemetryImpl::sTelemetry), 1.1050 + mProfileDir(aProfileDir) 1.1051 + { 1.1052 + } 1.1053 + 1.1054 +private: 1.1055 + const char* mShutdownTimeFilename; 1.1056 + nsCOMPtr<nsIFile> mFailedProfileLockFile; 1.1057 + nsRefPtr<TelemetryImpl> mTelemetry; 1.1058 + nsCOMPtr<nsIFile> mProfileDir; 1.1059 + 1.1060 +public: 1.1061 + void MainThread() { 1.1062 + mTelemetry->mCachedTelemetryData = true; 1.1063 + for (unsigned int i = 0, n = mTelemetry->mCallbacks.Count(); i < n; ++i) { 1.1064 + mTelemetry->mCallbacks[i]->Complete(); 1.1065 + } 1.1066 + mTelemetry->mCallbacks.Clear(); 1.1067 + } 1.1068 + 1.1069 + NS_IMETHOD Run() { 1.1070 + LoadFailedLockCount(mTelemetry->mFailedLockCount); 1.1071 + mTelemetry->mLastShutdownTime = 1.1072 + ReadLastShutdownDuration(mShutdownTimeFilename); 1.1073 + mTelemetry->ReadLateWritesStacks(mProfileDir); 1.1074 + nsCOMPtr<nsIRunnable> e = 1.1075 + NS_NewRunnableMethod(this, &nsFetchTelemetryData::MainThread); 1.1076 + NS_ENSURE_STATE(e); 1.1077 + NS_DispatchToMainThread(e, NS_DISPATCH_NORMAL); 1.1078 + return NS_OK; 1.1079 + } 1.1080 + 1.1081 +private: 1.1082 + nsresult 1.1083 + LoadFailedLockCount(uint32_t& failedLockCount) 1.1084 + { 1.1085 + failedLockCount = 0; 1.1086 + int64_t fileSize = 0; 1.1087 + nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize); 1.1088 + if (NS_FAILED(rv)) { 1.1089 + return rv; 1.1090 + } 1.1091 + NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize, 1.1092 + NS_ERROR_UNEXPECTED); 1.1093 + nsCOMPtr<nsIInputStream> inStream; 1.1094 + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), 1.1095 + mFailedProfileLockFile, 1.1096 + PR_RDONLY); 1.1097 + NS_ENSURE_SUCCESS(rv, rv); 1.1098 + NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount), 1.1099 + NS_ERROR_UNEXPECTED); 1.1100 + inStream->Close(); 1.1101 + 1.1102 + mFailedProfileLockFile->Remove(false); 1.1103 + return NS_OK; 1.1104 + } 1.1105 +}; 1.1106 + 1.1107 +static TimeStamp gRecordedShutdownStartTime; 1.1108 +static bool gAlreadyFreedShutdownTimeFileName = false; 1.1109 +static char *gRecordedShutdownTimeFileName = nullptr; 1.1110 + 1.1111 +static char * 1.1112 +GetShutdownTimeFileName() 1.1113 +{ 1.1114 + if (gAlreadyFreedShutdownTimeFileName) { 1.1115 + return nullptr; 1.1116 + } 1.1117 + 1.1118 + if (!gRecordedShutdownTimeFileName) { 1.1119 + nsCOMPtr<nsIFile> mozFile; 1.1120 + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); 1.1121 + if (!mozFile) 1.1122 + return nullptr; 1.1123 + 1.1124 + mozFile->AppendNative(NS_LITERAL_CSTRING("Telemetry.ShutdownTime.txt")); 1.1125 + nsAutoCString nativePath; 1.1126 + nsresult rv = mozFile->GetNativePath(nativePath); 1.1127 + if (!NS_SUCCEEDED(rv)) 1.1128 + return nullptr; 1.1129 + 1.1130 + gRecordedShutdownTimeFileName = PL_strdup(nativePath.get()); 1.1131 + } 1.1132 + 1.1133 + return gRecordedShutdownTimeFileName; 1.1134 +} 1.1135 + 1.1136 +NS_IMETHODIMP 1.1137 +TelemetryImpl::GetLastShutdownDuration(uint32_t *aResult) 1.1138 +{ 1.1139 + // The user must call AsyncFetchTelemetryData first. We return zero instead of 1.1140 + // reporting a failure so that the rest of telemetry can uniformly handle 1.1141 + // the read not being available yet. 1.1142 + if (!mCachedTelemetryData) { 1.1143 + *aResult = 0; 1.1144 + return NS_OK; 1.1145 + } 1.1146 + 1.1147 + *aResult = mLastShutdownTime; 1.1148 + return NS_OK; 1.1149 +} 1.1150 + 1.1151 +NS_IMETHODIMP 1.1152 +TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult) 1.1153 +{ 1.1154 + // The user must call AsyncFetchTelemetryData first. We return zero instead of 1.1155 + // reporting a failure so that the rest of telemetry can uniformly handle 1.1156 + // the read not being available yet. 1.1157 + if (!mCachedTelemetryData) { 1.1158 + *aResult = 0; 1.1159 + return NS_OK; 1.1160 + } 1.1161 + 1.1162 + *aResult = mFailedLockCount; 1.1163 + return NS_OK; 1.1164 +} 1.1165 + 1.1166 +NS_IMETHODIMP 1.1167 +TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback) 1.1168 +{ 1.1169 + // We have finished reading the data already, just call the callback. 1.1170 + if (mCachedTelemetryData) { 1.1171 + aCallback->Complete(); 1.1172 + return NS_OK; 1.1173 + } 1.1174 + 1.1175 + // We already have a read request running, just remember the callback. 1.1176 + if (mCallbacks.Count() != 0) { 1.1177 + mCallbacks.AppendObject(aCallback); 1.1178 + return NS_OK; 1.1179 + } 1.1180 + 1.1181 + // We make this check so that GetShutdownTimeFileName() doesn't get 1.1182 + // called; calling that function without telemetry enabled violates 1.1183 + // assumptions that the write-the-shutdown-timestamp machinery makes. 1.1184 + if (!Telemetry::CanRecord()) { 1.1185 + mCachedTelemetryData = true; 1.1186 + aCallback->Complete(); 1.1187 + return NS_OK; 1.1188 + } 1.1189 + 1.1190 + // Send the read to a background thread provided by the stream transport 1.1191 + // service to avoid a read in the main thread. 1.1192 + nsCOMPtr<nsIEventTarget> targetThread = 1.1193 + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 1.1194 + if (!targetThread) { 1.1195 + mCachedTelemetryData = true; 1.1196 + aCallback->Complete(); 1.1197 + return NS_OK; 1.1198 + } 1.1199 + 1.1200 + // We have to get the filename from the main thread. 1.1201 + const char *shutdownTimeFilename = GetShutdownTimeFileName(); 1.1202 + if (!shutdownTimeFilename) { 1.1203 + mCachedTelemetryData = true; 1.1204 + aCallback->Complete(); 1.1205 + return NS_OK; 1.1206 + } 1.1207 + 1.1208 + nsCOMPtr<nsIFile> profileDir; 1.1209 + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1.1210 + getter_AddRefs(profileDir)); 1.1211 + if (NS_FAILED(rv)) { 1.1212 + mCachedTelemetryData = true; 1.1213 + aCallback->Complete(); 1.1214 + return NS_OK; 1.1215 + } 1.1216 + 1.1217 + nsCOMPtr<nsIFile> failedProfileLockFile; 1.1218 + rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile), 1.1219 + profileDir); 1.1220 + if (NS_FAILED(rv)) { 1.1221 + mCachedTelemetryData = true; 1.1222 + aCallback->Complete(); 1.1223 + return NS_OK; 1.1224 + } 1.1225 + 1.1226 + mCallbacks.AppendObject(aCallback); 1.1227 + 1.1228 + nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(shutdownTimeFilename, 1.1229 + failedProfileLockFile, 1.1230 + profileDir); 1.1231 + 1.1232 + targetThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.1233 + return NS_OK; 1.1234 +} 1.1235 + 1.1236 +TelemetryImpl::TelemetryImpl(): 1.1237 +mHistogramMap(Telemetry::HistogramCount), 1.1238 +mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default), 1.1239 +mHashMutex("Telemetry::mHashMutex"), 1.1240 +mHangReportsMutex("Telemetry::mHangReportsMutex"), 1.1241 +mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex"), 1.1242 +mCachedTelemetryData(false), 1.1243 +mLastShutdownTime(0), 1.1244 +mFailedLockCount(0) 1.1245 +{ 1.1246 + // A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs 1.1247 + const char *trackedDBs[] = { 1.1248 + "addons.sqlite", "content-prefs.sqlite", "cookies.sqlite", 1.1249 + "downloads.sqlite", "extensions.sqlite", "formhistory.sqlite", 1.1250 + "index.sqlite", "healthreport.sqlite", "netpredictions.sqlite", 1.1251 + "permissions.sqlite", "places.sqlite", "search.sqlite", "signons.sqlite", 1.1252 + "urlclassifier3.sqlite", "webappsstore.sqlite" 1.1253 + }; 1.1254 + 1.1255 + for (size_t i = 0; i < ArrayLength(trackedDBs); i++) 1.1256 + mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i])); 1.1257 + 1.1258 +#ifdef DEBUG 1.1259 + // Mark immutable to prevent asserts on simultaneous access from multiple threads 1.1260 + mTrackedDBs.MarkImmutable(); 1.1261 +#endif 1.1262 +} 1.1263 + 1.1264 +TelemetryImpl::~TelemetryImpl() { 1.1265 + UnregisterWeakMemoryReporter(this); 1.1266 +} 1.1267 + 1.1268 +void 1.1269 +TelemetryImpl::InitMemoryReporter() { 1.1270 + RegisterWeakMemoryReporter(this); 1.1271 +} 1.1272 + 1.1273 +NS_IMETHODIMP 1.1274 +TelemetryImpl::NewHistogram(const nsACString &name, const nsACString &expiration, uint32_t min, 1.1275 + uint32_t max, uint32_t bucketCount, uint32_t histogramType, 1.1276 + JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1277 +{ 1.1278 + Histogram *h; 1.1279 + nsresult rv = HistogramGet(PromiseFlatCString(name).get(), PromiseFlatCString(expiration).get(), 1.1280 + min, max, bucketCount, histogramType, &h); 1.1281 + if (NS_FAILED(rv)) 1.1282 + return rv; 1.1283 + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); 1.1284 + h->SetFlags(Histogram::kExtendedStatisticsFlag); 1.1285 + return WrapAndReturnHistogram(h, cx, ret); 1.1286 +} 1.1287 + 1.1288 +bool 1.1289 +TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry, 1.1290 + const Stat *stat, 1.1291 + JSContext *cx, 1.1292 + JS::Handle<JSObject*> obj) 1.1293 +{ 1.1294 + if (stat->hitCount == 0) 1.1295 + return true; 1.1296 + 1.1297 + const nsACString &sql = entry->GetKey(); 1.1298 + 1.1299 + JS::Rooted<JSObject*> arrayObj(cx, JS_NewArrayObject(cx, 0)); 1.1300 + if (!arrayObj) { 1.1301 + return false; 1.1302 + } 1.1303 + return (JS_SetElement(cx, arrayObj, 0, stat->hitCount) 1.1304 + && JS_SetElement(cx, arrayObj, 1, stat->totalTime) 1.1305 + && JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj, 1.1306 + JSPROP_ENUMERATE)); 1.1307 +} 1.1308 + 1.1309 +bool 1.1310 +TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType *entry, JSContext *cx, 1.1311 + JS::Handle<JSObject*> obj) 1.1312 +{ 1.1313 + return ReflectSQL(entry, &entry->mData.mainThread, cx, obj); 1.1314 +} 1.1315 + 1.1316 +bool 1.1317 +TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType *entry, JSContext *cx, 1.1318 + JS::Handle<JSObject*> obj) 1.1319 +{ 1.1320 + return ReflectSQL(entry, &entry->mData.otherThreads, cx, obj); 1.1321 +} 1.1322 + 1.1323 +bool 1.1324 +TelemetryImpl::AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread, 1.1325 + bool privateSQL) 1.1326 +{ 1.1327 + JS::Rooted<JSObject*> statsObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1328 + if (!statsObj) 1.1329 + return false; 1.1330 + 1.1331 + AutoHashtable<SlowSQLEntryType> &sqlMap = 1.1332 + (privateSQL ? mPrivateSQL : mSanitizedSQL); 1.1333 + AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction = 1.1334 + (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL); 1.1335 + if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) { 1.1336 + return false; 1.1337 + } 1.1338 + 1.1339 + return JS_DefineProperty(cx, rootObj, 1.1340 + mainThread ? "mainThread" : "otherThreads", 1.1341 + statsObj, JSPROP_ENUMERATE); 1.1342 +} 1.1343 + 1.1344 +nsresult 1.1345 +TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id) 1.1346 +{ 1.1347 + if (!sTelemetry) { 1.1348 + return NS_ERROR_FAILURE; 1.1349 + } 1.1350 + 1.1351 + // Cache names 1.1352 + // Note the histogram names are statically allocated 1.1353 + TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap; 1.1354 + if (!map->Count()) { 1.1355 + for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) { 1.1356 + CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id()); 1.1357 + if (MOZ_UNLIKELY(!entry)) { 1.1358 + map->Clear(); 1.1359 + return NS_ERROR_OUT_OF_MEMORY; 1.1360 + } 1.1361 + entry->mData = (Telemetry::ID) i; 1.1362 + } 1.1363 + } 1.1364 + 1.1365 + CharPtrEntryType *entry = map->GetEntry(name); 1.1366 + if (!entry) { 1.1367 + return NS_ERROR_INVALID_ARG; 1.1368 + } 1.1369 + *id = entry->mData; 1.1370 + return NS_OK; 1.1371 +} 1.1372 + 1.1373 +nsresult 1.1374 +TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret) 1.1375 +{ 1.1376 + Telemetry::ID id; 1.1377 + nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id); 1.1378 + if (NS_FAILED(rv)) { 1.1379 + return rv; 1.1380 + } 1.1381 + 1.1382 + rv = GetHistogramByEnumId(id, ret); 1.1383 + if (NS_FAILED(rv)) 1.1384 + return rv; 1.1385 + 1.1386 + return NS_OK; 1.1387 +} 1.1388 + 1.1389 +NS_IMETHODIMP 1.1390 +TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name, 1.1391 + JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1392 +{ 1.1393 + Telemetry::ID id; 1.1394 + nsresult rv = GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id); 1.1395 + if (NS_FAILED(rv)) { 1.1396 + return rv; 1.1397 + } 1.1398 + const TelemetryHistogram &p = gHistograms[id]; 1.1399 + 1.1400 + Histogram *existing; 1.1401 + rv = GetHistogramByEnumId(id, &existing); 1.1402 + if (NS_FAILED(rv)) { 1.1403 + return rv; 1.1404 + } 1.1405 + 1.1406 + Histogram *clone; 1.1407 + rv = HistogramGet(PromiseFlatCString(name).get(), p.expiration(), 1.1408 + existing->declared_min(), existing->declared_max(), 1.1409 + existing->bucket_count(), p.histogramType, &clone); 1.1410 + if (NS_FAILED(rv)) 1.1411 + return rv; 1.1412 + 1.1413 + Histogram::SampleSet ss; 1.1414 + existing->SnapshotSample(&ss); 1.1415 + clone->AddSampleSet(ss); 1.1416 + return WrapAndReturnHistogram(clone, cx, ret); 1.1417 +} 1.1418 + 1.1419 +void 1.1420 +TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs) 1.1421 +{ 1.1422 + for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { 1.1423 + Histogram *h = *it; 1.1424 + 1.1425 + Telemetry::ID id; 1.1426 + nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id); 1.1427 + // This histogram isn't a static histogram, just ignore it. 1.1428 + if (NS_FAILED(rv)) { 1.1429 + continue; 1.1430 + } 1.1431 + 1.1432 + if (gCorruptHistograms[id]) { 1.1433 + continue; 1.1434 + } 1.1435 + 1.1436 + Histogram::SampleSet ss; 1.1437 + h->SnapshotSample(&ss); 1.1438 + Histogram::Inconsistencies check = h->FindCorruption(ss); 1.1439 + bool corrupt = (check != Histogram::NO_INCONSISTENCIES); 1.1440 + 1.1441 + if (corrupt) { 1.1442 + Telemetry::ID corruptID = Telemetry::HistogramCount; 1.1443 + if (check & Histogram::RANGE_CHECKSUM_ERROR) { 1.1444 + corruptID = Telemetry::RANGE_CHECKSUM_ERRORS; 1.1445 + } else if (check & Histogram::BUCKET_ORDER_ERROR) { 1.1446 + corruptID = Telemetry::BUCKET_ORDER_ERRORS; 1.1447 + } else if (check & Histogram::COUNT_HIGH_ERROR) { 1.1448 + corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS; 1.1449 + } else if (check & Histogram::COUNT_LOW_ERROR) { 1.1450 + corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS; 1.1451 + } 1.1452 + Telemetry::Accumulate(corruptID, 1); 1.1453 + } 1.1454 + 1.1455 + gCorruptHistograms[id] = corrupt; 1.1456 + } 1.1457 +} 1.1458 + 1.1459 +bool 1.1460 +TelemetryImpl::ShouldReflectHistogram(Histogram *h) 1.1461 +{ 1.1462 + const char *name = h->histogram_name().c_str(); 1.1463 + Telemetry::ID id; 1.1464 + nsresult rv = GetHistogramEnumId(name, &id); 1.1465 + if (NS_FAILED(rv)) { 1.1466 + // GetHistogramEnumId generally should not fail. But a lookup 1.1467 + // failure shouldn't prevent us from reflecting histograms into JS. 1.1468 + // 1.1469 + // However, these two histograms are created by Histogram itself for 1.1470 + // tracking corruption. We have our own histograms for that, so 1.1471 + // ignore these two. 1.1472 + if (strcmp(name, "Histogram.InconsistentCountHigh") == 0 1.1473 + || strcmp(name, "Histogram.InconsistentCountLow") == 0) { 1.1474 + return false; 1.1475 + } 1.1476 + return true; 1.1477 + } else { 1.1478 + return !gCorruptHistograms[id]; 1.1479 + } 1.1480 +} 1.1481 + 1.1482 +// Compute the name to pass into Histogram for the addon histogram 1.1483 +// 'name' from the addon 'id'. We can't use 'name' directly because it 1.1484 +// might conflict with other histograms in other addons or even with our 1.1485 +// own. 1.1486 +void 1.1487 +AddonHistogramName(const nsACString &id, const nsACString &name, 1.1488 + nsACString &ret) 1.1489 +{ 1.1490 + ret.Append(id); 1.1491 + ret.Append(NS_LITERAL_CSTRING(":")); 1.1492 + ret.Append(name); 1.1493 +} 1.1494 + 1.1495 +NS_IMETHODIMP 1.1496 +TelemetryImpl::RegisterAddonHistogram(const nsACString &id, 1.1497 + const nsACString &name, 1.1498 + uint32_t min, uint32_t max, 1.1499 + uint32_t bucketCount, 1.1500 + uint32_t histogramType) 1.1501 +{ 1.1502 + AddonEntryType *addonEntry = mAddonMap.GetEntry(id); 1.1503 + if (!addonEntry) { 1.1504 + addonEntry = mAddonMap.PutEntry(id); 1.1505 + if (MOZ_UNLIKELY(!addonEntry)) { 1.1506 + return NS_ERROR_OUT_OF_MEMORY; 1.1507 + } 1.1508 + addonEntry->mData = new AddonHistogramMapType(); 1.1509 + } 1.1510 + 1.1511 + AddonHistogramMapType *histogramMap = addonEntry->mData; 1.1512 + AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); 1.1513 + // Can't re-register the same histogram. 1.1514 + if (histogramEntry) { 1.1515 + return NS_ERROR_FAILURE; 1.1516 + } 1.1517 + 1.1518 + histogramEntry = histogramMap->PutEntry(name); 1.1519 + if (MOZ_UNLIKELY(!histogramEntry)) { 1.1520 + return NS_ERROR_OUT_OF_MEMORY; 1.1521 + } 1.1522 + 1.1523 + AddonHistogramInfo &info = histogramEntry->mData; 1.1524 + info.min = min; 1.1525 + info.max = max; 1.1526 + info.bucketCount = bucketCount; 1.1527 + info.histogramType = histogramType; 1.1528 + 1.1529 + return NS_OK; 1.1530 +} 1.1531 + 1.1532 +NS_IMETHODIMP 1.1533 +TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name, 1.1534 + JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1535 +{ 1.1536 + AddonEntryType *addonEntry = mAddonMap.GetEntry(id); 1.1537 + // The given id has not been registered. 1.1538 + if (!addonEntry) { 1.1539 + return NS_ERROR_INVALID_ARG; 1.1540 + } 1.1541 + 1.1542 + AddonHistogramMapType *histogramMap = addonEntry->mData; 1.1543 + AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); 1.1544 + // The given histogram name has not been registered. 1.1545 + if (!histogramEntry) { 1.1546 + return NS_ERROR_INVALID_ARG; 1.1547 + } 1.1548 + 1.1549 + AddonHistogramInfo &info = histogramEntry->mData; 1.1550 + if (!info.h) { 1.1551 + nsAutoCString actualName; 1.1552 + AddonHistogramName(id, name, actualName); 1.1553 + if (!CreateHistogramForAddon(actualName, info)) { 1.1554 + return NS_ERROR_FAILURE; 1.1555 + } 1.1556 + } 1.1557 + return WrapAndReturnHistogram(info.h, cx, ret); 1.1558 +} 1.1559 + 1.1560 +NS_IMETHODIMP 1.1561 +TelemetryImpl::UnregisterAddonHistograms(const nsACString &id) 1.1562 +{ 1.1563 + AddonEntryType *addonEntry = mAddonMap.GetEntry(id); 1.1564 + if (addonEntry) { 1.1565 + // Histogram's destructor is private, so this is the best we can do. 1.1566 + // The histograms the addon created *will* stick around, but they 1.1567 + // will be deleted if and when the addon registers histograms with 1.1568 + // the same names. 1.1569 + delete addonEntry->mData; 1.1570 + mAddonMap.RemoveEntry(id); 1.1571 + } 1.1572 + 1.1573 + return NS_OK; 1.1574 +} 1.1575 + 1.1576 +NS_IMETHODIMP 1.1577 +TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1578 +{ 1.1579 + JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1580 + if (!root_obj) 1.1581 + return NS_ERROR_FAILURE; 1.1582 + ret.setObject(*root_obj); 1.1583 + 1.1584 + // Ensure that all the HISTOGRAM_FLAG histograms have been created, so 1.1585 + // that their values are snapshotted. 1.1586 + for (size_t i = 0; i < Telemetry::HistogramCount; ++i) { 1.1587 + if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) { 1.1588 + Histogram *h; 1.1589 + DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h); 1.1590 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.1591 + } 1.1592 + }; 1.1593 + 1.1594 + StatisticsRecorder::Histograms hs; 1.1595 + StatisticsRecorder::GetHistograms(&hs); 1.1596 + 1.1597 + // We identify corrupt histograms first, rather than interspersing it 1.1598 + // in the loop below, to ensure that our corruption statistics don't 1.1599 + // depend on histogram enumeration order. 1.1600 + // 1.1601 + // Of course, we hope that all of these corruption-statistics 1.1602 + // histograms are not themselves corrupt... 1.1603 + IdentifyCorruptHistograms(hs); 1.1604 + 1.1605 + // OK, now we can actually reflect things. 1.1606 + JS::Rooted<JSObject*> hobj(cx); 1.1607 + for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { 1.1608 + Histogram *h = *it; 1.1609 + if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) { 1.1610 + continue; 1.1611 + } 1.1612 + 1.1613 + hobj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()); 1.1614 + if (!hobj) { 1.1615 + return NS_ERROR_FAILURE; 1.1616 + } 1.1617 + switch (ReflectHistogramSnapshot(cx, hobj, h)) { 1.1618 + case REFLECT_CORRUPT: 1.1619 + // We can still hit this case even if ShouldReflectHistograms 1.1620 + // returns true. The histogram lies outside of our control 1.1621 + // somehow; just skip it. 1.1622 + continue; 1.1623 + case REFLECT_FAILURE: 1.1624 + return NS_ERROR_FAILURE; 1.1625 + case REFLECT_OK: 1.1626 + if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), hobj, 1.1627 + JSPROP_ENUMERATE)) { 1.1628 + return NS_ERROR_FAILURE; 1.1629 + } 1.1630 + } 1.1631 + } 1.1632 + return NS_OK; 1.1633 +} 1.1634 + 1.1635 +bool 1.1636 +TelemetryImpl::CreateHistogramForAddon(const nsACString &name, 1.1637 + AddonHistogramInfo &info) 1.1638 +{ 1.1639 + Histogram *h; 1.1640 + nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never", 1.1641 + info.min, info.max, info.bucketCount, 1.1642 + info.histogramType, &h); 1.1643 + if (NS_FAILED(rv)) { 1.1644 + return false; 1.1645 + } 1.1646 + // Don't let this histogram be reported via the normal means 1.1647 + // (e.g. Telemetry.registeredHistograms); we'll make it available in 1.1648 + // other ways. 1.1649 + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); 1.1650 + info.h = h; 1.1651 + return true; 1.1652 +} 1.1653 + 1.1654 +bool 1.1655 +TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry, 1.1656 + JSContext *cx, JS::Handle<JSObject*> obj) 1.1657 +{ 1.1658 + AddonHistogramInfo &info = entry->mData; 1.1659 + 1.1660 + // Never even accessed the histogram. 1.1661 + if (!info.h) { 1.1662 + // Have to force creation of HISTOGRAM_FLAG histograms. 1.1663 + if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) 1.1664 + return true; 1.1665 + 1.1666 + if (!CreateHistogramForAddon(entry->GetKey(), info)) { 1.1667 + return false; 1.1668 + } 1.1669 + } 1.1670 + 1.1671 + if (IsEmpty(info.h)) { 1.1672 + return true; 1.1673 + } 1.1674 + 1.1675 + JS::Rooted<JSObject*> snapshot(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1676 + if (!snapshot) { 1.1677 + // Just consider this to be skippable. 1.1678 + return true; 1.1679 + } 1.1680 + switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) { 1.1681 + case REFLECT_FAILURE: 1.1682 + case REFLECT_CORRUPT: 1.1683 + return false; 1.1684 + case REFLECT_OK: 1.1685 + const nsACString &histogramName = entry->GetKey(); 1.1686 + if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), 1.1687 + snapshot, JSPROP_ENUMERATE)) { 1.1688 + return false; 1.1689 + } 1.1690 + break; 1.1691 + } 1.1692 + return true; 1.1693 +} 1.1694 + 1.1695 +bool 1.1696 +TelemetryImpl::AddonReflector(AddonEntryType *entry, 1.1697 + JSContext *cx, JS::Handle<JSObject*> obj) 1.1698 +{ 1.1699 + const nsACString &addonId = entry->GetKey(); 1.1700 + JS::Rooted<JSObject*> subobj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1701 + if (!subobj) { 1.1702 + return false; 1.1703 + } 1.1704 + 1.1705 + AddonHistogramMapType *map = entry->mData; 1.1706 + if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj) 1.1707 + && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), 1.1708 + subobj, JSPROP_ENUMERATE))) { 1.1709 + return false; 1.1710 + } 1.1711 + return true; 1.1712 +} 1.1713 + 1.1714 +NS_IMETHODIMP 1.1715 +TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1716 +{ 1.1717 + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1718 + if (!obj) { 1.1719 + return NS_ERROR_FAILURE; 1.1720 + } 1.1721 + 1.1722 + if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) { 1.1723 + return NS_ERROR_FAILURE; 1.1724 + } 1.1725 + ret.setObject(*obj); 1.1726 + return NS_OK; 1.1727 +} 1.1728 + 1.1729 +bool 1.1730 +TelemetryImpl::GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, bool includePrivateSql) 1.1731 +{ 1.1732 + JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1733 + if (!root_obj) 1.1734 + return false; 1.1735 + ret.setObject(*root_obj); 1.1736 + 1.1737 + MutexAutoLock hashMutex(mHashMutex); 1.1738 + // Add info about slow SQL queries on the main thread 1.1739 + if (!AddSQLInfo(cx, root_obj, true, includePrivateSql)) 1.1740 + return false; 1.1741 + // Add info about slow SQL queries on other threads 1.1742 + if (!AddSQLInfo(cx, root_obj, false, includePrivateSql)) 1.1743 + return false; 1.1744 + 1.1745 + return true; 1.1746 +} 1.1747 + 1.1748 +NS_IMETHODIMP 1.1749 +TelemetryImpl::GetSlowSQL(JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1750 +{ 1.1751 + if (GetSQLStats(cx, ret, false)) 1.1752 + return NS_OK; 1.1753 + return NS_ERROR_FAILURE; 1.1754 +} 1.1755 + 1.1756 +NS_IMETHODIMP 1.1757 +TelemetryImpl::GetDebugSlowSQL(JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1758 +{ 1.1759 + bool revealPrivateSql = 1.1760 + Preferences::GetBool("toolkit.telemetry.debugSlowSql", false); 1.1761 + if (GetSQLStats(cx, ret, revealPrivateSql)) 1.1762 + return NS_OK; 1.1763 + return NS_ERROR_FAILURE; 1.1764 +} 1.1765 + 1.1766 +NS_IMETHODIMP 1.1767 +TelemetryImpl::GetMaximalNumberOfConcurrentThreads(uint32_t *ret) 1.1768 +{ 1.1769 + *ret = nsThreadManager::get()->GetHighestNumberOfThreads(); 1.1770 + return NS_OK; 1.1771 +} 1.1772 + 1.1773 +NS_IMETHODIMP 1.1774 +TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.1775 +{ 1.1776 + MutexAutoLock hangReportMutex(mHangReportsMutex); 1.1777 + 1.1778 + const CombinedStacks& stacks = mHangReports.GetStacks(); 1.1779 + JS::Rooted<JSObject*> fullReportObj(cx, CreateJSStackObject(cx, stacks)); 1.1780 + if (!fullReportObj) { 1.1781 + return NS_ERROR_FAILURE; 1.1782 + } 1.1783 + 1.1784 + ret.setObject(*fullReportObj); 1.1785 + 1.1786 + JS::Rooted<JSObject*> durationArray(cx, JS_NewArrayObject(cx, 0)); 1.1787 + JS::Rooted<JSObject*> systemUptimeArray(cx, JS_NewArrayObject(cx, 0)); 1.1788 + JS::Rooted<JSObject*> firefoxUptimeArray(cx, JS_NewArrayObject(cx, 0)); 1.1789 + if (!durationArray || !systemUptimeArray || !firefoxUptimeArray) { 1.1790 + return NS_ERROR_FAILURE; 1.1791 + } 1.1792 + 1.1793 + bool ok = JS_DefineProperty(cx, fullReportObj, "durations", 1.1794 + durationArray, JSPROP_ENUMERATE); 1.1795 + if (!ok) { 1.1796 + return NS_ERROR_FAILURE; 1.1797 + } 1.1798 + 1.1799 + ok = JS_DefineProperty(cx, fullReportObj, "systemUptime", 1.1800 + systemUptimeArray, JSPROP_ENUMERATE); 1.1801 + if (!ok) { 1.1802 + return NS_ERROR_FAILURE; 1.1803 + } 1.1804 + 1.1805 + ok = JS_DefineProperty(cx, fullReportObj, "firefoxUptime", 1.1806 + firefoxUptimeArray, JSPROP_ENUMERATE); 1.1807 + if (!ok) { 1.1808 + return NS_ERROR_FAILURE; 1.1809 + } 1.1810 + 1.1811 + const size_t length = stacks.GetStackCount(); 1.1812 + for (size_t i = 0; i < length; ++i) { 1.1813 + if (!JS_SetElement(cx, durationArray, i, mHangReports.GetDuration(i))) { 1.1814 + return NS_ERROR_FAILURE; 1.1815 + } 1.1816 + if (!JS_SetElement(cx, systemUptimeArray, i, mHangReports.GetSystemUptime(i))) { 1.1817 + return NS_ERROR_FAILURE; 1.1818 + } 1.1819 + if (!JS_SetElement(cx, firefoxUptimeArray, i, mHangReports.GetFirefoxUptime(i))) { 1.1820 + return NS_ERROR_FAILURE; 1.1821 + } 1.1822 + } 1.1823 + 1.1824 + return NS_OK; 1.1825 +} 1.1826 + 1.1827 +static JSObject * 1.1828 +CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks) { 1.1829 + JS::Rooted<JSObject*> ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.1830 + if (!ret) { 1.1831 + return nullptr; 1.1832 + } 1.1833 + 1.1834 + JS::Rooted<JSObject*> moduleArray(cx, JS_NewArrayObject(cx, 0)); 1.1835 + if (!moduleArray) { 1.1836 + return nullptr; 1.1837 + } 1.1838 + bool ok = JS_DefineProperty(cx, ret, "memoryMap", moduleArray, 1.1839 + JSPROP_ENUMERATE); 1.1840 + if (!ok) { 1.1841 + return nullptr; 1.1842 + } 1.1843 + 1.1844 + const size_t moduleCount = stacks.GetModuleCount(); 1.1845 + for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) { 1.1846 + // Current module 1.1847 + const Telemetry::ProcessedStack::Module& module = 1.1848 + stacks.GetModule(moduleIndex); 1.1849 + 1.1850 + JS::Rooted<JSObject*> moduleInfoArray(cx, JS_NewArrayObject(cx, 0)); 1.1851 + if (!moduleInfoArray) { 1.1852 + return nullptr; 1.1853 + } 1.1854 + if (!JS_SetElement(cx, moduleArray, moduleIndex, moduleInfoArray)) { 1.1855 + return nullptr; 1.1856 + } 1.1857 + 1.1858 + unsigned index = 0; 1.1859 + 1.1860 + // Module name 1.1861 + JS::Rooted<JSString*> str(cx, JS_NewStringCopyZ(cx, module.mName.c_str())); 1.1862 + if (!str) { 1.1863 + return nullptr; 1.1864 + } 1.1865 + if (!JS_SetElement(cx, moduleInfoArray, index++, str)) { 1.1866 + return nullptr; 1.1867 + } 1.1868 + 1.1869 + // Module breakpad identifier 1.1870 + JS::Rooted<JSString*> id(cx, JS_NewStringCopyZ(cx, module.mBreakpadId.c_str())); 1.1871 + if (!id) { 1.1872 + return nullptr; 1.1873 + } 1.1874 + if (!JS_SetElement(cx, moduleInfoArray, index++, id)) { 1.1875 + return nullptr; 1.1876 + } 1.1877 + } 1.1878 + 1.1879 + JS::Rooted<JSObject*> reportArray(cx, JS_NewArrayObject(cx, 0)); 1.1880 + if (!reportArray) { 1.1881 + return nullptr; 1.1882 + } 1.1883 + ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE); 1.1884 + if (!ok) { 1.1885 + return nullptr; 1.1886 + } 1.1887 + 1.1888 + const size_t length = stacks.GetStackCount(); 1.1889 + for (size_t i = 0; i < length; ++i) { 1.1890 + // Represent call stack PCs as (module index, offset) pairs. 1.1891 + JS::Rooted<JSObject*> pcArray(cx, JS_NewArrayObject(cx, 0)); 1.1892 + if (!pcArray) { 1.1893 + return nullptr; 1.1894 + } 1.1895 + 1.1896 + if (!JS_SetElement(cx, reportArray, i, pcArray)) { 1.1897 + return nullptr; 1.1898 + } 1.1899 + 1.1900 + const CombinedStacks::Stack& stack = stacks.GetStack(i); 1.1901 + const uint32_t pcCount = stack.size(); 1.1902 + for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) { 1.1903 + const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex]; 1.1904 + JS::Rooted<JSObject*> framePair(cx, JS_NewArrayObject(cx, 0)); 1.1905 + if (!framePair) { 1.1906 + return nullptr; 1.1907 + } 1.1908 + int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex) ? 1.1909 + -1 : frame.mModIndex; 1.1910 + if (!JS_SetElement(cx, framePair, 0, modIndex)) { 1.1911 + return nullptr; 1.1912 + } 1.1913 + if (!JS_SetElement(cx, framePair, 1, static_cast<double>(frame.mOffset))) { 1.1914 + return nullptr; 1.1915 + } 1.1916 + if (!JS_SetElement(cx, pcArray, pcIndex, framePair)) { 1.1917 + return nullptr; 1.1918 + } 1.1919 + } 1.1920 + } 1.1921 + 1.1922 + return ret; 1.1923 +} 1.1924 + 1.1925 +static bool 1.1926 +IsValidBreakpadId(const std::string &breakpadId) { 1.1927 + if (breakpadId.size() < 33) { 1.1928 + return false; 1.1929 + } 1.1930 + for (unsigned i = 0, n = breakpadId.size(); i < n; ++i) { 1.1931 + char c = breakpadId[i]; 1.1932 + if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { 1.1933 + return false; 1.1934 + } 1.1935 + } 1.1936 + return true; 1.1937 +} 1.1938 + 1.1939 +// Read a stack from the given file name. In case of any error, aStack is 1.1940 +// unchanged. 1.1941 +static void 1.1942 +ReadStack(const char *aFileName, Telemetry::ProcessedStack &aStack) 1.1943 +{ 1.1944 + std::ifstream file(aFileName); 1.1945 + 1.1946 + size_t numModules; 1.1947 + file >> numModules; 1.1948 + if (file.fail()) { 1.1949 + return; 1.1950 + } 1.1951 + 1.1952 + char newline = file.get(); 1.1953 + if (file.fail() || newline != '\n') { 1.1954 + return; 1.1955 + } 1.1956 + 1.1957 + Telemetry::ProcessedStack stack; 1.1958 + for (size_t i = 0; i < numModules; ++i) { 1.1959 + std::string breakpadId; 1.1960 + file >> breakpadId; 1.1961 + if (file.fail() || !IsValidBreakpadId(breakpadId)) { 1.1962 + return; 1.1963 + } 1.1964 + 1.1965 + char space = file.get(); 1.1966 + if (file.fail() || space != ' ') { 1.1967 + return; 1.1968 + } 1.1969 + 1.1970 + std::string moduleName; 1.1971 + getline(file, moduleName); 1.1972 + if (file.fail() || moduleName[0] == ' ') { 1.1973 + return; 1.1974 + } 1.1975 + 1.1976 + Telemetry::ProcessedStack::Module module = { 1.1977 + moduleName, 1.1978 + breakpadId 1.1979 + }; 1.1980 + stack.AddModule(module); 1.1981 + } 1.1982 + 1.1983 + size_t numFrames; 1.1984 + file >> numFrames; 1.1985 + if (file.fail()) { 1.1986 + return; 1.1987 + } 1.1988 + 1.1989 + newline = file.get(); 1.1990 + if (file.fail() || newline != '\n') { 1.1991 + return; 1.1992 + } 1.1993 + 1.1994 + for (size_t i = 0; i < numFrames; ++i) { 1.1995 + uint16_t index; 1.1996 + file >> index; 1.1997 + uintptr_t offset; 1.1998 + file >> std::hex >> offset >> std::dec; 1.1999 + if (file.fail()) { 1.2000 + return; 1.2001 + } 1.2002 + 1.2003 + Telemetry::ProcessedStack::Frame frame = { 1.2004 + offset, 1.2005 + index 1.2006 + }; 1.2007 + stack.AddFrame(frame); 1.2008 + } 1.2009 + 1.2010 + aStack = stack; 1.2011 +} 1.2012 + 1.2013 +static JSObject* 1.2014 +CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time) 1.2015 +{ 1.2016 + /* Create JS representation of TimeHistogram, 1.2017 + in the format of Chromium-style histograms. */ 1.2018 + JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.2019 + if (!ret) { 1.2020 + return nullptr; 1.2021 + } 1.2022 + 1.2023 + if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0), 1.2024 + JSPROP_ENUMERATE) || 1.2025 + !JS_DefineProperty(cx, ret, "max", 1.2026 + time.GetBucketMax(ArrayLength(time) - 1), 1.2027 + JSPROP_ENUMERATE) || 1.2028 + !JS_DefineProperty(cx, ret, "histogram_type", 1.2029 + nsITelemetry::HISTOGRAM_EXPONENTIAL, 1.2030 + JSPROP_ENUMERATE)) { 1.2031 + return nullptr; 1.2032 + } 1.2033 + // TODO: calculate "sum", "log_sum", and "log_sum_squares" 1.2034 + if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE) || 1.2035 + !JS_DefineProperty(cx, ret, "log_sum", 0.0, JSPROP_ENUMERATE) || 1.2036 + !JS_DefineProperty(cx, ret, "log_sum_squares", 0.0, JSPROP_ENUMERATE)) { 1.2037 + return nullptr; 1.2038 + } 1.2039 + 1.2040 + JS::RootedObject ranges( 1.2041 + cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); 1.2042 + JS::RootedObject counts( 1.2043 + cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); 1.2044 + if (!ranges || !counts) { 1.2045 + return nullptr; 1.2046 + } 1.2047 + /* In a Chromium-style histogram, the first bucket is an "under" bucket 1.2048 + that represents all values below the histogram's range. */ 1.2049 + if (!JS_SetElement(cx, ranges, 0, time.GetBucketMin(0)) || 1.2050 + !JS_SetElement(cx, counts, 0, 0)) { 1.2051 + return nullptr; 1.2052 + } 1.2053 + for (size_t i = 0; i < ArrayLength(time); i++) { 1.2054 + if (!JS_SetElement(cx, ranges, i + 1, time.GetBucketMax(i)) || 1.2055 + !JS_SetElement(cx, counts, i + 1, time[i])) { 1.2056 + return nullptr; 1.2057 + } 1.2058 + } 1.2059 + if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) || 1.2060 + !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) { 1.2061 + return nullptr; 1.2062 + } 1.2063 + return ret; 1.2064 +} 1.2065 + 1.2066 +static JSObject* 1.2067 +CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang) 1.2068 +{ 1.2069 + JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.2070 + if (!ret) { 1.2071 + return nullptr; 1.2072 + } 1.2073 + 1.2074 + const Telemetry::HangHistogram::Stack& hangStack = hang.GetStack(); 1.2075 + JS::RootedObject stack(cx, 1.2076 + JS_NewArrayObject(cx, hangStack.length())); 1.2077 + if (!ret) { 1.2078 + return nullptr; 1.2079 + } 1.2080 + for (size_t i = 0; i < hangStack.length(); i++) { 1.2081 + JS::RootedString string(cx, JS_NewStringCopyZ(cx, hangStack[i])); 1.2082 + if (!JS_SetElement(cx, stack, i, string)) { 1.2083 + return nullptr; 1.2084 + } 1.2085 + } 1.2086 + 1.2087 + JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang)); 1.2088 + if (!time || 1.2089 + !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) || 1.2090 + !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE)) { 1.2091 + return nullptr; 1.2092 + } 1.2093 + return ret; 1.2094 +} 1.2095 + 1.2096 +static JSObject* 1.2097 +CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread) 1.2098 +{ 1.2099 + JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.2100 + if (!ret) { 1.2101 + return nullptr; 1.2102 + } 1.2103 + JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName())); 1.2104 + if (!name || 1.2105 + !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) { 1.2106 + return nullptr; 1.2107 + } 1.2108 + 1.2109 + JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity)); 1.2110 + if (!activity || 1.2111 + !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) { 1.2112 + return nullptr; 1.2113 + } 1.2114 + 1.2115 + JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0)); 1.2116 + if (!hangs) { 1.2117 + return nullptr; 1.2118 + } 1.2119 + for (size_t i = 0; i < thread.mHangs.length(); i++) { 1.2120 + JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i])); 1.2121 + if (!JS_SetElement(cx, hangs, i, obj)) { 1.2122 + return nullptr; 1.2123 + } 1.2124 + } 1.2125 + if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) { 1.2126 + return nullptr; 1.2127 + } 1.2128 + return ret; 1.2129 +} 1.2130 + 1.2131 +NS_IMETHODIMP 1.2132 +TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::MutableHandle<JS::Value> ret) 1.2133 +{ 1.2134 + JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0)); 1.2135 + if (!retObj) { 1.2136 + return NS_ERROR_FAILURE; 1.2137 + } 1.2138 + size_t threadIndex = 0; 1.2139 + 1.2140 +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR 1.2141 + /* First add active threads; we need to hold |iter| (and its lock) 1.2142 + throughout this method to avoid a race condition where a thread can 1.2143 + be recorded twice if the thread is destroyed while this method is 1.2144 + running */ 1.2145 + BackgroundHangMonitor::ThreadHangStatsIterator iter; 1.2146 + for (Telemetry::ThreadHangStats* histogram = iter.GetNext(); 1.2147 + histogram; histogram = iter.GetNext()) { 1.2148 + JS::RootedObject obj(cx, 1.2149 + CreateJSThreadHangStats(cx, *histogram)); 1.2150 + if (!JS_SetElement(cx, retObj, threadIndex++, obj)) { 1.2151 + return NS_ERROR_FAILURE; 1.2152 + } 1.2153 + } 1.2154 +#endif 1.2155 + 1.2156 + // Add saved threads next 1.2157 + MutexAutoLock autoLock(mThreadHangStatsMutex); 1.2158 + for (size_t i = 0; i < mThreadHangStats.length(); i++) { 1.2159 + JS::RootedObject obj(cx, 1.2160 + CreateJSThreadHangStats(cx, mThreadHangStats[i])); 1.2161 + if (!JS_SetElement(cx, retObj, threadIndex++, obj)) { 1.2162 + return NS_ERROR_FAILURE; 1.2163 + } 1.2164 + } 1.2165 + ret.setObject(*retObj); 1.2166 + return NS_OK; 1.2167 +} 1.2168 + 1.2169 +void 1.2170 +TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) 1.2171 +{ 1.2172 + nsAutoCString nativePath; 1.2173 + nsresult rv = aProfileDir->GetNativePath(nativePath); 1.2174 + if (NS_FAILED(rv)) { 1.2175 + return; 1.2176 + } 1.2177 + 1.2178 + const char *name = nativePath.get(); 1.2179 + PRDir *dir = PR_OpenDir(name); 1.2180 + if (!dir) { 1.2181 + return; 1.2182 + } 1.2183 + 1.2184 + PRDirEntry *ent; 1.2185 + const char *prefix = "Telemetry.LateWriteFinal-"; 1.2186 + unsigned int prefixLen = strlen(prefix); 1.2187 + while ((ent = PR_ReadDir(dir, PR_SKIP_NONE))) { 1.2188 + if (strncmp(prefix, ent->name, prefixLen) != 0) { 1.2189 + continue; 1.2190 + } 1.2191 + 1.2192 + nsAutoCString stackNativePath = nativePath; 1.2193 + stackNativePath += XPCOM_FILE_PATH_SEPARATOR; 1.2194 + stackNativePath += nsDependentCString(ent->name); 1.2195 + 1.2196 + Telemetry::ProcessedStack stack; 1.2197 + ReadStack(stackNativePath.get(), stack); 1.2198 + if (stack.GetStackSize() != 0) { 1.2199 + mLateWritesStacks.AddStack(stack); 1.2200 + } 1.2201 + // Delete the file so that we don't report it again on the next run. 1.2202 + PR_Delete(stackNativePath.get()); 1.2203 + } 1.2204 + PR_CloseDir(dir); 1.2205 +} 1.2206 + 1.2207 +NS_IMETHODIMP 1.2208 +TelemetryImpl::GetLateWrites(JSContext *cx, JS::MutableHandle<JS::Value> ret) 1.2209 +{ 1.2210 + // The user must call AsyncReadTelemetryData first. We return an empty list 1.2211 + // instead of reporting a failure so that the rest of telemetry can uniformly 1.2212 + // handle the read not being available yet. 1.2213 + 1.2214 + // FIXME: we allocate the js object again and again in the getter. We should 1.2215 + // figure out a way to cache it. In order to do that we have to call 1.2216 + // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl 1.2217 + // constructor, but it is not clear how to get a JSContext in there. 1.2218 + // Another option would be to call it in here when we first call 1.2219 + // CreateJSStackObject, but we would still need to figure out where to call 1.2220 + // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot 1.2221 + // and just set the pointer to nullptr is the telemetry destructor? 1.2222 + 1.2223 + JSObject *report; 1.2224 + if (!mCachedTelemetryData) { 1.2225 + CombinedStacks empty; 1.2226 + report = CreateJSStackObject(cx, empty); 1.2227 + } else { 1.2228 + report = CreateJSStackObject(cx, mLateWritesStacks); 1.2229 + } 1.2230 + 1.2231 + if (report == nullptr) { 1.2232 + return NS_ERROR_FAILURE; 1.2233 + } 1.2234 + 1.2235 + ret.setObject(*report); 1.2236 + return NS_OK; 1.2237 +} 1.2238 + 1.2239 +NS_IMETHODIMP 1.2240 +TelemetryImpl::RegisteredHistograms(uint32_t *aCount, char*** aHistograms) 1.2241 +{ 1.2242 + size_t count = ArrayLength(gHistograms); 1.2243 + size_t offset = 0; 1.2244 + char** histograms = static_cast<char**>(nsMemory::Alloc(count * sizeof(char*))); 1.2245 + 1.2246 + for (size_t i = 0; i < count; ++i) { 1.2247 + if (IsExpired(gHistograms[i].expiration())) { 1.2248 + offset++; 1.2249 + continue; 1.2250 + } 1.2251 + 1.2252 + const char* h = gHistograms[i].id(); 1.2253 + size_t len = strlen(h); 1.2254 + histograms[i - offset] = static_cast<char*>(nsMemory::Clone(h, len+1)); 1.2255 + } 1.2256 + 1.2257 + *aCount = count - offset; 1.2258 + *aHistograms = histograms; 1.2259 + return NS_OK; 1.2260 +} 1.2261 + 1.2262 +NS_IMETHODIMP 1.2263 +TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, 1.2264 + JS::MutableHandle<JS::Value> ret) 1.2265 +{ 1.2266 + Histogram *h; 1.2267 + nsresult rv = GetHistogramByName(name, &h); 1.2268 + if (NS_FAILED(rv)) 1.2269 + return rv; 1.2270 + 1.2271 + return WrapAndReturnHistogram(h, cx, ret); 1.2272 +} 1.2273 + 1.2274 +NS_IMETHODIMP 1.2275 +TelemetryImpl::GetCanRecord(bool *ret) { 1.2276 + *ret = mCanRecord; 1.2277 + return NS_OK; 1.2278 +} 1.2279 + 1.2280 +NS_IMETHODIMP 1.2281 +TelemetryImpl::SetCanRecord(bool canRecord) { 1.2282 + mCanRecord = !!canRecord; 1.2283 + return NS_OK; 1.2284 +} 1.2285 + 1.2286 +bool 1.2287 +TelemetryImpl::CanRecord() { 1.2288 + return !sTelemetry || sTelemetry->mCanRecord; 1.2289 +} 1.2290 + 1.2291 +NS_IMETHODIMP 1.2292 +TelemetryImpl::GetCanSend(bool *ret) { 1.2293 +#if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) 1.2294 + *ret = true; 1.2295 +#else 1.2296 + *ret = false; 1.2297 +#endif 1.2298 + return NS_OK; 1.2299 +} 1.2300 + 1.2301 +already_AddRefed<nsITelemetry> 1.2302 +TelemetryImpl::CreateTelemetryInstance() 1.2303 +{ 1.2304 + NS_ABORT_IF_FALSE(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()"); 1.2305 + sTelemetry = new TelemetryImpl(); 1.2306 + // AddRef for the local reference 1.2307 + NS_ADDREF(sTelemetry); 1.2308 + // AddRef for the caller 1.2309 + nsCOMPtr<nsITelemetry> ret = sTelemetry; 1.2310 + 1.2311 + sTelemetry->InitMemoryReporter(); 1.2312 + 1.2313 + return ret.forget(); 1.2314 +} 1.2315 + 1.2316 +void 1.2317 +TelemetryImpl::ShutdownTelemetry() 1.2318 +{ 1.2319 + // No point in collecting IO beyond this point 1.2320 + ClearIOReporting(); 1.2321 + NS_IF_RELEASE(sTelemetry); 1.2322 +} 1.2323 + 1.2324 +void 1.2325 +TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay, 1.2326 + SanitizedState state) 1.2327 +{ 1.2328 + AutoHashtable<SlowSQLEntryType> *slowSQLMap = nullptr; 1.2329 + if (state == Sanitized) 1.2330 + slowSQLMap = &(sTelemetry->mSanitizedSQL); 1.2331 + else 1.2332 + slowSQLMap = &(sTelemetry->mPrivateSQL); 1.2333 + 1.2334 + MutexAutoLock hashMutex(sTelemetry->mHashMutex); 1.2335 + 1.2336 + SlowSQLEntryType *entry = slowSQLMap->GetEntry(sql); 1.2337 + if (!entry) { 1.2338 + entry = slowSQLMap->PutEntry(sql); 1.2339 + if (MOZ_UNLIKELY(!entry)) 1.2340 + return; 1.2341 + entry->mData.mainThread.hitCount = 0; 1.2342 + entry->mData.mainThread.totalTime = 0; 1.2343 + entry->mData.otherThreads.hitCount = 0; 1.2344 + entry->mData.otherThreads.totalTime = 0; 1.2345 + } 1.2346 + 1.2347 + if (NS_IsMainThread()) { 1.2348 + entry->mData.mainThread.hitCount++; 1.2349 + entry->mData.mainThread.totalTime += delay; 1.2350 + } else { 1.2351 + entry->mData.otherThreads.hitCount++; 1.2352 + entry->mData.otherThreads.totalTime += delay; 1.2353 + } 1.2354 +} 1.2355 + 1.2356 +/** 1.2357 + * This method replaces string literals in SQL strings with the word :private 1.2358 + * 1.2359 + * States used in this state machine: 1.2360 + * 1.2361 + * NORMAL: 1.2362 + * - This is the active state when not iterating over a string literal or 1.2363 + * comment 1.2364 + * 1.2365 + * SINGLE_QUOTE: 1.2366 + * - Defined here: http://www.sqlite.org/lang_expr.html 1.2367 + * - This state represents iterating over a string literal opened with 1.2368 + * a single quote. 1.2369 + * - A single quote within the string can be encoded by putting 2 single quotes 1.2370 + * in a row, e.g. 'This literal contains an escaped quote ''' 1.2371 + * - Any double quotes found within a single-quoted literal are ignored 1.2372 + * - This state covers BLOB literals, e.g. X'ABC123' 1.2373 + * - The string literal and the enclosing quotes will be replaced with 1.2374 + * the text :private 1.2375 + * 1.2376 + * DOUBLE_QUOTE: 1.2377 + * - Same rules as the SINGLE_QUOTE state. 1.2378 + * - According to http://www.sqlite.org/lang_keywords.html, 1.2379 + * SQLite interprets text in double quotes as an identifier unless it's used in 1.2380 + * a context where it cannot be resolved to an identifier and a string literal 1.2381 + * is allowed. This method removes text in double-quotes for safety. 1.2382 + * 1.2383 + * DASH_COMMENT: 1.2384 + * - http://www.sqlite.org/lang_comment.html 1.2385 + * - A dash comment starts with two dashes in a row, 1.2386 + * e.g. DROP TABLE foo -- a comment 1.2387 + * - Any text following two dashes in a row is interpreted as a comment until 1.2388 + * end of input or a newline character 1.2389 + * - Any quotes found within the comment are ignored and no replacements made 1.2390 + * 1.2391 + * C_STYLE_COMMENT: 1.2392 + * - http://www.sqlite.org/lang_comment.html 1.2393 + * - A C-style comment starts with a forward slash and an asterisk, and ends 1.2394 + * with an asterisk and a forward slash 1.2395 + * - Any text following comment start is interpreted as a comment up to end of 1.2396 + * input or comment end 1.2397 + * - Any quotes found within the comment are ignored and no replacements made 1.2398 + */ 1.2399 +nsCString 1.2400 +TelemetryImpl::SanitizeSQL(const nsACString &sql) { 1.2401 + nsCString output; 1.2402 + int length = sql.Length(); 1.2403 + 1.2404 + typedef enum { 1.2405 + NORMAL, 1.2406 + SINGLE_QUOTE, 1.2407 + DOUBLE_QUOTE, 1.2408 + DASH_COMMENT, 1.2409 + C_STYLE_COMMENT, 1.2410 + } State; 1.2411 + 1.2412 + State state = NORMAL; 1.2413 + int fragmentStart = 0; 1.2414 + for (int i = 0; i < length; i++) { 1.2415 + char character = sql[i]; 1.2416 + char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0'; 1.2417 + 1.2418 + switch (character) { 1.2419 + case '\'': 1.2420 + case '"': 1.2421 + if (state == NORMAL) { 1.2422 + state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE; 1.2423 + output += nsDependentCSubstring(sql, fragmentStart, i - fragmentStart); 1.2424 + output += ":private"; 1.2425 + fragmentStart = -1; 1.2426 + } else if ((state == SINGLE_QUOTE && character == '\'') || 1.2427 + (state == DOUBLE_QUOTE && character == '"')) { 1.2428 + if (nextCharacter == character) { 1.2429 + // Two consecutive quotes within a string literal are a single escaped quote 1.2430 + i++; 1.2431 + } else { 1.2432 + state = NORMAL; 1.2433 + fragmentStart = i + 1; 1.2434 + } 1.2435 + } 1.2436 + break; 1.2437 + case '-': 1.2438 + if (state == NORMAL) { 1.2439 + if (nextCharacter == '-') { 1.2440 + state = DASH_COMMENT; 1.2441 + i++; 1.2442 + } 1.2443 + } 1.2444 + break; 1.2445 + case '\n': 1.2446 + if (state == DASH_COMMENT) { 1.2447 + state = NORMAL; 1.2448 + } 1.2449 + break; 1.2450 + case '/': 1.2451 + if (state == NORMAL) { 1.2452 + if (nextCharacter == '*') { 1.2453 + state = C_STYLE_COMMENT; 1.2454 + i++; 1.2455 + } 1.2456 + } 1.2457 + break; 1.2458 + case '*': 1.2459 + if (state == C_STYLE_COMMENT) { 1.2460 + if (nextCharacter == '/') { 1.2461 + state = NORMAL; 1.2462 + } 1.2463 + } 1.2464 + break; 1.2465 + default: 1.2466 + continue; 1.2467 + } 1.2468 + } 1.2469 + 1.2470 + if ((fragmentStart >= 0) && fragmentStart < length) 1.2471 + output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart); 1.2472 + 1.2473 + return output; 1.2474 +} 1.2475 + 1.2476 +// Slow SQL statements will be automatically 1.2477 +// trimmed to kMaxSlowStatementLength characters. 1.2478 +// This limit doesn't include the ellipsis and DB name, 1.2479 +// that are appended at the end of the stored statement. 1.2480 +const uint32_t kMaxSlowStatementLength = 1000; 1.2481 + 1.2482 +void 1.2483 +TelemetryImpl::RecordSlowStatement(const nsACString &sql, 1.2484 + const nsACString &dbName, 1.2485 + uint32_t delay) 1.2486 +{ 1.2487 + if (!sTelemetry || !sTelemetry->mCanRecord) 1.2488 + return; 1.2489 + 1.2490 + bool isFirefoxDB = sTelemetry->mTrackedDBs.Contains(dbName); 1.2491 + if (isFirefoxDB) { 1.2492 + nsAutoCString sanitizedSQL(SanitizeSQL(sql)); 1.2493 + if (sanitizedSQL.Length() > kMaxSlowStatementLength) { 1.2494 + sanitizedSQL.SetLength(kMaxSlowStatementLength); 1.2495 + sanitizedSQL += "..."; 1.2496 + } 1.2497 + sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get()); 1.2498 + StoreSlowSQL(sanitizedSQL, delay, Sanitized); 1.2499 + } else { 1.2500 + // Report aggregate DB-level statistics for addon DBs 1.2501 + nsAutoCString aggregate; 1.2502 + aggregate.AppendPrintf("Untracked SQL for %s", 1.2503 + nsPromiseFlatCString(dbName).get()); 1.2504 + StoreSlowSQL(aggregate, delay, Sanitized); 1.2505 + } 1.2506 + 1.2507 + nsAutoCString fullSQL; 1.2508 + fullSQL.AppendPrintf("%s /* %s */", 1.2509 + nsPromiseFlatCString(sql).get(), 1.2510 + nsPromiseFlatCString(dbName).get()); 1.2511 + StoreSlowSQL(fullSQL, delay, Unsanitized); 1.2512 +} 1.2513 + 1.2514 +#if defined(MOZ_ENABLE_PROFILER_SPS) 1.2515 +void 1.2516 +TelemetryImpl::RecordChromeHang(uint32_t aDuration, 1.2517 + Telemetry::ProcessedStack &aStack, 1.2518 + int32_t aSystemUptime, 1.2519 + int32_t aFirefoxUptime) 1.2520 +{ 1.2521 + if (!sTelemetry || !sTelemetry->mCanRecord) 1.2522 + return; 1.2523 + 1.2524 + MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex); 1.2525 + 1.2526 + sTelemetry->mHangReports.AddHang(aStack, aDuration, 1.2527 + aSystemUptime, aFirefoxUptime); 1.2528 +} 1.2529 +#endif 1.2530 + 1.2531 +void 1.2532 +TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats) 1.2533 +{ 1.2534 + if (!sTelemetry || !sTelemetry->mCanRecord) 1.2535 + return; 1.2536 + 1.2537 + MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex); 1.2538 + 1.2539 + sTelemetry->mThreadHangStats.append(Move(aStats)); 1.2540 +} 1.2541 + 1.2542 +NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter) 1.2543 +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance) 1.2544 + 1.2545 +#define NS_TELEMETRY_CID \ 1.2546 + {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}} 1.2547 +NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID); 1.2548 + 1.2549 +const Module::CIDEntry kTelemetryCIDs[] = { 1.2550 + { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor }, 1.2551 + { nullptr } 1.2552 +}; 1.2553 + 1.2554 +const Module::ContractIDEntry kTelemetryContracts[] = { 1.2555 + { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID }, 1.2556 + { nullptr } 1.2557 +}; 1.2558 + 1.2559 +const Module kTelemetryModule = { 1.2560 + Module::kVersion, 1.2561 + kTelemetryCIDs, 1.2562 + kTelemetryContracts, 1.2563 + nullptr, 1.2564 + nullptr, 1.2565 + nullptr, 1.2566 + TelemetryImpl::ShutdownTelemetry 1.2567 +}; 1.2568 + 1.2569 +NS_IMETHODIMP 1.2570 +TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret) 1.2571 +{ 1.2572 + if (sTelemetryIOObserver) { 1.2573 + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), 1.2574 + JS::NullPtr())); 1.2575 + if (!obj) { 1.2576 + return NS_ERROR_FAILURE; 1.2577 + } 1.2578 + 1.2579 + if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) { 1.2580 + return NS_ERROR_FAILURE; 1.2581 + } 1.2582 + ret.setObject(*obj); 1.2583 + return NS_OK; 1.2584 + } 1.2585 + ret.setNull(); 1.2586 + return NS_OK; 1.2587 +} 1.2588 + 1.2589 +NS_IMETHODIMP 1.2590 +TelemetryImpl::MsSinceProcessStart(double* aResult) 1.2591 +{ 1.2592 + bool error; 1.2593 + *aResult = (TimeStamp::NowLoRes() - 1.2594 + TimeStamp::ProcessCreation(error)).ToMilliseconds(); 1.2595 + if (error) { 1.2596 + return NS_ERROR_NOT_AVAILABLE; 1.2597 + } 1.2598 + return NS_OK; 1.2599 +} 1.2600 + 1.2601 +size_t 1.2602 +TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) 1.2603 +{ 1.2604 + size_t n = aMallocSizeOf(this); 1.2605 + // Ignore the hashtables in mAddonMap; they are not significant. 1.2606 + n += mAddonMap.SizeOfExcludingThis(nullptr, aMallocSizeOf); 1.2607 + n += mHistogramMap.SizeOfExcludingThis(nullptr, aMallocSizeOf); 1.2608 + { // Scope for mHashMutex lock 1.2609 + MutexAutoLock lock(mHashMutex); 1.2610 + n += mPrivateSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf); 1.2611 + n += mSanitizedSQL.SizeOfExcludingThis(nullptr, aMallocSizeOf); 1.2612 + } 1.2613 + n += mTrackedDBs.SizeOfExcludingThis(nullptr, aMallocSizeOf); 1.2614 + { // Scope for mHangReportsMutex lock 1.2615 + MutexAutoLock lock(mHangReportsMutex); 1.2616 + n += mHangReports.SizeOfExcludingThis(); 1.2617 + } 1.2618 + { // Scope for mThreadHangStatsMutex lock 1.2619 + MutexAutoLock lock(mThreadHangStatsMutex); 1.2620 + n += mThreadHangStats.sizeOfExcludingThis(aMallocSizeOf); 1.2621 + } 1.2622 + 1.2623 + // It's a bit gross that we measure this other stuff that lives outside of 1.2624 + // TelemetryImpl... oh well. 1.2625 + if (sTelemetryIOObserver) { 1.2626 + n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf); 1.2627 + } 1.2628 + StatisticsRecorder::Histograms hs; 1.2629 + StatisticsRecorder::GetHistograms(&hs); 1.2630 + for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { 1.2631 + Histogram *h = *it; 1.2632 + n += h->SizeOfIncludingThis(aMallocSizeOf); 1.2633 + } 1.2634 + 1.2635 + return n; 1.2636 +} 1.2637 + 1.2638 +} // anonymous namespace 1.2639 + 1.2640 +namespace mozilla { 1.2641 +void 1.2642 +RecordShutdownStartTimeStamp() { 1.2643 +#ifdef DEBUG 1.2644 + // FIXME: this function should only be called once, since it should be called 1.2645 + // at the earliest point we *know* we are shutting down. Unfortunately 1.2646 + // this assert has been firing. Given that if we are called multiple times 1.2647 + // we just keep the last timestamp, the assert is commented for now. 1.2648 + static bool recorded = false; 1.2649 + // MOZ_ASSERT(!recorded); 1.2650 + (void)recorded; // Silence unused-var warnings (remove when assert re-enabled) 1.2651 + recorded = true; 1.2652 +#endif 1.2653 + 1.2654 + if (!Telemetry::CanRecord()) 1.2655 + return; 1.2656 + 1.2657 + gRecordedShutdownStartTime = TimeStamp::Now(); 1.2658 + 1.2659 + GetShutdownTimeFileName(); 1.2660 +} 1.2661 + 1.2662 +void 1.2663 +RecordShutdownEndTimeStamp() { 1.2664 + if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName) 1.2665 + return; 1.2666 + 1.2667 + nsCString name(gRecordedShutdownTimeFileName); 1.2668 + PL_strfree(gRecordedShutdownTimeFileName); 1.2669 + gRecordedShutdownTimeFileName = nullptr; 1.2670 + gAlreadyFreedShutdownTimeFileName = true; 1.2671 + 1.2672 + nsCString tmpName = name; 1.2673 + tmpName += ".tmp"; 1.2674 + FILE *f = fopen(tmpName.get(), "w"); 1.2675 + if (!f) 1.2676 + return; 1.2677 + // On a normal release build this should be called just before 1.2678 + // calling _exit, but on a debug build or when the user forces a full 1.2679 + // shutdown this is called as late as possible, so we have to 1.2680 + // white list this write as write poisoning will be enabled. 1.2681 + MozillaRegisterDebugFILE(f); 1.2682 + 1.2683 + TimeStamp now = TimeStamp::Now(); 1.2684 + MOZ_ASSERT(now >= gRecordedShutdownStartTime); 1.2685 + TimeDuration diff = now - gRecordedShutdownStartTime; 1.2686 + uint32_t diff2 = diff.ToMilliseconds(); 1.2687 + int written = fprintf(f, "%d\n", diff2); 1.2688 + MozillaUnRegisterDebugFILE(f); 1.2689 + int rv = fclose(f); 1.2690 + if (written < 0 || rv != 0) { 1.2691 + PR_Delete(tmpName.get()); 1.2692 + return; 1.2693 + } 1.2694 + PR_Delete(name.get()); 1.2695 + PR_Rename(tmpName.get(), name.get()); 1.2696 +} 1.2697 + 1.2698 +namespace Telemetry { 1.2699 + 1.2700 +void 1.2701 +Accumulate(ID aHistogram, uint32_t aSample) 1.2702 +{ 1.2703 + if (!TelemetryImpl::CanRecord()) { 1.2704 + return; 1.2705 + } 1.2706 + Histogram *h; 1.2707 + nsresult rv = GetHistogramByEnumId(aHistogram, &h); 1.2708 + if (NS_SUCCEEDED(rv)) 1.2709 + h->Add(aSample); 1.2710 +} 1.2711 + 1.2712 +void 1.2713 +Accumulate(const char* name, uint32_t sample) 1.2714 +{ 1.2715 + if (!TelemetryImpl::CanRecord()) { 1.2716 + return; 1.2717 + } 1.2718 + ID id; 1.2719 + nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id); 1.2720 + if (NS_FAILED(rv)) { 1.2721 + return; 1.2722 + } 1.2723 + 1.2724 + Histogram *h; 1.2725 + rv = GetHistogramByEnumId(id, &h); 1.2726 + if (NS_SUCCEEDED(rv)) { 1.2727 + h->Add(sample); 1.2728 + } 1.2729 +} 1.2730 + 1.2731 +void 1.2732 +AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end) 1.2733 +{ 1.2734 + Accumulate(aHistogram, 1.2735 + static_cast<uint32_t>((end - start).ToMilliseconds())); 1.2736 +} 1.2737 + 1.2738 +bool 1.2739 +CanRecord() 1.2740 +{ 1.2741 + return TelemetryImpl::CanRecord(); 1.2742 +} 1.2743 + 1.2744 +base::Histogram* 1.2745 +GetHistogramById(ID id) 1.2746 +{ 1.2747 + Histogram *h = nullptr; 1.2748 + GetHistogramByEnumId(id, &h); 1.2749 + return h; 1.2750 +} 1.2751 + 1.2752 +void 1.2753 +RecordSlowSQLStatement(const nsACString &statement, 1.2754 + const nsACString &dbName, 1.2755 + uint32_t delay) 1.2756 +{ 1.2757 + TelemetryImpl::RecordSlowStatement(statement, dbName, delay); 1.2758 +} 1.2759 + 1.2760 +void Init() 1.2761 +{ 1.2762 + // Make the service manager hold a long-lived reference to the service 1.2763 + nsCOMPtr<nsITelemetry> telemetryService = 1.2764 + do_GetService("@mozilla.org/base/telemetry;1"); 1.2765 + MOZ_ASSERT(telemetryService); 1.2766 +} 1.2767 + 1.2768 +#if defined(MOZ_ENABLE_PROFILER_SPS) 1.2769 +void RecordChromeHang(uint32_t duration, 1.2770 + ProcessedStack &aStack, 1.2771 + int32_t aSystemUptime, 1.2772 + int32_t aFirefoxUptime) 1.2773 +{ 1.2774 + TelemetryImpl::RecordChromeHang(duration, aStack, 1.2775 + aSystemUptime, aFirefoxUptime); 1.2776 +} 1.2777 +#endif 1.2778 + 1.2779 +void RecordThreadHangStats(ThreadHangStats& aStats) 1.2780 +{ 1.2781 + TelemetryImpl::RecordThreadHangStats(aStats); 1.2782 +} 1.2783 + 1.2784 +ProcessedStack::ProcessedStack() 1.2785 +{ 1.2786 +} 1.2787 + 1.2788 +size_t ProcessedStack::GetStackSize() const 1.2789 +{ 1.2790 + return mStack.size(); 1.2791 +} 1.2792 + 1.2793 +const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const 1.2794 +{ 1.2795 + MOZ_ASSERT(aIndex < mStack.size()); 1.2796 + return mStack[aIndex]; 1.2797 +} 1.2798 + 1.2799 +void ProcessedStack::AddFrame(const Frame &aFrame) 1.2800 +{ 1.2801 + mStack.push_back(aFrame); 1.2802 +} 1.2803 + 1.2804 +size_t ProcessedStack::GetNumModules() const 1.2805 +{ 1.2806 + return mModules.size(); 1.2807 +} 1.2808 + 1.2809 +const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const 1.2810 +{ 1.2811 + MOZ_ASSERT(aIndex < mModules.size()); 1.2812 + return mModules[aIndex]; 1.2813 +} 1.2814 + 1.2815 +void ProcessedStack::AddModule(const Module &aModule) 1.2816 +{ 1.2817 + mModules.push_back(aModule); 1.2818 +} 1.2819 + 1.2820 +void ProcessedStack::Clear() { 1.2821 + mModules.clear(); 1.2822 + mStack.clear(); 1.2823 +} 1.2824 + 1.2825 +bool ProcessedStack::Module::operator==(const Module& aOther) const { 1.2826 + return mName == aOther.mName && 1.2827 + mBreakpadId == aOther.mBreakpadId; 1.2828 +} 1.2829 + 1.2830 +struct StackFrame 1.2831 +{ 1.2832 + uintptr_t mPC; // The program counter at this position in the call stack. 1.2833 + uint16_t mIndex; // The number of this frame in the call stack. 1.2834 + uint16_t mModIndex; // The index of module that has this program counter. 1.2835 +}; 1.2836 + 1.2837 + 1.2838 +#ifdef MOZ_ENABLE_PROFILER_SPS 1.2839 +static bool CompareByPC(const StackFrame &a, const StackFrame &b) 1.2840 +{ 1.2841 + return a.mPC < b.mPC; 1.2842 +} 1.2843 + 1.2844 +static bool CompareByIndex(const StackFrame &a, const StackFrame &b) 1.2845 +{ 1.2846 + return a.mIndex < b.mIndex; 1.2847 +} 1.2848 +#endif 1.2849 + 1.2850 +ProcessedStack 1.2851 +GetStackAndModules(const std::vector<uintptr_t>& aPCs) 1.2852 +{ 1.2853 + std::vector<StackFrame> rawStack; 1.2854 + for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(), 1.2855 + e = aPCs.end(); i != e; ++i) { 1.2856 + uintptr_t aPC = *i; 1.2857 + StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()), 1.2858 + std::numeric_limits<uint16_t>::max()}; 1.2859 + rawStack.push_back(Frame); 1.2860 + } 1.2861 + 1.2862 +#ifdef MOZ_ENABLE_PROFILER_SPS 1.2863 + // Remove all modules not referenced by a PC on the stack 1.2864 + std::sort(rawStack.begin(), rawStack.end(), CompareByPC); 1.2865 + 1.2866 + size_t moduleIndex = 0; 1.2867 + size_t stackIndex = 0; 1.2868 + size_t stackSize = rawStack.size(); 1.2869 + 1.2870 + SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf(); 1.2871 + rawModules.SortByAddress(); 1.2872 + 1.2873 + while (moduleIndex < rawModules.GetSize()) { 1.2874 + const SharedLibrary& module = rawModules.GetEntry(moduleIndex); 1.2875 + uintptr_t moduleStart = module.GetStart(); 1.2876 + uintptr_t moduleEnd = module.GetEnd() - 1; 1.2877 + // the interval is [moduleStart, moduleEnd) 1.2878 + 1.2879 + bool moduleReferenced = false; 1.2880 + for (;stackIndex < stackSize; ++stackIndex) { 1.2881 + uintptr_t pc = rawStack[stackIndex].mPC; 1.2882 + if (pc >= moduleEnd) 1.2883 + break; 1.2884 + 1.2885 + if (pc >= moduleStart) { 1.2886 + // If the current PC is within the current module, mark 1.2887 + // module as used 1.2888 + moduleReferenced = true; 1.2889 + rawStack[stackIndex].mPC -= moduleStart; 1.2890 + rawStack[stackIndex].mModIndex = moduleIndex; 1.2891 + } else { 1.2892 + // PC does not belong to any module. It is probably from 1.2893 + // the JIT. Use a fixed mPC so that we don't get different 1.2894 + // stacks on different runs. 1.2895 + rawStack[stackIndex].mPC = 1.2896 + std::numeric_limits<uintptr_t>::max(); 1.2897 + } 1.2898 + } 1.2899 + 1.2900 + if (moduleReferenced) { 1.2901 + ++moduleIndex; 1.2902 + } else { 1.2903 + // Remove module if no PCs within its address range 1.2904 + rawModules.RemoveEntries(moduleIndex, moduleIndex + 1); 1.2905 + } 1.2906 + } 1.2907 + 1.2908 + for (;stackIndex < stackSize; ++stackIndex) { 1.2909 + // These PCs are past the last module. 1.2910 + rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max(); 1.2911 + } 1.2912 + 1.2913 + std::sort(rawStack.begin(), rawStack.end(), CompareByIndex); 1.2914 +#endif 1.2915 + 1.2916 + // Copy the information to the return value. 1.2917 + ProcessedStack Ret; 1.2918 + for (std::vector<StackFrame>::iterator i = rawStack.begin(), 1.2919 + e = rawStack.end(); i != e; ++i) { 1.2920 + const StackFrame &rawFrame = *i; 1.2921 + ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex }; 1.2922 + Ret.AddFrame(frame); 1.2923 + } 1.2924 + 1.2925 +#ifdef MOZ_ENABLE_PROFILER_SPS 1.2926 + for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) { 1.2927 + const SharedLibrary &info = rawModules.GetEntry(i); 1.2928 + const std::string &name = info.GetName(); 1.2929 + std::string basename = name; 1.2930 +#ifdef XP_MACOSX 1.2931 + // FIXME: We want to use just the basename as the libname, but the 1.2932 + // current profiler addon needs the full path name, so we compute the 1.2933 + // basename in here. 1.2934 + size_t pos = name.rfind('/'); 1.2935 + if (pos != std::string::npos) { 1.2936 + basename = name.substr(pos + 1); 1.2937 + } 1.2938 +#endif 1.2939 + ProcessedStack::Module module = { 1.2940 + basename, 1.2941 + info.GetBreakpadId() 1.2942 + }; 1.2943 + Ret.AddModule(module); 1.2944 + } 1.2945 +#endif 1.2946 + 1.2947 + return Ret; 1.2948 +} 1.2949 + 1.2950 +void 1.2951 +WriteFailedProfileLock(nsIFile* aProfileDir) 1.2952 +{ 1.2953 + nsCOMPtr<nsIFile> file; 1.2954 + nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir); 1.2955 + NS_ENSURE_SUCCESS_VOID(rv); 1.2956 + int64_t fileSize = 0; 1.2957 + rv = file->GetFileSize(&fileSize); 1.2958 + // It's expected that the file might not exist yet 1.2959 + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { 1.2960 + return; 1.2961 + } 1.2962 + nsCOMPtr<nsIFileStream> fileStream; 1.2963 + rv = NS_NewLocalFileStream(getter_AddRefs(fileStream), file, 1.2964 + PR_RDWR | PR_CREATE_FILE, 0640); 1.2965 + NS_ENSURE_SUCCESS_VOID(rv); 1.2966 + NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize); 1.2967 + unsigned int failedLockCount = 0; 1.2968 + if (fileSize > 0) { 1.2969 + nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(fileStream); 1.2970 + NS_ENSURE_TRUE_VOID(inStream); 1.2971 + if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) { 1.2972 + failedLockCount = 0; 1.2973 + } 1.2974 + } 1.2975 + ++failedLockCount; 1.2976 + nsAutoCString bufStr; 1.2977 + bufStr.AppendInt(static_cast<int>(failedLockCount)); 1.2978 + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(fileStream); 1.2979 + NS_ENSURE_TRUE_VOID(seekStream); 1.2980 + // If we read in an existing failed lock count, we need to reset the file ptr 1.2981 + if (fileSize > 0) { 1.2982 + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); 1.2983 + NS_ENSURE_SUCCESS_VOID(rv); 1.2984 + } 1.2985 + nsCOMPtr<nsIOutputStream> outStream = do_QueryInterface(fileStream); 1.2986 + uint32_t bytesLeft = bufStr.Length(); 1.2987 + const char* bytes = bufStr.get(); 1.2988 + do { 1.2989 + uint32_t written = 0; 1.2990 + rv = outStream->Write(bytes, bytesLeft, &written); 1.2991 + if (NS_FAILED(rv)) { 1.2992 + break; 1.2993 + } 1.2994 + bytes += written; 1.2995 + bytesLeft -= written; 1.2996 + } while (bytesLeft > 0); 1.2997 + seekStream->SetEOF(); 1.2998 +} 1.2999 + 1.3000 +void 1.3001 +InitIOReporting(nsIFile* aXreDir) 1.3002 +{ 1.3003 + // Never initialize twice 1.3004 + if (sTelemetryIOObserver) { 1.3005 + return; 1.3006 + } 1.3007 + 1.3008 + sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir); 1.3009 + IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, 1.3010 + sTelemetryIOObserver); 1.3011 +} 1.3012 + 1.3013 +void 1.3014 +SetProfileDir(nsIFile* aProfD) 1.3015 +{ 1.3016 + if (!sTelemetryIOObserver || !aProfD) { 1.3017 + return; 1.3018 + } 1.3019 + nsAutoString profDirPath; 1.3020 + nsresult rv = aProfD->GetPath(profDirPath); 1.3021 + if (NS_FAILED(rv)) { 1.3022 + return; 1.3023 + } 1.3024 + sTelemetryIOObserver->AddPath(profDirPath, NS_LITERAL_STRING("{profile}")); 1.3025 +} 1.3026 + 1.3027 +void 1.3028 +TimeHistogram::Add(PRIntervalTime aTime) 1.3029 +{ 1.3030 + uint32_t timeMs = PR_IntervalToMilliseconds(aTime); 1.3031 + size_t index = mozilla::FloorLog2(timeMs); 1.3032 + operator[](index)++; 1.3033 +} 1.3034 + 1.3035 +uint32_t 1.3036 +HangHistogram::GetHash(const Stack& aStack) 1.3037 +{ 1.3038 + uint32_t hash = 0; 1.3039 + for (const char* const* label = aStack.begin(); 1.3040 + label != aStack.end(); label++) { 1.3041 + /* We only need to hash the pointer instead of the text content 1.3042 + because we are assuming constant pointers */ 1.3043 + hash = AddToHash(hash, *label); 1.3044 + } 1.3045 + return hash; 1.3046 +} 1.3047 + 1.3048 +bool 1.3049 +HangHistogram::operator==(const HangHistogram& aOther) const 1.3050 +{ 1.3051 + if (mHash != aOther.mHash) { 1.3052 + return false; 1.3053 + } 1.3054 + if (mStack.length() != aOther.mStack.length()) { 1.3055 + return false; 1.3056 + } 1.3057 + return PodEqual(mStack.begin(), aOther.mStack.begin(), mStack.length()); 1.3058 +} 1.3059 + 1.3060 + 1.3061 +} // namespace Telemetry 1.3062 +} // namespace mozilla 1.3063 + 1.3064 +NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule; 1.3065 + 1.3066 +/** 1.3067 + * The XRE_TelemetryAdd function is to be used by embedding applications 1.3068 + * that can't use mozilla::Telemetry::Accumulate() directly. 1.3069 + */ 1.3070 +void 1.3071 +XRE_TelemetryAccumulate(int aID, uint32_t aSample) 1.3072 +{ 1.3073 + mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample); 1.3074 +}