Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 | } |