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