LCOV - code coverage report
Current view: top level - js/src/gc - Statistics.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 218 707 30.8 %
Date: 2017-07-14 16:53:18 Functions: 25 64 39.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
       2             :  * vim: set ts=8 sts=4 et sw=4 tw=99:
       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             : #include "gc/Statistics.h"
       8             : 
       9             : #include "mozilla/ArrayUtils.h"
      10             : #include "mozilla/DebugOnly.h"
      11             : #include "mozilla/IntegerRange.h"
      12             : #include "mozilla/PodOperations.h"
      13             : #include "mozilla/Sprintf.h"
      14             : #include "mozilla/TimeStamp.h"
      15             : 
      16             : #include <ctype.h>
      17             : #include <stdarg.h>
      18             : #include <stdio.h>
      19             : 
      20             : #include "jsprf.h"
      21             : #include "jsutil.h"
      22             : 
      23             : #include "gc/Memory.h"
      24             : #include "vm/Debugger.h"
      25             : #include "vm/HelperThreads.h"
      26             : #include "vm/Runtime.h"
      27             : #include "vm/Time.h"
      28             : 
      29             : using namespace js;
      30             : using namespace js::gc;
      31             : using namespace js::gcstats;
      32             : 
      33             : using mozilla::DebugOnly;
      34             : using mozilla::EnumeratedArray;
      35             : using mozilla::IntegerRange;
      36             : using mozilla::PodArrayZero;
      37             : using mozilla::PodZero;
      38             : using mozilla::TimeStamp;
      39             : using mozilla::TimeDuration;
      40             : 
      41             : /*
      42             :  * If this fails, then you can either delete this assertion and allow all
      43             :  * larger-numbered reasons to pile up in the last telemetry bucket, or switch
      44             :  * to GC_REASON_3 and bump the max value.
      45             :  */
      46             : JS_STATIC_ASSERT(JS::gcreason::NUM_TELEMETRY_REASONS >= JS::gcreason::NUM_REASONS);
      47             : 
      48             : static inline decltype(mozilla::MakeEnumeratedRange(PhaseKind::FIRST, PhaseKind::LIMIT))
      49         212 : AllPhaseKinds()
      50             : {
      51         212 :     return mozilla::MakeEnumeratedRange(PhaseKind::FIRST, PhaseKind::LIMIT);
      52             : }
      53             : 
      54             : const char*
      55           0 : js::gcstats::ExplainInvocationKind(JSGCInvocationKind gckind)
      56             : {
      57           0 :     MOZ_ASSERT(gckind == GC_NORMAL || gckind == GC_SHRINK);
      58           0 :     if (gckind == GC_NORMAL)
      59           0 :          return "Normal";
      60             :     else
      61           0 :          return "Shrinking";
      62             : }
      63             : 
      64             : JS_PUBLIC_API(const char*)
      65           3 : JS::gcreason::ExplainReason(JS::gcreason::Reason reason)
      66             : {
      67           3 :     switch (reason) {
      68             : #define SWITCH_REASON(name)                         \
      69             :         case JS::gcreason::name:                    \
      70             :           return #name;
      71           0 :         GCREASONS(SWITCH_REASON)
      72             : 
      73             :         default:
      74           0 :           MOZ_CRASH("bad GC reason");
      75             : #undef SWITCH_REASON
      76             :     }
      77             : }
      78             : 
      79             : const char*
      80           0 : js::gcstats::ExplainAbortReason(gc::AbortReason reason)
      81             : {
      82           0 :     switch (reason) {
      83             : #define SWITCH_REASON(name)                         \
      84             :         case gc::AbortReason::name:                 \
      85             :           return #name;
      86           0 :         GC_ABORT_REASONS(SWITCH_REASON)
      87             : 
      88             :         default:
      89           0 :           MOZ_CRASH("bad GC abort reason");
      90             : #undef SWITCH_REASON
      91             :     }
      92             : }
      93             : 
      94             : struct PhaseKindInfo
      95             : {
      96             :     Phase firstPhase;
      97             :     uint8_t telemetryBucket;
      98             : };
      99             : 
     100             : // PhaseInfo objects form a tree.
     101             : struct PhaseInfo
     102             : {
     103             :     Phase parent;
     104             :     Phase firstChild;
     105             :     Phase nextSibling;
     106             :     Phase nextInPhase;
     107             :     PhaseKind phaseKind;
     108             :     uint8_t depth;
     109             :     const char* name;
     110             :     const char* path;
     111             : };
     112             : 
     113             : // A table of PhaseInfo indexed by Phase.
     114             : using PhaseTable = EnumeratedArray<Phase, Phase::LIMIT, PhaseInfo>;
     115             : 
     116             : // A table of PhaseKindInfo indexed by PhaseKind.
     117             : using PhaseKindTable = EnumeratedArray<PhaseKind, PhaseKind::LIMIT, PhaseKindInfo>;
     118             : 
     119             : #include "gc/StatsPhasesGenerated.cpp"
     120             : 
     121             : static double
     122           3 : t(TimeDuration duration)
     123             : {
     124           3 :     return duration.ToMilliseconds();
     125             : }
     126             : 
     127             : inline Phase
     128         707 : Statistics::currentPhase() const
     129             : {
     130         707 :     return phaseStack.empty() ? Phase::NONE : phaseStack.back();
     131             : }
     132             : 
     133             : PhaseKind
     134          48 : Statistics::currentPhaseKind() const
     135             : {
     136             :     // Public API to get the current phase kind, suppressing the synthetic
     137             :     // PhaseKind::MUTATOR phase.
     138             : 
     139          48 :     Phase phase = currentPhase();
     140          48 :     MOZ_ASSERT_IF(phase == Phase::MUTATOR, phaseStack.length() == 1);
     141          48 :     if (phase == Phase::NONE || phase == Phase::MUTATOR)
     142          27 :         return PhaseKind::NONE;
     143             : 
     144          21 :     return phases[phase].phaseKind;
     145             : }
     146             : 
     147             : Phase
     148         144 : Statistics::lookupChildPhase(PhaseKind phaseKind) const
     149             : {
     150         144 :     if (phaseKind == PhaseKind::IMPLICIT_SUSPENSION)
     151           0 :         return Phase::IMPLICIT_SUSPENSION;
     152         144 :     if (phaseKind == PhaseKind::EXPLICIT_SUSPENSION)
     153           0 :         return Phase::EXPLICIT_SUSPENSION;
     154             : 
     155         144 :     MOZ_ASSERT(phaseKind < PhaseKind::LIMIT);
     156             : 
     157             :     // Most phases only correspond to a single expanded phase so check for that
     158             :     // first.
     159         144 :     Phase phase = phaseKinds[phaseKind].firstPhase;
     160         144 :     if (phases[phase].nextInPhase == Phase::NONE) {
     161          72 :         MOZ_ASSERT(phases[phase].parent == currentPhase());
     162          72 :         return phase;
     163             :     }
     164             : 
     165             :     // Otherwise search all expanded phases that correspond to the required
     166             :     // phase to find the one whose parent is the current expanded phase.
     167          72 :     Phase parent = currentPhase();
     168         342 :     while (phases[phase].parent != parent) {
     169         135 :         phase = phases[phase].nextInPhase;
     170         135 :         MOZ_ASSERT(phase != Phase::NONE);
     171             :     }
     172             : 
     173          72 :     return phase;
     174             : }
     175             : 
     176             : inline decltype(mozilla::MakeEnumeratedRange(Phase::FIRST, Phase::LIMIT))
     177           4 : AllPhases()
     178             : {
     179           4 :     return mozilla::MakeEnumeratedRange(Phase::FIRST, Phase::LIMIT);
     180             : }
     181             : 
     182             : void
     183           0 : Statistics::gcDuration(TimeDuration* total, TimeDuration* maxPause) const
     184             : {
     185           0 :     *total = *maxPause = 0;
     186           0 :     for (auto& slice : slices_) {
     187           0 :         *total += slice.duration();
     188           0 :         if (slice.duration() > *maxPause)
     189           0 :             *maxPause = slice.duration();
     190             :     }
     191           0 :     if (*maxPause > maxPauseInInterval)
     192           0 :         maxPauseInInterval = *maxPause;
     193           0 : }
     194             : 
     195             : void
     196           0 : Statistics::sccDurations(TimeDuration* total, TimeDuration* maxPause) const
     197             : {
     198           0 :     *total = *maxPause = 0;
     199           0 :     for (size_t i = 0; i < sccTimes.length(); i++) {
     200           0 :         *total += sccTimes[i];
     201           0 :         *maxPause = Max(*maxPause, sccTimes[i]);
     202             :     }
     203           0 : }
     204             : 
     205             : typedef Vector<UniqueChars, 8, SystemAllocPolicy> FragmentVector;
     206             : 
     207             : static UniqueChars
     208           0 : Join(const FragmentVector& fragments, const char* separator = "")
     209             : {
     210           0 :     const size_t separatorLength = strlen(separator);
     211           0 :     size_t length = 0;
     212           0 :     for (size_t i = 0; i < fragments.length(); ++i) {
     213           0 :         length += fragments[i] ? strlen(fragments[i].get()) : 0;
     214           0 :         if (i < (fragments.length() - 1))
     215           0 :             length += separatorLength;
     216             :     }
     217             : 
     218           0 :     char* joined = js_pod_malloc<char>(length + 1);
     219           0 :     if (!joined)
     220           0 :         return UniqueChars();
     221             : 
     222           0 :     joined[length] = '\0';
     223           0 :     char* cursor = joined;
     224           0 :     for (size_t i = 0; i < fragments.length(); ++i) {
     225           0 :         if (fragments[i])
     226           0 :             strcpy(cursor, fragments[i].get());
     227           0 :         cursor += fragments[i] ? strlen(fragments[i].get()) : 0;
     228           0 :         if (i < (fragments.length() - 1)) {
     229           0 :             if (separatorLength)
     230           0 :                 strcpy(cursor, separator);
     231           0 :             cursor += separatorLength;
     232             :         }
     233             :     }
     234             : 
     235           0 :     return UniqueChars(joined);
     236             : }
     237             : 
     238             : static TimeDuration
     239           0 : SumChildTimes(Phase phase, const Statistics::PhaseTimeTable& phaseTimes)
     240             : {
     241           0 :     TimeDuration total = 0;
     242           0 :     for (phase = phases[phase].firstChild;
     243           0 :          phase != Phase::NONE;
     244           0 :          phase = phases[phase].nextSibling)
     245             :     {
     246           0 :         total += phaseTimes[phase];
     247             :     }
     248           0 :     return total;
     249             : }
     250             : 
     251             : UniqueChars
     252           0 : Statistics::formatCompactSliceMessage() const
     253             : {
     254             :     // Skip if we OOM'ed.
     255           0 :     if (slices_.length() == 0)
     256           0 :         return UniqueChars(nullptr);
     257             : 
     258           0 :     const size_t index = slices_.length() - 1;
     259           0 :     const SliceData& slice = slices_.back();
     260             : 
     261             :     char budgetDescription[200];
     262           0 :     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
     263             : 
     264             :     const char* format =
     265           0 :         "GC Slice %u - Pause: %.3fms of %s budget (@ %.3fms); Reason: %s; Reset: %s%s; Times: ";
     266             :     char buffer[1024];
     267           0 :     SprintfLiteral(buffer, format, index,
     268           0 :                    t(slice.duration()), budgetDescription, t(slice.start - slices_[0].start),
     269           0 :                    ExplainReason(slice.reason),
     270           0 :                    slice.wasReset() ? "yes - " : "no",
     271           0 :                    slice.wasReset() ? ExplainAbortReason(slice.resetReason) : "");
     272             : 
     273           0 :     FragmentVector fragments;
     274           0 :     if (!fragments.append(DuplicateString(buffer)) ||
     275           0 :         !fragments.append(formatCompactSlicePhaseTimes(slices_[index].phaseTimes)))
     276             :     {
     277           0 :         return UniqueChars(nullptr);
     278             :     }
     279           0 :     return Join(fragments);
     280             : }
     281             : 
     282             : UniqueChars
     283           0 : Statistics::formatCompactSummaryMessage() const
     284             : {
     285           0 :     const double bytesPerMiB = 1024 * 1024;
     286             : 
     287           0 :     FragmentVector fragments;
     288           0 :     if (!fragments.append(DuplicateString("Summary - ")))
     289           0 :         return UniqueChars(nullptr);
     290             : 
     291           0 :     TimeDuration total, longest;
     292           0 :     gcDuration(&total, &longest);
     293             : 
     294           0 :     const double mmu20 = computeMMU(TimeDuration::FromMilliseconds(20));
     295           0 :     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
     296             : 
     297             :     char buffer[1024];
     298           0 :     if (!nonincremental()) {
     299           0 :         SprintfLiteral(buffer,
     300             :                        "Max Pause: %.3fms; MMU 20ms: %.1f%%; MMU 50ms: %.1f%%; Total: %.3fms; ",
     301           0 :                        t(longest), mmu20 * 100., mmu50 * 100., t(total));
     302             :     } else {
     303           0 :         SprintfLiteral(buffer, "Non-Incremental: %.3fms (%s); ",
     304           0 :                        t(total), ExplainAbortReason(nonincrementalReason_));
     305             :     }
     306           0 :     if (!fragments.append(DuplicateString(buffer)))
     307           0 :         return UniqueChars(nullptr);
     308             : 
     309           0 :     SprintfLiteral(buffer,
     310             :                    "Zones: %d of %d (-%d); Compartments: %d of %d (-%d); HeapSize: %.3f MiB; " \
     311             :                    "HeapChange (abs): %+d (%d); ",
     312           0 :                    zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.sweptZoneCount,
     313           0 :                    zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
     314           0 :                    zoneStats.sweptCompartmentCount,
     315           0 :                    double(preBytes) / bytesPerMiB,
     316           0 :                    counts[STAT_NEW_CHUNK] - counts[STAT_DESTROY_CHUNK],
     317           0 :                    counts[STAT_NEW_CHUNK] + counts[STAT_DESTROY_CHUNK]);
     318           0 :     if (!fragments.append(DuplicateString(buffer)))
     319           0 :         return UniqueChars(nullptr);
     320             : 
     321           0 :     MOZ_ASSERT_IF(counts[STAT_ARENA_RELOCATED], gckind == GC_SHRINK);
     322           0 :     if (gckind == GC_SHRINK) {
     323           0 :         SprintfLiteral(buffer,
     324             :                        "Kind: %s; Relocated: %.3f MiB; ",
     325           0 :                        ExplainInvocationKind(gckind),
     326           0 :                        double(ArenaSize * counts[STAT_ARENA_RELOCATED]) / bytesPerMiB);
     327           0 :         if (!fragments.append(DuplicateString(buffer)))
     328           0 :             return UniqueChars(nullptr);
     329             :     }
     330             : 
     331           0 :     return Join(fragments);
     332             : }
     333             : 
     334             : UniqueChars
     335           0 : Statistics::formatCompactSlicePhaseTimes(const PhaseTimeTable& phaseTimes) const
     336             : {
     337           0 :     static const TimeDuration MaxUnaccountedTime = TimeDuration::FromMicroseconds(100);
     338             : 
     339           0 :     FragmentVector fragments;
     340             :     char buffer[128];
     341           0 :     for (auto phase : AllPhases()) {
     342           0 :         DebugOnly<uint8_t> level = phases[phase].depth;
     343           0 :         MOZ_ASSERT(level < 4);
     344             : 
     345           0 :         TimeDuration ownTime = phaseTimes[phase];
     346           0 :         TimeDuration childTime = SumChildTimes(phase, phaseTimes);
     347           0 :         if (ownTime > MaxUnaccountedTime) {
     348           0 :             SprintfLiteral(buffer, "%s: %.3fms", phases[phase].name, t(ownTime));
     349           0 :             if (!fragments.append(DuplicateString(buffer)))
     350           0 :                 return UniqueChars(nullptr);
     351             : 
     352           0 :             if (childTime && (ownTime - childTime) > MaxUnaccountedTime) {
     353           0 :                 MOZ_ASSERT(level < 3);
     354           0 :                 SprintfLiteral(buffer, "%s: %.3fms", "Other", t(ownTime - childTime));
     355           0 :                 if (!fragments.append(DuplicateString(buffer)))
     356           0 :                     return UniqueChars(nullptr);
     357             :             }
     358             :         }
     359             :     }
     360           0 :     return Join(fragments, ", ");
     361             : }
     362             : 
     363             : UniqueChars
     364           0 : Statistics::formatDetailedMessage() const
     365             : {
     366           0 :     FragmentVector fragments;
     367             : 
     368           0 :     if (!fragments.append(formatDetailedDescription()))
     369           0 :         return UniqueChars(nullptr);
     370             : 
     371           0 :     if (!slices_.empty()) {
     372           0 :         for (unsigned i = 0; i < slices_.length(); i++) {
     373           0 :             if (!fragments.append(formatDetailedSliceDescription(i, slices_[i])))
     374           0 :                 return UniqueChars(nullptr);
     375           0 :             if (!fragments.append(formatDetailedPhaseTimes(slices_[i].phaseTimes)))
     376           0 :                 return UniqueChars(nullptr);
     377             :         }
     378             :     }
     379           0 :     if (!fragments.append(formatDetailedTotals()))
     380           0 :         return UniqueChars(nullptr);
     381           0 :     if (!fragments.append(formatDetailedPhaseTimes(phaseTimes)))
     382           0 :         return UniqueChars(nullptr);
     383             : 
     384           0 :     return Join(fragments);
     385             : }
     386             : 
     387             : UniqueChars
     388           0 : Statistics::formatDetailedDescription() const
     389             : {
     390           0 :     const double bytesPerMiB = 1024 * 1024;
     391             : 
     392           0 :     TimeDuration sccTotal, sccLongest;
     393           0 :     sccDurations(&sccTotal, &sccLongest);
     394             : 
     395           0 :     const double mmu20 = computeMMU(TimeDuration::FromMilliseconds(20));
     396           0 :     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
     397             : 
     398             :     const char* format =
     399           0 : "=================================================================\n\
     400             :   Invocation Kind: %s\n\
     401             :   Reason: %s\n\
     402             :   Incremental: %s%s\n\
     403             :   Zones Collected: %d of %d (-%d)\n\
     404             :   Compartments Collected: %d of %d (-%d)\n\
     405             :   MinorGCs since last GC: %d\n\
     406             :   Store Buffer Overflows: %d\n\
     407             :   MMU 20ms:%.1f%%; 50ms:%.1f%%\n\
     408             :   SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\
     409             :   HeapSize: %.3f MiB\n\
     410             :   Chunk Delta (magnitude): %+d  (%d)\n\
     411             :   Arenas Relocated: %.3f MiB\n\
     412             : ";
     413             :     char buffer[1024];
     414           0 :     SprintfLiteral(buffer, format,
     415           0 :                    ExplainInvocationKind(gckind),
     416           0 :                    ExplainReason(slices_[0].reason),
     417           0 :                    nonincremental() ? "no - " : "yes",
     418           0 :                    nonincremental() ? ExplainAbortReason(nonincrementalReason_) : "",
     419           0 :                    zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.sweptZoneCount,
     420           0 :                    zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
     421           0 :                    zoneStats.sweptCompartmentCount,
     422             :                    getCount(STAT_MINOR_GC),
     423             :                    getCount(STAT_STOREBUFFER_OVERFLOW),
     424             :                    mmu20 * 100., mmu50 * 100.,
     425             :                    t(sccTotal), t(sccLongest),
     426           0 :                    double(preBytes) / bytesPerMiB,
     427           0 :                    getCount(STAT_NEW_CHUNK) - getCount(STAT_DESTROY_CHUNK),
     428           0 :                    getCount(STAT_NEW_CHUNK) + getCount(STAT_DESTROY_CHUNK),
     429           0 :                    double(ArenaSize * getCount(STAT_ARENA_RELOCATED)) / bytesPerMiB);
     430           0 :     return DuplicateString(buffer);
     431             : }
     432             : 
     433             : UniqueChars
     434           0 : Statistics::formatDetailedSliceDescription(unsigned i, const SliceData& slice) const
     435             : {
     436             :     char budgetDescription[200];
     437           0 :     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
     438             : 
     439             :     const char* format =
     440           0 : "\
     441             :   ---- Slice %u ----\n\
     442             :     Reason: %s\n\
     443             :     Reset: %s%s\n\
     444             :     State: %s -> %s\n\
     445             :     Page Faults: %ld\n\
     446             :     Pause: %.3fms of %s budget (@ %.3fms)\n\
     447             : ";
     448             :     char buffer[1024];
     449           0 :     SprintfLiteral(buffer, format, i, ExplainReason(slice.reason),
     450           0 :                    slice.wasReset() ? "yes - " : "no",
     451           0 :                    slice.wasReset() ? ExplainAbortReason(slice.resetReason) : "",
     452           0 :                    gc::StateName(slice.initialState), gc::StateName(slice.finalState),
     453           0 :                    uint64_t(slice.endFaults - slice.startFaults),
     454           0 :                    t(slice.duration()), budgetDescription, t(slice.start - slices_[0].start));
     455           0 :     return DuplicateString(buffer);
     456             : }
     457             : 
     458             : UniqueChars
     459           0 : Statistics::formatDetailedPhaseTimes(const PhaseTimeTable& phaseTimes) const
     460             : {
     461           0 :     static const TimeDuration MaxUnaccountedChildTime = TimeDuration::FromMicroseconds(50);
     462             : 
     463           0 :     FragmentVector fragments;
     464             :     char buffer[128];
     465           0 :     for (auto phase : AllPhases()) {
     466           0 :         uint8_t level = phases[phase].depth;
     467           0 :         TimeDuration ownTime = phaseTimes[phase];
     468           0 :         TimeDuration childTime = SumChildTimes(phase, phaseTimes);
     469           0 :         if (!ownTime.IsZero()) {
     470           0 :             SprintfLiteral(buffer, "      %*s: %.3fms\n",
     471           0 :                            level * 2, phases[phase].name, t(ownTime));
     472           0 :             if (!fragments.append(DuplicateString(buffer)))
     473           0 :                 return UniqueChars(nullptr);
     474             : 
     475           0 :             if (childTime && (ownTime - childTime) > MaxUnaccountedChildTime) {
     476           0 :                 SprintfLiteral(buffer, "      %*s: %.3fms\n",
     477           0 :                                (level + 1) * 2, "Other", t(ownTime - childTime));
     478           0 :                 if (!fragments.append(DuplicateString(buffer)))
     479           0 :                     return UniqueChars(nullptr);
     480             :             }
     481             :         }
     482             :     }
     483           0 :     return Join(fragments);
     484             : }
     485             : 
     486             : UniqueChars
     487           0 : Statistics::formatDetailedTotals() const
     488             : {
     489           0 :     TimeDuration total, longest;
     490           0 :     gcDuration(&total, &longest);
     491             : 
     492             :     const char* format =
     493           0 : "\
     494             :   ---- Totals ----\n\
     495             :     Total Time: %.3fms\n\
     496             :     Max Pause: %.3fms\n\
     497             : ";
     498             :     char buffer[1024];
     499           0 :     SprintfLiteral(buffer, format, t(total), t(longest));
     500           0 :     return DuplicateString(buffer);
     501             : }
     502             : 
     503             : void
     504           0 : Statistics::formatJsonSlice(size_t sliceNum, JSONPrinter& json) const
     505             : {
     506           0 :     json.beginObject();
     507           0 :     formatJsonSliceDescription(sliceNum, slices_[sliceNum], json);
     508             : 
     509           0 :     json.beginObjectProperty("times");
     510           0 :     formatJsonPhaseTimes(slices_[sliceNum].phaseTimes, json);
     511           0 :     json.endObject();
     512             : 
     513           0 :     json.endObject();
     514           0 : }
     515             : 
     516             : UniqueChars
     517           0 : Statistics::renderJsonSlice(size_t sliceNum) const
     518             : {
     519           0 :     Sprinter printer(nullptr, false);
     520           0 :     if (!printer.init())
     521           0 :         return UniqueChars(nullptr);
     522           0 :     JSONPrinter json(printer);
     523             : 
     524           0 :     formatJsonSlice(sliceNum, json);
     525           0 :     return UniqueChars(printer.release());
     526             : }
     527             : 
     528             : UniqueChars
     529           0 : Statistics::renderNurseryJson(JSRuntime* rt) const
     530             : {
     531           0 :     Sprinter printer(nullptr, false);
     532           0 :     if (!printer.init())
     533           0 :         return UniqueChars(nullptr);
     534           0 :     JSONPrinter json(printer);
     535           0 :     rt->gc.nursery().renderProfileJSON(json);
     536           0 :     return UniqueChars(printer.release());
     537             : }
     538             : 
     539             : UniqueChars
     540           0 : Statistics::renderJsonMessage(uint64_t timestamp, bool includeSlices) const
     541             : {
     542           0 :     if (aborted)
     543           0 :         return DuplicateString("{status:\"aborted\"}"); // May return nullptr
     544             : 
     545           0 :     Sprinter printer(nullptr, false);
     546           0 :     if (!printer.init())
     547           0 :         return UniqueChars(nullptr);
     548           0 :     JSONPrinter json(printer);
     549             : 
     550           0 :     json.beginObject();
     551           0 :     formatJsonDescription(timestamp, json);
     552             : 
     553           0 :     if (includeSlices) {
     554           0 :         json.beginListProperty("slices");
     555           0 :         for (unsigned i = 0; i < slices_.length(); i++)
     556           0 :             formatJsonSlice(i, json);
     557           0 :         json.endList();
     558             :     }
     559             : 
     560           0 :     json.beginObjectProperty("totals");
     561           0 :     formatJsonPhaseTimes(phaseTimes, json);
     562           0 :     json.endObject();
     563             : 
     564           0 :     json.endObject();
     565             : 
     566           0 :     return UniqueChars(printer.release());
     567             : }
     568             : 
     569             : void
     570           0 : Statistics::formatJsonDescription(uint64_t timestamp, JSONPrinter& json) const
     571             : {
     572           0 :     json.property("timestamp", timestamp);
     573             : 
     574           0 :     TimeDuration total, longest;
     575           0 :     gcDuration(&total, &longest);
     576           0 :     json.property("max_pause", longest, JSONPrinter::MILLISECONDS);
     577           0 :     json.property("total_time", total, JSONPrinter::MILLISECONDS);
     578             : 
     579           0 :     json.property("reason", ExplainReason(slices_[0].reason));
     580           0 :     json.property("zones_collected", zoneStats.collectedZoneCount);
     581           0 :     json.property("total_zones", zoneStats.zoneCount);
     582           0 :     json.property("total_compartments", zoneStats.compartmentCount);
     583           0 :     json.property("minor_gcs", counts[STAT_MINOR_GC]);
     584           0 :     json.property("store_buffer_overflows", counts[STAT_STOREBUFFER_OVERFLOW]);
     585           0 :     json.property("slices", slices_.length());
     586             : 
     587           0 :     const double mmu20 = computeMMU(TimeDuration::FromMilliseconds(20));
     588           0 :     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
     589           0 :     json.property("mmu_20ms", int(mmu20 * 100));
     590           0 :     json.property("mmu_50ms", int(mmu50 * 100));
     591             : 
     592           0 :     TimeDuration sccTotal, sccLongest;
     593           0 :     sccDurations(&sccTotal, &sccLongest);
     594           0 :     json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS);
     595           0 :     json.property("scc_sweep_max_pause", sccLongest, JSONPrinter::MILLISECONDS);
     596             : 
     597           0 :     json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_));
     598           0 :     json.property("allocated", uint64_t(preBytes) / 1024 / 1024);
     599           0 :     json.property("added_chunks", getCount(STAT_NEW_CHUNK));
     600           0 :     json.property("removed_chunks", getCount(STAT_DESTROY_CHUNK));
     601           0 :     json.property("major_gc_number", startingMajorGCNumber);
     602           0 :     json.property("minor_gc_number", startingMinorGCNumber);
     603           0 : }
     604             : 
     605             : void
     606           0 : Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice, JSONPrinter& json) const
     607             : {
     608           0 :     TimeDuration when = slice.start - slices_[0].start;
     609             :     char budgetDescription[200];
     610           0 :     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
     611           0 :     TimeStamp originTime = TimeStamp::ProcessCreation();
     612             : 
     613           0 :     json.property("slice", i);
     614           0 :     json.property("pause", slice.duration(), JSONPrinter::MILLISECONDS);
     615           0 :     json.property("when", when, JSONPrinter::MILLISECONDS);
     616           0 :     json.property("reason", ExplainReason(slice.reason));
     617           0 :     json.property("initial_state", gc::StateName(slice.initialState));
     618           0 :     json.property("final_state", gc::StateName(slice.finalState));
     619           0 :     json.property("budget", budgetDescription);
     620           0 :     json.property("major_gc_number", startingMajorGCNumber);
     621           0 :     if (thresholdTriggered) {
     622           0 :         json.floatProperty("trigger_amount", triggerAmount, 0);
     623           0 :         json.floatProperty("trigger_threshold", triggerThreshold, 0);
     624             :     }
     625           0 :     json.property("page_faults", int64_t(slice.endFaults - slice.startFaults));
     626           0 :     json.property("start_timestamp", slice.start - originTime, JSONPrinter::SECONDS);
     627           0 :     json.property("end_timestamp", slice.end - originTime, JSONPrinter::SECONDS);
     628           0 : }
     629             : 
     630             : void
     631           0 : Statistics::formatJsonPhaseTimes(const PhaseTimeTable& phaseTimes, JSONPrinter& json) const
     632             : {
     633           0 :     for (auto phase : AllPhases()) {
     634           0 :         TimeDuration ownTime = phaseTimes[phase];
     635           0 :         if (!ownTime.IsZero())
     636           0 :             json.property(phases[phase].path, ownTime, JSONPrinter::MILLISECONDS);
     637             :     }
     638           0 : }
     639             : 
     640           4 : Statistics::Statistics(JSRuntime* rt)
     641             :   : runtime(rt),
     642             :     fp(nullptr),
     643             :     nonincrementalReason_(gc::AbortReason::None),
     644             :     preBytes(0),
     645             :     thresholdTriggered(false),
     646             :     triggerAmount(0.0),
     647             :     triggerThreshold(0.0),
     648             :     maxPauseInInterval(0),
     649             :     sliceCallback(nullptr),
     650             :     nurseryCollectionCallback(nullptr),
     651             :     aborted(false),
     652             :     enableProfiling_(false),
     653           4 :     sliceCount_(0)
     654             : {
     655          24 :     for (auto& count : counts)
     656          20 :         count = 0;
     657           4 :     PodZero(&totalTimes_);
     658             : 
     659           4 :     MOZ_ALWAYS_TRUE(phaseStack.reserve(MAX_PHASE_NESTING));
     660           4 :     MOZ_ALWAYS_TRUE(suspendedPhases.reserve(MAX_SUSPENDED_PHASES));
     661             : 
     662           4 :     const char* env = getenv("MOZ_GCTIMER");
     663           4 :     if (env) {
     664           0 :         if (strcmp(env, "none") == 0) {
     665           0 :             fp = nullptr;
     666           0 :         } else if (strcmp(env, "stdout") == 0) {
     667           0 :             fp = stdout;
     668           0 :         } else if (strcmp(env, "stderr") == 0) {
     669           0 :             fp = stderr;
     670             :         } else {
     671           0 :             fp = fopen(env, "a");
     672           0 :             if (!fp)
     673           0 :                 MOZ_CRASH("Failed to open MOZ_GCTIMER log file.");
     674             :         }
     675             :     }
     676             : 
     677           4 :     env = getenv("JS_GC_PROFILE");
     678           4 :     if (env) {
     679           0 :         if (0 == strcmp(env, "help")) {
     680             :             fprintf(stderr, "JS_GC_PROFILE=N\n"
     681           0 :                     "\tReport major GC's taking more than N milliseconds.\n");
     682           0 :             exit(0);
     683             :         }
     684           0 :         enableProfiling_ = true;
     685           0 :         profileThreshold_ = TimeDuration::FromMilliseconds(atoi(env));
     686             :     }
     687           4 : }
     688             : 
     689           0 : Statistics::~Statistics()
     690             : {
     691           0 :     if (fp && fp != stdout && fp != stderr)
     692           0 :         fclose(fp);
     693           0 : }
     694             : 
     695             : /* static */ bool
     696           3 : Statistics::initialize()
     697             : {
     698             : #ifdef DEBUG
     699             :     // Sanity check generated tables.
     700         291 :     for (auto i : AllPhases()) {
     701         288 :         auto parent = phases[i].parent;
     702         288 :         if (parent != Phase::NONE) {
     703         252 :             MOZ_ASSERT(phases[i].depth == phases[parent].depth + 1);
     704             :         }
     705         288 :         auto firstChild = phases[i].firstChild;
     706         288 :         if (firstChild != Phase::NONE) {
     707          54 :             MOZ_ASSERT(i == phases[firstChild].parent);
     708          54 :             MOZ_ASSERT(phases[i].depth == phases[firstChild].depth - 1);
     709             :         }
     710         288 :         auto nextSibling = phases[i].nextSibling;
     711         288 :         if (nextSibling != Phase::NONE) {
     712         198 :             MOZ_ASSERT(parent == phases[nextSibling].parent);
     713         198 :             MOZ_ASSERT(phases[i].depth == phases[nextSibling].depth);
     714             :         }
     715         288 :         auto nextInPhase = phases[i].nextInPhase;
     716         288 :         if (nextInPhase != Phase::NONE) {
     717          81 :             MOZ_ASSERT(phases[i].phaseKind == phases[nextInPhase].phaseKind);
     718          81 :             MOZ_ASSERT(parent != phases[nextInPhase].parent);
     719             :         }
     720             :     }
     721         210 :     for (auto i : AllPhaseKinds()) {
     722         207 :         MOZ_ASSERT(phases[phaseKinds[i].firstPhase].phaseKind == i);
     723       14490 :         for (auto j : AllPhaseKinds()) {
     724       14283 :             MOZ_ASSERT_IF(i != j,
     725             :                           phaseKinds[i].telemetryBucket != phaseKinds[j].telemetryBucket);
     726             :         }
     727             :     }
     728             : #endif
     729             : 
     730           3 :     return true;
     731             : }
     732             : 
     733             : JS::GCSliceCallback
     734           9 : Statistics::setSliceCallback(JS::GCSliceCallback newCallback)
     735             : {
     736           9 :     JS::GCSliceCallback oldCallback = sliceCallback;
     737           9 :     sliceCallback = newCallback;
     738           9 :     return oldCallback;
     739             : }
     740             : 
     741             : JS::GCNurseryCollectionCallback
     742           3 : Statistics::setNurseryCollectionCallback(JS::GCNurseryCollectionCallback newCallback)
     743             : {
     744           3 :     auto oldCallback = nurseryCollectionCallback;
     745           3 :     nurseryCollectionCallback = newCallback;
     746           3 :     return oldCallback;
     747             : }
     748             : 
     749             : TimeDuration
     750           0 : Statistics::clearMaxGCPauseAccumulator()
     751             : {
     752           0 :     TimeDuration prior = maxPauseInInterval;
     753           0 :     maxPauseInInterval = 0;
     754           0 :     return prior;
     755             : }
     756             : 
     757             : TimeDuration
     758           0 : Statistics::getMaxGCPauseSinceClear()
     759             : {
     760           0 :     return maxPauseInInterval;
     761             : }
     762             : 
     763             : // Sum up the time for a phase, including instances of the phase with different
     764             : // parents.
     765             : static TimeDuration
     766          70 : SumPhase(PhaseKind phaseKind, const Statistics::PhaseTimeTable& times)
     767             : {
     768          70 :     TimeDuration sum = 0;
     769         170 :     for (Phase phase = phaseKinds[phaseKind].firstPhase;
     770         170 :          phase != Phase::NONE;
     771         100 :          phase = phases[phase].nextInPhase)
     772             :     {
     773         100 :         sum += times[phase];
     774             :     }
     775          70 :     return sum;
     776             : }
     777             : 
     778             : static void
     779          84 : CheckSelfTime(Phase parent,
     780             :               Phase child,
     781             :               const Statistics::PhaseTimeTable& times,
     782             :               const Statistics::PhaseTimeTable& selfTimes,
     783             :               TimeDuration childTime)
     784             : {
     785          84 :     if (selfTimes[parent] < childTime) {
     786           0 :         fprintf(stderr,
     787             :                 "Parent %s time = %.3fms with %.3fms remaining, child %s time %.3fms\n",
     788           0 :                 phases[parent].name,
     789           0 :                 times[parent].ToMilliseconds(),
     790           0 :                 selfTimes[parent].ToMilliseconds(),
     791           0 :                 phases[child].name,
     792           0 :                 childTime.ToMilliseconds());
     793           0 :         fflush(stderr);
     794           0 :         MOZ_CRASH();
     795             :     }
     796          84 : }
     797             : 
     798             : static PhaseKind
     799           1 : LongestPhaseSelfTime(const Statistics::PhaseTimeTable& times)
     800             : {
     801             :     // Start with total times per expanded phase, including children's times.
     802           1 :     Statistics::PhaseTimeTable selfTimes(times);
     803             : 
     804             :     // We have the total time spent in each phase, including descendant times.
     805             :     // Loop over the children and subtract their times from their parent's self
     806             :     // time.
     807          97 :     for (auto i : AllPhases()) {
     808          96 :         Phase parent = phases[i].parent;
     809          96 :         if (parent != Phase::NONE) {
     810          84 :             CheckSelfTime(parent, i, times, selfTimes, times[i]);
     811          84 :             selfTimes[parent] -= times[i];
     812             :         }
     813             :     }
     814             : 
     815             :     // Sum expanded phases corresponding to the same phase.
     816           1 :     EnumeratedArray<PhaseKind, PhaseKind::LIMIT, TimeDuration> phaseTimes;
     817          70 :     for (auto i : AllPhaseKinds())
     818          69 :         phaseTimes[i] = SumPhase(i, selfTimes);
     819             : 
     820             :     // Loop over this table to find the longest phase.
     821           1 :     TimeDuration longestTime = 0;
     822           1 :     PhaseKind longestPhase = PhaseKind::NONE;
     823          70 :     for (auto i : AllPhaseKinds()) {
     824          69 :         if (phaseTimes[i] > longestTime) {
     825           4 :             longestTime = phaseTimes[i];
     826           4 :             longestPhase = i;
     827             :         }
     828             :     }
     829             : 
     830           1 :     return longestPhase;
     831             : }
     832             : 
     833             : void
     834           0 : Statistics::printStats()
     835             : {
     836           0 :     if (aborted) {
     837           0 :         fprintf(fp, "OOM during GC statistics collection. The report is unavailable for this GC.\n");
     838             :     } else {
     839           0 :         UniqueChars msg = formatDetailedMessage();
     840           0 :         if (msg) {
     841             :             double secSinceStart =
     842           0 :                 (slices_[0].start - TimeStamp::ProcessCreation()).ToSeconds();
     843           0 :             fprintf(fp, "GC(T+%.3fs) %s\n", secSinceStart, msg.get());
     844             :         }
     845             :     }
     846           0 :     fflush(fp);
     847           0 : }
     848             : 
     849             : void
     850           1 : Statistics::beginGC(JSGCInvocationKind kind)
     851             : {
     852           1 :     slices_.clearAndFree();
     853           1 :     sccTimes.clearAndFree();
     854           1 :     gckind = kind;
     855           1 :     nonincrementalReason_ = gc::AbortReason::None;
     856             : 
     857           1 :     preBytes = runtime->gc.usage.gcBytes();
     858           1 :     startingMajorGCNumber = runtime->gc.majorGCCount();
     859           1 : }
     860             : 
     861             : void
     862           0 : Statistics::endGC()
     863             : {
     864           0 :     TimeDuration sccTotal, sccLongest;
     865           0 :     sccDurations(&sccTotal, &sccLongest);
     866             : 
     867           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC, !zoneStats.isCollectingAllZones());
     868           0 :     TimeDuration markTotal = SumPhase(PhaseKind::MARK, phaseTimes);
     869           0 :     TimeDuration markRootsTotal = SumPhase(PhaseKind::MARK_ROOTS, phaseTimes);
     870           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_MS, t(markTotal));
     871           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[Phase::SWEEP]));
     872           0 :     if (runtime->gc.isCompactingGc()) {
     873           0 :         runtime->addTelemetry(JS_TELEMETRY_GC_COMPACT_MS,
     874           0 :                               t(phaseTimes[Phase::COMPACT]));
     875             :     }
     876           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_ROOTS_MS, t(markRootsTotal));
     877           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[Phase::SWEEP_MARK_GRAY]));
     878           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL, nonincremental());
     879           0 :     if (nonincremental())
     880           0 :         runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL_REASON, uint32_t(nonincrementalReason_));
     881           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_INCREMENTAL_DISABLED, !runtime->gc.isIncrementalGCAllowed());
     882           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, t(sccTotal));
     883           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS, t(sccLongest));
     884             : 
     885           0 :     TimeDuration total, longest;
     886           0 :     gcDuration(&total, &longest);
     887             : 
     888           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MS, t(total));
     889           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MAX_PAUSE_MS, t(longest));
     890           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MAX_PAUSE_MS_2, t(longest));
     891             : 
     892           0 :     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
     893           0 :     runtime->addTelemetry(JS_TELEMETRY_GC_MMU_50, mmu50 * 100);
     894           0 :     thresholdTriggered = false;
     895           0 : }
     896             : 
     897             : void
     898          24 : Statistics::beginNurseryCollection(JS::gcreason::Reason reason)
     899             : {
     900          24 :     count(STAT_MINOR_GC);
     901          24 :     startingMinorGCNumber = runtime->gc.minorGCCount();
     902          24 :     if (nurseryCollectionCallback) {
     903          24 :         (*nurseryCollectionCallback)(TlsContext.get(),
     904             :                                      JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START,
     905          24 :                                      reason);
     906             :     }
     907          24 : }
     908             : 
     909             : void
     910          24 : Statistics::endNurseryCollection(JS::gcreason::Reason reason)
     911             : {
     912          24 :     if (nurseryCollectionCallback) {
     913          24 :         (*nurseryCollectionCallback)(TlsContext.get(),
     914             :                                      JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END,
     915          24 :                                      reason);
     916             :     }
     917          24 : }
     918             : 
     919             : void
     920           3 : Statistics::beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
     921             :                        SliceBudget budget, JS::gcreason::Reason reason)
     922             : {
     923           3 :     this->zoneStats = zoneStats;
     924             : 
     925           3 :     bool first = !runtime->gc.isIncrementalGCInProgress();
     926           3 :     if (first)
     927           1 :         beginGC(gckind);
     928             : 
     929           6 :     if (!slices_.emplaceBack(budget,
     930             :                              reason,
     931           6 :                              TimeStamp::Now(),
     932           6 :                              GetPageFaultCount(),
     933           6 :                              runtime->gc.state()))
     934             :     {
     935             :         // If we are OOM, set a flag to indicate we have missing slice data.
     936           0 :         aborted = true;
     937           0 :         return;
     938             :     }
     939             : 
     940           3 :     runtime->addTelemetry(JS_TELEMETRY_GC_REASON, reason);
     941             : 
     942             :     // Slice callbacks should only fire for the outermost level.
     943           3 :     bool wasFullGC = zoneStats.isCollectingAllZones();
     944           3 :     if (sliceCallback) {
     945           3 :         JSContext* cx = TlsContext.get();
     946           3 :         JS::GCDescription desc(!wasFullGC, false, gckind, reason);
     947           3 :         if (first)
     948           1 :             (*sliceCallback)(cx, JS::GC_CYCLE_BEGIN, desc);
     949           3 :         (*sliceCallback)(cx, JS::GC_SLICE_BEGIN, desc);
     950             :     }
     951             : }
     952             : 
     953             : void
     954           3 : Statistics::endSlice()
     955             : {
     956           3 :     if (!aborted) {
     957           3 :         auto& slice = slices_.back();
     958           3 :         slice.end = TimeStamp::Now();
     959           3 :         slice.endFaults = GetPageFaultCount();
     960           3 :         slice.finalState = runtime->gc.state();
     961             : 
     962           3 :         TimeDuration sliceTime = slice.end - slice.start;
     963           3 :         runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_MS, t(sliceTime));
     964           3 :         runtime->addTelemetry(JS_TELEMETRY_GC_RESET, slice.wasReset());
     965           3 :         if (slice.wasReset())
     966           0 :             runtime->addTelemetry(JS_TELEMETRY_GC_RESET_REASON, uint32_t(slice.resetReason));
     967             : 
     968           3 :         if (slice.budget.isTimeBudget()) {
     969           3 :             int64_t budget_ms = slice.budget.timeBudget.budget;
     970           3 :             runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_MS, budget_ms);
     971           3 :             if (budget_ms == runtime->gc.defaultSliceBudget())
     972           0 :                 runtime->addTelemetry(JS_TELEMETRY_GC_ANIMATION_MS, t(sliceTime));
     973             : 
     974             :             // Record any phase that goes more than 2x over its budget.
     975           3 :             if (sliceTime.ToMilliseconds() > 2 * budget_ms) {
     976           1 :                 reportLongestPhase(slice.phaseTimes, JS_TELEMETRY_GC_SLOW_PHASE);
     977             :                 // If we spend a significant length of time waiting for parallel
     978             :                 // tasks then report the longest task.
     979           1 :                 TimeDuration joinTime = SumPhase(PhaseKind::JOIN_PARALLEL_TASKS, slice.phaseTimes);
     980           1 :                 if (joinTime.ToMilliseconds() > budget_ms)
     981           0 :                     reportLongestPhase(slice.parallelTimes, JS_TELEMETRY_GC_SLOW_TASK);
     982             :             }
     983             :         }
     984             : 
     985           3 :         sliceCount_++;
     986             :     }
     987             : 
     988           3 :     bool last = !runtime->gc.isIncrementalGCInProgress();
     989           3 :     if (last) {
     990           0 :         if (fp)
     991           0 :             printStats();
     992             : 
     993           0 :         if (!aborted)
     994           0 :             endGC();
     995             :     }
     996             : 
     997           3 :     if (enableProfiling_ && !aborted && slices_.back().duration() >= profileThreshold_)
     998           0 :         printSliceProfile();
     999             : 
    1000             :     // Slice callbacks should only fire for the outermost level.
    1001           3 :     if (!aborted) {
    1002           3 :         bool wasFullGC = zoneStats.isCollectingAllZones();
    1003           3 :         if (sliceCallback) {
    1004           3 :             JSContext* cx = TlsContext.get();
    1005           3 :             JS::GCDescription desc(!wasFullGC, last, gckind, slices_.back().reason);
    1006           3 :             (*sliceCallback)(cx, JS::GC_SLICE_END, desc);
    1007           3 :             if (last)
    1008           0 :                 (*sliceCallback)(cx, JS::GC_CYCLE_END, desc);
    1009             :         }
    1010             :     }
    1011             : 
    1012             :     // Do this after the slice callback since it uses these values.
    1013           3 :     if (last) {
    1014           0 :         for (auto& count : counts)
    1015           0 :             count = 0;
    1016             : 
    1017             :         // Clear the timers at the end of a GC, preserving the data for PhaseKind::MUTATOR.
    1018           0 :         auto mutatorStartTime = phaseStartTimes[Phase::MUTATOR];
    1019           0 :         auto mutatorTime = phaseTimes[Phase::MUTATOR];
    1020           0 :         PodZero(&phaseStartTimes);
    1021           0 :         PodZero(&phaseTimes);
    1022           0 :         phaseStartTimes[Phase::MUTATOR] = mutatorStartTime;
    1023           0 :         phaseTimes[Phase::MUTATOR] = mutatorTime;
    1024             :     }
    1025             : 
    1026           3 :     aborted = false;
    1027           3 : }
    1028             : 
    1029             : void
    1030           1 : Statistics::reportLongestPhase(const PhaseTimeTable& times, int telemetryId)
    1031             : {
    1032           1 :     PhaseKind longest = LongestPhaseSelfTime(times);
    1033           1 :     if (longest == PhaseKind::NONE)
    1034           0 :         return;
    1035             : 
    1036           1 :     uint8_t bucket = phaseKinds[longest].telemetryBucket;
    1037           1 :     runtime->addTelemetry(telemetryId, bucket);
    1038             : }
    1039             : 
    1040             : bool
    1041           0 : Statistics::startTimingMutator()
    1042             : {
    1043           0 :     if (phaseStack.length() != 0) {
    1044             :         // Should only be called from outside of GC.
    1045           0 :         MOZ_ASSERT(phaseStack.length() == 1);
    1046           0 :         MOZ_ASSERT(phaseStack[0] == Phase::MUTATOR);
    1047           0 :         return false;
    1048             :     }
    1049             : 
    1050           0 :     MOZ_ASSERT(suspendedPhases.empty());
    1051             : 
    1052           0 :     timedGCTime = 0;
    1053           0 :     phaseStartTimes[Phase::MUTATOR] = TimeStamp();
    1054           0 :     phaseTimes[Phase::MUTATOR] = 0;
    1055           0 :     timedGCStart = TimeStamp();
    1056             : 
    1057           0 :     beginPhase(PhaseKind::MUTATOR);
    1058           0 :     return true;
    1059             : }
    1060             : 
    1061             : bool
    1062           0 : Statistics::stopTimingMutator(double& mutator_ms, double& gc_ms)
    1063             : {
    1064             :     // This should only be called from outside of GC, while timing the mutator.
    1065           0 :     if (phaseStack.length() != 1 || phaseStack[0] != Phase::MUTATOR)
    1066           0 :         return false;
    1067             : 
    1068           0 :     endPhase(PhaseKind::MUTATOR);
    1069           0 :     mutator_ms = t(phaseTimes[Phase::MUTATOR]);
    1070           0 :     gc_ms = t(timedGCTime);
    1071             : 
    1072           0 :     return true;
    1073             : }
    1074             : 
    1075             : void
    1076           0 : Statistics::suspendPhases(PhaseKind suspension)
    1077             : {
    1078           0 :     MOZ_ASSERT(suspension == PhaseKind::EXPLICIT_SUSPENSION ||
    1079             :                suspension == PhaseKind::IMPLICIT_SUSPENSION);
    1080           0 :     while (!phaseStack.empty()) {
    1081           0 :         MOZ_ASSERT(suspendedPhases.length() < MAX_SUSPENDED_PHASES);
    1082           0 :         Phase parent = phaseStack.back();
    1083           0 :         suspendedPhases.infallibleAppend(parent);
    1084           0 :         recordPhaseEnd(parent);
    1085             :     }
    1086           0 :     suspendedPhases.infallibleAppend(lookupChildPhase(suspension));
    1087           0 : }
    1088             : 
    1089             : void
    1090           0 : Statistics::resumePhases()
    1091             : {
    1092           0 :     MOZ_ASSERT(suspendedPhases.back() == Phase::EXPLICIT_SUSPENSION ||
    1093             :                suspendedPhases.back() == Phase::IMPLICIT_SUSPENSION);
    1094           0 :     suspendedPhases.popBack();
    1095             : 
    1096           0 :     while (!suspendedPhases.empty() &&
    1097           0 :            suspendedPhases.back() != Phase::EXPLICIT_SUSPENSION &&
    1098           0 :            suspendedPhases.back() != Phase::IMPLICIT_SUSPENSION)
    1099             :     {
    1100           0 :         Phase resumePhase = suspendedPhases.popCopy();
    1101           0 :         if (resumePhase == Phase::MUTATOR)
    1102           0 :             timedGCTime += TimeStamp::Now() - timedGCStart;
    1103           0 :         recordPhaseBegin(resumePhase);
    1104             :     }
    1105           0 : }
    1106             : 
    1107             : void
    1108         142 : Statistics::beginPhase(PhaseKind phaseKind)
    1109             : {
    1110             :     // No longer timing these phases. We should never see these.
    1111         142 :     MOZ_ASSERT(phaseKind != PhaseKind::GC_BEGIN && phaseKind != PhaseKind::GC_END);
    1112             : 
    1113             :     // PhaseKind::MUTATOR is suspended while performing GC.
    1114         142 :     if (currentPhase() == Phase::MUTATOR)
    1115           0 :         suspendPhases(PhaseKind::IMPLICIT_SUSPENSION);
    1116             : 
    1117         142 :     recordPhaseBegin(lookupChildPhase(phaseKind));
    1118         142 : }
    1119             : 
    1120             : void
    1121         142 : Statistics::recordPhaseBegin(Phase phase)
    1122             : {
    1123         142 :     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
    1124             : 
    1125             :     // Guard against any other re-entry.
    1126         142 :     MOZ_ASSERT(!phaseStartTimes[phase]);
    1127             : 
    1128         142 :     MOZ_ASSERT(phaseStack.length() < MAX_PHASE_NESTING);
    1129             : 
    1130         142 :     Phase current = currentPhase();
    1131         142 :     MOZ_ASSERT(phases[phase].parent == current);
    1132             : 
    1133         142 :     TimeStamp now = TimeStamp::Now();
    1134             : 
    1135         142 :     if (current != Phase::NONE) {
    1136             : #ifdef ANDROID
    1137             :         // Sadly this happens sometimes.
    1138             :         if (now < phaseStartTimes[currentPhase()]) {
    1139             :             now = phaseStartTimes[currentPhase()];
    1140             :             aborted = true;
    1141             :         }
    1142             : #endif
    1143             : 
    1144          89 :         MOZ_RELEASE_ASSERT(now >= phaseStartTimes[currentPhase()]);
    1145             :     }
    1146             : 
    1147         142 :     phaseStack.infallibleAppend(phase);
    1148         142 :     phaseStartTimes[phase] = now;
    1149         142 : }
    1150             : 
    1151             : void
    1152         142 : Statistics::recordPhaseEnd(Phase phase)
    1153             : {
    1154         142 :     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
    1155             : 
    1156         142 :     MOZ_ASSERT(phaseStartTimes[phase]);
    1157             : 
    1158         142 :     TimeStamp now = TimeStamp::Now();
    1159             : 
    1160             : #ifdef ANDROID
    1161             :     // Sadly this happens sometimes.
    1162             :     if (now < phaseStartTimes[phase]) {
    1163             :         now = phaseStartTimes[phase];
    1164             :         aborted = true;
    1165             :     }
    1166             : #endif
    1167             : 
    1168         142 :     MOZ_RELEASE_ASSERT(now >= phaseStartTimes[phase]);
    1169             : 
    1170         142 :     if (phase == Phase::MUTATOR)
    1171           0 :         timedGCStart = now;
    1172             : 
    1173         142 :     phaseStack.popBack();
    1174             : 
    1175         142 :     TimeDuration t = now - phaseStartTimes[phase];
    1176         142 :     if (!slices_.empty())
    1177          49 :         slices_.back().phaseTimes[phase] += t;
    1178         142 :     phaseTimes[phase] += t;
    1179         142 :     phaseStartTimes[phase] = TimeStamp();
    1180         142 : }
    1181             : 
    1182             : void
    1183         142 : Statistics::endPhase(PhaseKind phaseKind)
    1184             : {
    1185         142 :     Phase phase = currentPhase();
    1186         142 :     MOZ_ASSERT(phase != Phase::NONE);
    1187         142 :     MOZ_ASSERT(phases[phase].phaseKind == phaseKind);
    1188             : 
    1189         142 :     recordPhaseEnd(phase);
    1190             : 
    1191             :     // When emptying the stack, we may need to return to timing the mutator
    1192             :     // (PhaseKind::MUTATOR).
    1193         337 :     if (phaseStack.empty() &&
    1194         142 :         !suspendedPhases.empty() &&
    1195           0 :         suspendedPhases.back() == Phase::IMPLICIT_SUSPENSION)
    1196             :     {
    1197           0 :         resumePhases();
    1198             :     }
    1199         142 : }
    1200             : 
    1201             : void
    1202           2 : Statistics::recordParallelPhase(PhaseKind phaseKind, TimeDuration duration)
    1203             : {
    1204           2 :     Phase phase = lookupChildPhase(phaseKind);
    1205             : 
    1206             :     // Record the duration for all phases in the tree up to the root. This is
    1207             :     // not strictly necessary but makes the invariant that parent phase times
    1208             :     // include their children apply to both phaseTimes and parallelTimes.
    1209          10 :     while (phase != Phase::NONE) {
    1210           4 :         if (!slices_.empty())
    1211           4 :             slices_.back().parallelTimes[phase] += duration;
    1212           4 :         parallelTimes[phase] += duration;
    1213           4 :         phase = phases[phase].parent;
    1214             :     }
    1215           2 : }
    1216             : 
    1217             : void
    1218           0 : Statistics::endParallelPhase(PhaseKind phaseKind, const GCParallelTask* task)
    1219             : {
    1220           0 :     Phase phase = lookupChildPhase(phaseKind);
    1221           0 :     phaseStack.popBack();
    1222             : 
    1223           0 :     if (!slices_.empty())
    1224           0 :         slices_.back().phaseTimes[phase] += task->duration();
    1225           0 :     phaseTimes[phase] += task->duration();
    1226           0 :     phaseStartTimes[phase] = TimeStamp();
    1227           0 : }
    1228             : 
    1229             : TimeStamp
    1230           0 : Statistics::beginSCC()
    1231             : {
    1232           0 :     return TimeStamp::Now();
    1233             : }
    1234             : 
    1235             : void
    1236           0 : Statistics::endSCC(unsigned scc, TimeStamp start)
    1237             : {
    1238           0 :     if (scc >= sccTimes.length() && !sccTimes.resize(scc + 1))
    1239           0 :         return;
    1240             : 
    1241           0 :     sccTimes[scc] += TimeStamp::Now() - start;
    1242             : }
    1243             : 
    1244             : /*
    1245             :  * MMU (minimum mutator utilization) is a measure of how much garbage collection
    1246             :  * is affecting the responsiveness of the system. MMU measurements are given
    1247             :  * with respect to a certain window size. If we report MMU(50ms) = 80%, then
    1248             :  * that means that, for any 50ms window of time, at least 80% of the window is
    1249             :  * devoted to the mutator. In other words, the GC is running for at most 20% of
    1250             :  * the window, or 10ms. The GC can run multiple slices during the 50ms window
    1251             :  * as long as the total time it spends is at most 10ms.
    1252             :  */
    1253             : double
    1254           0 : Statistics::computeMMU(TimeDuration window) const
    1255             : {
    1256           0 :     MOZ_ASSERT(!slices_.empty());
    1257             : 
    1258           0 :     TimeDuration gc = slices_[0].end - slices_[0].start;
    1259           0 :     TimeDuration gcMax = gc;
    1260             : 
    1261           0 :     if (gc >= window)
    1262           0 :         return 0.0;
    1263             : 
    1264           0 :     int startIndex = 0;
    1265           0 :     for (size_t endIndex = 1; endIndex < slices_.length(); endIndex++) {
    1266           0 :         auto* startSlice = &slices_[startIndex];
    1267           0 :         auto& endSlice = slices_[endIndex];
    1268           0 :         gc += endSlice.end - endSlice.start;
    1269             : 
    1270           0 :         while (endSlice.end - startSlice->end >= window) {
    1271           0 :             gc -= startSlice->end - startSlice->start;
    1272           0 :             startSlice = &slices_[++startIndex];
    1273             :         }
    1274             : 
    1275           0 :         TimeDuration cur = gc;
    1276           0 :         if (endSlice.end - startSlice->start > window)
    1277           0 :             cur -= (endSlice.end - startSlice->start - window);
    1278           0 :         if (cur > gcMax)
    1279           0 :             gcMax = cur;
    1280             :     }
    1281             : 
    1282           0 :     return double((window - gcMax) / window);
    1283             : }
    1284             : 
    1285             : void
    1286           0 : Statistics::maybePrintProfileHeaders()
    1287             : {
    1288             :     static int printedHeader = 0;
    1289           0 :     if ((printedHeader++ % 200) == 0) {
    1290           0 :         printProfileHeader();
    1291           0 :         for (ZoneGroupsIter group(runtime); !group.done(); group.next()) {
    1292           0 :             if (group->nursery().enableProfiling()) {
    1293           0 :                 Nursery::printProfileHeader();
    1294           0 :                 break;
    1295             :             }
    1296             :         }
    1297             :     }
    1298           0 : }
    1299             : 
    1300             : void
    1301           0 : Statistics::printProfileHeader()
    1302             : {
    1303           0 :     if (!enableProfiling_)
    1304           0 :         return;
    1305             : 
    1306           0 :     fprintf(stderr, "MajorGC:               Reason States      ");
    1307           0 :     fprintf(stderr, " %6s", "total");
    1308             : #define PRINT_PROFILE_HEADER(name, text, phase)                               \
    1309             :     fprintf(stderr, " %6s", text);
    1310           0 : FOR_EACH_GC_PROFILE_TIME(PRINT_PROFILE_HEADER)
    1311             : #undef PRINT_PROFILE_HEADER
    1312           0 :     fprintf(stderr, "\n");
    1313             : }
    1314             : 
    1315             : /* static */ void
    1316           0 : Statistics::printProfileTimes(const ProfileDurations& times)
    1317             : {
    1318           0 :     for (auto time : times)
    1319           0 :         fprintf(stderr, " %6" PRIi64, static_cast<int64_t>(time.ToMilliseconds()));
    1320           0 :     fprintf(stderr, "\n");
    1321           0 : }
    1322             : 
    1323             : void
    1324           0 : Statistics::printSliceProfile()
    1325             : {
    1326           0 :     const SliceData& slice = slices_.back();
    1327             : 
    1328           0 :     maybePrintProfileHeaders();
    1329             : 
    1330           0 :     fprintf(stderr, "MajorGC: %20s %1d -> %1d      ",
    1331           0 :             ExplainReason(slice.reason), int(slice.initialState), int(slice.finalState));
    1332             : 
    1333           0 :     ProfileDurations times;
    1334           0 :     times[ProfileKey::Total] = slice.duration();
    1335           0 :     totalTimes_[ProfileKey::Total] += times[ProfileKey::Total];
    1336             : 
    1337             : #define GET_PROFILE_TIME(name, text, phase)                                   \
    1338             :     times[ProfileKey::name] = SumPhase(phase, slice.phaseTimes);              \
    1339             :     totalTimes_[ProfileKey::name] += times[ProfileKey::name];
    1340           0 : FOR_EACH_GC_PROFILE_TIME(GET_PROFILE_TIME)
    1341             : #undef GET_PROFILE_TIME
    1342             : 
    1343           0 :     printProfileTimes(times);
    1344           0 : }
    1345             : 
    1346             : void
    1347           0 : Statistics::printTotalProfileTimes()
    1348             : {
    1349           0 :     if (enableProfiling_) {
    1350           0 :         fprintf(stderr, "MajorGC TOTALS: %7" PRIu64 " slices:           ", sliceCount_);
    1351           0 :         printProfileTimes(totalTimes_);
    1352             :     }
    1353           0 : }
    1354             : 

Generated by: LCOV version 1.13