js/public/MemoryMetrics.h
author Brian Hackett <bhackett1024@gmail.com>
Sun, 13 Sep 2015 16:55:58 -0600
changeset 262257 124d73f46e52640746f0414f951f717bb1c97b93
parent 260789 dd0900e3e6f19ba910e28a7f75af9e47a8c0936e
child 264996 28dda1c9988d040d3ca173275d8b5ba4939b4fb5
permissions -rw-r--r--
Bug 1198861 - Backout faaafe8c3d1e for massive regressions.

/* -*- 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 "mozilla/TypeTraits.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) \
    /* Avoid self-comparison warnings by comparing enums indirectly. */ \
    n += (mozilla::IsSame<int[ServoSizes::servoKind], int[ServoSizes::GCHeapUsed]>::value) \
         ? 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, 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 */