Blob Blame History Raw
/* -*- 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 "jspubtd.h"

#include "js/AllocPolicy.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. */
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, objectsMallocHeapElementsNormal) \
  MACRO(Objects, MallocHeap, objectsMallocHeapElementsAsmJS)  \
  MACRO(Objects, MallocHeap, objectsMallocHeapMisc)           \
  MACRO(Objects, NonHeap, objectsNonHeapElementsNormal)       \
  MACRO(Objects, NonHeap, objectsNonHeapElementsShared)       \
  MACRO(Objects, NonHeap, objectsNonHeapElementsWasm)         \
  MACRO(Objects, NonHeap, objectsNonHeapCodeWasm)

  ClassInfo() : FOR_EACH_SIZE(ZERO_SIZE) wasmGuardPages(0) {}

  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) size_t wasmGuardPages;

#undef FOR_EACH_SIZE
};

struct ShapeInfo {
#define FOR_EACH_SIZE(MACRO)                           \
  MACRO(Other, GCHeapUsed, shapesGCHeapTree)           \
  MACRO(Other, GCHeapUsed, shapesGCHeapDict)           \
  MACRO(Other, GCHeapUsed, shapesGCHeapBase)           \
  MACRO(Other, MallocHeap, shapesMallocHeapTreeTables) \
  MACRO(Other, MallocHeap, shapesMallocHeapDictTables) \
  MACRO(Other, MallocHeap, shapesMallocHeapTreeKids)

  ShapeInfo() : FOR_EACH_SIZE(ZERO_SIZE) dummy() {}

  void add(const ShapeInfo& other) { FOR_EACH_SIZE(ADD_OTHER_SIZE) }

  void subtract(const ShapeInfo& other){FOR_EACH_SIZE(SUB_OTHER_SIZE)}

  size_t sizeOfAllThings() const {
    size_t n = 0;
    FOR_EACH_SIZE(ADD_SIZE_TO_N)
    return n;
  }

  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(_, 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, 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,
 * compartments, and realms within it.
 */
struct RuntimeSizes {
#define FOR_EACH_SIZE(MACRO)                        \
  MACRO(_, MallocHeap, object)                      \
  MACRO(_, MallocHeap, atomsTable)                  \
  MACRO(_, MallocHeap, atomsMarkBitmaps)            \
  MACRO(_, MallocHeap, contexts)                    \
  MACRO(_, MallocHeap, temporary)                   \
  MACRO(_, MallocHeap, interpreterStack)            \
  MACRO(_, MallocHeap, mathCache)                   \
  MACRO(_, MallocHeap, sharedImmutableStringsCache) \
  MACRO(_, MallocHeap, sharedIntlData)              \
  MACRO(_, MallocHeap, uncompressedSourceCache)     \
  MACRO(_, MallocHeap, scriptData)                  \
  MACRO(_, MallocHeap, tracelogger)

  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)     \
  MACRO(Other, GCHeapUnused, scope)       \
  MACRO(Other, GCHeapUnused, regExpShared)

  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;
      case JS::TraceKind::Scope:
        scope += n;
        break;
      case JS::TraceKind::RegExpShared:
        regExpShared += 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, GCHeapUsed, scopesGCHeap)            \
  MACRO(Other, MallocHeap, scopesMallocHeap)        \
  MACRO(Other, GCHeapUsed, regExpSharedsGCHeap)     \
  MACRO(Other, MallocHeap, regExpSharedsMallocHeap) \
  MACRO(Other, MallocHeap, typePool)                \
  MACRO(Other, MallocHeap, regexpZone)              \
  MACRO(Other, MallocHeap, jitZone)                 \
  MACRO(Other, MallocHeap, baselineStubsOptimized)  \
  MACRO(Other, MallocHeap, cachedCFG)               \
  MACRO(Other, MallocHeap, uniqueIdMap)             \
  MACRO(Other, MallocHeap, shapeTables)

  ZoneStats()
      : FOR_EACH_SIZE(ZERO_SIZE) unusedGCThings(),
        stringInfo(),
        shapeInfo(),
        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)),
        shapeInfo(mozilla::Move(other.shapeInfo)),
        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();

  void addSizes(const ZoneStats& other) {
    MOZ_ASSERT(isTotals);
    FOR_EACH_SIZE(ADD_OTHER_SIZE)
    unusedGCThings.addSizes(other.unusedGCThings);
    stringInfo.add(other.stringInfo);
    shapeInfo.add(other.shapeInfo);
  }

  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();
    n += shapeInfo.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);
    shapeInfo.addToTabSizes(sizes);
  }

  void addToServoSizes(JS::ServoSizes* sizes) const {
    MOZ_ASSERT(isTotals);
    FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
    unusedGCThings.addToServoSizes(sizes);
    stringInfo.addToServoSizes(sizes);
    shapeInfo.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;
  ShapeInfo shapeInfo;
  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, savedStacksSet)                    \
  MACRO(Other, MallocHeap, varNamesSet)                       \
  MACRO(Other, MallocHeap, nonSyntacticLexicalScopesTable)    \
  MACRO(Other, MallocHeap, jitCompartment)                    \
  MACRO(Other, MallocHeap, privateData)                       \
  MACRO(Other, MallocHeap, scriptCountsMap)

  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(const CompartmentStats&) = delete;  // disallow copying

  ~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();

  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(JSContext* cx,
                                              RuntimeStats* rtStats,
                                              ObjectPrivateVisitor* opv,
                                              bool anonymize);

extern JS_PUBLIC_API size_t SystemCompartmentCount(JSContext* cx);

extern JS_PUBLIC_API size_t UserCompartmentCount(JSContext* cx);

extern JS_PUBLIC_API size_t PeakSizeOfTemporary(const JSContext* cx);

extern JS_PUBLIC_API bool AddSizeOfTab(JSContext* cx, JS::HandleObject obj,
                                       mozilla::MallocSizeOf mallocSizeOf,
                                       ObjectPrivateVisitor* opv,
                                       TabSizes* sizes);

extern JS_PUBLIC_API bool AddServoSizeOf(JSContext* cx,
                                         mozilla::MallocSizeOf mallocSizeOf,
                                         ObjectPrivateVisitor* opv,
                                         ServoSizes* sizes);

extern JS_PUBLIC_API void CollectTraceLoggerStateStats(RuntimeStats* rtStats);

}  // 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 */