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/. */
7 #include "gc/Statistics.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/Sprintf.h"
11 #include "mozilla/TimeStamp.h"
16 #include <type_traits>
19 #include "gc/GCInternals.h"
20 #include "gc/Memory.h"
21 #include "js/Printer.h"
22 #include "util/GetPidProvider.h"
23 #include "util/Text.h"
24 #include "vm/JSONPrinter.h"
25 #include "vm/Runtime.h"
28 #include "gc/PrivateIterators-inl.h"
31 using namespace js::gc
;
32 using namespace js::gcstats
;
34 using mozilla::DebugOnly
;
35 using mozilla::EnumeratedArray
;
37 using mozilla::TimeDuration
;
38 using mozilla::TimeStamp
;
40 using JS::SliceBudget
;
42 static const size_t BYTES_PER_MB
= 1024 * 1024;
45 * If this fails, then you can either delete this assertion and allow all
46 * larger-numbered reasons to pile up in the last telemetry bucket, or switch
47 * to GC_REASON_3 and bump the max value.
49 static_assert(JS::GCReason::NUM_TELEMETRY_REASONS
>= JS::GCReason::NUM_REASONS
);
51 static inline auto AllPhaseKinds() {
52 return mozilla::MakeEnumeratedRange(PhaseKind::FIRST
, PhaseKind::LIMIT
);
55 static inline auto MajorGCPhaseKinds() {
56 return mozilla::MakeEnumeratedRange(PhaseKind::GC_BEGIN
,
57 PhaseKind(size_t(PhaseKind::GC_END
) + 1));
60 static const char* ExplainGCOptions(JS::GCOptions options
) {
62 case JS::GCOptions::Normal
:
64 case JS::GCOptions::Shrink
:
66 case JS::GCOptions::Shutdown
:
70 MOZ_CRASH("Unexpected GCOptions value");
73 JS_PUBLIC_API
const char* JS::ExplainGCReason(JS::GCReason reason
) {
75 #define SWITCH_REASON(name, _) \
76 case JS::GCReason::name: \
78 GCREASONS(SWITCH_REASON
)
81 case JS::GCReason::NO_REASON
:
85 MOZ_CRASH("bad GC reason");
89 JS_PUBLIC_API
bool JS::InternalGCReason(JS::GCReason reason
) {
90 return reason
< JS::GCReason::FIRST_FIREFOX_REASON
;
93 const char* js::gcstats::ExplainAbortReason(GCAbortReason reason
) {
95 #define SWITCH_REASON(name, _) \
96 case GCAbortReason::name: \
98 GC_ABORT_REASONS(SWITCH_REASON
)
101 MOZ_CRASH("bad GC abort reason");
106 static FILE* MaybeOpenFileFromEnv(const char* env
,
107 FILE* defaultFile
= nullptr) {
108 const char* value
= getenv(env
);
114 if (strcmp(value
, "none") == 0) {
116 } else if (strcmp(value
, "stdout") == 0) {
118 } else if (strcmp(value
, "stderr") == 0) {
122 if (value
[0] != '/') {
123 const char* dir
= getenv("MOZ_UPLOAD_DIR");
125 SprintfLiteral(path
, "%s/%s", dir
, value
);
130 file
= fopen(value
, "a");
131 if (!file
|| setvbuf(file
, nullptr, _IOLBF
, 256) != 0) {
132 perror("Error opening log file");
133 MOZ_CRASH("Failed to open log file.");
140 struct PhaseKindInfo
{
142 uint8_t telemetryBucket
;
146 // PhaseInfo objects form a tree.
151 Phase nextWithPhaseKind
;
158 // A table of PhaseInfo indexed by Phase.
159 using PhaseTable
= EnumeratedArray
<Phase
, PhaseInfo
, size_t(Phase::LIMIT
)>;
161 // A table of PhaseKindInfo indexed by PhaseKind.
162 using PhaseKindTable
=
163 EnumeratedArray
<PhaseKind
, PhaseKindInfo
, size_t(PhaseKind::LIMIT
)>;
165 #include "gc/StatsPhasesGenerated.inc"
167 // Iterate the phases in a phase kind.
172 explicit PhaseIter(PhaseKind kind
) : phase(phaseKinds
[kind
].firstPhase
) {}
173 bool done() const { return phase
== Phase::NONE
; }
174 void next() { phase
= phases
[phase
].nextWithPhaseKind
; }
175 Phase
get() const { return phase
; }
176 operator Phase() const { return phase
; }
179 static double t(TimeDuration duration
) { return duration
.ToMilliseconds(); }
181 static TimeDuration
TimeBetween(TimeStamp start
, TimeStamp end
) {
183 MOZ_ASSERT(end
>= start
);
185 // Sadly this happens sometimes.
187 return TimeDuration::Zero();
193 inline JSContext
* Statistics::context() {
194 return gc
->rt
->mainContextFromOwnThread();
197 inline Phase
Statistics::currentPhase() const {
198 return phaseStack
.empty() ? Phase::NONE
: phaseStack
.back();
201 PhaseKind
Statistics::currentPhaseKind() const {
202 // Public API to get the current phase kind, suppressing the synthetic
203 // PhaseKind::MUTATOR phase.
205 Phase phase
= currentPhase();
206 MOZ_ASSERT_IF(phase
== Phase::MUTATOR
, phaseStack
.length() == 1);
207 if (phase
== Phase::NONE
|| phase
== Phase::MUTATOR
) {
208 return PhaseKind::NONE
;
211 return phases
[phase
].phaseKind
;
214 static Phase
LookupPhaseWithParent(PhaseKind phaseKind
, Phase parentPhase
) {
215 for (PhaseIter
phase(phaseKind
); !phase
.done(); phase
.next()) {
216 if (phases
[phase
].parent
== parentPhase
) {
224 static const char* PhaseKindName(PhaseKind kind
) {
225 if (kind
== PhaseKind::NONE
) {
229 return phaseKinds
[kind
].name
;
232 Phase
Statistics::lookupChildPhase(PhaseKind phaseKind
) const {
233 if (phaseKind
== PhaseKind::IMPLICIT_SUSPENSION
) {
234 return Phase::IMPLICIT_SUSPENSION
;
236 if (phaseKind
== PhaseKind::EXPLICIT_SUSPENSION
) {
237 return Phase::EXPLICIT_SUSPENSION
;
240 MOZ_ASSERT(phaseKind
< PhaseKind::LIMIT
);
242 // Search all expanded phases that correspond to the required
243 // phase to find the one whose parent is the current expanded phase.
244 Phase phase
= LookupPhaseWithParent(phaseKind
, currentPhase());
246 if (phase
== Phase::NONE
) {
247 MOZ_CRASH_UNSAFE_PRINTF(
248 "Child phase kind %s not found under current phase kind %s",
249 PhaseKindName(phaseKind
), PhaseKindName(currentPhaseKind()));
255 inline auto AllPhases() {
256 return mozilla::MakeEnumeratedRange(Phase::FIRST
, Phase::LIMIT
);
259 void Statistics::gcDuration(TimeDuration
* total
, TimeDuration
* maxPause
) const {
260 *total
= *maxPause
= TimeDuration::Zero();
261 for (const auto& slice
: slices_
) {
262 *total
+= slice
.duration();
263 if (slice
.duration() > *maxPause
) {
264 *maxPause
= slice
.duration();
267 if (*maxPause
> maxPauseInInterval
) {
268 maxPauseInInterval
= *maxPause
;
272 void Statistics::sccDurations(TimeDuration
* total
,
273 TimeDuration
* maxPause
) const {
274 *total
= *maxPause
= TimeDuration::Zero();
275 for (const auto& duration
: sccTimes
) {
277 *maxPause
= std::max(*maxPause
, duration
);
281 using FragmentVector
= Vector
<UniqueChars
, 8, SystemAllocPolicy
>;
283 static UniqueChars
Join(const FragmentVector
& fragments
,
284 const char* separator
= "") {
285 const size_t separatorLength
= strlen(separator
);
287 for (size_t i
= 0; i
< fragments
.length(); ++i
) {
288 length
+= fragments
[i
] ? strlen(fragments
[i
].get()) : 0;
289 if (i
< (fragments
.length() - 1)) {
290 length
+= separatorLength
;
294 char* joined
= js_pod_malloc
<char>(length
+ 1);
296 return UniqueChars();
299 joined
[length
] = '\0';
300 char* cursor
= joined
;
301 for (size_t i
= 0; i
< fragments
.length(); ++i
) {
303 strcpy(cursor
, fragments
[i
].get());
305 cursor
+= fragments
[i
] ? strlen(fragments
[i
].get()) : 0;
306 if (i
< (fragments
.length() - 1)) {
307 if (separatorLength
) {
308 strcpy(cursor
, separator
);
310 cursor
+= separatorLength
;
314 return UniqueChars(joined
);
317 static TimeDuration
SumChildTimes(Phase phase
,
318 const Statistics::PhaseTimes
& phaseTimes
) {
320 for (phase
= phases
[phase
].firstChild
; phase
!= Phase::NONE
;
321 phase
= phases
[phase
].nextSibling
) {
322 total
+= phaseTimes
[phase
];
327 UniqueChars
Statistics::formatCompactSliceMessage() const {
328 // Skip if we OOM'ed.
329 if (slices_
.length() == 0) {
330 return UniqueChars(nullptr);
333 const size_t index
= slices_
.length() - 1;
334 const SliceData
& slice
= slices_
.back();
336 char budgetDescription
[200];
337 slice
.budget
.describe(budgetDescription
, sizeof(budgetDescription
) - 1);
340 "GC Slice %u - Pause: %.3fms of %s budget (@ %.3fms); Reason: %s; Reset: "
343 SprintfLiteral(buffer
, format
, index
, t(slice
.duration()), budgetDescription
,
344 t(slice
.start
- slices_
[0].start
),
345 ExplainGCReason(slice
.reason
),
346 slice
.wasReset() ? "yes - " : "no",
347 slice
.wasReset() ? ExplainAbortReason(slice
.resetReason
) : "");
349 FragmentVector fragments
;
350 if (!fragments
.append(DuplicateString(buffer
)) ||
352 formatCompactSlicePhaseTimes(slices_
[index
].phaseTimes
))) {
353 return UniqueChars(nullptr);
355 return Join(fragments
);
358 UniqueChars
Statistics::formatCompactSummaryMessage() const {
359 FragmentVector fragments
;
360 if (!fragments
.append(DuplicateString("Summary - "))) {
361 return UniqueChars(nullptr);
364 TimeDuration total
, longest
;
365 gcDuration(&total
, &longest
);
367 const double mmu20
= computeMMU(TimeDuration::FromMilliseconds(20));
368 const double mmu50
= computeMMU(TimeDuration::FromMilliseconds(50));
371 if (!nonincremental()) {
372 SprintfLiteral(buffer
,
373 "Max Pause: %.3fms; MMU 20ms: %.1f%%; MMU 50ms: %.1f%%; "
375 t(longest
), mmu20
* 100., mmu50
* 100., t(total
));
377 SprintfLiteral(buffer
, "Non-Incremental: %.3fms (%s); ", t(total
),
378 ExplainAbortReason(nonincrementalReason_
));
380 if (!fragments
.append(DuplicateString(buffer
))) {
381 return UniqueChars(nullptr);
384 SprintfLiteral(buffer
,
385 "Zones: %zu of %zu (-%zu); Compartments: %zu of %zu (-%zu); "
386 "HeapSize: %.3f MiB; "
387 "HeapChange (abs): %+d (%u); ",
388 zoneStats
.collectedZoneCount
, zoneStats
.zoneCount
,
389 zoneStats
.sweptZoneCount
, zoneStats
.collectedCompartmentCount
,
390 zoneStats
.compartmentCount
, zoneStats
.sweptCompartmentCount
,
391 double(preTotalHeapBytes
) / BYTES_PER_MB
,
392 int32_t(counts
[COUNT_NEW_CHUNK
] - counts
[COUNT_DESTROY_CHUNK
]),
393 counts
[COUNT_NEW_CHUNK
] + counts
[COUNT_DESTROY_CHUNK
]);
394 if (!fragments
.append(DuplicateString(buffer
))) {
395 return UniqueChars(nullptr);
398 MOZ_ASSERT_IF(counts
[COUNT_ARENA_RELOCATED
],
399 gcOptions
== JS::GCOptions::Shrink
);
400 if (gcOptions
== JS::GCOptions::Shrink
) {
402 buffer
, "Kind: %s; Relocated: %.3f MiB; ", ExplainGCOptions(gcOptions
),
403 double(ArenaSize
* counts
[COUNT_ARENA_RELOCATED
]) / BYTES_PER_MB
);
404 if (!fragments
.append(DuplicateString(buffer
))) {
405 return UniqueChars(nullptr);
409 return Join(fragments
);
412 UniqueChars
Statistics::formatCompactSlicePhaseTimes(
413 const PhaseTimes
& phaseTimes
) const {
414 static const TimeDuration MaxUnaccountedTime
=
415 TimeDuration::FromMicroseconds(100);
417 FragmentVector fragments
;
419 for (auto phase
: AllPhases()) {
420 DebugOnly
<uint8_t> level
= phases
[phase
].depth
;
421 MOZ_ASSERT(level
< 4);
423 TimeDuration ownTime
= phaseTimes
[phase
];
424 TimeDuration childTime
= SumChildTimes(phase
, phaseTimes
);
425 if (ownTime
> MaxUnaccountedTime
) {
426 SprintfLiteral(buffer
, "%s: %.3fms", phases
[phase
].name
, t(ownTime
));
427 if (!fragments
.append(DuplicateString(buffer
))) {
428 return UniqueChars(nullptr);
431 if (childTime
&& (ownTime
- childTime
) > MaxUnaccountedTime
) {
432 MOZ_ASSERT(level
< 3);
433 SprintfLiteral(buffer
, "%s: %.3fms", "Other", t(ownTime
- childTime
));
434 if (!fragments
.append(DuplicateString(buffer
))) {
435 return UniqueChars(nullptr);
440 return Join(fragments
, ", ");
443 UniqueChars
Statistics::formatDetailedMessage() const {
444 FragmentVector fragments
;
446 if (!fragments
.append(formatDetailedDescription())) {
447 return UniqueChars(nullptr);
450 if (!slices_
.empty()) {
451 for (unsigned i
= 0; i
< slices_
.length(); i
++) {
452 if (!fragments
.append(formatDetailedSliceDescription(i
, slices_
[i
]))) {
453 return UniqueChars(nullptr);
455 if (!fragments
.append(formatDetailedPhaseTimes(slices_
[i
].phaseTimes
))) {
456 return UniqueChars(nullptr);
460 if (!fragments
.append(formatDetailedTotals())) {
461 return UniqueChars(nullptr);
463 if (!fragments
.append(formatDetailedPhaseTimes(phaseTimes
))) {
464 return UniqueChars(nullptr);
467 return Join(fragments
);
470 UniqueChars
Statistics::formatDetailedDescription() const {
471 TimeDuration sccTotal
, sccLongest
;
472 sccDurations(&sccTotal
, &sccLongest
);
474 const double mmu20
= computeMMU(TimeDuration::FromMilliseconds(20));
475 const double mmu50
= computeMMU(TimeDuration::FromMilliseconds(50));
478 "=================================================================\n\
479 Invocation Kind: %s\n\
482 Zones Collected: %d of %d (-%d)\n\
483 Compartments Collected: %d of %d (-%d)\n\
484 MinorGCs since last GC: %d\n\
485 Store Buffer Overflows: %d\n\
486 MMU 20ms:%.1f%%; 50ms:%.1f%%\n\
487 SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\
488 HeapSize: %.3f MiB\n\
489 Chunk Delta (magnitude): %+d (%d)\n\
490 Arenas Relocated: %.3f MiB\n\
495 buffer
, format
, ExplainGCOptions(gcOptions
),
496 ExplainGCReason(slices_
[0].reason
), nonincremental() ? "no - " : "yes",
497 nonincremental() ? ExplainAbortReason(nonincrementalReason_
) : "",
498 zoneStats
.collectedZoneCount
, zoneStats
.zoneCount
,
499 zoneStats
.sweptZoneCount
, zoneStats
.collectedCompartmentCount
,
500 zoneStats
.compartmentCount
, zoneStats
.sweptCompartmentCount
,
501 getCount(COUNT_MINOR_GC
), getCount(COUNT_STOREBUFFER_OVERFLOW
),
502 mmu20
* 100., mmu50
* 100., t(sccTotal
), t(sccLongest
),
503 double(preTotalHeapBytes
) / BYTES_PER_MB
,
504 getCount(COUNT_NEW_CHUNK
) - getCount(COUNT_DESTROY_CHUNK
),
505 getCount(COUNT_NEW_CHUNK
) + getCount(COUNT_DESTROY_CHUNK
),
506 double(ArenaSize
* getCount(COUNT_ARENA_RELOCATED
)) / BYTES_PER_MB
);
508 return DuplicateString(buffer
);
511 UniqueChars
Statistics::formatDetailedSliceDescription(
512 unsigned i
, const SliceData
& slice
) const {
513 char budgetDescription
[200];
514 slice
.budget
.describe(budgetDescription
, sizeof(budgetDescription
) - 1);
518 ---- Slice %u ----\n\
523 Page Faults: %" PRIu64
525 Pause: %.3fms of %s budget (@ %.3fms)\n\
528 char triggerBuffer
[100] = "n/a";
530 Trigger trigger
= slice
.trigger
.value();
531 SprintfLiteral(triggerBuffer
, "%.3f MiB of %.3f MiB threshold\n",
532 double(trigger
.amount
) / BYTES_PER_MB
,
533 double(trigger
.threshold
) / BYTES_PER_MB
);
538 buffer
, format
, i
, ExplainGCReason(slice
.reason
), triggerBuffer
,
539 slice
.wasReset() ? "yes - " : "no",
540 slice
.wasReset() ? ExplainAbortReason(slice
.resetReason
) : "",
541 gc::StateName(slice
.initialState
), gc::StateName(slice
.finalState
),
542 uint64_t(slice
.endFaults
- slice
.startFaults
), t(slice
.duration()),
543 budgetDescription
, t(slice
.start
- slices_
[0].start
));
544 return DuplicateString(buffer
);
547 static bool IncludePhase(TimeDuration duration
) {
548 // Don't include durations that will print as "0.000ms".
549 return duration
.ToMilliseconds() >= 0.001;
552 UniqueChars
Statistics::formatDetailedPhaseTimes(
553 const PhaseTimes
& phaseTimes
) const {
554 static const TimeDuration MaxUnaccountedChildTime
=
555 TimeDuration::FromMicroseconds(50);
557 FragmentVector fragments
;
559 for (auto phase
: AllPhases()) {
560 uint8_t level
= phases
[phase
].depth
;
561 TimeDuration ownTime
= phaseTimes
[phase
];
562 TimeDuration childTime
= SumChildTimes(phase
, phaseTimes
);
563 if (IncludePhase(ownTime
)) {
564 SprintfLiteral(buffer
, " %*s%s: %.3fms\n", level
* 2, "",
565 phases
[phase
].name
, t(ownTime
));
566 if (!fragments
.append(DuplicateString(buffer
))) {
567 return UniqueChars(nullptr);
570 if (childTime
&& (ownTime
- childTime
) > MaxUnaccountedChildTime
) {
571 SprintfLiteral(buffer
, " %*s%s: %.3fms\n", (level
+ 1) * 2, "",
572 "Other", t(ownTime
- childTime
));
573 if (!fragments
.append(DuplicateString(buffer
))) {
574 return UniqueChars(nullptr);
579 return Join(fragments
);
582 UniqueChars
Statistics::formatDetailedTotals() const {
583 TimeDuration total
, longest
;
584 gcDuration(&total
, &longest
);
589 Total Time: %.3fms\n\
593 SprintfLiteral(buffer
, format
, t(total
), t(longest
));
594 return DuplicateString(buffer
);
597 void Statistics::formatJsonSlice(size_t sliceNum
, JSONPrinter
& json
) const {
599 * We number each of the slice properties to keep the code in
600 * GCTelemetry.sys.mjs in sync. See MAX_SLICE_KEYS.
603 formatJsonSliceDescription(sliceNum
, slices_
[sliceNum
], json
); // # 1-11
605 json
.beginObjectProperty("times"); // # 12
606 formatJsonPhaseTimes(slices_
[sliceNum
].phaseTimes
, json
);
612 UniqueChars
Statistics::renderJsonSlice(size_t sliceNum
) const {
613 Sprinter
printer(nullptr, false);
614 if (!printer
.init()) {
615 return UniqueChars(nullptr);
617 JSONPrinter
json(printer
, false);
619 formatJsonSlice(sliceNum
, json
);
620 return printer
.release();
623 UniqueChars
Statistics::renderNurseryJson() const {
624 Sprinter
printer(nullptr, false);
625 if (!printer
.init()) {
626 return UniqueChars(nullptr);
628 JSONPrinter
json(printer
, false);
629 gc
->nursery().renderProfileJSON(json
);
630 return printer
.release();
634 void Statistics::log(const char* fmt
, ...) {
638 TimeDuration sinceStart
=
639 TimeBetween(TimeStamp::FirstTimeStamp(), TimeStamp::Now());
640 fprintf(gcDebugFile
, "%12.3f: ", sinceStart
.ToMicroseconds());
641 vfprintf(gcDebugFile
, fmt
, args
);
642 fprintf(gcDebugFile
, "\n");
649 UniqueChars
Statistics::renderJsonMessage() const {
651 * The format of the JSON message is specified by the GCMajorMarkerPayload
652 * type in profiler.firefox.com
653 * https://github.com/firefox-devtools/profiler/blob/master/src/types/markers.js#L62
655 * All the properties listed here are created within the timings property
656 * of the GCMajor marker.
659 return DuplicateString("{status:\"aborted\"}"); // May return nullptr
662 Sprinter
printer(nullptr, false);
663 if (!printer
.init()) {
664 return UniqueChars(nullptr);
666 JSONPrinter
json(printer
, false);
669 json
.property("status", "completed");
670 formatJsonDescription(json
);
672 json
.beginObjectProperty("totals");
673 formatJsonPhaseTimes(phaseTimes
, json
);
678 return printer
.release();
681 void Statistics::formatJsonDescription(JSONPrinter
& json
) const {
682 // If you change JSON properties here, please update:
684 // https://github.com/firefox-devtools/profiler
686 TimeDuration total
, longest
;
687 gcDuration(&total
, &longest
);
688 json
.property("max_pause", longest
, JSONPrinter::MILLISECONDS
);
689 json
.property("total_time", total
, JSONPrinter::MILLISECONDS
);
690 // We might be able to omit reason if profiler.firefox.com was able to retrive
691 // it from the first slice. But it doesn't do this yet.
692 json
.property("reason", ExplainGCReason(slices_
[0].reason
));
693 json
.property("zones_collected", zoneStats
.collectedZoneCount
);
694 json
.property("total_zones", zoneStats
.zoneCount
);
695 json
.property("total_compartments", zoneStats
.compartmentCount
);
696 json
.property("minor_gcs", getCount(COUNT_MINOR_GC
));
697 json
.property("minor_gc_number", gc
->minorGCCount());
698 json
.property("major_gc_number", gc
->majorGCCount());
699 uint32_t storebufferOverflows
= getCount(COUNT_STOREBUFFER_OVERFLOW
);
700 if (storebufferOverflows
) {
701 json
.property("store_buffer_overflows", storebufferOverflows
);
703 json
.property("slices", slices_
.length());
705 const double mmu20
= computeMMU(TimeDuration::FromMilliseconds(20));
706 const double mmu50
= computeMMU(TimeDuration::FromMilliseconds(50));
707 json
.property("mmu_20ms", int(mmu20
* 100));
708 json
.property("mmu_50ms", int(mmu50
* 100));
710 TimeDuration sccTotal
, sccLongest
;
711 sccDurations(&sccTotal
, &sccLongest
);
712 json
.property("scc_sweep_total", sccTotal
, JSONPrinter::MILLISECONDS
);
713 json
.property("scc_sweep_max_pause", sccLongest
, JSONPrinter::MILLISECONDS
);
715 if (nonincrementalReason_
!= GCAbortReason::None
) {
716 json
.property("nonincremental_reason",
717 ExplainAbortReason(nonincrementalReason_
));
719 json
.property("allocated_bytes", preTotalHeapBytes
);
720 json
.property("post_heap_size", postTotalHeapBytes
);
722 uint32_t addedChunks
= getCount(COUNT_NEW_CHUNK
);
724 json
.property("added_chunks", addedChunks
);
726 uint32_t removedChunks
= getCount(COUNT_DESTROY_CHUNK
);
728 json
.property("removed_chunks", removedChunks
);
730 json
.property("major_gc_number", startingMajorGCNumber
);
731 json
.property("minor_gc_number", startingMinorGCNumber
);
732 json
.property("slice_number", startingSliceNumber
);
735 void Statistics::formatJsonSliceDescription(unsigned i
, const SliceData
& slice
,
736 JSONPrinter
& json
) const {
737 // If you change JSON properties here, please update:
739 // https://github.com/firefox-devtools/profiler
741 char budgetDescription
[200];
742 slice
.budget
.describe(budgetDescription
, sizeof(budgetDescription
) - 1);
743 TimeStamp originTime
= TimeStamp::ProcessCreation();
745 json
.property("slice", i
);
746 json
.property("pause", slice
.duration(), JSONPrinter::MILLISECONDS
);
747 json
.property("reason", ExplainGCReason(slice
.reason
));
748 json
.property("initial_state", gc::StateName(slice
.initialState
));
749 json
.property("final_state", gc::StateName(slice
.finalState
));
750 json
.property("budget", budgetDescription
);
751 json
.property("major_gc_number", startingMajorGCNumber
);
753 Trigger trigger
= slice
.trigger
.value();
754 json
.property("trigger_amount", trigger
.amount
);
755 json
.property("trigger_threshold", trigger
.threshold
);
757 MOZ_ASSERT(slice
.endFaults
>= slice
.startFaults
);
758 size_t numFaults
= slice
.endFaults
- slice
.startFaults
;
759 if (numFaults
!= 0) {
760 json
.property("page_faults", numFaults
);
762 json
.property("start_timestamp", TimeBetween(originTime
, slice
.start
),
763 JSONPrinter::SECONDS
);
766 void Statistics::formatJsonPhaseTimes(const PhaseTimes
& phaseTimes
,
767 JSONPrinter
& json
) const {
768 for (auto phase
: AllPhases()) {
769 TimeDuration ownTime
= phaseTimes
[phase
];
770 if (!ownTime
.IsZero()) {
771 json
.property(phases
[phase
].path
, ownTime
, JSONPrinter::MILLISECONDS
);
776 Statistics::Statistics(GCRuntime
* gc
)
778 gcTimerFile(nullptr),
779 gcDebugFile(nullptr),
780 nonincrementalReason_(GCAbortReason::None
),
781 creationTime_(TimeStamp::Now()),
782 tenuredAllocsSinceMinorGC(0),
783 preTotalHeapBytes(0),
784 postTotalHeapBytes(0),
785 preCollectedHeapBytes(0),
786 startingMinorGCNumber(0),
787 startingMajorGCNumber(0),
788 startingSliceNumber(0),
789 sliceCallback(nullptr),
791 enableProfiling_(false),
793 for (auto& count
: counts
) {
797 for (auto& stat
: stats
) {
802 for (const auto& duration
: totalTimes_
) {
803 using ElementType
= std::remove_reference_t
<decltype(duration
)>;
804 static_assert(!std::is_trivially_constructible_v
<ElementType
>,
805 "Statistics::Statistics will only initialize "
806 "totalTimes_'s elements if their default constructor is "
808 MOZ_ASSERT(duration
.IsZero(),
809 "totalTimes_ default-initialization should have "
810 "default-initialized every element of totalTimes_ to zero");
814 MOZ_ALWAYS_TRUE(phaseStack
.reserve(MAX_PHASE_NESTING
));
815 MOZ_ALWAYS_TRUE(suspendedPhases
.reserve(MAX_SUSPENDED_PHASES
));
817 gcTimerFile
= MaybeOpenFileFromEnv("MOZ_GCTIMER");
818 gcDebugFile
= MaybeOpenFileFromEnv("JS_GC_DEBUG");
819 gcProfileFile
= MaybeOpenFileFromEnv("JS_GC_PROFILE_FILE", stderr
);
821 gc::ReadProfileEnv("JS_GC_PROFILE",
822 "Report major GCs taking more than N milliseconds for "
823 "all or just the main runtime\n",
824 &enableProfiling_
, &profileWorkers_
, &profileThreshold_
);
827 Statistics::~Statistics() {
828 if (gcTimerFile
&& gcTimerFile
!= stdout
&& gcTimerFile
!= stderr
) {
831 if (gcDebugFile
&& gcDebugFile
!= stdout
&& gcDebugFile
!= stderr
) {
837 bool Statistics::initialize() {
839 // Sanity check generated tables.
840 for (auto i
: AllPhases()) {
841 auto parent
= phases
[i
].parent
;
842 if (parent
!= Phase::NONE
) {
843 MOZ_ASSERT(phases
[i
].depth
== phases
[parent
].depth
+ 1);
845 auto firstChild
= phases
[i
].firstChild
;
846 if (firstChild
!= Phase::NONE
) {
847 MOZ_ASSERT(i
== phases
[firstChild
].parent
);
848 MOZ_ASSERT(phases
[i
].depth
== phases
[firstChild
].depth
- 1);
850 auto nextSibling
= phases
[i
].nextSibling
;
851 if (nextSibling
!= Phase::NONE
) {
852 MOZ_ASSERT(parent
== phases
[nextSibling
].parent
);
853 MOZ_ASSERT(phases
[i
].depth
== phases
[nextSibling
].depth
);
855 auto nextWithPhaseKind
= phases
[i
].nextWithPhaseKind
;
856 if (nextWithPhaseKind
!= Phase::NONE
) {
857 MOZ_ASSERT(phases
[i
].phaseKind
== phases
[nextWithPhaseKind
].phaseKind
);
858 MOZ_ASSERT(parent
!= phases
[nextWithPhaseKind
].parent
);
861 for (auto i
: AllPhaseKinds()) {
862 MOZ_ASSERT(phases
[phaseKinds
[i
].firstPhase
].phaseKind
== i
);
863 for (auto j
: AllPhaseKinds()) {
864 MOZ_ASSERT_IF(i
!= j
, phaseKinds
[i
].telemetryBucket
!=
865 phaseKinds
[j
].telemetryBucket
);
873 JS::GCSliceCallback
Statistics::setSliceCallback(
874 JS::GCSliceCallback newCallback
) {
875 JS::GCSliceCallback oldCallback
= sliceCallback
;
876 sliceCallback
= newCallback
;
880 TimeDuration
Statistics::clearMaxGCPauseAccumulator() {
881 TimeDuration prior
= maxPauseInInterval
;
882 maxPauseInInterval
= TimeDuration::Zero();
886 TimeDuration
Statistics::getMaxGCPauseSinceClear() {
887 return maxPauseInInterval
;
890 // Sum up the time for a phase, including instances of the phase with different
892 static TimeDuration
SumPhase(PhaseKind phaseKind
,
893 const Statistics::PhaseTimes
& times
) {
895 for (PhaseIter
phase(phaseKind
); !phase
.done(); phase
.next()) {
901 static bool CheckSelfTime(Phase parent
, Phase child
,
902 const Statistics::PhaseTimes
& times
,
903 const Statistics::PhaseTimes
& selfTimes
,
904 TimeDuration childTime
) {
905 if (selfTimes
[parent
] < childTime
) {
908 "Parent %s time = %.3fms with %.3fms remaining, child %s time %.3fms\n",
909 phases
[parent
].name
, times
[parent
].ToMilliseconds(),
910 selfTimes
[parent
].ToMilliseconds(), phases
[child
].name
,
911 childTime
.ToMilliseconds());
919 static PhaseKind
FindLongestPhaseKind(const Statistics::PhaseKindTimes
& times
) {
920 TimeDuration longestTime
;
921 PhaseKind phaseKind
= PhaseKind::NONE
;
922 for (auto i
: MajorGCPhaseKinds()) {
923 if (times
[i
] > longestTime
) {
924 longestTime
= times
[i
];
932 static PhaseKind
LongestPhaseSelfTimeInMajorGC(
933 const Statistics::PhaseTimes
& times
) {
934 // Start with total times per expanded phase, including children's times.
935 Statistics::PhaseTimes
selfTimes(times
);
937 // We have the total time spent in each phase, including descendant times.
938 // Loop over the children and subtract their times from their parent's self
940 for (auto i
: AllPhases()) {
941 Phase parent
= phases
[i
].parent
;
942 if (parent
!= Phase::NONE
) {
943 bool ok
= CheckSelfTime(parent
, i
, times
, selfTimes
, times
[i
]);
945 // This happens very occasionally in release builds and frequently
946 // in Windows debug builds. Skip collecting longest phase telemetry
949 MOZ_ASSERT(ok
, "Inconsistent time data; see bug 1400153");
952 return PhaseKind::NONE
;
955 selfTimes
[parent
] -= times
[i
];
959 // Sum expanded phases corresponding to the same phase.
960 Statistics::PhaseKindTimes phaseKindTimes
;
961 for (auto i
: AllPhaseKinds()) {
962 phaseKindTimes
[i
] = SumPhase(i
, selfTimes
);
965 return FindLongestPhaseKind(phaseKindTimes
);
968 void Statistics::printStats() {
971 "OOM during GC statistics collection. The report is unavailable "
974 UniqueChars msg
= formatDetailedMessage();
976 double secSinceStart
=
977 TimeBetween(TimeStamp::ProcessCreation(), slices_
[0].start
)
979 fprintf(gcTimerFile
, "GC(T+%.3fs) %s\n", secSinceStart
, msg
.get());
985 void Statistics::beginGC(JS::GCOptions options
, const TimeStamp
& currentTime
) {
986 slices_
.clearAndFree();
987 sccTimes
.clearAndFree();
989 nonincrementalReason_
= GCAbortReason::None
;
991 preTotalHeapBytes
= gc
->heapSize
.bytes();
993 preCollectedHeapBytes
= 0;
995 startingMajorGCNumber
= gc
->majorGCCount();
996 startingSliceNumber
= gc
->gcNumber();
998 if (gc
->lastGCEndTime()) {
999 timeSinceLastGC
= TimeBetween(gc
->lastGCEndTime(), currentTime
);
1002 totalGCTime_
= TimeDuration::Zero();
1005 void Statistics::measureInitialHeapSize() {
1006 MOZ_ASSERT(preCollectedHeapBytes
== 0);
1007 for (GCZonesIter
zone(gc
); !zone
.done(); zone
.next()) {
1008 preCollectedHeapBytes
+= zone
->gcHeapSize
.bytes();
1012 void Statistics::endGC() {
1013 postTotalHeapBytes
= gc
->heapSize
.bytes();
1018 TimeDuration
Statistics::sumTotalParallelTime(PhaseKind phaseKind
) const {
1020 for (const SliceData
& slice
: slices_
) {
1021 total
+= slice
.totalParallelTimes
[phaseKind
];
1026 void Statistics::sendGCTelemetry() {
1027 JSRuntime
* runtime
= gc
->rt
;
1028 // NOTE: "Compartmental" is term that was deprecated with the
1029 // introduction of zone-based GC, but the old telemetry probe
1030 // continues to be used.
1031 runtime
->metrics().GC_IS_COMPARTMENTAL(!gc
->fullGCRequested
);
1032 runtime
->metrics().GC_ZONE_COUNT(zoneStats
.zoneCount
);
1033 runtime
->metrics().GC_ZONES_COLLECTED(zoneStats
.collectedZoneCount
);
1035 TimeDuration prepareTotal
= phaseTimes
[Phase::PREPARE
];
1036 TimeDuration markTotal
= SumPhase(PhaseKind::MARK
, phaseTimes
);
1037 TimeDuration markRootsTotal
= SumPhase(PhaseKind::MARK_ROOTS
, phaseTimes
);
1039 // Gray and weak marking time is counted under MARK_WEAK and not MARK_GRAY.
1040 TimeDuration markWeakTotal
= SumPhase(PhaseKind::MARK_WEAK
, phaseTimes
);
1041 TimeDuration markGrayNotWeak
=
1042 SumPhase(PhaseKind::MARK_GRAY
, phaseTimes
) +
1043 SumPhase(PhaseKind::MARK_INCOMING_GRAY
, phaseTimes
);
1044 TimeDuration markGrayWeak
= SumPhase(PhaseKind::MARK_GRAY_WEAK
, phaseTimes
);
1045 TimeDuration markGrayTotal
= markGrayNotWeak
+ markGrayWeak
;
1046 TimeDuration markNotGrayOrWeak
= markTotal
- markGrayNotWeak
- markWeakTotal
;
1047 if (markNotGrayOrWeak
< TimeDuration::FromMilliseconds(0)) {
1048 markNotGrayOrWeak
= TimeDuration::Zero();
1051 size_t markCount
= getCount(COUNT_CELLS_MARKED
);
1053 runtime
->metrics().GC_PREPARE_MS(prepareTotal
);
1054 runtime
->metrics().GC_MARK_MS(markNotGrayOrWeak
);
1055 if (markTotal
>= TimeDuration::FromMicroseconds(1)) {
1056 double markRate
= double(markCount
) / t(markTotal
);
1057 runtime
->metrics().GC_MARK_RATE_2(uint32_t(markRate
));
1059 runtime
->metrics().GC_SWEEP_MS(phaseTimes
[Phase::SWEEP
]);
1060 if (gc
->didCompactZones()) {
1061 runtime
->metrics().GC_COMPACT_MS(phaseTimes
[Phase::COMPACT
]);
1063 runtime
->metrics().GC_MARK_ROOTS_US(markRootsTotal
);
1064 runtime
->metrics().GC_MARK_GRAY_MS_2(markGrayTotal
);
1065 runtime
->metrics().GC_MARK_WEAK_MS(markWeakTotal
);
1066 runtime
->metrics().GC_NON_INCREMENTAL(nonincremental());
1067 if (nonincremental()) {
1068 runtime
->metrics().GC_NON_INCREMENTAL_REASON(
1069 uint32_t(nonincrementalReason_
));
1073 // Reset happens non-incrementally, so only the last slice can be reset.
1074 for (size_t i
= 0; i
< slices_
.length() - 1; i
++) {
1075 MOZ_ASSERT(!slices_
[i
].wasReset());
1078 const auto& lastSlice
= slices_
.back();
1079 runtime
->metrics().GC_RESET(lastSlice
.wasReset());
1080 if (lastSlice
.wasReset()) {
1081 runtime
->metrics().GC_RESET_REASON(uint32_t(lastSlice
.resetReason
));
1084 TimeDuration total
, longest
;
1085 gcDuration(&total
, &longest
);
1087 runtime
->metrics().GC_MS(total
);
1088 runtime
->metrics().GC_MAX_PAUSE_MS_2(longest
);
1090 const double mmu50
= computeMMU(TimeDuration::FromMilliseconds(50));
1091 runtime
->metrics().GC_MMU_50(mmu50
* 100.0);
1093 // Record scheduling telemetry for the main runtime but not for workers, which
1094 // are scheduled differently.
1095 if (!runtime
->parentRuntime
&& timeSinceLastGC
) {
1096 runtime
->metrics().GC_TIME_BETWEEN_S(timeSinceLastGC
);
1097 if (!nonincremental()) {
1098 runtime
->metrics().GC_SLICE_COUNT(slices_
.length());
1102 if (!lastSlice
.wasReset() && preCollectedHeapBytes
!= 0) {
1103 size_t bytesSurvived
= 0;
1104 for (ZonesIter
zone(runtime
, WithAtoms
); !zone
.done(); zone
.next()) {
1105 if (zone
->wasCollected()) {
1106 bytesSurvived
+= zone
->gcHeapSize
.retainedBytes();
1110 MOZ_ASSERT(preCollectedHeapBytes
>= bytesSurvived
);
1111 double survivalRate
=
1112 100.0 * double(bytesSurvived
) / double(preCollectedHeapBytes
);
1113 runtime
->metrics().GC_TENURED_SURVIVAL_RATE(survivalRate
);
1115 // Calculate 'effectiveness' in MB / second, on main thread only for now.
1116 if (!runtime
->parentRuntime
) {
1117 size_t bytesFreed
= preCollectedHeapBytes
- bytesSurvived
;
1118 TimeDuration clampedTotal
=
1119 TimeDuration::Max(total
, TimeDuration::FromMilliseconds(1));
1120 double effectiveness
=
1121 (double(bytesFreed
) / BYTES_PER_MB
) / clampedTotal
.ToSeconds();
1122 runtime
->metrics().GC_EFFECTIVENESS(uint32_t(effectiveness
));
1126 // Parallel marking stats.
1127 bool usedParallelMarking
= false;
1128 if (gc
->isParallelMarkingEnabled()) {
1129 TimeDuration wallTime
= SumPhase(PhaseKind::PARALLEL_MARK
, phaseTimes
);
1130 TimeDuration parallelMarkTime
=
1131 sumTotalParallelTime(PhaseKind::PARALLEL_MARK_MARK
);
1132 TimeDuration parallelRunTime
=
1133 parallelMarkTime
+ sumTotalParallelTime(PhaseKind::PARALLEL_MARK_OTHER
);
1134 usedParallelMarking
= wallTime
&& parallelMarkTime
;
1135 if (usedParallelMarking
) {
1136 uint32_t threadCount
= gc
->markers
.length();
1137 double speedup
= parallelMarkTime
/ wallTime
;
1138 double utilization
= parallelRunTime
/ (wallTime
* threadCount
);
1139 runtime
->metrics().GC_PARALLEL_MARK_SPEEDUP(uint32_t(speedup
* 100.0));
1140 runtime
->metrics().GC_PARALLEL_MARK_UTILIZATION(
1141 std::clamp(utilization
* 100.0, 0.0, 100.0));
1142 runtime
->metrics().GC_PARALLEL_MARK_INTERRUPTIONS(
1143 getCount(COUNT_PARALLEL_MARK_INTERRUPTIONS
));
1146 runtime
->metrics().GC_PARALLEL_MARK(usedParallelMarking
);
1149 void Statistics::beginNurseryCollection() {
1150 count(COUNT_MINOR_GC
);
1151 startingMinorGCNumber
= gc
->minorGCCount();
1154 void Statistics::endNurseryCollection() { tenuredAllocsSinceMinorGC
= 0; }
1156 Statistics::SliceData::SliceData(const SliceBudget
& budget
,
1157 Maybe
<Trigger
> trigger
, JS::GCReason reason
,
1158 TimeStamp start
, size_t startFaults
,
1159 gc::State initialState
)
1163 initialState(initialState
),
1165 startFaults(startFaults
) {}
1167 TimeDuration
Statistics::SliceData::duration() const {
1168 return TimeBetween(start
, end
);
1171 void Statistics::beginSlice(const ZoneGCStats
& zoneStats
, JS::GCOptions options
,
1172 const SliceBudget
& budget
, JS::GCReason reason
,
1173 bool budgetWasIncreased
) {
1174 MOZ_ASSERT(phaseStack
.empty() ||
1175 (phaseStack
.length() == 1 && phaseStack
[0] == Phase::MUTATOR
));
1177 this->zoneStats
= zoneStats
;
1179 TimeStamp currentTime
= TimeStamp::Now();
1181 bool first
= !gc
->isIncrementalGCInProgress();
1183 beginGC(options
, currentTime
);
1186 JSRuntime
* runtime
= gc
->rt
;
1187 if (!runtime
->parentRuntime
&& !slices_
.empty()) {
1188 TimeDuration timeSinceLastSlice
=
1189 TimeBetween(slices_
.back().end
, currentTime
);
1190 runtime
->metrics().GC_TIME_BETWEEN_SLICES_MS(timeSinceLastSlice
);
1193 Maybe
<Trigger
> trigger
= recordedTrigger
;
1194 recordedTrigger
.reset();
1196 if (!slices_
.emplaceBack(budget
, trigger
, reason
, currentTime
,
1197 GetPageFaultCount(), gc
->state())) {
1198 // If we are OOM, set a flag to indicate we have missing slice data.
1203 runtime
->metrics().GC_REASON_2(uint32_t(reason
));
1204 runtime
->metrics().GC_BUDGET_WAS_INCREASED(budgetWasIncreased
);
1206 // Slice callbacks should only fire for the outermost level.
1207 if (sliceCallback
) {
1208 JSContext
* cx
= context();
1209 JS::GCDescription
desc(!gc
->fullGCRequested
, false, options
, reason
);
1211 (*sliceCallback
)(cx
, JS::GC_CYCLE_BEGIN
, desc
);
1213 (*sliceCallback
)(cx
, JS::GC_SLICE_BEGIN
, desc
);
1219 void Statistics::endSlice() {
1220 MOZ_ASSERT(phaseStack
.empty() ||
1221 (phaseStack
.length() == 1 && phaseStack
[0] == Phase::MUTATOR
));
1224 auto& slice
= slices_
.back();
1225 slice
.end
= TimeStamp::Now();
1226 slice
.endFaults
= GetPageFaultCount();
1227 slice
.finalState
= gc
->state();
1231 sendSliceTelemetry(slice
);
1235 totalGCTime_
+= slice
.duration();
1238 bool last
= !gc
->isIncrementalGCInProgress();
1250 ShouldPrintProfile(gc
->rt
, enableProfiling_
, profileWorkers_
,
1251 profileThreshold_
, slices_
.back().duration())) {
1252 printSliceProfile();
1255 // Slice callbacks should only fire for the outermost level.
1257 if (sliceCallback
) {
1258 JSContext
* cx
= context();
1259 JS::GCDescription
desc(!gc
->fullGCRequested
, last
, gcOptions
,
1260 slices_
.back().reason
);
1261 (*sliceCallback
)(cx
, JS::GC_SLICE_END
, desc
);
1263 (*sliceCallback
)(cx
, JS::GC_CYCLE_END
, desc
);
1268 // Do this after the slice callback since it uses these values.
1270 for (auto& count
: counts
) {
1274 // Clear the timers at the end of a GC, preserving the data for
1275 // PhaseKind::MUTATOR.
1276 auto mutatorStartTime
= phaseStartTimes
[Phase::MUTATOR
];
1277 auto mutatorTime
= phaseTimes
[Phase::MUTATOR
];
1279 phaseStartTimes
= PhaseTimeStamps();
1281 phaseEndTimes
= PhaseTimeStamps();
1283 phaseTimes
= PhaseTimes();
1285 phaseStartTimes
[Phase::MUTATOR
] = mutatorStartTime
;
1286 phaseTimes
[Phase::MUTATOR
] = mutatorTime
;
1292 void Statistics::sendSliceTelemetry(const SliceData
& slice
) {
1293 JSRuntime
* runtime
= gc
->rt
;
1294 TimeDuration sliceTime
= slice
.duration();
1295 runtime
->metrics().GC_SLICE_MS(sliceTime
);
1297 if (slice
.budget
.isTimeBudget()) {
1298 TimeDuration budgetDuration
= slice
.budget
.timeBudgetDuration();
1299 runtime
->metrics().GC_BUDGET_MS_2(budgetDuration
);
1301 if (IsCurrentlyAnimating(runtime
->lastAnimationTime
, slice
.end
)) {
1302 runtime
->metrics().GC_ANIMATION_MS(sliceTime
);
1305 bool wasLongSlice
= false;
1306 if (sliceTime
> budgetDuration
) {
1307 // Record how long we went over budget.
1308 TimeDuration overrun
= sliceTime
- budgetDuration
;
1309 runtime
->metrics().GC_BUDGET_OVERRUN(overrun
);
1311 // Long GC slices are those that go 50% or 5ms over their budget.
1312 wasLongSlice
= (overrun
> TimeDuration::FromMilliseconds(5)) ||
1313 (overrun
> (budgetDuration
/ int64_t(2)));
1315 // Record the longest phase in any long slice.
1317 PhaseKind longest
= LongestPhaseSelfTimeInMajorGC(slice
.phaseTimes
);
1318 reportLongestPhaseInMajorGC(longest
, [runtime
](auto sample
) {
1319 runtime
->metrics().GC_SLOW_PHASE(sample
);
1322 // If the longest phase was waiting for parallel tasks then record the
1324 if (longest
== PhaseKind::JOIN_PARALLEL_TASKS
) {
1325 PhaseKind longestParallel
=
1326 FindLongestPhaseKind(slice
.maxParallelTimes
);
1327 reportLongestPhaseInMajorGC(longestParallel
, [runtime
](auto sample
) {
1328 runtime
->metrics().GC_SLOW_TASK(sample
);
1334 // Record `wasLongSlice` for all TimeBudget slices.
1335 runtime
->metrics().GC_SLICE_WAS_LONG(wasLongSlice
);
1339 template <typename Fn
>
1340 void Statistics::reportLongestPhaseInMajorGC(PhaseKind longest
, Fn reportFn
) {
1341 if (longest
!= PhaseKind::NONE
) {
1342 uint8_t bucket
= phaseKinds
[longest
].telemetryBucket
;
1347 bool Statistics::startTimingMutator() {
1348 if (phaseStack
.length() != 0) {
1349 // Should only be called from outside of GC.
1350 MOZ_ASSERT(phaseStack
.length() == 1);
1351 MOZ_ASSERT(phaseStack
[0] == Phase::MUTATOR
);
1355 MOZ_ASSERT(suspendedPhases
.empty());
1357 timedGCTime
= TimeDuration::Zero();
1358 phaseStartTimes
[Phase::MUTATOR
] = TimeStamp();
1359 phaseTimes
[Phase::MUTATOR
] = TimeDuration::Zero();
1360 timedGCStart
= TimeStamp();
1362 beginPhase(PhaseKind::MUTATOR
);
1366 bool Statistics::stopTimingMutator(double& mutator_ms
, double& gc_ms
) {
1367 // This should only be called from outside of GC, while timing the mutator.
1368 if (phaseStack
.length() != 1 || phaseStack
[0] != Phase::MUTATOR
) {
1372 endPhase(PhaseKind::MUTATOR
);
1373 mutator_ms
= t(phaseTimes
[Phase::MUTATOR
]);
1374 gc_ms
= t(timedGCTime
);
1379 void Statistics::suspendPhases(PhaseKind suspension
) {
1380 MOZ_ASSERT(suspension
== PhaseKind::EXPLICIT_SUSPENSION
||
1381 suspension
== PhaseKind::IMPLICIT_SUSPENSION
);
1382 while (!phaseStack
.empty()) {
1383 MOZ_ASSERT(suspendedPhases
.length() < MAX_SUSPENDED_PHASES
);
1384 Phase parent
= phaseStack
.back();
1385 suspendedPhases
.infallibleAppend(parent
);
1386 recordPhaseEnd(parent
);
1388 suspendedPhases
.infallibleAppend(lookupChildPhase(suspension
));
1391 void Statistics::resumePhases() {
1392 MOZ_ASSERT(suspendedPhases
.back() == Phase::EXPLICIT_SUSPENSION
||
1393 suspendedPhases
.back() == Phase::IMPLICIT_SUSPENSION
);
1394 suspendedPhases
.popBack();
1396 while (!suspendedPhases
.empty() &&
1397 suspendedPhases
.back() != Phase::EXPLICIT_SUSPENSION
&&
1398 suspendedPhases
.back() != Phase::IMPLICIT_SUSPENSION
) {
1399 Phase resumePhase
= suspendedPhases
.popCopy();
1400 if (resumePhase
== Phase::MUTATOR
) {
1401 timedGCTime
+= TimeBetween(timedGCStart
, TimeStamp::Now());
1403 recordPhaseBegin(resumePhase
);
1407 void Statistics::beginPhase(PhaseKind phaseKind
) {
1408 // No longer timing these phases. We should never see these.
1409 MOZ_ASSERT(phaseKind
!= PhaseKind::GC_BEGIN
&&
1410 phaseKind
!= PhaseKind::GC_END
);
1412 // PhaseKind::MUTATOR is suspended while performing GC.
1413 if (currentPhase() == Phase::MUTATOR
) {
1414 suspendPhases(PhaseKind::IMPLICIT_SUSPENSION
);
1417 recordPhaseBegin(lookupChildPhase(phaseKind
));
1420 void Statistics::recordPhaseBegin(Phase phase
) {
1421 MOZ_ASSERT(CurrentThreadCanAccessRuntime(gc
->rt
));
1423 // Guard against any other re-entry.
1424 MOZ_ASSERT(!phaseStartTimes
[phase
]);
1426 MOZ_ASSERT(phaseStack
.length() < MAX_PHASE_NESTING
);
1428 Phase current
= currentPhase();
1429 MOZ_ASSERT(phases
[phase
].parent
== current
);
1431 TimeStamp now
= TimeStamp::Now();
1433 if (current
!= Phase::NONE
) {
1434 MOZ_ASSERT(now
>= phaseStartTimes
[currentPhase()],
1435 "Inconsistent time data; see bug 1400153");
1436 if (now
< phaseStartTimes
[currentPhase()]) {
1437 now
= phaseStartTimes
[currentPhase()];
1442 phaseStack
.infallibleAppend(phase
);
1443 phaseStartTimes
[phase
] = now
;
1444 log("begin: %s", phases
[phase
].path
);
1447 void Statistics::recordPhaseEnd(Phase phase
) {
1448 MOZ_ASSERT(CurrentThreadCanAccessRuntime(gc
->rt
));
1450 MOZ_ASSERT(phaseStartTimes
[phase
]);
1452 TimeStamp now
= TimeStamp::Now();
1454 // Make sure this phase ends after it starts.
1455 MOZ_ASSERT(now
>= phaseStartTimes
[phase
],
1456 "Inconsistent time data; see bug 1400153");
1459 // Make sure this phase ends after all of its children. Note that some
1460 // children might not have run in this instance, in which case they will
1461 // have run in a previous instance of this parent or not at all.
1462 for (Phase kid
= phases
[phase
].firstChild
; kid
!= Phase::NONE
;
1463 kid
= phases
[kid
].nextSibling
) {
1464 if (phaseEndTimes
[kid
].IsNull()) {
1467 if (phaseEndTimes
[kid
] > now
) {
1469 "Parent %s ended at %.3fms, before child %s ended at %.3fms?\n",
1471 t(TimeBetween(TimeStamp::FirstTimeStamp(), now
)),
1473 t(TimeBetween(TimeStamp::FirstTimeStamp(), phaseEndTimes
[kid
])));
1475 MOZ_ASSERT(phaseEndTimes
[kid
] <= now
,
1476 "Inconsistent time data; see bug 1400153");
1480 if (now
< phaseStartTimes
[phase
]) {
1481 now
= phaseStartTimes
[phase
];
1485 if (phase
== Phase::MUTATOR
) {
1489 phaseStack
.popBack();
1491 TimeDuration t
= TimeBetween(phaseStartTimes
[phase
], now
);
1492 if (!slices_
.empty()) {
1493 slices_
.back().phaseTimes
[phase
] += t
;
1495 phaseTimes
[phase
] += t
;
1496 phaseStartTimes
[phase
] = TimeStamp();
1499 phaseEndTimes
[phase
] = now
;
1500 log("end: %s", phases
[phase
].path
);
1504 void Statistics::endPhase(PhaseKind phaseKind
) {
1505 Phase phase
= currentPhase();
1506 MOZ_ASSERT(phase
!= Phase::NONE
);
1507 MOZ_ASSERT(phases
[phase
].phaseKind
== phaseKind
);
1509 recordPhaseEnd(phase
);
1511 // When emptying the stack, we may need to return to timing the mutator
1512 // (PhaseKind::MUTATOR).
1513 if (phaseStack
.empty() && !suspendedPhases
.empty() &&
1514 suspendedPhases
.back() == Phase::IMPLICIT_SUSPENSION
) {
1519 void Statistics::recordParallelPhase(PhaseKind phaseKind
,
1520 TimeDuration duration
) {
1521 MOZ_ASSERT(CurrentThreadCanAccessRuntime(gc
->rt
));
1527 slices_
.back().totalParallelTimes
[phaseKind
] += duration
;
1529 // Also record the maximum task time for each phase. Don't record times for
1531 TimeDuration
& maxTime
= slices_
.back().maxParallelTimes
[phaseKind
];
1532 maxTime
= std::max(maxTime
, duration
);
1535 TimeStamp
Statistics::beginSCC() { return TimeStamp::Now(); }
1537 void Statistics::endSCC(unsigned scc
, TimeStamp start
) {
1538 if (scc
>= sccTimes
.length() && !sccTimes
.resize(scc
+ 1)) {
1542 sccTimes
[scc
] += TimeBetween(start
, TimeStamp::Now());
1546 * Calculate minimum mutator utilization for previous incremental GC.
1548 * MMU (minimum mutator utilization) is a measure of how much garbage collection
1549 * is affecting the responsiveness of the system. MMU measurements are given
1550 * with respect to a certain window size. If we report MMU(50ms) = 80%, then
1551 * that means that, for any 50ms window of time, at least 80% of the window is
1552 * devoted to the mutator. In other words, the GC is running for at most 20% of
1553 * the window, or 10ms. The GC can run multiple slices during the 50ms window
1554 * as long as the total time it spends is at most 10ms.
1556 double Statistics::computeMMU(TimeDuration window
) const {
1557 MOZ_ASSERT(window
> TimeDuration::Zero());
1558 MOZ_ASSERT(!slices().empty());
1560 // Examine all ranges of slices from |startIndex| to |endIndex| inclusive
1561 // whose timestamps span less than the window duration. The time spent in GC
1562 // in each range is stored in |gcInRange| by maintaining a running total. The
1563 // maximum value of this after adjustment to the window size is recorded in
1566 size_t startIndex
= 0;
1567 const SliceData
* startSlice
= &sliceAt(startIndex
);
1568 TimeDuration gcInRange
= startSlice
->duration();
1569 if (gcInRange
>= window
) {
1573 TimeDuration maxGCInWindow
= gcInRange
;
1575 for (size_t endIndex
= 1; endIndex
< slices().length(); endIndex
++) {
1576 const SliceData
* endSlice
= &sliceAt(endIndex
);
1577 if (endSlice
->duration() >= window
) {
1581 gcInRange
+= endSlice
->duration();
1583 while (TimeBetween(startSlice
->end
, endSlice
->end
) >= window
) {
1584 gcInRange
-= startSlice
->duration();
1586 MOZ_ASSERT(startIndex
<= endIndex
);
1587 startSlice
= &sliceAt(startIndex
);
1590 TimeDuration totalInRange
= TimeBetween(startSlice
->start
, endSlice
->end
);
1591 MOZ_ASSERT(gcInRange
<= totalInRange
);
1593 TimeDuration gcInWindow
= gcInRange
;
1594 if (totalInRange
> window
) {
1595 gcInWindow
-= (totalInRange
- window
);
1597 MOZ_ASSERT(gcInWindow
<= window
);
1599 if (gcInWindow
> maxGCInWindow
) {
1600 maxGCInWindow
= gcInWindow
;
1604 MOZ_ASSERT(maxGCInWindow
>= TimeDuration::Zero());
1605 MOZ_ASSERT(maxGCInWindow
<= window
);
1606 return (window
- maxGCInWindow
) / window
;
1609 void Statistics::maybePrintProfileHeaders() {
1610 static int printedHeader
= 0;
1611 if ((printedHeader
++ % 200) == 0) {
1612 printProfileHeader();
1613 if (gc
->nursery().enableProfiling()) {
1614 gc
->nursery().printProfileHeader();
1619 // The following macros define GC profile metadata fields that are printed
1620 // before the timing information defined by FOR_EACH_GC_PROFILE_TIME.
1622 #define FOR_EACH_GC_PROFILE_COMMON_METADATA(_) \
1623 _("PID", 7, "%7zu", pid) \
1624 _("Runtime", 14, "0x%12p", runtime)
1626 #define FOR_EACH_GC_PROFILE_SLICE_METADATA(_) \
1627 _("Timestamp", 10, "%10.6f", timestamp.ToSeconds()) \
1628 _("Reason", 20, "%-20.20s", reason) \
1629 _("States", 6, "%6s", formatGCStates(slice)) \
1630 _("FSNR", 4, "%4s", formatGCFlags(slice)) \
1631 _("SizeKB", 8, "%8zu", sizeKB) \
1632 _("Zs", 3, "%3zu", zoneCount) \
1633 _("Cs", 3, "%3zu", compartmentCount) \
1634 _("Rs", 3, "%3zu", realmCount) \
1635 _("Budget", 6, "%6s", formatBudget(slice))
1637 #define FOR_EACH_GC_PROFILE_METADATA(_) \
1638 FOR_EACH_GC_PROFILE_COMMON_METADATA(_) \
1639 FOR_EACH_GC_PROFILE_SLICE_METADATA(_)
1641 void Statistics::printProfileHeader() {
1642 if (!enableProfiling_
) {
1647 if (!sprinter
.init()) {
1650 sprinter
.put(MajorGCProfilePrefix
);
1652 #define PRINT_METADATA_NAME(name, width, _1, _2) \
1653 sprinter.printf(" %-*s", width, name);
1655 FOR_EACH_GC_PROFILE_METADATA(PRINT_METADATA_NAME
)
1656 #undef PRINT_METADATA_NAME
1658 #define PRINT_PROFILE_NAME(_1, text, _2) sprinter.printf(" %-6.6s", text);
1660 FOR_EACH_GC_PROFILE_TIME(PRINT_PROFILE_NAME
)
1661 #undef PRINT_PROFILE_NAME
1665 JS::UniqueChars str
= sprinter
.release();
1669 fputs(str
.get(), profileFile());
1672 static TimeDuration
SumAllPhaseKinds(const Statistics::PhaseKindTimes
& times
) {
1674 for (PhaseKind kind
: AllPhaseKinds()) {
1680 void Statistics::printSliceProfile() {
1681 maybePrintProfileHeaders();
1683 const SliceData
& slice
= slices_
.back();
1684 ProfileDurations times
= getProfileTimes(slice
);
1685 updateTotalProfileTimes(times
);
1688 if (!sprinter
.init()) {
1691 sprinter
.put(MajorGCProfilePrefix
);
1693 size_t pid
= getpid();
1694 JSRuntime
* runtime
= gc
->rt
;
1695 TimeDuration timestamp
= TimeBetween(creationTime(), slice
.end
);
1696 const char* reason
= ExplainGCReason(slice
.reason
);
1697 size_t sizeKB
= gc
->heapSize
.bytes() / 1024;
1698 size_t zoneCount
= zoneStats
.zoneCount
;
1699 size_t compartmentCount
= zoneStats
.compartmentCount
;
1700 size_t realmCount
= zoneStats
.realmCount
;
1702 #define PRINT_FIELD_VALUE(_1, _2, format, value) \
1703 sprinter.printf(" " format, value);
1705 FOR_EACH_GC_PROFILE_METADATA(PRINT_FIELD_VALUE
)
1706 #undef PRINT_FIELD_VALUE
1708 printProfileTimes(times
, sprinter
);
1710 JS::UniqueChars str
= sprinter
.release();
1714 fputs(str
.get(), profileFile());
1717 Statistics::ProfileDurations
Statistics::getProfileTimes(
1718 const SliceData
& slice
) const {
1719 ProfileDurations times
;
1721 times
[ProfileKey::Total
] = slice
.duration();
1722 times
[ProfileKey::Background
] = SumAllPhaseKinds(slice
.totalParallelTimes
);
1724 #define GET_PROFILE_TIME(name, text, phase) \
1725 if (phase != PhaseKind::NONE) { \
1726 times[ProfileKey::name] = SumPhase(phase, slice.phaseTimes); \
1728 FOR_EACH_GC_PROFILE_TIME(GET_PROFILE_TIME
)
1729 #undef GET_PROFILE_TIME
1734 void Statistics::updateTotalProfileTimes(const ProfileDurations
& times
) {
1735 #define UPDATE_PROFILE_TIME(name, _, phase) \
1736 totalTimes_[ProfileKey::name] += times[ProfileKey::name];
1737 FOR_EACH_GC_PROFILE_TIME(UPDATE_PROFILE_TIME
)
1738 #undef UPDATE_PROFILE_TIME
1741 const char* Statistics::formatGCStates(const SliceData
& slice
) {
1743 SprintfLiteral(formatBuffer_
, "%1d -> %1d", int(slice
.initialState
),
1744 int(slice
.finalState
));
1745 MOZ_ASSERT(r
> 0 && r
< FormatBufferLength
);
1746 return formatBuffer_
;
1749 const char* Statistics::formatGCFlags(const SliceData
& slice
) {
1750 bool fullGC
= gc
->fullGCRequested
;
1751 bool shrinkingGC
= gcOptions
== JS::GCOptions::Shrink
;
1752 bool nonIncrementalGC
= nonincrementalReason_
!= GCAbortReason::None
;
1753 bool wasReset
= slice
.resetReason
!= GCAbortReason::None
;
1755 MOZ_ASSERT(FormatBufferLength
>= 5);
1756 formatBuffer_
[0] = fullGC
? 'F' : ' ';
1757 formatBuffer_
[1] = shrinkingGC
? 'S' : ' ';
1758 formatBuffer_
[2] = nonIncrementalGC
? 'N' : ' ';
1759 formatBuffer_
[3] = wasReset
? 'R' : ' ';
1760 formatBuffer_
[4] = '\0';
1762 return formatBuffer_
;
1765 const char* Statistics::formatBudget(const SliceData
& slice
) {
1766 if (nonincrementalReason_
!= GCAbortReason::None
||
1767 !slice
.budget
.isTimeBudget()) {
1768 formatBuffer_
[0] = '\0';
1769 return formatBuffer_
;
1773 SprintfLiteral(formatBuffer_
, "%6" PRIi64
, slice
.budget
.timeBudget());
1774 MOZ_ASSERT(r
> 0 && r
< FormatBufferLength
);
1775 return formatBuffer_
;
1779 void Statistics::printProfileTimes(const ProfileDurations
& times
,
1780 Sprinter
& sprinter
) {
1781 for (auto time
: times
) {
1782 int64_t millis
= int64_t(time
.ToMilliseconds());
1783 sprinter
.printf(" %6" PRIi64
, millis
);
1789 constexpr size_t SliceMetadataFormatWidth() {
1790 size_t fieldCount
= 0;
1791 size_t totalWidth
= 0;
1793 #define UPDATE_COUNT_AND_WIDTH(_1, width, _2, _3) \
1795 totalWidth += width;
1796 FOR_EACH_GC_PROFILE_SLICE_METADATA(UPDATE_COUNT_AND_WIDTH
)
1797 #undef UPDATE_COUNT_AND_WIDTH
1799 // Add padding between fields.
1800 totalWidth
+= fieldCount
- 1;
1805 void Statistics::printTotalProfileTimes() {
1806 if (!enableProfiling_
) {
1811 if (!sprinter
.init()) {
1814 sprinter
.put(MajorGCProfilePrefix
);
1816 size_t pid
= getpid();
1817 JSRuntime
* runtime
= gc
->rt
;
1819 #define PRINT_FIELD_VALUE(_1, _2, format, value) \
1820 sprinter.printf(" " format, value);
1822 FOR_EACH_GC_PROFILE_COMMON_METADATA(PRINT_FIELD_VALUE
)
1823 #undef PRINT_FIELD_VALUE
1825 // Use whole width of per-slice metadata to print total slices so the profile
1826 // totals that follow line up.
1827 size_t width
= SliceMetadataFormatWidth();
1828 sprinter
.printf(" %-*s", int(width
), formatTotalSlices());
1829 printProfileTimes(totalTimes_
, sprinter
);
1831 JS::UniqueChars str
= sprinter
.release();
1835 fputs(str
.get(), profileFile());
1838 const char* Statistics::formatTotalSlices() {
1839 DebugOnly
<int> r
= SprintfLiteral(
1840 formatBuffer_
, "TOTALS: %7" PRIu64
" slices:", sliceCount_
);
1841 MOZ_ASSERT(r
> 0 && r
< FormatBufferLength
);
1842 return formatBuffer_
;