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