mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
538b35a4ee
- Bug 1147951, part 2 - Remove unused JAVASCRIPT definition from nsJSEnvironment.cpp. r=baku (607909217) - Bug 1147951, part 1 - Remove uses of JAVASCRIPT2 from Console.cpp. r=baku (46cde7cfa) - pointer style (5504c22d4) - Bug 1165384 - Add a typedef for the statistics phase table; r=sfink (484a24237) - Bug 1165385 - Remove the rarely used !fullFormat mode of MOZ_GCTIMER; r=sfink (ab8b17eb1) - Bug 1165390 - Make the detailed statistics formatting methods have consistent names; r=sfink (55c5db543) - Bug 1165410 - Reimplement GC statistics JSON output formatter; r=sfink (04c13c874) - Bug 1166789 - Cleanup javascript.options.mem.log formatting; r=sfink, r=mccr8 (f23b455b4) - Bug 1171451 - Use the correct type for the argv argument to NS_CreateJSArgv and the nsJSArgArray constructor; r=jst (edfa21a59) - Bug 886459, part 1 - Remove unused includes of nsIJSRuntimeService.h. r=bholley (bbc277ac1) - Bug 886459, part 2 - Remove context callbacks from XPCJSRuntime. r=bholley (2c3c8515a) - Bug 886459, part 3 - Remove simple uses of nsIJSRuntimeService to get the JSRuntime. r=bholley (ff5bfe304) - pointer style (2ea264afd) - Bug 1169457 - Add null check in OnWrapperDestroy. r=jimm (741739513) - Bug 886459, part 4 - Remove nsIJSRuntimeService. r=bholley,aklotz (61563f53b) - Bug 1176642 - Use absolute_import in mach_commands.py files; r=glandium (a9fcb3b3f) - Bug 1176642 - Defer import of autotry and pprint; r=chmanchester (de40855cb) - Bug 1178772 - Add check_macroassembler_style.py: Verify that each MacroAssembler declaration maps to all its definitions. r=h4writer (fd406593a) - Bug 1152556 - Add moz.build bugzilla metadata in dom/media. r=kinetik (fa2ffa121) - Bug 1152556 - Add moz.build bugzilla metadata in dom/media webrtc. r=abr (d208b839a) - re-enable peerconnection (42e8c412b) - Bug 1152538 - Enable WebRTC identity, r=jesup (13a47adcb) - Bug 1231123 - Simplify LaunchApp on BSDs by dropping fork/exec version. r=jld (c35e6e36f) - bug 1171120 - Fix mtransport+signalling to build on iOS. r=ekr (7034b20ab) - Bug 1150609 Patch 1 WebRTC SDP - add tmmbr to offer. r=jesup (52ca72d09) - Bug 1150609 Patch 2 - WebRTC enable tmmbr r=jesup (d59c6adb9) - Bug 1150609 Patch 3 - WebRTC enable tmmbr unittest changes. r=jesup (eeffed826) - Bug 1087161 - Upgrading B2G toolchain to gcc-4.9 (851194ca0)
1434 lines
45 KiB
C++
1434 lines
45 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "gc/Statistics.h"
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/PodOperations.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
|
|
#include "jsprf.h"
|
|
#include "jsutil.h"
|
|
#include "prmjtime.h"
|
|
|
|
#include "gc/Memory.h"
|
|
#include "vm/Debugger.h"
|
|
#include "vm/HelperThreads.h"
|
|
#include "vm/Runtime.h"
|
|
|
|
using namespace js;
|
|
using namespace js::gc;
|
|
using namespace js::gcstats;
|
|
|
|
using mozilla::PodArrayZero;
|
|
using mozilla::PodZero;
|
|
|
|
/* Except for the first and last, slices of less than 10ms are not reported. */
|
|
static const int64_t SLICE_MIN_REPORT_TIME = 10 * PRMJ_USEC_PER_MSEC;
|
|
|
|
class gcstats::StatisticsSerializer
|
|
{
|
|
typedef Vector<char, 128, SystemAllocPolicy> CharBuffer;
|
|
CharBuffer buf_;
|
|
bool asJSON_;
|
|
bool needComma_;
|
|
bool oom_;
|
|
|
|
static const int MaxFieldValueLength = 128;
|
|
|
|
public:
|
|
enum Mode {
|
|
AsJSON = true,
|
|
AsText = false
|
|
};
|
|
|
|
explicit StatisticsSerializer(Mode asJSON)
|
|
: buf_(), asJSON_(asJSON), needComma_(false), oom_(false)
|
|
{}
|
|
|
|
bool isJSON() { return asJSON_; }
|
|
|
|
bool isOOM() { return oom_; }
|
|
|
|
void endLine() {
|
|
if (!asJSON_) {
|
|
p("\n");
|
|
needComma_ = false;
|
|
}
|
|
}
|
|
|
|
void extra(const char* str) {
|
|
if (!asJSON_) {
|
|
needComma_ = false;
|
|
p(str);
|
|
}
|
|
}
|
|
|
|
void appendString(const char* name, const char* value) {
|
|
put(name, value, "", true);
|
|
}
|
|
|
|
void appendNumber(const char* name, const char* vfmt, const char* units, ...) {
|
|
va_list va;
|
|
va_start(va, units);
|
|
append(name, vfmt, va, units);
|
|
va_end(va);
|
|
}
|
|
|
|
void appendDecimal(const char* name, const char* units, double d) {
|
|
if (d < 0)
|
|
d = 0;
|
|
if (asJSON_)
|
|
appendNumber(name, "%d.%03d", units, int(d), int(d * 1000.) % 1000);
|
|
else
|
|
appendNumber(name, "%.1f", units, d);
|
|
}
|
|
|
|
void appendIfNonzeroMS(const char* name, double v) {
|
|
if (asJSON_ || v >= 0.1)
|
|
appendDecimal(name, "ms", v);
|
|
}
|
|
|
|
void beginObject(const char* name) {
|
|
if (needComma_)
|
|
pJSON(", ");
|
|
if (asJSON_ && name) {
|
|
putKey(name);
|
|
pJSON(": ");
|
|
}
|
|
pJSON("{");
|
|
needComma_ = false;
|
|
}
|
|
|
|
void endObject() {
|
|
needComma_ = false;
|
|
pJSON("}");
|
|
needComma_ = true;
|
|
}
|
|
|
|
void beginArray(const char* name) {
|
|
if (needComma_)
|
|
pJSON(", ");
|
|
if (asJSON_)
|
|
putKey(name);
|
|
pJSON(": [");
|
|
needComma_ = false;
|
|
}
|
|
|
|
void endArray() {
|
|
needComma_ = false;
|
|
pJSON("]");
|
|
needComma_ = true;
|
|
}
|
|
|
|
char16_t* finishJSString() {
|
|
char* buf = finishCString();
|
|
if (!buf)
|
|
return nullptr;
|
|
|
|
size_t nchars = strlen(buf);
|
|
char16_t* out = js_pod_malloc<char16_t>(nchars + 1);
|
|
if (!out) {
|
|
oom_ = true;
|
|
js_free(buf);
|
|
return nullptr;
|
|
}
|
|
|
|
CopyAndInflateChars(out, buf, nchars);
|
|
js_free(buf);
|
|
|
|
out[nchars] = 0;
|
|
return out;
|
|
}
|
|
|
|
char* finishCString() {
|
|
if (oom_)
|
|
return nullptr;
|
|
|
|
buf_.append('\0');
|
|
|
|
char* buf = buf_.extractRawBuffer();
|
|
if (!buf)
|
|
oom_ = true;
|
|
|
|
return buf;
|
|
}
|
|
|
|
private:
|
|
void append(const char* name, const char* vfmt,
|
|
va_list va, const char* units)
|
|
{
|
|
char val[MaxFieldValueLength];
|
|
JS_vsnprintf(val, MaxFieldValueLength, vfmt, va);
|
|
put(name, val, units, false);
|
|
}
|
|
|
|
void p(const char* cstr) {
|
|
if (oom_)
|
|
return;
|
|
|
|
if (!buf_.append(cstr, strlen(cstr)))
|
|
oom_ = true;
|
|
}
|
|
|
|
void p(const char c) {
|
|
if (oom_)
|
|
return;
|
|
|
|
if (!buf_.append(c))
|
|
oom_ = true;
|
|
}
|
|
|
|
void pJSON(const char* str) {
|
|
if (asJSON_)
|
|
p(str);
|
|
}
|
|
|
|
void put(const char* name, const char* val, const char* units, bool valueIsQuoted) {
|
|
if (needComma_)
|
|
p(", ");
|
|
needComma_ = true;
|
|
|
|
putKey(name);
|
|
p(": ");
|
|
if (valueIsQuoted)
|
|
putQuoted(val);
|
|
else
|
|
p(val);
|
|
if (!asJSON_)
|
|
p(units);
|
|
}
|
|
|
|
void putQuoted(const char* str) {
|
|
pJSON("\"");
|
|
p(str);
|
|
pJSON("\"");
|
|
}
|
|
|
|
void putKey(const char* str) {
|
|
if (!asJSON_) {
|
|
p(str);
|
|
return;
|
|
}
|
|
|
|
p("\"");
|
|
const char* c = str;
|
|
while (*c) {
|
|
if (*c == ' ' || *c == '\t')
|
|
p('_');
|
|
else if (isupper(*c))
|
|
p(tolower(*c));
|
|
else if (*c == '+')
|
|
p("added_");
|
|
else if (*c == '-')
|
|
p("removed_");
|
|
else if (*c != '(' && *c != ')')
|
|
p(*c);
|
|
c++;
|
|
}
|
|
p("\"");
|
|
}
|
|
};
|
|
|
|
const char*
|
|
js::gcstats::ExplainInvocationKind(JSGCInvocationKind gckind)
|
|
{
|
|
MOZ_ASSERT(gckind == GC_NORMAL || gckind == GC_SHRINK);
|
|
if (gckind == GC_NORMAL)
|
|
return "Normal";
|
|
else
|
|
return "Shrinking";
|
|
}
|
|
|
|
const char*
|
|
js::gcstats::ExplainReason(JS::gcreason::Reason reason)
|
|
{
|
|
switch (reason) {
|
|
#define SWITCH_REASON(name) \
|
|
case JS::gcreason::name: \
|
|
return #name;
|
|
GCREASONS(SWITCH_REASON)
|
|
|
|
default:
|
|
MOZ_CRASH("bad GC reason");
|
|
#undef SWITCH_REASON
|
|
}
|
|
}
|
|
|
|
static double
|
|
t(int64_t t)
|
|
{
|
|
return double(t) / PRMJ_USEC_PER_MSEC;
|
|
}
|
|
|
|
struct PhaseInfo
|
|
{
|
|
Phase index;
|
|
const char* name;
|
|
Phase parent;
|
|
};
|
|
|
|
// The zeroth entry in the timing arrays is used for phases that have a
|
|
// unique lineage.
|
|
static const size_t PHASE_DAG_NONE = 0;
|
|
|
|
// These are really just fields of PhaseInfo, but I have to initialize them
|
|
// programmatically, which prevents making phases[] const. (And marking these
|
|
// fields mutable does not work on Windows; the whole thing gets created in
|
|
// read-only memory anyway.)
|
|
struct ExtraPhaseInfo
|
|
{
|
|
// Depth in the tree of each phase type
|
|
size_t depth;
|
|
|
|
// Index into the set of parallel arrays of timing data, for parents with
|
|
// at least one multi-parented child
|
|
size_t dagSlot;
|
|
};
|
|
|
|
static const Phase PHASE_NO_PARENT = PHASE_LIMIT;
|
|
|
|
struct DagChildEdge {
|
|
Phase parent;
|
|
Phase child;
|
|
} dagChildEdges[] = {
|
|
{ PHASE_MARK, PHASE_MARK_ROOTS },
|
|
{ PHASE_MINOR_GC, PHASE_MARK_ROOTS },
|
|
{ PHASE_TRACE_HEAP, PHASE_MARK_ROOTS },
|
|
{ PHASE_EVICT_NURSERY, PHASE_MARK_ROOTS },
|
|
{ PHASE_COMPACT_UPDATE, PHASE_MARK_ROOTS }
|
|
};
|
|
|
|
/*
|
|
* Note that PHASE_MUTATOR, PHASE_GC_BEGIN, and PHASE_GC_END never have any
|
|
* child phases. If beginPhase is called while one of these is active, they
|
|
* will automatically be suspended and resumed when the phase stack is next
|
|
* empty. Timings for these phases are thus exclusive of any other phase.
|
|
*/
|
|
|
|
static const PhaseInfo phases[] = {
|
|
{ PHASE_MUTATOR, "Mutator Running", PHASE_NO_PARENT },
|
|
{ PHASE_GC_BEGIN, "Begin Callback", PHASE_NO_PARENT },
|
|
{ PHASE_WAIT_BACKGROUND_THREAD, "Wait Background Thread", PHASE_NO_PARENT },
|
|
{ PHASE_MARK_DISCARD_CODE, "Mark Discard Code", PHASE_NO_PARENT },
|
|
{ PHASE_RELAZIFY_FUNCTIONS, "Relazify Functions", PHASE_NO_PARENT },
|
|
{ PHASE_PURGE, "Purge", PHASE_NO_PARENT },
|
|
{ PHASE_MARK, "Mark", PHASE_NO_PARENT },
|
|
{ PHASE_UNMARK, "Unmark", PHASE_MARK },
|
|
/* PHASE_MARK_ROOTS */
|
|
{ PHASE_MARK_DELAYED, "Mark Delayed", PHASE_MARK },
|
|
{ PHASE_SWEEP, "Sweep", PHASE_NO_PARENT },
|
|
{ PHASE_SWEEP_MARK, "Mark During Sweeping", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_MARK_TYPES, "Mark Types During Sweeping", PHASE_SWEEP_MARK },
|
|
{ PHASE_SWEEP_MARK_INCOMING_BLACK, "Mark Incoming Black Pointers", PHASE_SWEEP_MARK },
|
|
{ PHASE_SWEEP_MARK_WEAK, "Mark Weak", PHASE_SWEEP_MARK },
|
|
{ PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers", PHASE_SWEEP_MARK },
|
|
{ PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK },
|
|
{ PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK },
|
|
{ PHASE_FINALIZE_START, "Finalize Start Callback", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_SYMBOL_REGISTRY, "Sweep Symbol Registry", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_DISCARD_CODE, "Sweep Discard Code", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_INNER_VIEWS, "Sweep Inner Views", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_CC_WRAPPER, "Sweep Cross Compartment Wrappers", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_BASE_SHAPE, "Sweep Base Shapes", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_INITIAL_SHAPE, "Sweep Initial Shapes", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_TYPE_OBJECT, "Sweep Type Objects", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_BREAKPOINT, "Sweep Breakpoints", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_REGEXP, "Sweep Regexps", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_MISC, "Sweep Miscellaneous", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_TYPES, "Sweep type information", PHASE_SWEEP_COMPARTMENTS },
|
|
{ PHASE_SWEEP_TYPES_BEGIN, "Sweep type tables and compilations", PHASE_SWEEP_TYPES },
|
|
{ PHASE_SWEEP_TYPES_END, "Free type arena", PHASE_SWEEP_TYPES },
|
|
{ PHASE_SWEEP_OBJECT, "Sweep Object", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_STRING, "Sweep String", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_SCRIPT, "Sweep Script", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_SHAPE, "Sweep Shape", PHASE_SWEEP },
|
|
{ PHASE_SWEEP_JITCODE, "Sweep JIT code", PHASE_SWEEP },
|
|
{ PHASE_FINALIZE_END, "Finalize End Callback", PHASE_SWEEP },
|
|
{ PHASE_DESTROY, "Deallocate", PHASE_SWEEP },
|
|
{ PHASE_COMPACT, "Compact", PHASE_NO_PARENT },
|
|
{ PHASE_COMPACT_MOVE, "Compact Move", PHASE_COMPACT },
|
|
{ PHASE_COMPACT_UPDATE, "Compact Update", PHASE_COMPACT },
|
|
/* PHASE_MARK_ROOTS */
|
|
{ PHASE_COMPACT_UPDATE_CELLS, "Compact Update Cells", PHASE_COMPACT_UPDATE, },
|
|
{ PHASE_GC_END, "End Callback", PHASE_NO_PARENT },
|
|
{ PHASE_MINOR_GC, "All Minor GCs", PHASE_NO_PARENT },
|
|
/* PHASE_MARK_ROOTS */
|
|
{ PHASE_EVICT_NURSERY, "Minor GCs to Evict Nursery", PHASE_NO_PARENT },
|
|
/* PHASE_MARK_ROOTS */
|
|
{ PHASE_TRACE_HEAP, "Trace Heap", PHASE_NO_PARENT },
|
|
/* PHASE_MARK_ROOTS */
|
|
{ PHASE_MARK_ROOTS, "Mark Roots", PHASE_MULTI_PARENTS },
|
|
{ PHASE_BUFFER_GRAY_ROOTS, "Buffer Gray Roots", PHASE_MARK_ROOTS },
|
|
{ PHASE_MARK_CCWS, "Mark Cross Compartment Wrappers", PHASE_MARK_ROOTS },
|
|
{ PHASE_MARK_ROOTERS, "Mark Rooters", PHASE_MARK_ROOTS },
|
|
{ PHASE_MARK_RUNTIME_DATA, "Mark Runtime-wide Data", PHASE_MARK_ROOTS },
|
|
{ PHASE_MARK_EMBEDDING, "Mark Embedding", PHASE_MARK_ROOTS },
|
|
{ PHASE_MARK_COMPARTMENTS, "Mark Compartments", PHASE_MARK_ROOTS },
|
|
{ PHASE_LIMIT, nullptr, PHASE_NO_PARENT }
|
|
};
|
|
|
|
static ExtraPhaseInfo phaseExtra[PHASE_LIMIT] = { { 0, 0 } };
|
|
|
|
// Mapping from all nodes with a multi-parented child to a Vector of all
|
|
// multi-parented children and their descendants. (Single-parented children will
|
|
// not show up in this list.)
|
|
static mozilla::Vector<Phase> dagDescendants[Statistics::MAX_MULTIPARENT_PHASES + 1];
|
|
|
|
struct AllPhaseIterator {
|
|
int current;
|
|
int baseLevel;
|
|
size_t activeSlot;
|
|
mozilla::Vector<Phase>::Range descendants;
|
|
|
|
explicit AllPhaseIterator(Statistics::PhaseTimeTable table)
|
|
: current(0)
|
|
, baseLevel(0)
|
|
, activeSlot(PHASE_DAG_NONE)
|
|
, descendants(dagDescendants[PHASE_DAG_NONE].all()) /* empty range */
|
|
{
|
|
}
|
|
|
|
void get(Phase* phase, size_t* dagSlot, size_t* level = nullptr) {
|
|
MOZ_ASSERT(!done());
|
|
*dagSlot = activeSlot;
|
|
*phase = descendants.empty() ? Phase(current) : descendants.front();
|
|
if (level)
|
|
*level = phaseExtra[*phase].depth + baseLevel;
|
|
}
|
|
|
|
void advance() {
|
|
MOZ_ASSERT(!done());
|
|
|
|
if (!descendants.empty()) {
|
|
descendants.popFront();
|
|
if (!descendants.empty())
|
|
return;
|
|
|
|
++current;
|
|
activeSlot = PHASE_DAG_NONE;
|
|
baseLevel = 0;
|
|
return;
|
|
}
|
|
|
|
if (phaseExtra[current].dagSlot != PHASE_DAG_NONE) {
|
|
activeSlot = phaseExtra[current].dagSlot;
|
|
descendants = dagDescendants[activeSlot].all();
|
|
MOZ_ASSERT(!descendants.empty());
|
|
baseLevel += phaseExtra[current].depth + 1;
|
|
return;
|
|
}
|
|
|
|
++current;
|
|
}
|
|
|
|
bool done() const {
|
|
return phases[current].parent == PHASE_MULTI_PARENTS;
|
|
}
|
|
};
|
|
|
|
static void
|
|
FormatPhaseTimes(StatisticsSerializer& ss, const char* name, Statistics::PhaseTimeTable times)
|
|
{
|
|
ss.beginObject(name);
|
|
|
|
for (AllPhaseIterator iter(times); !iter.done(); iter.advance()) {
|
|
Phase phase;
|
|
size_t dagSlot;
|
|
iter.get(&phase, &dagSlot);
|
|
ss.appendIfNonzeroMS(phases[phase].name, t(times[dagSlot][phase]));
|
|
}
|
|
ss.endObject();
|
|
}
|
|
|
|
void
|
|
Statistics::gcDuration(int64_t* total, int64_t* maxPause) const
|
|
{
|
|
*total = *maxPause = 0;
|
|
for (const SliceData* slice = slices.begin(); slice != slices.end(); slice++) {
|
|
*total += slice->duration();
|
|
if (slice->duration() > *maxPause)
|
|
*maxPause = slice->duration();
|
|
}
|
|
if (*maxPause > maxPauseInInterval)
|
|
maxPauseInInterval = *maxPause;
|
|
}
|
|
|
|
void
|
|
Statistics::sccDurations(int64_t* total, int64_t* maxPause)
|
|
{
|
|
*total = *maxPause = 0;
|
|
for (size_t i = 0; i < sccTimes.length(); i++) {
|
|
*total += sccTimes[i];
|
|
*maxPause = Max(*maxPause, sccTimes[i]);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Statistics::formatData(StatisticsSerializer& ss, uint64_t timestamp)
|
|
{
|
|
MOZ_ASSERT(!aborted);
|
|
|
|
int64_t total, longest;
|
|
gcDuration(&total, &longest);
|
|
|
|
int64_t sccTotal, sccLongest;
|
|
sccDurations(&sccTotal, &sccLongest);
|
|
|
|
double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC);
|
|
double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC);
|
|
|
|
ss.beginObject(nullptr);
|
|
if (ss.isJSON())
|
|
ss.appendNumber("Timestamp", "%llu", "", (unsigned long long)timestamp);
|
|
if (slices.length() > 1 || ss.isJSON())
|
|
ss.appendDecimal("Max Pause", "ms", t(longest));
|
|
else
|
|
ss.appendString("Reason", ExplainReason(slices[0].reason));
|
|
ss.appendDecimal("Total Time", "ms", t(total));
|
|
ss.appendNumber("Zones Collected", "%d", "", zoneStats.collectedZoneCount);
|
|
ss.appendNumber("Total Zones", "%d", "", zoneStats.zoneCount);
|
|
ss.appendNumber("Total Compartments", "%d", "", zoneStats.compartmentCount);
|
|
ss.appendNumber("Minor GCs", "%d", "", counts[STAT_MINOR_GC]);
|
|
ss.appendNumber("Store Buffer Overflows", "%d", "", counts[STAT_STOREBUFFER_OVERFLOW]);
|
|
ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100));
|
|
ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100));
|
|
ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal));
|
|
ss.appendDecimal("SCC Sweep Max Pause", "ms", t(sccLongest));
|
|
if (nonincrementalReason_ || ss.isJSON()) {
|
|
ss.appendString("Nonincremental Reason",
|
|
nonincrementalReason_ ? nonincrementalReason_ : "none");
|
|
}
|
|
ss.appendNumber("Allocated", "%u", "MB", unsigned(preBytes / 1024 / 1024));
|
|
ss.appendNumber("+Chunks", "%d", "", counts[STAT_NEW_CHUNK]);
|
|
ss.appendNumber("-Chunks", "%d", "", counts[STAT_DESTROY_CHUNK]);
|
|
ss.endLine();
|
|
|
|
if (slices.length() > 1 || ss.isJSON()) {
|
|
ss.beginArray("Slices");
|
|
for (size_t i = 0; i < slices.length(); i++) {
|
|
int64_t width = slices[i].duration();
|
|
if (i != 0 && i != slices.length() - 1 && width < SLICE_MIN_REPORT_TIME &&
|
|
!slices[i].resetReason && !ss.isJSON())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char budgetDescription[200];
|
|
slices[i].budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
|
|
|
|
ss.beginObject(nullptr);
|
|
ss.extra(" ");
|
|
ss.appendNumber("Slice", "%d", "", i);
|
|
ss.appendDecimal("Pause", "", t(width));
|
|
ss.extra(" (");
|
|
ss.appendDecimal("When", "ms", t(slices[i].start - slices[0].start));
|
|
ss.appendString("Reason", ExplainReason(slices[i].reason));
|
|
ss.appendString("Budget", budgetDescription);
|
|
if (ss.isJSON()) {
|
|
ss.appendDecimal("Page Faults", "",
|
|
double(slices[i].endFaults - slices[i].startFaults));
|
|
|
|
ss.appendNumber("Start Timestamp", "%llu", "", (unsigned long long)slices[i].start);
|
|
ss.appendNumber("End Timestamp", "%llu", "", (unsigned long long)slices[i].end);
|
|
}
|
|
if (slices[i].resetReason)
|
|
ss.appendString("Reset", slices[i].resetReason);
|
|
ss.extra("): ");
|
|
FormatPhaseTimes(ss, "Times", slices[i].phaseTimes);
|
|
ss.endLine();
|
|
ss.endObject();
|
|
}
|
|
ss.endArray();
|
|
}
|
|
ss.extra(" Totals: ");
|
|
FormatPhaseTimes(ss, "Totals", phaseTimes);
|
|
ss.endObject();
|
|
|
|
return !ss.isOOM();
|
|
}
|
|
|
|
typedef Vector<UniqueChars, 8, SystemAllocPolicy> FragmentVector;
|
|
|
|
static UniqueChars
|
|
Join(const FragmentVector& fragments, const char* separator = "") {
|
|
const size_t separatorLength = strlen(separator);
|
|
size_t length = 0;
|
|
for (size_t i = 0; i < fragments.length(); ++i) {
|
|
length += fragments[i] ? strlen(fragments[i].get()) : 0;
|
|
if (i < (fragments.length() - 1))
|
|
length += separatorLength;
|
|
}
|
|
|
|
char* joined = js_pod_malloc<char>(length + 1);
|
|
joined[length] = '\0';
|
|
|
|
char* cursor = joined;
|
|
for (size_t i = 0; i < fragments.length(); ++i) {
|
|
if (fragments[i])
|
|
strcpy(cursor, fragments[i].get());
|
|
cursor += fragments[i] ? strlen(fragments[i].get()) : 0;
|
|
if (i < (fragments.length() - 1)) {
|
|
if (separatorLength)
|
|
strcpy(cursor, separator);
|
|
cursor += separatorLength;
|
|
}
|
|
}
|
|
|
|
return UniqueChars(joined);
|
|
}
|
|
|
|
static int64_t
|
|
SumChildTimes(size_t phaseSlot, Phase phase, Statistics::PhaseTimeTable phaseTimes)
|
|
{
|
|
// Sum the contributions from single-parented children.
|
|
int64_t total = 0;
|
|
for (unsigned i = 0; i < PHASE_LIMIT; i++) {
|
|
if (phases[i].parent == phase)
|
|
total += phaseTimes[phaseSlot][i];
|
|
}
|
|
|
|
// Sum the contributions from multi-parented children.
|
|
size_t dagSlot = phaseExtra[phase].dagSlot;
|
|
if (dagSlot != PHASE_DAG_NONE) {
|
|
for (size_t i = 0; i < mozilla::ArrayLength(dagChildEdges); i++) {
|
|
if (dagChildEdges[i].parent == phase) {
|
|
Phase child = dagChildEdges[i].child;
|
|
total += phaseTimes[dagSlot][child];
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatCompactSliceMessage() const
|
|
{
|
|
// Skip if we OOM'ed.
|
|
if (slices.length() == 0)
|
|
return UniqueChars(nullptr);
|
|
|
|
const size_t index = slices.length() - 1;
|
|
const SliceData& slice = slices[index];
|
|
|
|
char budgetDescription[200];
|
|
slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
|
|
|
|
const char* format =
|
|
"GC Slice %u - Pause: %.3fms of %s budget (@ %.3fms); Reason: %s; Reset: %s%s; Times: ";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
JS_snprintf(buffer, sizeof(buffer), format, index,
|
|
t(slice.duration()), budgetDescription, t(slice.start - slices[0].start),
|
|
ExplainReason(slice.reason),
|
|
slice.resetReason ? "yes - " : "no", slice.resetReason ? slice.resetReason : "");
|
|
|
|
FragmentVector fragments;
|
|
if (!fragments.append(make_string_copy(buffer)) ||
|
|
!fragments.append(formatCompactSlicePhaseTimes(slices[index].phaseTimes)))
|
|
{
|
|
return UniqueChars(nullptr);
|
|
}
|
|
return Join(fragments);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatCompactSummaryMessage() const
|
|
{
|
|
const double bytesPerMiB = 1024 * 1024;
|
|
|
|
FragmentVector fragments;
|
|
if (!fragments.append(make_string_copy("Summary - ")))
|
|
return UniqueChars(nullptr);
|
|
|
|
int64_t total, longest;
|
|
gcDuration(&total, &longest);
|
|
|
|
const double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC);
|
|
const double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC);
|
|
|
|
char buffer[1024];
|
|
if (!nonincrementalReason_) {
|
|
JS_snprintf(buffer, sizeof(buffer),
|
|
"Max Pause: %.3fms; MMU 20ms: %.1f%%; MMU 50ms: %.1f%%; Total: %.3fms; ",
|
|
t(longest), mmu20 * 100., mmu50 * 100., t(total));
|
|
} else {
|
|
JS_snprintf(buffer, sizeof(buffer), "Non-Incremental: %.3fms; ", t(total));
|
|
}
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
|
|
JS_snprintf(buffer, sizeof(buffer),
|
|
"Zones: %d of %d; Compartments: %d of %d; HeapSize: %.3f MiB; "\
|
|
"HeapChange (abs): %+d (%d); ",
|
|
zoneStats.collectedZoneCount, zoneStats.zoneCount,
|
|
zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
|
|
double(preBytes) / bytesPerMiB,
|
|
counts[STAT_NEW_CHUNK] - counts[STAT_DESTROY_CHUNK],
|
|
counts[STAT_NEW_CHUNK] + counts[STAT_DESTROY_CHUNK]);
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
|
|
MOZ_ASSERT_IF(counts[STAT_ARENA_RELOCATED], gckind == GC_SHRINK);
|
|
if (gckind == GC_SHRINK) {
|
|
JS_snprintf(buffer, sizeof(buffer),
|
|
"Kind: %s; Relocated: %.3f MiB; ",
|
|
ExplainInvocationKind(gckind),
|
|
double(ArenaSize * counts[STAT_ARENA_RELOCATED]) / bytesPerMiB);
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
}
|
|
|
|
return Join(fragments);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatCompactSlicePhaseTimes(PhaseTimeTable phaseTimes) const
|
|
{
|
|
static const int64_t MaxUnaccountedTimeUS = 100;
|
|
|
|
FragmentVector fragments;
|
|
char buffer[128];
|
|
for (AllPhaseIterator iter(phaseTimes); !iter.done(); iter.advance()) {
|
|
Phase phase;
|
|
size_t dagSlot;
|
|
size_t level;
|
|
iter.get(&phase, &dagSlot, &level);
|
|
MOZ_ASSERT(level < 4);
|
|
|
|
int64_t ownTime = phaseTimes[dagSlot][phase];
|
|
int64_t childTime = SumChildTimes(dagSlot, phase, phaseTimes);
|
|
if (ownTime > MaxUnaccountedTimeUS) {
|
|
JS_snprintf(buffer, sizeof(buffer), "%s: %.3fms", phases[phase].name, t(ownTime));
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
|
|
if (childTime && (ownTime - childTime) > MaxUnaccountedTimeUS) {
|
|
MOZ_ASSERT(level < 3);
|
|
JS_snprintf(buffer, sizeof(buffer), "%s: %.3fms", "Other", t(ownTime - childTime));
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
}
|
|
}
|
|
}
|
|
return Join(fragments, ", ");
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatDetailedMessage()
|
|
{
|
|
FragmentVector fragments;
|
|
|
|
if (!fragments.append(formatDetailedDescription()))
|
|
return UniqueChars(nullptr);
|
|
|
|
if (slices.length() > 1) {
|
|
for (unsigned i = 0; i < slices.length(); i++) {
|
|
if (!fragments.append(formatDetailedSliceDescription(i, slices[i])))
|
|
return UniqueChars(nullptr);
|
|
if (!fragments.append(formatDetailedPhaseTimes(slices[i].phaseTimes)))
|
|
return UniqueChars(nullptr);
|
|
}
|
|
}
|
|
if (!fragments.append(formatDetailedTotals()))
|
|
return UniqueChars(nullptr);
|
|
if (!fragments.append(formatDetailedPhaseTimes(phaseTimes)))
|
|
return UniqueChars(nullptr);
|
|
|
|
return Join(fragments);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatDetailedDescription()
|
|
{
|
|
const double bytesPerMiB = 1024 * 1024;
|
|
|
|
int64_t sccTotal, sccLongest;
|
|
sccDurations(&sccTotal, &sccLongest);
|
|
|
|
double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC);
|
|
double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC);
|
|
|
|
const char* format =
|
|
"=================================================================\n\
|
|
Invocation Kind: %s\n\
|
|
Reason: %s\n\
|
|
Incremental: %s%s\n\
|
|
Zones Collected: %d of %d\n\
|
|
Compartments Collected: %d of %d\n\
|
|
MinorGCs since last GC: %d\n\
|
|
Store Buffer Overflows: %d\n\
|
|
MMU 20ms:%.1f%%; 50ms:%.1f%%\n\
|
|
SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\
|
|
HeapSize: %.3f MiB\n\
|
|
Chunk Delta (magnitude): %+d (%d)\n\
|
|
Arenas Relocated: %.3f MiB\n\
|
|
";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
JS_snprintf(buffer, sizeof(buffer), format,
|
|
ExplainInvocationKind(gckind),
|
|
ExplainReason(slices[0].reason),
|
|
nonincrementalReason_ ? "no - " : "yes",
|
|
nonincrementalReason_ ? nonincrementalReason_ : "",
|
|
zoneStats.collectedZoneCount, zoneStats.zoneCount,
|
|
zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
|
|
counts[STAT_MINOR_GC],
|
|
counts[STAT_STOREBUFFER_OVERFLOW],
|
|
mmu20 * 100., mmu50 * 100.,
|
|
t(sccTotal), t(sccLongest),
|
|
double(preBytes) / bytesPerMiB,
|
|
counts[STAT_NEW_CHUNK] - counts[STAT_DESTROY_CHUNK], counts[STAT_NEW_CHUNK] +
|
|
counts[STAT_DESTROY_CHUNK],
|
|
double(ArenaSize * counts[STAT_ARENA_RELOCATED]) / bytesPerMiB);
|
|
return make_string_copy(buffer);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatDetailedSliceDescription(unsigned i, const SliceData& slice)
|
|
{
|
|
char budgetDescription[200];
|
|
slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
|
|
|
|
const char* format =
|
|
"\
|
|
---- Slice %u ----\n\
|
|
Reason: %s\n\
|
|
Reset: %s%s\n\
|
|
Page Faults: %ld\n\
|
|
Pause: %.3fms of %s budget (@ %.3fms)\n\
|
|
";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
JS_snprintf(buffer, sizeof(buffer), format, i,
|
|
ExplainReason(slice.reason),
|
|
slice.resetReason ? "yes - " : "no", slice.resetReason ? slice.resetReason : "",
|
|
uint64_t(slice.endFaults - slice.startFaults),
|
|
t(slice.duration()), budgetDescription, t(slice.start - slices[0].start));
|
|
return make_string_copy(buffer);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatDetailedPhaseTimes(PhaseTimeTable phaseTimes)
|
|
{
|
|
static const char* LevelToIndent[] = { "", " ", " ", " " };
|
|
static const int64_t MaxUnaccountedChildTimeUS = 50;
|
|
|
|
FragmentVector fragments;
|
|
char buffer[128];
|
|
for (AllPhaseIterator iter(phaseTimes); !iter.done(); iter.advance()) {
|
|
Phase phase;
|
|
size_t dagSlot;
|
|
size_t level;
|
|
iter.get(&phase, &dagSlot, &level);
|
|
MOZ_ASSERT(level < 4);
|
|
|
|
int64_t ownTime = phaseTimes[dagSlot][phase];
|
|
int64_t childTime = SumChildTimes(dagSlot, phase, phaseTimes);
|
|
if (ownTime > 0) {
|
|
JS_snprintf(buffer, sizeof(buffer), " %s%s: %.3fms\n",
|
|
LevelToIndent[level], phases[phase].name, t(ownTime));
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
|
|
if (childTime && (ownTime - childTime) > MaxUnaccountedChildTimeUS) {
|
|
MOZ_ASSERT(level < 3);
|
|
JS_snprintf(buffer, sizeof(buffer), " %s%s: %.3fms\n",
|
|
LevelToIndent[level + 1], "Other", t(ownTime - childTime));
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
}
|
|
}
|
|
}
|
|
return Join(fragments);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatDetailedTotals()
|
|
{
|
|
int64_t total, longest;
|
|
gcDuration(&total, &longest);
|
|
|
|
const char* format =
|
|
"\
|
|
---- Totals ----\n\
|
|
Total Time: %.3fms\n\
|
|
Max Pause: %.3fms\n\
|
|
";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
JS_snprintf(buffer, sizeof(buffer), format, t(total), t(longest));
|
|
return make_string_copy(buffer);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatJsonMessage(uint64_t timestamp)
|
|
{
|
|
MOZ_ASSERT(!aborted);
|
|
|
|
FragmentVector fragments;
|
|
|
|
if (!fragments.append(make_string_copy("{")) ||
|
|
!fragments.append(formatJsonDescription(timestamp)) ||
|
|
!fragments.append(make_string_copy("\"slices\":[")))
|
|
{
|
|
return UniqueChars(nullptr);
|
|
}
|
|
|
|
for (unsigned i = 0; i < slices.length(); i++) {
|
|
if (!fragments.append(make_string_copy("{")) ||
|
|
!fragments.append(formatJsonSliceDescription(i, slices[i])) ||
|
|
!fragments.append(make_string_copy("\"times\":{")) ||
|
|
!fragments.append(formatJsonPhaseTimes(slices[i].phaseTimes)) ||
|
|
!fragments.append(make_string_copy("}}")) ||
|
|
(i < (slices.length() - 1) && !fragments.append(make_string_copy(","))))
|
|
{
|
|
return UniqueChars(nullptr);
|
|
}
|
|
}
|
|
|
|
if (!fragments.append(make_string_copy("],\"totals\":{")) ||
|
|
!fragments.append(formatJsonPhaseTimes(phaseTimes)) ||
|
|
!fragments.append(make_string_copy("}}")))
|
|
{
|
|
return UniqueChars(nullptr);
|
|
}
|
|
|
|
return Join(fragments);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatJsonDescription(uint64_t timestamp)
|
|
{
|
|
int64_t total, longest;
|
|
gcDuration(&total, &longest);
|
|
|
|
int64_t sccTotal, sccLongest;
|
|
sccDurations(&sccTotal, &sccLongest);
|
|
|
|
double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC);
|
|
double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC);
|
|
|
|
const char *format =
|
|
"\"timestamp\":%llu,"
|
|
"\"max_pause\":%lu.%03lu,"
|
|
"\"total_time\":%lu.%03lu,"
|
|
"\"zones_collected\":%d,"
|
|
"\"total_zones\":%d,"
|
|
"\"total_compartments\":%d,"
|
|
"\"minor_gcs\":%d,"
|
|
"\"store_buffer_overflows\":%d,"
|
|
"\"mmu_20ms\":%d,"
|
|
"\"mmu_50ms\":%d,"
|
|
"\"scc_sweep_total\":%lu.%03lu,"
|
|
"\"scc_sweep_max_pause\":%lu.%03lu,"
|
|
"\"nonincremental_reason\":\"%s\","
|
|
"\"allocated\":%u,"
|
|
"\"added_chunks\":%d,"
|
|
"\"removed_chunks\":%d,";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
JS_snprintf(buffer, sizeof(buffer), format,
|
|
(unsigned long long)timestamp,
|
|
longest / 1000, longest % 1000,
|
|
total / 1000, total % 1000,
|
|
zoneStats.collectedZoneCount,
|
|
zoneStats.zoneCount,
|
|
zoneStats.compartmentCount,
|
|
counts[STAT_MINOR_GC],
|
|
counts[STAT_STOREBUFFER_OVERFLOW],
|
|
int(mmu20 * 100),
|
|
int(mmu50 * 100),
|
|
sccTotal / 1000, sccTotal % 1000,
|
|
sccLongest / 1000, sccLongest % 1000,
|
|
nonincrementalReason_ ? nonincrementalReason_ : "none",
|
|
unsigned(preBytes / 1024 / 1024),
|
|
counts[STAT_NEW_CHUNK],
|
|
counts[STAT_DESTROY_CHUNK]);
|
|
return make_string_copy(buffer);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice)
|
|
{
|
|
int64_t duration = slices[i].duration();
|
|
int64_t when = slices[i].start - slices[0].start;
|
|
char budgetDescription[200];
|
|
slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
|
|
int64_t pageFaults = slices[i].endFaults - slices[i].startFaults;
|
|
|
|
const char* format =
|
|
"\"slice\":%d,"
|
|
"\"pause\":%lu.%03lu,"
|
|
"\"when\":%lu.%03lu,"
|
|
"\"reason\":\"%s\","
|
|
"\"budget\":\"%s\","
|
|
"\"page_faults\":%llu,"
|
|
"\"start_timestamp\":%llu,"
|
|
"\"end_timestamp\":%llu,";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
JS_snprintf(buffer, sizeof(buffer), format,
|
|
i,
|
|
duration / 1000, duration % 1000,
|
|
when / 1000, when % 1000,
|
|
ExplainReason(slices[i].reason),
|
|
budgetDescription,
|
|
pageFaults,
|
|
slices[i].start,
|
|
slices[i].end);
|
|
return make_string_copy(buffer);
|
|
}
|
|
|
|
UniqueChars
|
|
FilterJsonKey(const char*const buffer)
|
|
{
|
|
char* mut = strdup(buffer);
|
|
char* c = mut;
|
|
while (*c) {
|
|
if (!isalpha(*c))
|
|
*c = '_';
|
|
else if (isupper(*c))
|
|
*c = tolower(*c);
|
|
++c;
|
|
}
|
|
return UniqueChars(mut);
|
|
}
|
|
|
|
UniqueChars
|
|
Statistics::formatJsonPhaseTimes(PhaseTimeTable phaseTimes)
|
|
{
|
|
FragmentVector fragments;
|
|
char buffer[128];
|
|
for (AllPhaseIterator iter(phaseTimes); !iter.done(); iter.advance()) {
|
|
Phase phase;
|
|
size_t dagSlot;
|
|
iter.get(&phase, &dagSlot);
|
|
|
|
UniqueChars name = FilterJsonKey(phases[phase].name);
|
|
int64_t ownTime = phaseTimes[dagSlot][phase];
|
|
JS_snprintf(buffer, sizeof(buffer), "\"%s\":%lu.%03lu",
|
|
name.get(), ownTime / 1000, ownTime % 1000);
|
|
|
|
if (!fragments.append(make_string_copy(buffer)))
|
|
return UniqueChars(nullptr);
|
|
}
|
|
return Join(fragments, ",");
|
|
}
|
|
|
|
char16_t*
|
|
Statistics::formatMessage()
|
|
{
|
|
StatisticsSerializer ss(StatisticsSerializer::AsText);
|
|
formatData(ss, 0);
|
|
return ss.finishJSString();
|
|
}
|
|
|
|
Statistics::Statistics(JSRuntime* rt)
|
|
: runtime(rt),
|
|
startupTime(PRMJ_Now()),
|
|
fp(nullptr),
|
|
gcDepth(0),
|
|
nonincrementalReason_(nullptr),
|
|
timedGCStart(0),
|
|
preBytes(0),
|
|
maxPauseInInterval(0),
|
|
phaseNestingDepth(0),
|
|
activeDagSlot(PHASE_DAG_NONE),
|
|
suspendedPhaseNestingDepth(0),
|
|
sliceCallback(nullptr),
|
|
aborted(false)
|
|
{
|
|
PodArrayZero(phaseTotals);
|
|
PodArrayZero(counts);
|
|
PodArrayZero(phaseStartTimes);
|
|
for (size_t d = 0; d < MAX_MULTIPARENT_PHASES + 1; d++)
|
|
PodArrayZero(phaseTimes[d]);
|
|
|
|
static bool initialized = false;
|
|
if (!initialized) {
|
|
initialized = true;
|
|
|
|
for (size_t i = 0; i < PHASE_LIMIT; i++)
|
|
MOZ_ASSERT(phases[i].index == i);
|
|
|
|
// Create a static table of descendants for every phase with multiple
|
|
// children. This assumes that all descendants come linearly in the
|
|
// list, which is reasonable since full dags are not supported; any
|
|
// path from the leaf to the root must encounter at most one node with
|
|
// multiple parents.
|
|
size_t dagSlot = 0;
|
|
for (size_t i = 0; i < mozilla::ArrayLength(dagChildEdges); i++) {
|
|
Phase parent = dagChildEdges[i].parent;
|
|
if (!phaseExtra[parent].dagSlot)
|
|
phaseExtra[parent].dagSlot = ++dagSlot;
|
|
|
|
Phase child = dagChildEdges[i].child;
|
|
MOZ_ASSERT(phases[child].parent == PHASE_MULTI_PARENTS);
|
|
int j = child;
|
|
do {
|
|
if (!dagDescendants[phaseExtra[parent].dagSlot].append(Phase(j))) {
|
|
initialized = false;
|
|
return;
|
|
}
|
|
j++;
|
|
} while (j != PHASE_LIMIT && phases[j].parent != PHASE_MULTI_PARENTS);
|
|
}
|
|
MOZ_ASSERT(dagSlot <= MAX_MULTIPARENT_PHASES);
|
|
|
|
// Fill in the depth of each node in the tree. Multi-parented nodes
|
|
// have depth 0.
|
|
mozilla::Vector<Phase> stack;
|
|
if (!stack.append(PHASE_LIMIT)) { // Dummy entry to avoid special-casing the first node
|
|
initialized = false;
|
|
return;
|
|
}
|
|
for (int i = 0; i < PHASE_LIMIT; i++) {
|
|
if (phases[i].parent == PHASE_NO_PARENT ||
|
|
phases[i].parent == PHASE_MULTI_PARENTS)
|
|
{
|
|
stack.clear();
|
|
} else {
|
|
while (stack.back() != phases[i].parent)
|
|
stack.popBack();
|
|
}
|
|
phaseExtra[i].depth = stack.length();
|
|
if (!stack.append(Phase(i))) {
|
|
initialized = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
char* env = getenv("MOZ_GCTIMER");
|
|
if (env) {
|
|
if (strcmp(env, "none") == 0) {
|
|
fp = nullptr;
|
|
} else if (strcmp(env, "stdout") == 0) {
|
|
fp = stdout;
|
|
} else if (strcmp(env, "stderr") == 0) {
|
|
fp = stderr;
|
|
} else {
|
|
fp = fopen(env, "a");
|
|
if (!fp)
|
|
MOZ_CRASH("Failed to open MOZ_GCTIMER log file.");
|
|
}
|
|
}
|
|
}
|
|
|
|
Statistics::~Statistics()
|
|
{
|
|
if (fp && fp != stdout && fp != stderr)
|
|
fclose(fp);
|
|
}
|
|
|
|
JS::GCSliceCallback
|
|
Statistics::setSliceCallback(JS::GCSliceCallback newCallback)
|
|
{
|
|
JS::GCSliceCallback oldCallback = sliceCallback;
|
|
sliceCallback = newCallback;
|
|
return oldCallback;
|
|
}
|
|
|
|
int64_t
|
|
Statistics::clearMaxGCPauseAccumulator()
|
|
{
|
|
int64_t prior = maxPauseInInterval;
|
|
maxPauseInInterval = 0;
|
|
return prior;
|
|
}
|
|
|
|
int64_t
|
|
Statistics::getMaxGCPauseSinceClear()
|
|
{
|
|
return maxPauseInInterval;
|
|
}
|
|
|
|
static int64_t
|
|
SumPhase(Phase phase, Statistics::PhaseTimeTable times)
|
|
{
|
|
int64_t sum = 0;
|
|
for (size_t i = 0; i < Statistics::MAX_MULTIPARENT_PHASES + 1; i++)
|
|
sum += times[i][phase];
|
|
return sum;
|
|
}
|
|
|
|
void
|
|
Statistics::printStats()
|
|
{
|
|
if (aborted) {
|
|
fprintf(fp, "OOM during GC statistics collection. The report is unavailable for this GC.\n");
|
|
} else {
|
|
UniqueChars msg = formatDetailedMessage();
|
|
if (msg)
|
|
fprintf(fp, "GC(T+%.3fs) %s\n", t(slices[0].start - startupTime) / 1000.0, msg.get());
|
|
}
|
|
fflush(fp);
|
|
}
|
|
|
|
void
|
|
Statistics::beginGC(JSGCInvocationKind kind)
|
|
{
|
|
slices.clearAndFree();
|
|
sccTimes.clearAndFree();
|
|
gckind = kind;
|
|
nonincrementalReason_ = nullptr;
|
|
|
|
preBytes = runtime->gc.usage.gcBytes();
|
|
}
|
|
|
|
void
|
|
Statistics::endGC()
|
|
{
|
|
for (size_t j = 0; j < MAX_MULTIPARENT_PHASES + 1; j++)
|
|
for (int i = 0; i < PHASE_LIMIT; i++)
|
|
phaseTotals[j][i] += phaseTimes[j][i];
|
|
|
|
int64_t total, longest;
|
|
gcDuration(&total, &longest);
|
|
|
|
int64_t sccTotal, sccLongest;
|
|
sccDurations(&sccTotal, &sccLongest);
|
|
|
|
int64_t markTotal = SumPhase(PHASE_MARK, phaseTimes);
|
|
int64_t markRootsTotal = SumPhase(PHASE_MARK_ROOTS, phaseTimes);
|
|
|
|
if (!aborted) {
|
|
double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC);
|
|
}
|
|
|
|
if (fp)
|
|
printStats();
|
|
|
|
// Clear the timers at the end of a GC because we accumulate time in
|
|
// between GCs for some (which come before PHASE_GC_BEGIN in the list.)
|
|
PodZero(&phaseStartTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN);
|
|
for (size_t d = PHASE_DAG_NONE; d < MAX_MULTIPARENT_PHASES + 1; d++)
|
|
PodZero(&phaseTimes[d][PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN);
|
|
|
|
aborted = false;
|
|
}
|
|
|
|
void
|
|
Statistics::beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
|
|
SliceBudget budget, JS::gcreason::Reason reason)
|
|
{
|
|
this->zoneStats = zoneStats;
|
|
|
|
bool first = !runtime->gc.isIncrementalGCInProgress();
|
|
if (first)
|
|
beginGC(gckind);
|
|
|
|
SliceData data(budget, reason, PRMJ_Now(), GetPageFaultCount());
|
|
if (!slices.append(data)) {
|
|
// OOM testing fails if we CrashAtUnhandlableOOM here.
|
|
aborted = true;
|
|
return;
|
|
}
|
|
|
|
// Slice callbacks should only fire for the outermost level
|
|
if (++gcDepth == 1) {
|
|
bool wasFullGC = zoneStats.isCollectingAllZones();
|
|
if (sliceCallback)
|
|
(*sliceCallback)(runtime, first ? JS::GC_CYCLE_BEGIN : JS::GC_SLICE_BEGIN,
|
|
JS::GCDescription(!wasFullGC, gckind));
|
|
}
|
|
}
|
|
|
|
void
|
|
Statistics::endSlice()
|
|
{
|
|
if (!aborted) {
|
|
slices.back().end = PRMJ_Now();
|
|
slices.back().endFaults = GetPageFaultCount();
|
|
|
|
}
|
|
|
|
bool last = !runtime->gc.isIncrementalGCInProgress();
|
|
if (last)
|
|
endGC();
|
|
|
|
// Slice callbacks should only fire for the outermost level
|
|
if (--gcDepth == 0) {
|
|
bool wasFullGC = zoneStats.isCollectingAllZones();
|
|
if (sliceCallback)
|
|
(*sliceCallback)(runtime, last ? JS::GC_CYCLE_END : JS::GC_SLICE_END,
|
|
JS::GCDescription(!wasFullGC, gckind));
|
|
}
|
|
|
|
/* Do this after the slice callback since it uses these values. */
|
|
if (last)
|
|
PodArrayZero(counts);
|
|
}
|
|
|
|
void
|
|
Statistics::startTimingMutator()
|
|
{
|
|
// Should only be called from outside of GC
|
|
MOZ_ASSERT(phaseNestingDepth == 0);
|
|
MOZ_ASSERT(suspendedPhaseNestingDepth == 0);
|
|
|
|
timedGCTime = 0;
|
|
phaseStartTimes[PHASE_MUTATOR] = 0;
|
|
phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR] = 0;
|
|
timedGCStart = 0;
|
|
|
|
beginPhase(PHASE_MUTATOR);
|
|
}
|
|
|
|
bool
|
|
Statistics::stopTimingMutator(double& mutator_ms, double& gc_ms)
|
|
{
|
|
// This should only be called from outside of GC, while timing the mutator.
|
|
if (phaseNestingDepth != 1 || phaseNesting[0] != PHASE_MUTATOR)
|
|
return false;
|
|
|
|
endPhase(PHASE_MUTATOR);
|
|
mutator_ms = t(phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR]);
|
|
gc_ms = t(timedGCTime);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Statistics::beginPhase(Phase phase)
|
|
{
|
|
Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
|
|
|
|
// Re-entry is allowed during callbacks, so pause callback phases while
|
|
// other phases are in progress, auto-resuming after they end. As a result,
|
|
// nested GC time will not be accounted against the callback phases.
|
|
//
|
|
// Reuse this mechanism for managing PHASE_MUTATOR.
|
|
if (parent == PHASE_GC_BEGIN || parent == PHASE_GC_END || parent == PHASE_MUTATOR) {
|
|
MOZ_ASSERT(suspendedPhaseNestingDepth < mozilla::ArrayLength(suspendedPhases));
|
|
suspendedPhases[suspendedPhaseNestingDepth++] = parent;
|
|
recordPhaseEnd(parent);
|
|
parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
|
|
}
|
|
|
|
// Guard against any other re-entry.
|
|
MOZ_ASSERT(!phaseStartTimes[phase]);
|
|
|
|
MOZ_ASSERT(phases[phase].index == phase);
|
|
MOZ_ASSERT(phaseNestingDepth < MAX_NESTING);
|
|
MOZ_ASSERT(phases[phase].parent == parent || phases[phase].parent == PHASE_MULTI_PARENTS);
|
|
|
|
phaseNesting[phaseNestingDepth] = phase;
|
|
phaseNestingDepth++;
|
|
|
|
if (phases[phase].parent == PHASE_MULTI_PARENTS)
|
|
activeDagSlot = phaseExtra[parent].dagSlot;
|
|
|
|
phaseStartTimes[phase] = PRMJ_Now();
|
|
}
|
|
|
|
void
|
|
Statistics::recordPhaseEnd(Phase phase)
|
|
{
|
|
int64_t now = PRMJ_Now();
|
|
|
|
if (phase == PHASE_MUTATOR)
|
|
timedGCStart = now;
|
|
|
|
phaseNestingDepth--;
|
|
|
|
int64_t t = now - phaseStartTimes[phase];
|
|
if (!slices.empty())
|
|
slices.back().phaseTimes[activeDagSlot][phase] += t;
|
|
phaseTimes[activeDagSlot][phase] += t;
|
|
phaseStartTimes[phase] = 0;
|
|
}
|
|
|
|
void
|
|
Statistics::endPhase(Phase phase)
|
|
{
|
|
recordPhaseEnd(phase);
|
|
|
|
if (phases[phase].parent == PHASE_MULTI_PARENTS)
|
|
activeDagSlot = PHASE_DAG_NONE;
|
|
|
|
// When emptying the stack, we may need to resume a callback phase
|
|
// (PHASE_GC_BEGIN/END) or return to timing the mutator (PHASE_MUTATOR).
|
|
if (phaseNestingDepth == 0 && suspendedPhaseNestingDepth > 0) {
|
|
Phase resumePhase = suspendedPhases[--suspendedPhaseNestingDepth];
|
|
if (resumePhase == PHASE_MUTATOR)
|
|
timedGCTime += PRMJ_Now() - timedGCStart;
|
|
beginPhase(resumePhase);
|
|
}
|
|
}
|
|
|
|
void
|
|
Statistics::endParallelPhase(Phase phase, const GCParallelTask* task)
|
|
{
|
|
phaseNestingDepth--;
|
|
|
|
if (!slices.empty())
|
|
slices.back().phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
|
|
phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
|
|
phaseStartTimes[phase] = 0;
|
|
}
|
|
|
|
int64_t
|
|
Statistics::beginSCC()
|
|
{
|
|
return PRMJ_Now();
|
|
}
|
|
|
|
void
|
|
Statistics::endSCC(unsigned scc, int64_t start)
|
|
{
|
|
if (scc >= sccTimes.length() && !sccTimes.resize(scc + 1))
|
|
return;
|
|
|
|
sccTimes[scc] += PRMJ_Now() - start;
|
|
}
|
|
|
|
/*
|
|
* MMU (minimum mutator utilization) is a measure of how much garbage collection
|
|
* is affecting the responsiveness of the system. MMU measurements are given
|
|
* with respect to a certain window size. If we report MMU(50ms) = 80%, then
|
|
* that means that, for any 50ms window of time, at least 80% of the window is
|
|
* devoted to the mutator. In other words, the GC is running for at most 20% of
|
|
* the window, or 10ms. The GC can run multiple slices during the 50ms window
|
|
* as long as the total time it spends is at most 10ms.
|
|
*/
|
|
double
|
|
Statistics::computeMMU(int64_t window) const
|
|
{
|
|
MOZ_ASSERT(!slices.empty());
|
|
|
|
int64_t gc = slices[0].end - slices[0].start;
|
|
int64_t gcMax = gc;
|
|
|
|
if (gc >= window)
|
|
return 0.0;
|
|
|
|
int startIndex = 0;
|
|
for (size_t endIndex = 1; endIndex < slices.length(); endIndex++) {
|
|
gc += slices[endIndex].end - slices[endIndex].start;
|
|
|
|
while (slices[endIndex].end - slices[startIndex].end >= window) {
|
|
gc -= slices[startIndex].end - slices[startIndex].start;
|
|
startIndex++;
|
|
}
|
|
|
|
int64_t cur = gc;
|
|
if (slices[endIndex].end - slices[startIndex].start > window)
|
|
cur -= (slices[endIndex].end - slices[startIndex].start - window);
|
|
if (cur > gcMax)
|
|
gcMax = cur;
|
|
}
|
|
|
|
return double(window - gcMax) / window;
|
|
}
|