diff options
Diffstat (limited to 'js/src/gc/Statistics.h')
-rw-r--r-- | js/src/gc/Statistics.h | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h new file mode 100644 index 000000000..c9e5871e3 --- /dev/null +++ b/js/src/gc/Statistics.h @@ -0,0 +1,505 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gc_Statistics_h +#define gc_Statistics_h + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/Maybe.h" +#include "mozilla/PodOperations.h" + +#include "jsalloc.h" +#include "jsgc.h" +#include "jspubtd.h" + +#include "js/GCAPI.h" +#include "js/Vector.h" + +using mozilla::Maybe; + +namespace js { + +class GCParallelTask; + +namespace gcstats { + +enum Phase : uint8_t { + PHASE_MUTATOR, + PHASE_GC_BEGIN, + PHASE_WAIT_BACKGROUND_THREAD, + PHASE_MARK_DISCARD_CODE, + PHASE_RELAZIFY_FUNCTIONS, + PHASE_PURGE, + PHASE_MARK, + PHASE_UNMARK, + PHASE_MARK_DELAYED, + PHASE_SWEEP, + PHASE_SWEEP_MARK, + PHASE_SWEEP_MARK_TYPES, + PHASE_SWEEP_MARK_INCOMING_BLACK, + PHASE_SWEEP_MARK_WEAK, + PHASE_SWEEP_MARK_INCOMING_GRAY, + PHASE_SWEEP_MARK_GRAY, + PHASE_SWEEP_MARK_GRAY_WEAK, + PHASE_FINALIZE_START, + PHASE_WEAK_ZONEGROUP_CALLBACK, + PHASE_WEAK_COMPARTMENT_CALLBACK, + PHASE_SWEEP_ATOMS, + PHASE_SWEEP_SYMBOL_REGISTRY, + PHASE_SWEEP_COMPARTMENTS, + PHASE_SWEEP_DISCARD_CODE, + PHASE_SWEEP_INNER_VIEWS, + PHASE_SWEEP_CC_WRAPPER, + PHASE_SWEEP_BASE_SHAPE, + PHASE_SWEEP_INITIAL_SHAPE, + PHASE_SWEEP_TYPE_OBJECT, + PHASE_SWEEP_BREAKPOINT, + PHASE_SWEEP_REGEXP, + PHASE_SWEEP_MISC, + PHASE_SWEEP_TYPES, + PHASE_SWEEP_TYPES_BEGIN, + PHASE_SWEEP_TYPES_END, + PHASE_SWEEP_OBJECT, + PHASE_SWEEP_STRING, + PHASE_SWEEP_SCRIPT, + PHASE_SWEEP_SCOPE, + PHASE_SWEEP_SHAPE, + PHASE_SWEEP_JITCODE, + PHASE_FINALIZE_END, + PHASE_DESTROY, + PHASE_COMPACT, + PHASE_COMPACT_MOVE, + PHASE_COMPACT_UPDATE, + PHASE_COMPACT_UPDATE_CELLS, + PHASE_GC_END, + PHASE_MINOR_GC, + PHASE_EVICT_NURSERY, + PHASE_TRACE_HEAP, + PHASE_BARRIER, + PHASE_UNMARK_GRAY, + PHASE_MARK_ROOTS, + PHASE_BUFFER_GRAY_ROOTS, + PHASE_MARK_CCWS, + PHASE_MARK_STACK, + PHASE_MARK_RUNTIME_DATA, + PHASE_MARK_EMBEDDING, + PHASE_MARK_COMPARTMENTS, + PHASE_PURGE_SHAPE_TABLES, + + PHASE_LIMIT, + PHASE_NONE = PHASE_LIMIT, + PHASE_EXPLICIT_SUSPENSION = PHASE_LIMIT, + PHASE_IMPLICIT_SUSPENSION, + PHASE_MULTI_PARENTS +}; + +enum Stat { + STAT_NEW_CHUNK, + STAT_DESTROY_CHUNK, + STAT_MINOR_GC, + + // Number of times a 'put' into a storebuffer overflowed, triggering a + // compaction + STAT_STOREBUFFER_OVERFLOW, + + // Number of arenas relocated by compacting GC. + STAT_ARENA_RELOCATED, + + STAT_LIMIT +}; + +struct ZoneGCStats +{ + /* Number of zones collected in this GC. */ + int collectedZoneCount; + + /* Total number of zones in the Runtime at the start of this GC. */ + int zoneCount; + + /* Number of zones swept in this GC. */ + int sweptZoneCount; + + /* Total number of compartments in all zones collected. */ + int collectedCompartmentCount; + + /* Total number of compartments in the Runtime at the start of this GC. */ + int compartmentCount; + + /* Total number of compartments swept by this GC. */ + int sweptCompartmentCount; + + bool isCollectingAllZones() const { return collectedZoneCount == zoneCount; } + + ZoneGCStats() + : collectedZoneCount(0), zoneCount(0), sweptZoneCount(0), + collectedCompartmentCount(0), compartmentCount(0), sweptCompartmentCount(0) + {} +}; + +#define FOR_EACH_GC_PROFILE_TIME(_) \ + _(BeginCallback, "beginCB", PHASE_GC_BEGIN) \ + _(WaitBgThread, "waitBG", PHASE_WAIT_BACKGROUND_THREAD) \ + _(DiscardCode, "discard", PHASE_MARK_DISCARD_CODE) \ + _(RelazifyFunc, "relazify", PHASE_RELAZIFY_FUNCTIONS) \ + _(PurgeTables, "purgeTables", PHASE_PURGE_SHAPE_TABLES) \ + _(Purge, "purge", PHASE_PURGE) \ + _(Mark, "mark", PHASE_MARK) \ + _(Sweep, "sweep", PHASE_SWEEP) \ + _(Compact, "compact", PHASE_COMPACT) \ + _(EndCallback, "endCB", PHASE_GC_END) \ + _(Barriers, "barriers", PHASE_BARRIER) + +const char* ExplainAbortReason(gc::AbortReason reason); +const char* ExplainInvocationKind(JSGCInvocationKind gckind); + +/* + * Struct for collecting timing statistics on a "phase tree". The tree is + * specified as a limited DAG, but the timings are collected for the whole tree + * that you would get by expanding out the DAG by duplicating subtrees rooted + * at nodes with multiple parents. + * + * During execution, a child phase can be activated multiple times, and the + * total time will be accumulated. (So for example, you can start and end + * PHASE_MARK_ROOTS multiple times before completing the parent phase.) + * + * Incremental GC is represented by recording separate timing results for each + * slice within the overall GC. + */ +struct Statistics +{ + /* + * Phases are allowed to have multiple parents, though any path from root + * to leaf is allowed at most one multi-parented phase. We keep a full set + * of timings for each of the multi-parented phases, to be able to record + * all the timings in the expanded tree induced by our dag. + * + * Note that this wastes quite a bit of space, since we have a whole + * separate array of timing data containing all the phases. We could be + * more clever and keep an array of pointers biased by the offset of the + * multi-parented phase, and thereby preserve the simple + * timings[slot][PHASE_*] indexing. But the complexity doesn't seem worth + * the few hundred bytes of savings. If we want to extend things to full + * DAGs, this decision should be reconsidered. + */ + static const size_t MaxMultiparentPhases = 6; + static const size_t NumTimingArrays = MaxMultiparentPhases + 1; + + /* Create a convenient type for referring to tables of phase times. */ + using PhaseTimeTable = int64_t[NumTimingArrays][PHASE_LIMIT]; + + static MOZ_MUST_USE bool initialize(); + + explicit Statistics(JSRuntime* rt); + ~Statistics(); + + void beginPhase(Phase phase); + void endPhase(Phase phase); + void endParallelPhase(Phase phase, const GCParallelTask* task); + + // Occasionally, we may be in the middle of something that is tracked by + // this class, and we need to do something unusual (eg evict the nursery) + // that doesn't normally nest within the current phase. Suspend the + // currently tracked phase stack, at which time the caller is free to do + // other tracked operations. + // + // This also happens internally with PHASE_GC_BEGIN and other "non-GC" + // phases. While in these phases, any beginPhase will automatically suspend + // the non-GC phase, until that inner stack is complete, at which time it + // will automatically resume the non-GC phase. Explicit suspensions do not + // get auto-resumed. + void suspendPhases(Phase suspension = PHASE_EXPLICIT_SUSPENSION); + + // Resume a suspended stack of phases. + void resumePhases(); + + void beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, + SliceBudget budget, JS::gcreason::Reason reason); + void endSlice(); + + MOZ_MUST_USE bool startTimingMutator(); + MOZ_MUST_USE bool stopTimingMutator(double& mutator_ms, double& gc_ms); + + // Note when we sweep a zone or compartment. + void sweptZone() { ++zoneStats.sweptZoneCount; } + void sweptCompartment() { ++zoneStats.sweptCompartmentCount; } + + void reset(gc::AbortReason reason) { + MOZ_ASSERT(reason != gc::AbortReason::None); + if (!aborted) + slices.back().resetReason = reason; + } + + void nonincremental(gc::AbortReason reason) { + MOZ_ASSERT(reason != gc::AbortReason::None); + nonincrementalReason_ = reason; + } + + bool nonincremental() const { + return nonincrementalReason_ != gc::AbortReason::None; + } + + const char* nonincrementalReason() const { + return ExplainAbortReason(nonincrementalReason_); + } + + void count(Stat s) { + MOZ_ASSERT(s < STAT_LIMIT); + counts[s]++; + } + + void beginNurseryCollection(JS::gcreason::Reason reason); + void endNurseryCollection(JS::gcreason::Reason reason); + + int64_t beginSCC(); + void endSCC(unsigned scc, int64_t start); + + UniqueChars formatCompactSliceMessage() const; + UniqueChars formatCompactSummaryMessage() const; + UniqueChars formatJsonMessage(uint64_t timestamp); + UniqueChars formatDetailedMessage(); + + JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback); + JS::GCNurseryCollectionCallback setNurseryCollectionCallback( + JS::GCNurseryCollectionCallback callback); + + int64_t clearMaxGCPauseAccumulator(); + int64_t getMaxGCPauseSinceClear(); + + // Return the current phase, suppressing the synthetic PHASE_MUTATOR phase. + Phase currentPhase() { + if (phaseNestingDepth == 0) + return PHASE_NONE; + if (phaseNestingDepth == 1) + return phaseNesting[0] == PHASE_MUTATOR ? PHASE_NONE : phaseNesting[0]; + return phaseNesting[phaseNestingDepth - 1]; + } + + static const size_t MAX_NESTING = 20; + + struct SliceData { + SliceData(SliceBudget budget, JS::gcreason::Reason reason, int64_t start, + double startTimestamp, size_t startFaults, gc::State initialState) + : budget(budget), reason(reason), + initialState(initialState), + finalState(gc::State::NotActive), + resetReason(gc::AbortReason::None), + start(start), startTimestamp(startTimestamp), + startFaults(startFaults) + { + for (auto i : mozilla::MakeRange(NumTimingArrays)) + mozilla::PodArrayZero(phaseTimes[i]); + } + + SliceBudget budget; + JS::gcreason::Reason reason; + gc::State initialState, finalState; + gc::AbortReason resetReason; + int64_t start, end; + double startTimestamp, endTimestamp; + size_t startFaults, endFaults; + PhaseTimeTable phaseTimes; + + int64_t duration() const { return end - start; } + bool wasReset() const { return resetReason != gc::AbortReason::None; } + }; + + typedef Vector<SliceData, 8, SystemAllocPolicy> SliceDataVector; + typedef SliceDataVector::ConstRange SliceRange; + + SliceRange sliceRange() const { return slices.all(); } + size_t slicesLength() const { return slices.length(); } + + /* Print total profile times on shutdown. */ + void printTotalProfileTimes(); + + private: + JSRuntime* runtime; + + int64_t startupTime; + + /* File pointer used for MOZ_GCTIMER output. */ + FILE* fp; + + /* + * GCs can't really nest, but a second GC can be triggered from within the + * JSGC_END callback. + */ + int gcDepth; + + ZoneGCStats zoneStats; + + JSGCInvocationKind gckind; + + gc::AbortReason nonincrementalReason_; + + SliceDataVector slices; + + /* Most recent time when the given phase started. */ + int64_t phaseStartTimes[PHASE_LIMIT]; + + /* Bookkeeping for GC timings when timingMutator is true */ + int64_t timedGCStart; + int64_t timedGCTime; + + /* Total time in a given phase for this GC. */ + PhaseTimeTable phaseTimes; + + /* Total time in a given phase over all GCs. */ + PhaseTimeTable phaseTotals; + + /* Number of events of this type for this GC. */ + unsigned int counts[STAT_LIMIT]; + + /* Allocated space before the GC started. */ + size_t preBytes; + + /* Records the maximum GC pause in an API-controlled interval (in us). */ + mutable int64_t maxPauseInInterval; + + /* Phases that are currently on stack. */ + Phase phaseNesting[MAX_NESTING]; + size_t phaseNestingDepth; + size_t activeDagSlot; + + /* + * Certain phases can interrupt the phase stack, eg callback phases. When + * this happens, we move the suspended phases over to a sepearate list, + * terminated by a dummy PHASE_SUSPENSION phase (so that we can nest + * suspensions by suspending multiple stacks with a PHASE_SUSPENSION in + * between). + */ + Phase suspendedPhases[MAX_NESTING * 3]; + size_t suspended; + + /* Sweep times for SCCs of compartments. */ + Vector<int64_t, 0, SystemAllocPolicy> sccTimes; + + JS::GCSliceCallback sliceCallback; + JS::GCNurseryCollectionCallback nurseryCollectionCallback; + + /* + * True if we saw an OOM while allocating slices. The statistics for this + * GC will be invalid. + */ + bool aborted; + + /* Profiling data. */ + + enum class ProfileKey + { + Total, +#define DEFINE_TIME_KEY(name, text, phase) \ + name, +FOR_EACH_GC_PROFILE_TIME(DEFINE_TIME_KEY) +#undef DEFINE_TIME_KEY + KeyCount + }; + + using ProfileTimes = mozilla::EnumeratedArray<ProfileKey, ProfileKey::KeyCount, int64_t>; + + int64_t profileThreshold_; + bool enableProfiling_; + ProfileTimes totalTimes_; + uint64_t sliceCount_; + + void beginGC(JSGCInvocationKind kind); + void endGC(); + + void recordPhaseEnd(Phase phase); + + void gcDuration(int64_t* total, int64_t* maxPause) const; + void sccDurations(int64_t* total, int64_t* maxPause); + void printStats(); + + UniqueChars formatCompactSlicePhaseTimes(const PhaseTimeTable phaseTimes) const; + + UniqueChars formatDetailedDescription(); + UniqueChars formatDetailedSliceDescription(unsigned i, const SliceData& slice); + UniqueChars formatDetailedPhaseTimes(const PhaseTimeTable phaseTimes); + UniqueChars formatDetailedTotals(); + + UniqueChars formatJsonDescription(uint64_t timestamp); + UniqueChars formatJsonSliceDescription(unsigned i, const SliceData& slice); + UniqueChars formatJsonPhaseTimes(const PhaseTimeTable phaseTimes); + + double computeMMU(int64_t resolution) const; + + void printSliceProfile(); + static void printProfileHeader(); + static void printProfileTimes(const ProfileTimes& times); +}; + +struct MOZ_RAII AutoGCSlice +{ + AutoGCSlice(Statistics& stats, const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, + SliceBudget budget, JS::gcreason::Reason reason) + : stats(stats) + { + stats.beginSlice(zoneStats, gckind, budget, reason); + } + ~AutoGCSlice() { stats.endSlice(); } + + Statistics& stats; +}; + +struct MOZ_RAII AutoPhase +{ + AutoPhase(Statistics& stats, Phase phase) + : stats(stats), task(nullptr), phase(phase), enabled(true) + { + stats.beginPhase(phase); + } + + AutoPhase(Statistics& stats, bool condition, Phase phase) + : stats(stats), task(nullptr), phase(phase), enabled(condition) + { + if (enabled) + stats.beginPhase(phase); + } + + AutoPhase(Statistics& stats, const GCParallelTask& task, Phase phase) + : stats(stats), task(&task), phase(phase), enabled(true) + { + if (enabled) + stats.beginPhase(phase); + } + + ~AutoPhase() { + if (enabled) { + if (task) + stats.endParallelPhase(phase, task); + else + stats.endPhase(phase); + } + } + + Statistics& stats; + const GCParallelTask* task; + Phase phase; + bool enabled; +}; + +struct MOZ_RAII AutoSCC +{ + AutoSCC(Statistics& stats, unsigned scc) + : stats(stats), scc(scc) + { + start = stats.beginSCC(); + } + ~AutoSCC() { + stats.endSCC(scc, start); + } + + Statistics& stats; + unsigned scc; + int64_t start; +}; + +} /* namespace gcstats */ +} /* namespace js */ + +#endif /* gc_Statistics_h */ |