mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
915d8780c8
Basically, this change is all about aggregating SpiderMonkey's fine-grained measurements into the new set of coarse-grained measurements, called ServoSizes (which is similar to the existing TabSizes). The change utilizes and extends the existing macro machinery to do this in a way that has some chance to maintaining correctness over the long-term despite the fact that this code is so fiddly. --HG-- extra : rebase_source : 1dc3d92830902d9e24496dcdc5f1ee8a6fe39fb4
894 lines
29 KiB
C++
894 lines
29 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/. */
|
|
|
|
#ifndef js_MemoryMetrics_h
|
|
#define js_MemoryMetrics_h
|
|
|
|
// These declarations are highly likely to change in the future. Depend on them
|
|
// at your own risk.
|
|
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/PodOperations.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "jsalloc.h"
|
|
#include "jspubtd.h"
|
|
|
|
#include "js/HashTable.h"
|
|
#include "js/TracingAPI.h"
|
|
#include "js/Utility.h"
|
|
#include "js/Vector.h"
|
|
|
|
class nsISupports; // Needed for ObjectPrivateVisitor.
|
|
|
|
namespace JS {
|
|
|
|
struct TabSizes
|
|
{
|
|
enum Kind {
|
|
Objects,
|
|
Strings,
|
|
Private,
|
|
Other
|
|
};
|
|
|
|
TabSizes() { mozilla::PodZero(this); }
|
|
|
|
void add(Kind kind, size_t n) {
|
|
switch (kind) {
|
|
case Objects: objects += n; break;
|
|
case Strings: strings += n; break;
|
|
case Private: private_ += n; break;
|
|
case Other: other += n; break;
|
|
default: MOZ_CRASH("bad TabSizes kind");
|
|
}
|
|
}
|
|
|
|
size_t objects;
|
|
size_t strings;
|
|
size_t private_;
|
|
size_t other;
|
|
};
|
|
|
|
// These are the measurements used by Servo. It's important that this is a POD
|
|
// struct so that Servo can have a parallel |repr(C)| Rust equivalent.
|
|
struct ServoSizes
|
|
{
|
|
enum Kind {
|
|
GCHeapUsed,
|
|
GCHeapUnused,
|
|
GCHeapAdmin,
|
|
GCHeapDecommitted,
|
|
MallocHeap,
|
|
NonHeap,
|
|
Ignore
|
|
};
|
|
|
|
ServoSizes() { mozilla::PodZero(this); }
|
|
|
|
void add(Kind kind, size_t n) {
|
|
switch (kind) {
|
|
case GCHeapUsed: gcHeapUsed += n; break;
|
|
case GCHeapUnused: gcHeapUnused += n; break;
|
|
case GCHeapAdmin: gcHeapAdmin += n; break;
|
|
case GCHeapDecommitted: gcHeapDecommitted += n; break;
|
|
case MallocHeap: mallocHeap += n; break;
|
|
case NonHeap: nonHeap += n; break;
|
|
case Ignore: /* do nothing */ break;
|
|
default: MOZ_CRASH("bad ServoSizes kind");
|
|
}
|
|
}
|
|
|
|
size_t gcHeapUsed;
|
|
size_t gcHeapUnused;
|
|
size_t gcHeapAdmin;
|
|
size_t gcHeapDecommitted;
|
|
size_t mallocHeap;
|
|
size_t nonHeap;
|
|
};
|
|
|
|
} // namespace JS
|
|
|
|
namespace js {
|
|
|
|
// In memory reporting, we have concept of "sundries", line items which are too
|
|
// small to be worth reporting individually. Under some circumstances, a memory
|
|
// reporter gets tossed into the sundries bucket if it's smaller than
|
|
// MemoryReportingSundriesThreshold() bytes.
|
|
//
|
|
// We need to define this value here, rather than in the code which actually
|
|
// generates the memory reports, because NotableStringInfo uses this value.
|
|
JS_FRIEND_API(size_t) MemoryReportingSundriesThreshold();
|
|
|
|
// This hash policy avoids flattening ropes (which perturbs the site being
|
|
// measured and requires a JSContext) at the expense of doing a FULL ROPE COPY
|
|
// on every hash and match! Beware.
|
|
struct InefficientNonFlatteningStringHashPolicy
|
|
{
|
|
typedef JSString* Lookup;
|
|
static HashNumber hash(const Lookup& l);
|
|
static bool match(const JSString* const& k, const Lookup& l);
|
|
};
|
|
|
|
struct CStringHashPolicy
|
|
{
|
|
typedef const char* Lookup;
|
|
static HashNumber hash(const Lookup& l);
|
|
static bool match(const char* const& k, const Lookup& l);
|
|
};
|
|
|
|
// This file features many classes with numerous size_t fields, and each such
|
|
// class has one or more methods that need to operate on all of these fields.
|
|
// Writing these individually is error-prone -- it's easy to add a new field
|
|
// without updating all the required methods. So we define a single macro list
|
|
// in each class to name the fields (and notable characteristics of them), and
|
|
// then use the following macros to transform those lists into the required
|
|
// methods.
|
|
//
|
|
// - The |tabKind| value is used when measuring TabSizes.
|
|
//
|
|
// - The |servoKind| value is used when measuring ServoSizes and also for
|
|
// the various sizeOfLiveGCThings() methods.
|
|
//
|
|
// In some classes, one or more of the macro arguments aren't used. We use '_'
|
|
// for those.
|
|
//
|
|
#define DECL_SIZE(tabKind, servoKind, mSize) size_t mSize;
|
|
#define ZERO_SIZE(tabKind, servoKind, mSize) mSize(0),
|
|
#define COPY_OTHER_SIZE(tabKind, servoKind, mSize) mSize(other.mSize),
|
|
#define ADD_OTHER_SIZE(tabKind, servoKind, mSize) mSize += other.mSize;
|
|
#define SUB_OTHER_SIZE(tabKind, servoKind, mSize) MOZ_ASSERT(mSize >= other.mSize); \
|
|
mSize -= other.mSize;
|
|
#define ADD_SIZE_TO_N(tabKind, servoKind, mSize) n += mSize;
|
|
#define ADD_SIZE_TO_N_IF_LIVE_GC_THING(tabKind, servoKind, mSize) n += (ServoSizes::servoKind == ServoSizes::GCHeapUsed) ? mSize : 0;
|
|
#define ADD_TO_TAB_SIZES(tabKind, servoKind, mSize) sizes->add(JS::TabSizes::tabKind, mSize);
|
|
#define ADD_TO_SERVO_SIZES(tabKind, servoKind, mSize) sizes->add(JS::ServoSizes::servoKind, mSize);
|
|
|
|
} // namespace js
|
|
|
|
namespace JS {
|
|
|
|
struct ClassInfo
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(Objects, GCHeapUsed, objectsGCHeap) \
|
|
macro(Objects, MallocHeap, objectsMallocHeapSlots) \
|
|
macro(Objects, MallocHeap, objectsMallocHeapElementsNonAsmJS) \
|
|
macro(Objects, MallocHeap, objectsMallocHeapElementsAsmJS) \
|
|
macro(Objects, NonHeap, objectsNonHeapElementsAsmJS) \
|
|
macro(Objects, NonHeap, objectsNonHeapElementsMapped) \
|
|
macro(Objects, NonHeap, objectsNonHeapCodeAsmJS) \
|
|
macro(Objects, MallocHeap, objectsMallocHeapMisc) \
|
|
\
|
|
macro(Other, GCHeapUsed, shapesGCHeapTree) \
|
|
macro(Other, GCHeapUsed, shapesGCHeapDict) \
|
|
macro(Other, GCHeapUsed, shapesGCHeapBase) \
|
|
macro(Other, MallocHeap, shapesMallocHeapTreeTables) \
|
|
macro(Other, MallocHeap, shapesMallocHeapDictTables) \
|
|
macro(Other, MallocHeap, shapesMallocHeapTreeKids)
|
|
|
|
ClassInfo()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
dummy()
|
|
{}
|
|
|
|
void add(const ClassInfo& other) {
|
|
FOR_EACH_SIZE(ADD_OTHER_SIZE)
|
|
}
|
|
|
|
void subtract(const ClassInfo& other) {
|
|
FOR_EACH_SIZE(SUB_OTHER_SIZE)
|
|
}
|
|
|
|
size_t sizeOfAllThings() const {
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N)
|
|
return n;
|
|
}
|
|
|
|
bool isNotable() const {
|
|
static const size_t NotabilityThreshold = 16 * 1024;
|
|
return sizeOfAllThings() >= NotabilityThreshold;
|
|
}
|
|
|
|
size_t sizeOfLiveGCThings() const {
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
|
|
return n;
|
|
}
|
|
|
|
void addToTabSizes(TabSizes* sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
|
|
}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
// Holds data about a notable class (one whose combined object and shape
|
|
// instances use more than a certain amount of memory) so we can report it
|
|
// individually.
|
|
//
|
|
// The only difference between this class and ClassInfo is that this class
|
|
// holds a copy of the filename.
|
|
struct NotableClassInfo : public ClassInfo
|
|
{
|
|
NotableClassInfo();
|
|
NotableClassInfo(const char* className, const ClassInfo& info);
|
|
NotableClassInfo(NotableClassInfo&& info);
|
|
NotableClassInfo& operator=(NotableClassInfo&& info);
|
|
|
|
~NotableClassInfo() {
|
|
js_free(className_);
|
|
}
|
|
|
|
char* className_;
|
|
|
|
private:
|
|
NotableClassInfo(const NotableClassInfo& info) = delete;
|
|
};
|
|
|
|
// Data for tracking JIT-code memory usage.
|
|
struct CodeSizes
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(_, NonHeap, ion) \
|
|
macro(_, NonHeap, baseline) \
|
|
macro(_, NonHeap, regexp) \
|
|
macro(_, NonHeap, other) \
|
|
macro(_, NonHeap, unused)
|
|
|
|
CodeSizes()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
dummy()
|
|
{}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
// Data for tracking GC memory usage.
|
|
struct GCSizes
|
|
{
|
|
// |nurseryDecommitted| is marked as NonHeap rather than GCHeapDecommitted
|
|
// because we don't consider the nursery to be part of the GC heap.
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(_, MallocHeap, marker) \
|
|
macro(_, NonHeap, nurseryCommitted) \
|
|
macro(_, NonHeap, nurseryDecommitted) \
|
|
macro(_, MallocHeap, nurseryMallocedBuffers) \
|
|
macro(_, MallocHeap, storeBufferVals) \
|
|
macro(_, MallocHeap, storeBufferCells) \
|
|
macro(_, MallocHeap, storeBufferSlots) \
|
|
macro(_, MallocHeap, storeBufferWholeCells) \
|
|
macro(_, MallocHeap, storeBufferRelocVals) \
|
|
macro(_, MallocHeap, storeBufferRelocCells) \
|
|
macro(_, MallocHeap, storeBufferGenerics)
|
|
|
|
GCSizes()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
dummy()
|
|
{}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
// This class holds information about the memory taken up by identical copies of
|
|
// a particular string. Multiple JSStrings may have their sizes aggregated
|
|
// together into one StringInfo object. Note that two strings with identical
|
|
// chars will not be aggregated together if one is a short string and the other
|
|
// is not.
|
|
struct StringInfo
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(Strings, GCHeapUsed, gcHeapLatin1) \
|
|
macro(Strings, GCHeapUsed, gcHeapTwoByte) \
|
|
macro(Strings, MallocHeap, mallocHeapLatin1) \
|
|
macro(Strings, MallocHeap, mallocHeapTwoByte)
|
|
|
|
StringInfo()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
numCopies(0)
|
|
{}
|
|
|
|
void add(const StringInfo& other) {
|
|
FOR_EACH_SIZE(ADD_OTHER_SIZE);
|
|
numCopies++;
|
|
}
|
|
|
|
void subtract(const StringInfo& other) {
|
|
FOR_EACH_SIZE(SUB_OTHER_SIZE);
|
|
numCopies--;
|
|
}
|
|
|
|
bool isNotable() const {
|
|
static const size_t NotabilityThreshold = 16 * 1024;
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N)
|
|
return n >= NotabilityThreshold;
|
|
}
|
|
|
|
size_t sizeOfLiveGCThings() const {
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
|
|
return n;
|
|
}
|
|
|
|
void addToTabSizes(TabSizes* sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
|
|
}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
uint32_t numCopies; // How many copies of the string have we seen?
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
// Holds data about a notable string (one which, counting all duplicates, uses
|
|
// more than a certain amount of memory) so we can report it individually.
|
|
//
|
|
// The only difference between this class and StringInfo is that
|
|
// NotableStringInfo holds a copy of some or all of the string's chars.
|
|
struct NotableStringInfo : public StringInfo
|
|
{
|
|
static const size_t MAX_SAVED_CHARS = 1024;
|
|
|
|
NotableStringInfo();
|
|
NotableStringInfo(JSString* str, const StringInfo& info);
|
|
NotableStringInfo(NotableStringInfo&& info);
|
|
NotableStringInfo& operator=(NotableStringInfo&& info);
|
|
|
|
~NotableStringInfo() {
|
|
js_free(buffer);
|
|
}
|
|
|
|
char* buffer;
|
|
size_t length;
|
|
|
|
private:
|
|
NotableStringInfo(const NotableStringInfo& info) = delete;
|
|
};
|
|
|
|
// This class holds information about the memory taken up by script sources
|
|
// from a particular file.
|
|
struct ScriptSourceInfo
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(_, MallocHeap, compressed) \
|
|
macro(_, MallocHeap, uncompressed) \
|
|
macro(_, MallocHeap, misc)
|
|
|
|
ScriptSourceInfo()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
numScripts(0)
|
|
{}
|
|
|
|
void add(const ScriptSourceInfo& other) {
|
|
FOR_EACH_SIZE(ADD_OTHER_SIZE)
|
|
numScripts++;
|
|
}
|
|
|
|
void subtract(const ScriptSourceInfo& other) {
|
|
FOR_EACH_SIZE(SUB_OTHER_SIZE)
|
|
numScripts--;
|
|
}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
}
|
|
|
|
bool isNotable() const {
|
|
static const size_t NotabilityThreshold = 16 * 1024;
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N)
|
|
return n >= NotabilityThreshold;
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
uint32_t numScripts; // How many ScriptSources come from this file? (It
|
|
// can be more than one in XML files that have
|
|
// multiple scripts in CDATA sections.)
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
// Holds data about a notable script source file (one whose combined
|
|
// script sources use more than a certain amount of memory) so we can report it
|
|
// individually.
|
|
//
|
|
// The only difference between this class and ScriptSourceInfo is that this
|
|
// class holds a copy of the filename.
|
|
struct NotableScriptSourceInfo : public ScriptSourceInfo
|
|
{
|
|
NotableScriptSourceInfo();
|
|
NotableScriptSourceInfo(const char* filename, const ScriptSourceInfo& info);
|
|
NotableScriptSourceInfo(NotableScriptSourceInfo&& info);
|
|
NotableScriptSourceInfo& operator=(NotableScriptSourceInfo&& info);
|
|
|
|
~NotableScriptSourceInfo() {
|
|
js_free(filename_);
|
|
}
|
|
|
|
char* filename_;
|
|
|
|
private:
|
|
NotableScriptSourceInfo(const NotableScriptSourceInfo& info) = delete;
|
|
};
|
|
|
|
// These measurements relate directly to the JSRuntime, and not to zones and
|
|
// compartments within it.
|
|
struct RuntimeSizes
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(_, MallocHeap, object) \
|
|
macro(_, MallocHeap, atomsTable) \
|
|
macro(_, MallocHeap, contexts) \
|
|
macro(_, MallocHeap, dtoa) \
|
|
macro(_, MallocHeap, temporary) \
|
|
macro(_, MallocHeap, interpreterStack) \
|
|
macro(_, MallocHeap, mathCache) \
|
|
macro(_, MallocHeap, uncompressedSourceCache) \
|
|
macro(_, MallocHeap, compressedSourceSet) \
|
|
macro(_, MallocHeap, scriptData)
|
|
|
|
RuntimeSizes()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
scriptSourceInfo(),
|
|
code(),
|
|
gc(),
|
|
notableScriptSources()
|
|
{
|
|
allScriptSources = js_new<ScriptSourcesHashMap>();
|
|
if (!allScriptSources || !allScriptSources->init())
|
|
MOZ_CRASH("oom");
|
|
}
|
|
|
|
~RuntimeSizes() {
|
|
// |allScriptSources| is usually deleted and set to nullptr before this
|
|
// destructor runs. But there are failure cases due to OOMs that may
|
|
// prevent that, so it doesn't hurt to try again here.
|
|
js_delete(allScriptSources);
|
|
}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
scriptSourceInfo.addToServoSizes(sizes);
|
|
code.addToServoSizes(sizes);
|
|
gc.addToServoSizes(sizes);
|
|
}
|
|
|
|
// The script source measurements in |scriptSourceInfo| are initially for
|
|
// all script sources. At the end, if the measurement granularity is
|
|
// FineGrained, we subtract the measurements of the notable script sources
|
|
// and move them into |notableScriptSources|.
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
ScriptSourceInfo scriptSourceInfo;
|
|
CodeSizes code;
|
|
GCSizes gc;
|
|
|
|
typedef js::HashMap<const char*, ScriptSourceInfo,
|
|
js::CStringHashPolicy,
|
|
js::SystemAllocPolicy> ScriptSourcesHashMap;
|
|
|
|
// |allScriptSources| is only used transiently. During the reporting phase
|
|
// it is filled with info about every script source in the runtime. It's
|
|
// then used to fill in |notableScriptSources| (which actually gets
|
|
// reported), and immediately discarded afterwards.
|
|
ScriptSourcesHashMap* allScriptSources;
|
|
js::Vector<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> notableScriptSources;
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
struct UnusedGCThingSizes
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(Other, GCHeapUnused, object) \
|
|
macro(Other, GCHeapUnused, script) \
|
|
macro(Other, GCHeapUnused, lazyScript) \
|
|
macro(Other, GCHeapUnused, shape) \
|
|
macro(Other, GCHeapUnused, baseShape) \
|
|
macro(Other, GCHeapUnused, objectGroup) \
|
|
macro(Other, GCHeapUnused, string) \
|
|
macro(Other, GCHeapUnused, symbol) \
|
|
macro(Other, GCHeapUnused, jitcode) \
|
|
|
|
UnusedGCThingSizes()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
dummy()
|
|
{}
|
|
|
|
UnusedGCThingSizes(UnusedGCThingSizes&& other)
|
|
: FOR_EACH_SIZE(COPY_OTHER_SIZE)
|
|
dummy()
|
|
{}
|
|
|
|
void addToKind(JS::TraceKind kind, intptr_t n) {
|
|
switch (kind) {
|
|
case JS::TraceKind::Object: object += n; break;
|
|
case JS::TraceKind::String: string += n; break;
|
|
case JS::TraceKind::Symbol: symbol += n; break;
|
|
case JS::TraceKind::Script: script += n; break;
|
|
case JS::TraceKind::Shape: shape += n; break;
|
|
case JS::TraceKind::BaseShape: baseShape += n; break;
|
|
case JS::TraceKind::JitCode: jitcode += n; break;
|
|
case JS::TraceKind::LazyScript: lazyScript += n; break;
|
|
case JS::TraceKind::ObjectGroup: objectGroup += n; break;
|
|
default:
|
|
MOZ_CRASH("Bad trace kind for UnusedGCThingSizes");
|
|
}
|
|
}
|
|
|
|
void addSizes(const UnusedGCThingSizes& other) {
|
|
FOR_EACH_SIZE(ADD_OTHER_SIZE)
|
|
}
|
|
|
|
size_t totalSize() const {
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N)
|
|
return n;
|
|
}
|
|
|
|
void addToTabSizes(JS::TabSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
|
|
}
|
|
|
|
void addToServoSizes(JS::ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
struct ZoneStats
|
|
{
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(Other, GCHeapUsed, symbolsGCHeap) \
|
|
macro(Other, GCHeapAdmin, gcHeapArenaAdmin) \
|
|
macro(Other, GCHeapUsed, lazyScriptsGCHeap) \
|
|
macro(Other, MallocHeap, lazyScriptsMallocHeap) \
|
|
macro(Other, GCHeapUsed, jitCodesGCHeap) \
|
|
macro(Other, GCHeapUsed, objectGroupsGCHeap) \
|
|
macro(Other, MallocHeap, objectGroupsMallocHeap) \
|
|
macro(Other, MallocHeap, typePool) \
|
|
macro(Other, MallocHeap, baselineStubsOptimized)
|
|
|
|
ZoneStats()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
unusedGCThings(),
|
|
stringInfo(),
|
|
extra(),
|
|
allStrings(nullptr),
|
|
notableStrings(),
|
|
isTotals(true)
|
|
{}
|
|
|
|
ZoneStats(ZoneStats&& other)
|
|
: FOR_EACH_SIZE(COPY_OTHER_SIZE)
|
|
unusedGCThings(mozilla::Move(other.unusedGCThings)),
|
|
stringInfo(mozilla::Move(other.stringInfo)),
|
|
extra(other.extra),
|
|
allStrings(other.allStrings),
|
|
notableStrings(mozilla::Move(other.notableStrings)),
|
|
isTotals(other.isTotals)
|
|
{
|
|
other.allStrings = nullptr;
|
|
MOZ_ASSERT(!other.isTotals);
|
|
}
|
|
|
|
~ZoneStats() {
|
|
// |allStrings| is usually deleted and set to nullptr before this
|
|
// destructor runs. But there are failure cases due to OOMs that may
|
|
// prevent that, so it doesn't hurt to try again here.
|
|
js_delete(allStrings);
|
|
}
|
|
|
|
bool initStrings(JSRuntime* rt);
|
|
|
|
void addSizes(const ZoneStats& other) {
|
|
MOZ_ASSERT(isTotals);
|
|
FOR_EACH_SIZE(ADD_OTHER_SIZE)
|
|
unusedGCThings.addSizes(other.unusedGCThings);
|
|
stringInfo.add(other.stringInfo);
|
|
}
|
|
|
|
size_t sizeOfLiveGCThings() const {
|
|
MOZ_ASSERT(isTotals);
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
|
|
n += stringInfo.sizeOfLiveGCThings();
|
|
return n;
|
|
}
|
|
|
|
void addToTabSizes(JS::TabSizes* sizes) const {
|
|
MOZ_ASSERT(isTotals);
|
|
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
|
|
unusedGCThings.addToTabSizes(sizes);
|
|
stringInfo.addToTabSizes(sizes);
|
|
}
|
|
|
|
void addToServoSizes(JS::ServoSizes *sizes) const {
|
|
MOZ_ASSERT(isTotals);
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
unusedGCThings.addToServoSizes(sizes);
|
|
stringInfo.addToServoSizes(sizes);
|
|
}
|
|
|
|
// These string measurements are initially for all strings. At the end,
|
|
// if the measurement granularity is FineGrained, we subtract the
|
|
// measurements of the notable script sources and move them into
|
|
// |notableStrings|.
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
UnusedGCThingSizes unusedGCThings;
|
|
StringInfo stringInfo;
|
|
void* extra; // This field can be used by embedders.
|
|
|
|
typedef js::HashMap<JSString*, StringInfo,
|
|
js::InefficientNonFlatteningStringHashPolicy,
|
|
js::SystemAllocPolicy> StringsHashMap;
|
|
|
|
// |allStrings| is only used transiently. During the zone traversal it is
|
|
// filled with info about every string in the zone. It's then used to fill
|
|
// in |notableStrings| (which actually gets reported), and immediately
|
|
// discarded afterwards.
|
|
StringsHashMap* allStrings;
|
|
js::Vector<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings;
|
|
bool isTotals;
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
struct CompartmentStats
|
|
{
|
|
// We assume that |objectsPrivate| is on the malloc heap, but it's not
|
|
// actually guaranteed. But for Servo, at least, it's a moot point because
|
|
// it doesn't provide an ObjectPrivateVisitor so the value will always be
|
|
// zero.
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(Private, MallocHeap, objectsPrivate) \
|
|
macro(Other, GCHeapUsed, scriptsGCHeap) \
|
|
macro(Other, MallocHeap, scriptsMallocHeapData) \
|
|
macro(Other, MallocHeap, baselineData) \
|
|
macro(Other, MallocHeap, baselineStubsFallback) \
|
|
macro(Other, MallocHeap, ionData) \
|
|
macro(Other, MallocHeap, typeInferenceTypeScripts) \
|
|
macro(Other, MallocHeap, typeInferenceAllocationSiteTables) \
|
|
macro(Other, MallocHeap, typeInferenceArrayTypeTables) \
|
|
macro(Other, MallocHeap, typeInferenceObjectTypeTables) \
|
|
macro(Other, MallocHeap, compartmentObject) \
|
|
macro(Other, MallocHeap, compartmentTables) \
|
|
macro(Other, MallocHeap, innerViewsTable) \
|
|
macro(Other, MallocHeap, lazyArrayBuffersTable) \
|
|
macro(Other, MallocHeap, objectMetadataTable) \
|
|
macro(Other, MallocHeap, crossCompartmentWrappersTable) \
|
|
macro(Other, MallocHeap, regexpCompartment) \
|
|
macro(Other, MallocHeap, savedStacksSet)
|
|
|
|
CompartmentStats()
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
classInfo(),
|
|
extra(),
|
|
allClasses(nullptr),
|
|
notableClasses(),
|
|
isTotals(true)
|
|
{}
|
|
|
|
CompartmentStats(CompartmentStats&& other)
|
|
: FOR_EACH_SIZE(COPY_OTHER_SIZE)
|
|
classInfo(mozilla::Move(other.classInfo)),
|
|
extra(other.extra),
|
|
allClasses(other.allClasses),
|
|
notableClasses(mozilla::Move(other.notableClasses)),
|
|
isTotals(other.isTotals)
|
|
{
|
|
other.allClasses = nullptr;
|
|
MOZ_ASSERT(!other.isTotals);
|
|
}
|
|
|
|
~CompartmentStats() {
|
|
// |allClasses| is usually deleted and set to nullptr before this
|
|
// destructor runs. But there are failure cases due to OOMs that may
|
|
// prevent that, so it doesn't hurt to try again here.
|
|
js_delete(allClasses);
|
|
}
|
|
|
|
bool initClasses(JSRuntime* rt);
|
|
|
|
void addSizes(const CompartmentStats& other) {
|
|
MOZ_ASSERT(isTotals);
|
|
FOR_EACH_SIZE(ADD_OTHER_SIZE)
|
|
classInfo.add(other.classInfo);
|
|
}
|
|
|
|
size_t sizeOfLiveGCThings() const {
|
|
MOZ_ASSERT(isTotals);
|
|
size_t n = 0;
|
|
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
|
|
n += classInfo.sizeOfLiveGCThings();
|
|
return n;
|
|
}
|
|
|
|
void addToTabSizes(TabSizes* sizes) const {
|
|
MOZ_ASSERT(isTotals);
|
|
FOR_EACH_SIZE(ADD_TO_TAB_SIZES);
|
|
classInfo.addToTabSizes(sizes);
|
|
}
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
MOZ_ASSERT(isTotals);
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES);
|
|
classInfo.addToServoSizes(sizes);
|
|
}
|
|
|
|
// The class measurements in |classInfo| are initially for all classes. At
|
|
// the end, if the measurement granularity is FineGrained, we subtract the
|
|
// measurements of the notable classes and move them into |notableClasses|.
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
ClassInfo classInfo;
|
|
void* extra; // This field can be used by embedders.
|
|
|
|
typedef js::HashMap<const char*, ClassInfo,
|
|
js::CStringHashPolicy,
|
|
js::SystemAllocPolicy> ClassesHashMap;
|
|
|
|
// These are similar to |allStrings| and |notableStrings| in ZoneStats.
|
|
ClassesHashMap* allClasses;
|
|
js::Vector<NotableClassInfo, 0, js::SystemAllocPolicy> notableClasses;
|
|
bool isTotals;
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
typedef js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> CompartmentStatsVector;
|
|
typedef js::Vector<ZoneStats, 0, js::SystemAllocPolicy> ZoneStatsVector;
|
|
|
|
struct RuntimeStats
|
|
{
|
|
// |gcHeapChunkTotal| is ignored because it's the sum of all the other
|
|
// values. |gcHeapGCThings| is ignored because it's the sum of some of the
|
|
// values from the zones and compartments. Both of those values are not
|
|
// reported directly, but are just present for sanity-checking other
|
|
// values.
|
|
#define FOR_EACH_SIZE(macro) \
|
|
macro(_, Ignore, gcHeapChunkTotal) \
|
|
macro(_, GCHeapDecommitted, gcHeapDecommittedArenas) \
|
|
macro(_, GCHeapUnused, gcHeapUnusedChunks) \
|
|
macro(_, GCHeapUnused, gcHeapUnusedArenas) \
|
|
macro(_, GCHeapAdmin, gcHeapChunkAdmin) \
|
|
macro(_, Ignore, gcHeapGCThings)
|
|
|
|
explicit RuntimeStats(mozilla::MallocSizeOf mallocSizeOf)
|
|
: FOR_EACH_SIZE(ZERO_SIZE)
|
|
runtime(),
|
|
cTotals(),
|
|
zTotals(),
|
|
compartmentStatsVector(),
|
|
zoneStatsVector(),
|
|
currZoneStats(nullptr),
|
|
mallocSizeOf_(mallocSizeOf)
|
|
{}
|
|
|
|
// Here's a useful breakdown of the GC heap.
|
|
//
|
|
// - rtStats.gcHeapChunkTotal
|
|
// - decommitted bytes
|
|
// - rtStats.gcHeapDecommittedArenas (decommitted arenas in non-empty chunks)
|
|
// - unused bytes
|
|
// - rtStats.gcHeapUnusedChunks (empty chunks)
|
|
// - rtStats.gcHeapUnusedArenas (empty arenas within non-empty chunks)
|
|
// - rtStats.zTotals.unusedGCThings.totalSize() (empty GC thing slots within non-empty arenas)
|
|
// - used bytes
|
|
// - rtStats.gcHeapChunkAdmin
|
|
// - rtStats.zTotals.gcHeapArenaAdmin
|
|
// - rtStats.gcHeapGCThings (in-use GC things)
|
|
// == rtStats.zTotals.sizeOfLiveGCThings() + rtStats.cTotals.sizeOfLiveGCThings()
|
|
//
|
|
// It's possible that some arenas in empty chunks may be decommitted, but
|
|
// we don't count those under rtStats.gcHeapDecommittedArenas because (a)
|
|
// it's rare, and (b) this means that rtStats.gcHeapUnusedChunks is a
|
|
// multiple of the chunk size, which is good.
|
|
|
|
void addToServoSizes(ServoSizes *sizes) const {
|
|
FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
|
|
runtime.addToServoSizes(sizes);
|
|
}
|
|
|
|
FOR_EACH_SIZE(DECL_SIZE)
|
|
|
|
RuntimeSizes runtime;
|
|
|
|
CompartmentStats cTotals; // The sum of this runtime's compartments' measurements.
|
|
ZoneStats zTotals; // The sum of this runtime's zones' measurements.
|
|
|
|
CompartmentStatsVector compartmentStatsVector;
|
|
ZoneStatsVector zoneStatsVector;
|
|
|
|
ZoneStats* currZoneStats;
|
|
|
|
mozilla::MallocSizeOf mallocSizeOf_;
|
|
|
|
virtual void initExtraCompartmentStats(JSCompartment* c, CompartmentStats* cstats) = 0;
|
|
virtual void initExtraZoneStats(JS::Zone* zone, ZoneStats* zstats) = 0;
|
|
|
|
#undef FOR_EACH_SIZE
|
|
};
|
|
|
|
class ObjectPrivateVisitor
|
|
{
|
|
public:
|
|
// Within CollectRuntimeStats, this method is called for each JS object
|
|
// that has an nsISupports pointer.
|
|
virtual size_t sizeOfIncludingThis(nsISupports* aSupports) = 0;
|
|
|
|
// A callback that gets a JSObject's nsISupports pointer, if it has one.
|
|
// Note: this function does *not* addref |iface|.
|
|
typedef bool(*GetISupportsFun)(JSObject* obj, nsISupports** iface);
|
|
GetISupportsFun getISupports_;
|
|
|
|
explicit ObjectPrivateVisitor(GetISupportsFun getISupports)
|
|
: getISupports_(getISupports)
|
|
{}
|
|
};
|
|
|
|
extern JS_PUBLIC_API(bool)
|
|
CollectRuntimeStats(JSRuntime* rt, RuntimeStats* rtStats, ObjectPrivateVisitor* opv, bool anonymize);
|
|
|
|
extern JS_PUBLIC_API(size_t)
|
|
SystemCompartmentCount(JSRuntime* rt);
|
|
|
|
extern JS_PUBLIC_API(size_t)
|
|
UserCompartmentCount(JSRuntime* rt);
|
|
|
|
extern JS_PUBLIC_API(size_t)
|
|
PeakSizeOfTemporary(const JSRuntime* rt);
|
|
|
|
extern JS_PUBLIC_API(bool)
|
|
AddSizeOfTab(JSRuntime* rt, JS::HandleObject obj, mozilla::MallocSizeOf mallocSizeOf,
|
|
ObjectPrivateVisitor* opv, TabSizes* sizes);
|
|
|
|
extern JS_PUBLIC_API(bool)
|
|
AddServoSizeOf(JSRuntime *rt, mozilla::MallocSizeOf mallocSizeOf,
|
|
ObjectPrivateVisitor *opv, ServoSizes *sizes);
|
|
|
|
} // namespace JS
|
|
|
|
#undef DECL_SIZE
|
|
#undef ZERO_SIZE
|
|
#undef COPY_OTHER_SIZE
|
|
#undef ADD_OTHER_SIZE
|
|
#undef SUB_OTHER_SIZE
|
|
#undef ADD_SIZE_TO_N
|
|
#undef ADD_SIZE_TO_N_IF_LIVE_GC_THING
|
|
#undef ADD_TO_TAB_SIZES
|
|
|
|
#endif /* js_MemoryMetrics_h */
|