Statistics.h (18730B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef gc_Statistics_h 8 #define gc_Statistics_h 9 10 #include "mozilla/Array.h" 11 #include "mozilla/Atomics.h" 12 #include "mozilla/EnumeratedArray.h" 13 #include "mozilla/Maybe.h" 14 #include "mozilla/TimeStamp.h" 15 16 #include "jspubtd.h" 17 #include "NamespaceImports.h" 18 19 #include "gc/GCEnum.h" 20 #include "js/AllocPolicy.h" 21 #include "js/GCAPI.h" 22 #include "js/SliceBudget.h" 23 #include "js/Vector.h" 24 #include "vm/Logging.h" 25 26 namespace js { 27 28 class JS_PUBLIC_API Sprinter; 29 class JSONPrinter; 30 31 namespace gcstats { 32 33 // Phase data is generated by a script. If you need to add phases, edit 34 // js/src/gc/GenerateStatsPhases.py 35 36 #include "gc/StatsPhasesGenerated.h" 37 38 // Counts can be incremented with Statistics::count(). They're reset at the end 39 // of a Major GC. 40 enum Count { 41 COUNT_NEW_CHUNK, 42 COUNT_DESTROY_CHUNK, 43 COUNT_MINOR_GC, 44 45 // Number of times a 'put' into a storebuffer overflowed, triggering a 46 // compaction 47 COUNT_STOREBUFFER_OVERFLOW, 48 49 // Number of arenas relocated by compacting GC. 50 COUNT_ARENA_RELOCATED, 51 52 // Number of cells marked during the marking phase. Excludes atoms marked when 53 // not collecting the atoms zone. 54 COUNT_CELLS_MARKED, 55 56 // Number of times work was donated to a requesting thread during parallel 57 // marking. 58 COUNT_PARALLEL_MARK_INTERRUPTIONS, 59 60 COUNT_LIMIT 61 }; 62 63 // Stats can be set with Statistics::setStat(). They're not reset automatically. 64 enum Stat { 65 // Number of strings promoted. 66 STAT_STRINGS_PROMOTED, 67 68 // Number of strings deduplicated. 69 STAT_STRINGS_DEDUPLICATED, 70 71 // Number of BigInts promoted. 72 STAT_BIGINTS_PROMOTED, 73 74 STAT_LIMIT 75 }; 76 77 struct ZoneGCStats { 78 /* Number of zones collected in this GC. */ 79 size_t collectedZoneCount = 0; 80 81 /* Total number of zones in the Runtime at the start of this GC. */ 82 size_t zoneCount = 0; 83 84 /* Number of zones swept in this GC. */ 85 size_t sweptZoneCount = 0; 86 87 /* Total number of compartments in all zones collected. */ 88 size_t collectedCompartmentCount = 0; 89 90 /* Total number of compartments in the Runtime at the start of this GC. */ 91 size_t compartmentCount = 0; 92 93 /* Total number of compartments swept by this GC. */ 94 size_t sweptCompartmentCount = 0; 95 96 /* Total number of realms in the Runtime at the start of this GC. */ 97 size_t realmCount = 0; 98 99 ZoneGCStats() = default; 100 }; 101 102 struct Trigger { 103 size_t amount = 0; 104 size_t threshold = 0; 105 }; 106 107 #define FOR_EACH_GC_PROFILE_TIME(_) \ 108 _(Total, "total", PhaseKind::NONE) \ 109 _(Background, "bgwrk", PhaseKind::NONE) \ 110 _(MinorForMajor, "evct4m", PhaseKind::EVICT_NURSERY_FOR_MAJOR_GC) \ 111 _(WaitBgThread, "waitBG", PhaseKind::WAIT_BACKGROUND_THREAD) \ 112 _(Prepare, "prep", PhaseKind::PREPARE) \ 113 _(Mark, "mark", PhaseKind::MARK) \ 114 _(Sweep, "sweep", PhaseKind::SWEEP) \ 115 _(Compact, "cmpct", PhaseKind::COMPACT) \ 116 _(Decommit, "dcmmt", PhaseKind::DECOMMIT) 117 118 static const char* const MajorGCProfilePrefix = "MajorGC:"; 119 static const char* const MinorGCProfilePrefix = "MinorGC:"; 120 121 const char* ExplainAbortReason(GCAbortReason reason); 122 123 /* 124 * Struct for collecting timing statistics on a "phase tree". The tree is 125 * specified as a limited DAG, but the timings are collected for the whole tree 126 * that you would get by expanding out the DAG by duplicating subtrees rooted 127 * at nodes with multiple parents. 128 * 129 * During execution, a child phase can be activated multiple times, and the 130 * total time will be accumulated. (So for example, you can start and end 131 * PhaseKind::MARK_ROOTS multiple times before completing the parent phase.) 132 * 133 * Incremental GC is represented by recording separate timing results for each 134 * slice within the overall GC. 135 */ 136 struct Statistics { 137 template <typename T, size_t Length> 138 using Array = mozilla::Array<T, Length>; 139 140 template <typename IndexType, typename ValueType, IndexType SizeAsEnumValue> 141 using EnumeratedArray = 142 mozilla::EnumeratedArray<IndexType, ValueType, size_t(SizeAsEnumValue)>; 143 144 using TimeDuration = mozilla::TimeDuration; 145 using TimeStamp = mozilla::TimeStamp; 146 147 // Create types for tables of times, by phase and phase kind. 148 using PhaseTimes = EnumeratedArray<Phase, TimeDuration, Phase::LIMIT>; 149 using PhaseKindTimes = 150 EnumeratedArray<PhaseKind, TimeDuration, PhaseKind::LIMIT>; 151 152 using PhaseTimeStamps = EnumeratedArray<Phase, TimeStamp, Phase::LIMIT>; 153 154 [[nodiscard]] static bool initialize(); 155 156 explicit Statistics(gc::GCRuntime* gc); 157 ~Statistics(); 158 159 Statistics(const Statistics&) = delete; 160 Statistics& operator=(const Statistics&) = delete; 161 162 void beginPhase(PhaseKind phaseKind); 163 void endPhase(PhaseKind phaseKind); 164 void recordParallelPhase(PhaseKind phaseKind, TimeDuration duration); 165 166 // Occasionally, we may be in the middle of something that is tracked by 167 // this class, and we need to do something unusual (eg evict the nursery) 168 // that doesn't normally nest within the current phase. Suspend the 169 // currently tracked phase stack, at which time the caller is free to do 170 // other tracked operations. 171 // 172 // This also happens internally with the PhaseKind::MUTATOR "phase". While in 173 // this phase, any beginPhase will automatically suspend the non-GC phase, 174 // until that inner stack is complete, at which time it will automatically 175 // resume the non-GC phase. Explicit suspensions do not get auto-resumed. 176 void suspendPhases(PhaseKind suspension = PhaseKind::EXPLICIT_SUSPENSION); 177 178 // Resume a suspended stack of phases. 179 void resumePhases(); 180 181 void beginSlice(const ZoneGCStats& zoneStats, JS::GCOptions options, 182 const JS::SliceBudget& budget, JS::GCReason reason, 183 bool budgetWasIncreased); 184 void endSlice(); 185 186 [[nodiscard]] bool startTimingMutator(); 187 [[nodiscard]] bool stopTimingMutator(double& mutator_ms, double& gc_ms); 188 189 // Note when we sweep a zone or compartment. 190 void sweptZone() { ++zoneStats.sweptZoneCount; } 191 void sweptCompartment() { ++zoneStats.sweptCompartmentCount; } 192 193 void reset(GCAbortReason reason) { 194 MOZ_ASSERT(reason != GCAbortReason::None); 195 if (!aborted) { 196 slices_.back().resetReason = reason; 197 } 198 } 199 200 void measureInitialHeapSizes(); 201 202 void nonincremental(GCAbortReason reason) { 203 MOZ_ASSERT(reason != GCAbortReason::None); 204 nonincrementalReason_ = reason; 205 JS_LOG(gc, Info, "non-incremental for reason %s", nonincrementalReason()); 206 } 207 208 bool nonincremental() const { 209 return nonincrementalReason_ != GCAbortReason::None; 210 } 211 212 const char* nonincrementalReason() const { 213 return ExplainAbortReason(nonincrementalReason_); 214 } 215 216 void count(Count s) { counts[s]++; } 217 void addCount(Count s, uint32_t count) { counts[s] += count; } 218 219 uint32_t getCount(Count s) const { return uint32_t(counts[s]); } 220 221 void setStat(Stat s, uint32_t value) { stats[s] = value; } 222 223 uint32_t getStat(Stat s) const { return stats[s]; } 224 225 void recordTrigger(size_t amount, size_t threshold) { 226 recordedTrigger = mozilla::Some(Trigger{amount, threshold}); 227 } 228 bool hasTrigger() const { return recordedTrigger.isSome(); } 229 230 // tenured allocs don't include nursery evictions. 231 void setAllocsSinceMinorGCTenured(uint32_t allocs) { 232 tenuredAllocsSinceMinorGC = allocs; 233 } 234 235 uint32_t allocsSinceMinorGCTenured() { return tenuredAllocsSinceMinorGC; } 236 237 void beginNurseryCollection(); 238 void endNurseryCollection(); 239 240 TimeStamp beginSCC(); 241 void endSCC(unsigned scc, TimeStamp start); 242 243 UniqueChars formatCompactSliceMessage() const; 244 UniqueChars formatCompactSummaryMessage() const; 245 UniqueChars formatDetailedMessage() const; 246 247 JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback); 248 249 TimeDuration clearMaxGCPauseAccumulator(); 250 TimeDuration getMaxGCPauseSinceClear(); 251 252 PhaseKind currentPhaseKind() const; 253 254 static const size_t MAX_SUSPENDED_PHASES = MAX_PHASE_NESTING * 3; 255 256 struct SliceData { 257 SliceData(const JS::SliceBudget& budget, mozilla::Maybe<Trigger> trigger, 258 JS::GCReason reason, TimeStamp start, size_t startFaults, 259 gc::State initialState); 260 261 JS::SliceBudget budget; 262 JS::GCReason reason = JS::GCReason::NO_REASON; 263 mozilla::Maybe<Trigger> trigger; 264 gc::State initialState = gc::State::NotActive; 265 gc::State finalState = gc::State::NotActive; 266 GCAbortReason resetReason = GCAbortReason::None; 267 TimeStamp start; 268 TimeStamp end; 269 size_t startFaults = 0; 270 size_t endFaults = 0; 271 PhaseTimes phaseTimes; 272 PhaseKindTimes totalParallelTimes; 273 PhaseKindTimes maxParallelTimes; 274 275 TimeDuration duration() const; 276 bool wasReset() const { return resetReason != GCAbortReason::None; } 277 }; 278 279 using SliceDataVector = Vector<SliceData, 8, SystemAllocPolicy>; 280 281 const SliceDataVector& slices() const { return slices_; } 282 const SliceData& sliceAt(size_t index) const { return slices_[index]; } 283 284 const SliceData* lastSlice() const { 285 if (slices_.length() == 0) { 286 return nullptr; 287 } 288 289 return &slices_.back(); 290 } 291 292 TimeStamp start() const { return slices_[0].start; } 293 294 TimeStamp end() const { return slices_.back().end; } 295 296 TimeStamp creationTime() const { return creationTime_; } 297 298 TimeDuration totalGCTime() const { return totalGCTime_; } 299 size_t initialCollectedBytes() const { return preCollectedGCHeapBytes; } 300 301 // File to write profiling information to, either stderr or file specified 302 // with JS_GC_PROFILE_FILE. 303 FILE* profileFile() const { return gcProfileFile; } 304 305 // Occasionally print header lines for profiling information. 306 void maybePrintProfileHeaders(); 307 308 // Print header line for profile times. 309 void printProfileHeader(); 310 311 // Print total profile times on shutdown. 312 void printTotalProfileTimes(); 313 314 // These JSON strings are used by the firefox profiler to display the GC 315 // markers. 316 317 // Return JSON for a whole major GC 318 UniqueChars renderJsonMessage() const; 319 320 // Return JSON for the timings of just the given slice. 321 UniqueChars renderJsonSlice(size_t sliceNum) const; 322 323 // Return JSON for the previous nursery collection. 324 UniqueChars renderNurseryJson() const; 325 326 bool bufferAllocStatsEnabled() const { return enableBufferAllocStats_; } 327 328 private: 329 gc::GCRuntime* const gc; 330 331 /* File used for MOZ_GCTIMER output. */ 332 FILE* gcTimerFile; 333 334 /* File used for JS_GC_PROFILE output. */ 335 FILE* gcProfileFile; 336 337 ZoneGCStats zoneStats; 338 339 JS::GCOptions gcOptions = JS::GCOptions::Normal; 340 341 GCAbortReason nonincrementalReason_; 342 343 SliceDataVector slices_; 344 345 /* Most recent time when the given phase started. */ 346 PhaseTimeStamps phaseStartTimes; 347 348 #ifdef DEBUG 349 /* Most recent time when the given phase ended. */ 350 PhaseTimeStamps phaseEndTimes; 351 #endif 352 353 TimeStamp creationTime_; 354 355 /* Bookkeeping for GC timings when timingMutator is true */ 356 TimeStamp timedGCStart; 357 TimeDuration timedGCTime; 358 359 /* Total main thread time in a given phase for this GC. */ 360 PhaseTimes phaseTimes; 361 362 /* Total main thread time for this GC. */ 363 TimeDuration totalGCTime_; 364 365 /* Number of events of this type for this GC. */ 366 EnumeratedArray<Count, mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire>, 367 COUNT_LIMIT> 368 counts; 369 370 /* Other GC statistics. */ 371 EnumeratedArray<Stat, uint32_t, STAT_LIMIT> stats; 372 373 /* 374 * These events cannot be kept in the above array, we need to take their 375 * address. 376 */ 377 uint32_t tenuredAllocsSinceMinorGC; 378 379 /* Total GC heap size before and after the GC ran. */ 380 size_t preTotalGCHeapBytes; 381 size_t postTotalGCHeapBytes; 382 383 /* GC heap size for collected zones before GC ran. */ 384 size_t preCollectedGCHeapBytes; 385 386 /* Total malloc heap size before and after the GC ran. */ 387 size_t preTotalMallocHeapBytes; 388 size_t postTotalMallocHeapBytes; 389 390 /* 391 * If a GC slice was triggered by exceeding some threshold, record the 392 * threshold and the value that exceeded it. This happens before the slice 393 * starts so this is recorded here first and then transferred to SliceData. 394 */ 395 mozilla::Maybe<Trigger> recordedTrigger; 396 397 /* GC numbers as of the beginning of the collection. */ 398 uint64_t startingMinorGCNumber; 399 uint64_t startingMajorGCNumber; 400 uint64_t startingSliceNumber; 401 402 /* Records the maximum GC pause in an API-controlled interval. */ 403 mutable TimeDuration maxPauseInInterval; 404 405 /* Phases that are currently on stack. */ 406 Vector<Phase, MAX_PHASE_NESTING, SystemAllocPolicy> phaseStack; 407 408 /* 409 * Certain phases can interrupt the phase stack, eg callback phases. When 410 * this happens, we move the suspended phases over to a sepearate list, 411 * terminated by a dummy PhaseKind::SUSPENSION phase (so that we can nest 412 * suspensions by suspending multiple stacks with a PhaseKind::SUSPENSION in 413 * between). 414 */ 415 Vector<Phase, MAX_SUSPENDED_PHASES, SystemAllocPolicy> suspendedPhases; 416 417 /* Sweep times for SCCs of compartments. */ 418 Vector<TimeDuration, 0, SystemAllocPolicy> sccTimes; 419 420 TimeDuration timeSinceLastGC; 421 422 JS::GCSliceCallback sliceCallback; 423 424 /* 425 * True if we saw an OOM while allocating slices or we saw an impossible 426 * timestamp. The statistics for this GC will be invalid. 427 */ 428 bool aborted; 429 430 /* Profiling data. */ 431 432 enum class ProfileKey { 433 #define DEFINE_PROFILE_KEY(name, _1, _2) name, 434 FOR_EACH_GC_PROFILE_TIME(DEFINE_PROFILE_KEY) 435 #undef DEFINE_PROFILE_KEY 436 KeyCount 437 }; 438 439 using ProfileDurations = 440 EnumeratedArray<ProfileKey, TimeDuration, ProfileKey::KeyCount>; 441 442 bool enableProfiling_ = false; 443 bool profileWorkers_ = false; 444 bool enableBufferAllocStats_ = false; 445 TimeDuration profileThreshold_; 446 ProfileDurations totalTimes_; 447 uint64_t sliceCount_; 448 449 char formatBuffer_[32]; 450 static constexpr int FormatBufferLength = sizeof(formatBuffer_); 451 452 JSContext* context(); 453 454 Phase currentPhase() const; 455 Phase lookupChildPhase(PhaseKind phaseKind) const; 456 457 void beginGC(JS::GCOptions options, const TimeStamp& currentTime); 458 void endGC(); 459 460 void sendGCTelemetry(); 461 void sendSliceTelemetry(const SliceData& slice); 462 463 TimeDuration sumTotalParallelTime(PhaseKind phaseKind) const; 464 465 void recordPhaseBegin(Phase phase); 466 void recordPhaseEnd(Phase phase); 467 468 void gcDuration(TimeDuration* total, TimeDuration* maxPause) const; 469 void sccDurations(TimeDuration* total, TimeDuration* maxPause) const; 470 void printStats(); 471 472 template <typename GleanFn> 473 void reportLongestPhaseInMajorGC(PhaseKind longest, GleanFn gleanReportFn); 474 475 UniqueChars formatCompactSlicePhaseTimes(const PhaseTimes& phaseTimes) const; 476 477 UniqueChars formatDetailedDescription() const; 478 UniqueChars formatDetailedSliceDescription(unsigned i, 479 const SliceData& slice) const; 480 UniqueChars formatDetailedPhaseTimes(const PhaseTimes& phaseTimes) const; 481 UniqueChars formatDetailedTotals() const; 482 483 void formatJsonDescription(JSONPrinter&) const; 484 void formatJsonSliceDescription(unsigned i, const SliceData& slice, 485 JSONPrinter&) const; 486 void formatJsonPhaseTimes(const PhaseTimes& phaseTimes, JSONPrinter&) const; 487 void formatJsonSlice(size_t sliceNum, JSONPrinter&) const; 488 489 double computeMMU(TimeDuration window) const; 490 491 void printSliceProfile(); 492 ProfileDurations getProfileTimes(const SliceData& slice) const; 493 void updateTotalProfileTimes(const ProfileDurations& times); 494 const char* formatGCStates(const SliceData& slice); 495 const char* formatGCFlags(const SliceData& slice); 496 const char* formatBudget(const SliceData& slice); 497 const char* formatTotalSlices(); 498 499 size_t getMallocHeapSize(); 500 501 void getBufferedAllocatorStats(Zone* zone, size_t& mediumChunks, 502 size_t& mediumTenuredChunks, 503 size_t& largeNurseryAllocs, 504 size_t& largeTenuredAllocs); 505 506 static void printProfileTimes(const ProfileDurations& times, 507 Sprinter& sprinter); 508 }; 509 510 struct MOZ_RAII AutoGCSlice { 511 AutoGCSlice(Statistics& stats, const ZoneGCStats& zoneStats, 512 JS::GCOptions options, const JS::SliceBudget& budget, 513 JS::GCReason reason, bool budgetWasIncreased) 514 : stats(stats) { 515 stats.beginSlice(zoneStats, options, budget, reason, budgetWasIncreased); 516 } 517 ~AutoGCSlice() { stats.endSlice(); } 518 519 Statistics& stats; 520 }; 521 522 struct MOZ_RAII AutoPhase { 523 AutoPhase(Statistics& stats, PhaseKind phaseKind) 524 : stats(stats), phaseKind(phaseKind), enabled(true) { 525 stats.beginPhase(phaseKind); 526 } 527 528 AutoPhase(Statistics& stats, bool condition, PhaseKind phaseKind) 529 : stats(stats), phaseKind(phaseKind), enabled(condition) { 530 if (enabled) { 531 stats.beginPhase(phaseKind); 532 } 533 } 534 535 ~AutoPhase() { 536 if (enabled) { 537 stats.endPhase(phaseKind); 538 } 539 } 540 541 Statistics& stats; 542 PhaseKind phaseKind; 543 bool enabled; 544 }; 545 546 struct MOZ_RAII AutoSCC { 547 AutoSCC(Statistics& stats, unsigned scc) : stats(stats), scc(scc) { 548 start = stats.beginSCC(); 549 } 550 ~AutoSCC() { stats.endSCC(scc, start); } 551 552 Statistics& stats; 553 unsigned scc; 554 mozilla::TimeStamp start; 555 }; 556 557 void ReadProfileEnv(const char* envName, const char* helpText, bool* enableOut, 558 bool* workersOut, mozilla::TimeDuration* thresholdOut); 559 560 } /* namespace gcstats */ 561 562 struct StringStats { 563 // number of strings that were deduplicated, and their sizes in characters 564 // and bytes 565 uint64_t deduplicatedStrings = 0; 566 uint64_t deduplicatedChars = 0; 567 uint64_t deduplicatedBytes = 0; 568 569 // number of malloced bytes associated with tenured strings (the actual 570 // malloc will have happened when the strings were allocated in the nursery; 571 // the ownership of the bytes will be transferred to the tenured strings) 572 uint64_t tenuredBytes = 0; 573 574 StringStats& operator+=(const StringStats& other) { 575 deduplicatedStrings += other.deduplicatedStrings; 576 deduplicatedChars += other.deduplicatedChars; 577 deduplicatedBytes += other.deduplicatedBytes; 578 tenuredBytes += other.tenuredBytes; 579 return *this; 580 } 581 582 void noteTenured(size_t mallocBytes) { tenuredBytes += mallocBytes; } 583 584 void noteDeduplicated(size_t numChars, size_t mallocBytes) { 585 deduplicatedStrings++; 586 deduplicatedChars += numChars; 587 deduplicatedBytes += mallocBytes; 588 } 589 }; 590 591 } /* namespace js */ 592 593 #endif /* gc_Statistics_h */