toolkit/components/telemetry/Telemetry.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial