tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */