1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef js_MemoryMetrics_h
8 #define js_MemoryMetrics_h
9 
10 // These declarations are highly likely to change in the future. Depend on them
11 // at your own risk.
12 
13 #include "mozilla/MemoryReporting.h"
14 #include "mozilla/PodOperations.h"
15 #include "mozilla/TypeTraits.h"
16 
17 #include <string.h>
18 
19 #include "jsalloc.h"
20 #include "jspubtd.h"
21 
22 #include "js/HashTable.h"
23 #include "js/TracingAPI.h"
24 #include "js/Utility.h"
25 #include "js/Vector.h"
26 
27 class nsISupports;      // Needed for ObjectPrivateVisitor.
28 
29 namespace JS {
30 
31 struct TabSizes
32 {
33     enum Kind {
34         Objects,
35         Strings,
36         Private,
37         Other
38     };
39 
TabSizesTabSizes40     TabSizes() { mozilla::PodZero(this); }
41 
addTabSizes42     void add(Kind kind, size_t n) {
43         switch (kind) {
44             case Objects: objects  += n; break;
45             case Strings: strings  += n; break;
46             case Private: private_ += n; break;
47             case Other:   other    += n; break;
48             default:      MOZ_CRASH("bad TabSizes kind");
49         }
50     }
51 
52     size_t objects;
53     size_t strings;
54     size_t private_;
55     size_t other;
56 };
57 
58 /** These are the measurements used by Servo. */
59 struct ServoSizes
60 {
61     enum Kind {
62         GCHeapUsed,
63         GCHeapUnused,
64         GCHeapAdmin,
65         GCHeapDecommitted,
66         MallocHeap,
67         NonHeap,
68         Ignore
69     };
70 
ServoSizesServoSizes71     ServoSizes() { mozilla::PodZero(this); }
72 
addServoSizes73     void add(Kind kind, size_t n) {
74         switch (kind) {
75             case GCHeapUsed:        gcHeapUsed        += n; break;
76             case GCHeapUnused:      gcHeapUnused      += n; break;
77             case GCHeapAdmin:       gcHeapAdmin       += n; break;
78             case GCHeapDecommitted: gcHeapDecommitted += n; break;
79             case MallocHeap:        mallocHeap        += n; break;
80             case NonHeap:           nonHeap           += n; break;
81             case Ignore:            /* do nothing */        break;
82             default:                MOZ_CRASH("bad ServoSizes kind");
83         }
84     }
85 
86     size_t gcHeapUsed;
87     size_t gcHeapUnused;
88     size_t gcHeapAdmin;
89     size_t gcHeapDecommitted;
90     size_t mallocHeap;
91     size_t nonHeap;
92 };
93 
94 } // namespace JS
95 
96 namespace js {
97 
98 /**
99  * In memory reporting, we have concept of "sundries", line items which are too
100  * small to be worth reporting individually.  Under some circumstances, a memory
101  * reporter gets tossed into the sundries bucket if it's smaller than
102  * MemoryReportingSundriesThreshold() bytes.
103  *
104  * We need to define this value here, rather than in the code which actually
105  * generates the memory reports, because NotableStringInfo uses this value.
106  */
107 JS_FRIEND_API(size_t) MemoryReportingSundriesThreshold();
108 
109 /**
110  * This hash policy avoids flattening ropes (which perturbs the site being
111  * measured and requires a JSContext) at the expense of doing a FULL ROPE COPY
112  * on every hash and match! Beware.
113  */
114 struct InefficientNonFlatteningStringHashPolicy
115 {
116     typedef JSString* Lookup;
117     static HashNumber hash(const Lookup& l);
118     static bool match(const JSString* const& k, const Lookup& l);
119 };
120 
121 struct CStringHashPolicy
122 {
123     typedef const char* Lookup;
124     static HashNumber hash(const Lookup& l);
125     static bool match(const char* const& k, const Lookup& l);
126 };
127 
128 // This file features many classes with numerous size_t fields, and each such
129 // class has one or more methods that need to operate on all of these fields.
130 // Writing these individually is error-prone -- it's easy to add a new field
131 // without updating all the required methods.  So we define a single macro list
132 // in each class to name the fields (and notable characteristics of them), and
133 // then use the following macros to transform those lists into the required
134 // methods.
135 //
136 // - The |tabKind| value is used when measuring TabSizes.
137 //
138 // - The |servoKind| value is used when measuring ServoSizes and also for
139 //   the various sizeOfLiveGCThings() methods.
140 //
141 // In some classes, one or more of the macro arguments aren't used.  We use '_'
142 // for those.
143 //
144 #define DECL_SIZE(tabKind, servoKind, mSize)        size_t mSize;
145 #define ZERO_SIZE(tabKind, servoKind, mSize)        mSize(0),
146 #define COPY_OTHER_SIZE(tabKind, servoKind, mSize)  mSize(other.mSize),
147 #define ADD_OTHER_SIZE(tabKind, servoKind, mSize)   mSize += other.mSize;
148 #define SUB_OTHER_SIZE(tabKind, servoKind, mSize) \
149     MOZ_ASSERT(mSize >= other.mSize); \
150     mSize -= other.mSize;
151 #define ADD_SIZE_TO_N(tabKind, servoKind, mSize)                  n += mSize;
152 #define ADD_SIZE_TO_N_IF_LIVE_GC_THING(tabKind, servoKind, mSize) \
153     /* Avoid self-comparison warnings by comparing enums indirectly. */ \
154     n += (mozilla::IsSame<int[ServoSizes::servoKind], int[ServoSizes::GCHeapUsed]>::value) \
155          ? mSize \
156          : 0;
157 #define ADD_TO_TAB_SIZES(tabKind, servoKind, mSize)               sizes->add(JS::TabSizes::tabKind, mSize);
158 #define ADD_TO_SERVO_SIZES(tabKind, servoKind, mSize)             sizes->add(JS::ServoSizes::servoKind, mSize);
159 
160 } // namespace js
161 
162 namespace JS {
163 
164 struct ClassInfo
165 {
166 #define FOR_EACH_SIZE(macro) \
167     macro(Objects, GCHeapUsed, objectsGCHeap) \
168     macro(Objects, MallocHeap, objectsMallocHeapSlots) \
169     macro(Objects, MallocHeap, objectsMallocHeapElementsNormal) \
170     macro(Objects, MallocHeap, objectsMallocHeapElementsAsmJS) \
171     macro(Objects, MallocHeap, objectsMallocHeapMisc) \
172     macro(Objects, NonHeap,    objectsNonHeapElementsNormal) \
173     macro(Objects, NonHeap,    objectsNonHeapElementsShared) \
174     macro(Objects, NonHeap,    objectsNonHeapElementsWasm) \
175     macro(Objects, NonHeap,    objectsNonHeapCodeWasm)
176 
ClassInfoClassInfo177     ClassInfo()
178       : FOR_EACH_SIZE(ZERO_SIZE)
179         wasmGuardPages(0)
180     {}
181 
addClassInfo182     void add(const ClassInfo& other) {
183         FOR_EACH_SIZE(ADD_OTHER_SIZE)
184     }
185 
subtractClassInfo186     void subtract(const ClassInfo& other) {
187         FOR_EACH_SIZE(SUB_OTHER_SIZE)
188     }
189 
sizeOfAllThingsClassInfo190     size_t sizeOfAllThings() const {
191         size_t n = 0;
192         FOR_EACH_SIZE(ADD_SIZE_TO_N)
193         return n;
194     }
195 
isNotableClassInfo196     bool isNotable() const {
197         static const size_t NotabilityThreshold = 16 * 1024;
198         return sizeOfAllThings() >= NotabilityThreshold;
199     }
200 
sizeOfLiveGCThingsClassInfo201     size_t sizeOfLiveGCThings() const {
202         size_t n = 0;
203         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
204         return n;
205     }
206 
addToTabSizesClassInfo207     void addToTabSizes(TabSizes* sizes) const {
208         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
209     }
210 
addToServoSizesClassInfo211     void addToServoSizes(ServoSizes *sizes) const {
212         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
213     }
214 
215     FOR_EACH_SIZE(DECL_SIZE)
216     size_t wasmGuardPages;
217 
218 #undef FOR_EACH_SIZE
219 };
220 
221 struct ShapeInfo
222 {
223 #define FOR_EACH_SIZE(macro) \
224     macro(Other,   GCHeapUsed, shapesGCHeapTree) \
225     macro(Other,   GCHeapUsed, shapesGCHeapDict) \
226     macro(Other,   GCHeapUsed, shapesGCHeapBase) \
227     macro(Other,   MallocHeap, shapesMallocHeapTreeTables) \
228     macro(Other,   MallocHeap, shapesMallocHeapDictTables) \
229     macro(Other,   MallocHeap, shapesMallocHeapTreeKids)
230 
ShapeInfoShapeInfo231     ShapeInfo()
232       : FOR_EACH_SIZE(ZERO_SIZE)
233         dummy()
234     {}
235 
addShapeInfo236     void add(const ShapeInfo& other) {
237         FOR_EACH_SIZE(ADD_OTHER_SIZE)
238     }
239 
subtractShapeInfo240     void subtract(const ShapeInfo& other) {
241         FOR_EACH_SIZE(SUB_OTHER_SIZE)
242     }
243 
sizeOfAllThingsShapeInfo244     size_t sizeOfAllThings() const {
245         size_t n = 0;
246         FOR_EACH_SIZE(ADD_SIZE_TO_N)
247         return n;
248     }
249 
sizeOfLiveGCThingsShapeInfo250     size_t sizeOfLiveGCThings() const {
251         size_t n = 0;
252         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
253         return n;
254     }
255 
addToTabSizesShapeInfo256     void addToTabSizes(TabSizes* sizes) const {
257         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
258     }
259 
addToServoSizesShapeInfo260     void addToServoSizes(ServoSizes *sizes) const {
261         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
262     }
263 
264     FOR_EACH_SIZE(DECL_SIZE)
265     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
266 
267 #undef FOR_EACH_SIZE
268 };
269 
270 /**
271  * Holds data about a notable class (one whose combined object and shape
272  * instances use more than a certain amount of memory) so we can report it
273  * individually.
274  *
275  * The only difference between this class and ClassInfo is that this class
276  * holds a copy of the filename.
277  */
278 struct NotableClassInfo : public ClassInfo
279 {
280     NotableClassInfo();
281     NotableClassInfo(const char* className, const ClassInfo& info);
282     NotableClassInfo(NotableClassInfo&& info);
283     NotableClassInfo& operator=(NotableClassInfo&& info);
284 
~NotableClassInfoNotableClassInfo285     ~NotableClassInfo() {
286         js_free(className_);
287     }
288 
289     char* className_;
290 
291   private:
292     NotableClassInfo(const NotableClassInfo& info) = delete;
293 };
294 
295 /** Data for tracking JIT-code memory usage. */
296 struct CodeSizes
297 {
298 #define FOR_EACH_SIZE(macro) \
299     macro(_, NonHeap, ion) \
300     macro(_, NonHeap, baseline) \
301     macro(_, NonHeap, regexp) \
302     macro(_, NonHeap, other) \
303     macro(_, NonHeap, unused)
304 
CodeSizesCodeSizes305     CodeSizes()
306       : FOR_EACH_SIZE(ZERO_SIZE)
307         dummy()
308     {}
309 
addToServoSizesCodeSizes310     void addToServoSizes(ServoSizes *sizes) const {
311         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
312     }
313 
314     FOR_EACH_SIZE(DECL_SIZE)
315     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
316 
317 #undef FOR_EACH_SIZE
318 };
319 
320 /** Data for tracking GC memory usage. */
321 struct GCSizes
322 {
323     // |nurseryDecommitted| is marked as NonHeap rather than GCHeapDecommitted
324     // because we don't consider the nursery to be part of the GC heap.
325 #define FOR_EACH_SIZE(macro) \
326     macro(_, MallocHeap, marker) \
327     macro(_, NonHeap,    nurseryCommitted) \
328     macro(_, MallocHeap, nurseryMallocedBuffers) \
329     macro(_, MallocHeap, storeBufferVals) \
330     macro(_, MallocHeap, storeBufferCells) \
331     macro(_, MallocHeap, storeBufferSlots) \
332     macro(_, MallocHeap, storeBufferWholeCells) \
333     macro(_, MallocHeap, storeBufferGenerics)
334 
GCSizesGCSizes335     GCSizes()
336       : FOR_EACH_SIZE(ZERO_SIZE)
337         dummy()
338     {}
339 
addToServoSizesGCSizes340     void addToServoSizes(ServoSizes *sizes) const {
341         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
342     }
343 
344     FOR_EACH_SIZE(DECL_SIZE)
345     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
346 
347 #undef FOR_EACH_SIZE
348 };
349 
350 /**
351  * This class holds information about the memory taken up by identical copies of
352  * a particular string.  Multiple JSStrings may have their sizes aggregated
353  * together into one StringInfo object.  Note that two strings with identical
354  * chars will not be aggregated together if one is a short string and the other
355  * is not.
356  */
357 struct StringInfo
358 {
359 #define FOR_EACH_SIZE(macro) \
360     macro(Strings, GCHeapUsed, gcHeapLatin1) \
361     macro(Strings, GCHeapUsed, gcHeapTwoByte) \
362     macro(Strings, MallocHeap, mallocHeapLatin1) \
363     macro(Strings, MallocHeap, mallocHeapTwoByte)
364 
StringInfoStringInfo365     StringInfo()
366       : FOR_EACH_SIZE(ZERO_SIZE)
367         numCopies(0)
368     {}
369 
addStringInfo370     void add(const StringInfo& other) {
371         FOR_EACH_SIZE(ADD_OTHER_SIZE);
372         numCopies++;
373     }
374 
subtractStringInfo375     void subtract(const StringInfo& other) {
376         FOR_EACH_SIZE(SUB_OTHER_SIZE);
377         numCopies--;
378     }
379 
isNotableStringInfo380     bool isNotable() const {
381         static const size_t NotabilityThreshold = 16 * 1024;
382         size_t n = 0;
383         FOR_EACH_SIZE(ADD_SIZE_TO_N)
384         return n >= NotabilityThreshold;
385     }
386 
sizeOfLiveGCThingsStringInfo387     size_t sizeOfLiveGCThings() const {
388         size_t n = 0;
389         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
390         return n;
391     }
392 
addToTabSizesStringInfo393     void addToTabSizes(TabSizes* sizes) const {
394         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
395     }
396 
addToServoSizesStringInfo397     void addToServoSizes(ServoSizes *sizes) const {
398         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
399     }
400 
401     FOR_EACH_SIZE(DECL_SIZE)
402     uint32_t numCopies;     // How many copies of the string have we seen?
403 
404 #undef FOR_EACH_SIZE
405 };
406 
407 /**
408  * Holds data about a notable string (one which, counting all duplicates, uses
409  * more than a certain amount of memory) so we can report it individually.
410  *
411  * The only difference between this class and StringInfo is that
412  * NotableStringInfo holds a copy of some or all of the string's chars.
413  */
414 struct NotableStringInfo : public StringInfo
415 {
416     static const size_t MAX_SAVED_CHARS = 1024;
417 
418     NotableStringInfo();
419     NotableStringInfo(JSString* str, const StringInfo& info);
420     NotableStringInfo(NotableStringInfo&& info);
421     NotableStringInfo& operator=(NotableStringInfo&& info);
422 
~NotableStringInfoNotableStringInfo423     ~NotableStringInfo() {
424         js_free(buffer);
425     }
426 
427     char* buffer;
428     size_t length;
429 
430   private:
431     NotableStringInfo(const NotableStringInfo& info) = delete;
432 };
433 
434 /**
435  * This class holds information about the memory taken up by script sources
436  * from a particular file.
437  */
438 struct ScriptSourceInfo
439 {
440 #define FOR_EACH_SIZE(macro) \
441     macro(_, MallocHeap, misc)
442 
ScriptSourceInfoScriptSourceInfo443     ScriptSourceInfo()
444       : FOR_EACH_SIZE(ZERO_SIZE)
445         numScripts(0)
446     {}
447 
addScriptSourceInfo448     void add(const ScriptSourceInfo& other) {
449         FOR_EACH_SIZE(ADD_OTHER_SIZE)
450         numScripts++;
451     }
452 
subtractScriptSourceInfo453     void subtract(const ScriptSourceInfo& other) {
454         FOR_EACH_SIZE(SUB_OTHER_SIZE)
455         numScripts--;
456     }
457 
addToServoSizesScriptSourceInfo458     void addToServoSizes(ServoSizes *sizes) const {
459         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
460     }
461 
isNotableScriptSourceInfo462     bool isNotable() const {
463         static const size_t NotabilityThreshold = 16 * 1024;
464         size_t n = 0;
465         FOR_EACH_SIZE(ADD_SIZE_TO_N)
466         return n >= NotabilityThreshold;
467     }
468 
469     FOR_EACH_SIZE(DECL_SIZE)
470     uint32_t numScripts;    // How many ScriptSources come from this file? (It
471                             // can be more than one in XML files that have
472                             // multiple scripts in CDATA sections.)
473 #undef FOR_EACH_SIZE
474 };
475 
476 /**
477  * Holds data about a notable script source file (one whose combined
478  * script sources use more than a certain amount of memory) so we can report it
479  * individually.
480  *
481  * The only difference between this class and ScriptSourceInfo is that this
482  * class holds a copy of the filename.
483  */
484 struct NotableScriptSourceInfo : public ScriptSourceInfo
485 {
486     NotableScriptSourceInfo();
487     NotableScriptSourceInfo(const char* filename, const ScriptSourceInfo& info);
488     NotableScriptSourceInfo(NotableScriptSourceInfo&& info);
489     NotableScriptSourceInfo& operator=(NotableScriptSourceInfo&& info);
490 
~NotableScriptSourceInfoNotableScriptSourceInfo491     ~NotableScriptSourceInfo() {
492         js_free(filename_);
493     }
494 
495     char* filename_;
496 
497   private:
498     NotableScriptSourceInfo(const NotableScriptSourceInfo& info) = delete;
499 };
500 
501 /**
502  * These measurements relate directly to the JSRuntime, and not to zones and
503  * compartments within it.
504  */
505 struct RuntimeSizes
506 {
507 #define FOR_EACH_SIZE(macro) \
508     macro(_, MallocHeap, object) \
509     macro(_, MallocHeap, atomsTable) \
510     macro(_, MallocHeap, contexts) \
511     macro(_, MallocHeap, temporary) \
512     macro(_, MallocHeap, interpreterStack) \
513     macro(_, MallocHeap, mathCache) \
514     macro(_, MallocHeap, sharedImmutableStringsCache) \
515     macro(_, MallocHeap, sharedIntlData) \
516     macro(_, MallocHeap, uncompressedSourceCache) \
517     macro(_, MallocHeap, scriptData)
518 
RuntimeSizesRuntimeSizes519     RuntimeSizes()
520       : FOR_EACH_SIZE(ZERO_SIZE)
521         scriptSourceInfo(),
522         code(),
523         gc(),
524         notableScriptSources()
525     {
526         allScriptSources = js_new<ScriptSourcesHashMap>();
527         if (!allScriptSources || !allScriptSources->init())
528             MOZ_CRASH("oom");
529     }
530 
~RuntimeSizesRuntimeSizes531     ~RuntimeSizes() {
532         // |allScriptSources| is usually deleted and set to nullptr before this
533         // destructor runs. But there are failure cases due to OOMs that may
534         // prevent that, so it doesn't hurt to try again here.
535         js_delete(allScriptSources);
536     }
537 
addToServoSizesRuntimeSizes538     void addToServoSizes(ServoSizes *sizes) const {
539         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
540         scriptSourceInfo.addToServoSizes(sizes);
541         code.addToServoSizes(sizes);
542         gc.addToServoSizes(sizes);
543     }
544 
545     // The script source measurements in |scriptSourceInfo| are initially for
546     // all script sources.  At the end, if the measurement granularity is
547     // FineGrained, we subtract the measurements of the notable script sources
548     // and move them into |notableScriptSources|.
549     FOR_EACH_SIZE(DECL_SIZE)
550     ScriptSourceInfo scriptSourceInfo;
551     CodeSizes code;
552     GCSizes gc;
553 
554     typedef js::HashMap<const char*, ScriptSourceInfo,
555                         js::CStringHashPolicy,
556                         js::SystemAllocPolicy> ScriptSourcesHashMap;
557 
558     // |allScriptSources| is only used transiently.  During the reporting phase
559     // it is filled with info about every script source in the runtime.  It's
560     // then used to fill in |notableScriptSources| (which actually gets
561     // reported), and immediately discarded afterwards.
562     ScriptSourcesHashMap* allScriptSources;
563     js::Vector<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> notableScriptSources;
564 
565 #undef FOR_EACH_SIZE
566 };
567 
568 struct UnusedGCThingSizes
569 {
570 #define FOR_EACH_SIZE(macro) \
571     macro(Other, GCHeapUnused, object) \
572     macro(Other, GCHeapUnused, script) \
573     macro(Other, GCHeapUnused, lazyScript) \
574     macro(Other, GCHeapUnused, shape) \
575     macro(Other, GCHeapUnused, baseShape) \
576     macro(Other, GCHeapUnused, objectGroup) \
577     macro(Other, GCHeapUnused, string) \
578     macro(Other, GCHeapUnused, symbol) \
579     macro(Other, GCHeapUnused, jitcode) \
580     macro(Other, GCHeapUnused, scope)
581 
UnusedGCThingSizesUnusedGCThingSizes582     UnusedGCThingSizes()
583       : FOR_EACH_SIZE(ZERO_SIZE)
584         dummy()
585     {}
586 
UnusedGCThingSizesUnusedGCThingSizes587     UnusedGCThingSizes(UnusedGCThingSizes&& other)
588       : FOR_EACH_SIZE(COPY_OTHER_SIZE)
589         dummy()
590     {}
591 
addToKindUnusedGCThingSizes592     void addToKind(JS::TraceKind kind, intptr_t n) {
593         switch (kind) {
594           case JS::TraceKind::Object:       object += n;      break;
595           case JS::TraceKind::String:       string += n;      break;
596           case JS::TraceKind::Symbol:       symbol += n;      break;
597           case JS::TraceKind::Script:       script += n;      break;
598           case JS::TraceKind::Shape:        shape += n;       break;
599           case JS::TraceKind::BaseShape:    baseShape += n;   break;
600           case JS::TraceKind::JitCode:      jitcode += n;     break;
601           case JS::TraceKind::LazyScript:   lazyScript += n;  break;
602           case JS::TraceKind::ObjectGroup:  objectGroup += n; break;
603           case JS::TraceKind::Scope:        scope += n;       break;
604           default:
605             MOZ_CRASH("Bad trace kind for UnusedGCThingSizes");
606         }
607     }
608 
addSizesUnusedGCThingSizes609     void addSizes(const UnusedGCThingSizes& other) {
610         FOR_EACH_SIZE(ADD_OTHER_SIZE)
611     }
612 
totalSizeUnusedGCThingSizes613     size_t totalSize() const {
614         size_t n = 0;
615         FOR_EACH_SIZE(ADD_SIZE_TO_N)
616         return n;
617     }
618 
addToTabSizesUnusedGCThingSizes619     void addToTabSizes(JS::TabSizes *sizes) const {
620         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
621     }
622 
addToServoSizesUnusedGCThingSizes623     void addToServoSizes(JS::ServoSizes *sizes) const {
624         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
625     }
626 
627     FOR_EACH_SIZE(DECL_SIZE)
628     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
629 
630 #undef FOR_EACH_SIZE
631 };
632 
633 struct ZoneStats
634 {
635 #define FOR_EACH_SIZE(macro) \
636     macro(Other,   GCHeapUsed,  symbolsGCHeap) \
637     macro(Other,   GCHeapAdmin, gcHeapArenaAdmin) \
638     macro(Other,   GCHeapUsed,  lazyScriptsGCHeap) \
639     macro(Other,   MallocHeap,  lazyScriptsMallocHeap) \
640     macro(Other,   GCHeapUsed,  jitCodesGCHeap) \
641     macro(Other,   GCHeapUsed,  objectGroupsGCHeap) \
642     macro(Other,   MallocHeap,  objectGroupsMallocHeap) \
643     macro(Other,   GCHeapUsed,  scopesGCHeap) \
644     macro(Other,   MallocHeap,  scopesMallocHeap) \
645     macro(Other,   MallocHeap,  typePool) \
646     macro(Other,   MallocHeap,  baselineStubsOptimized) \
647     macro(Other,   MallocHeap,  uniqueIdMap) \
648     macro(Other,   MallocHeap,  shapeTables)
649 
ZoneStatsZoneStats650     ZoneStats()
651       : FOR_EACH_SIZE(ZERO_SIZE)
652         unusedGCThings(),
653         stringInfo(),
654         shapeInfo(),
655         extra(),
656         allStrings(nullptr),
657         notableStrings(),
658         isTotals(true)
659     {}
660 
ZoneStatsZoneStats661     ZoneStats(ZoneStats&& other)
662       : FOR_EACH_SIZE(COPY_OTHER_SIZE)
663         unusedGCThings(mozilla::Move(other.unusedGCThings)),
664         stringInfo(mozilla::Move(other.stringInfo)),
665         shapeInfo(mozilla::Move(other.shapeInfo)),
666         extra(other.extra),
667         allStrings(other.allStrings),
668         notableStrings(mozilla::Move(other.notableStrings)),
669         isTotals(other.isTotals)
670     {
671         other.allStrings = nullptr;
672         MOZ_ASSERT(!other.isTotals);
673     }
674 
~ZoneStatsZoneStats675     ~ZoneStats() {
676         // |allStrings| is usually deleted and set to nullptr before this
677         // destructor runs. But there are failure cases due to OOMs that may
678         // prevent that, so it doesn't hurt to try again here.
679         js_delete(allStrings);
680     }
681 
682     bool initStrings(JSRuntime* rt);
683 
addSizesZoneStats684     void addSizes(const ZoneStats& other) {
685         MOZ_ASSERT(isTotals);
686         FOR_EACH_SIZE(ADD_OTHER_SIZE)
687         unusedGCThings.addSizes(other.unusedGCThings);
688         stringInfo.add(other.stringInfo);
689         shapeInfo.add(other.shapeInfo);
690     }
691 
sizeOfLiveGCThingsZoneStats692     size_t sizeOfLiveGCThings() const {
693         MOZ_ASSERT(isTotals);
694         size_t n = 0;
695         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
696         n += stringInfo.sizeOfLiveGCThings();
697         n += shapeInfo.sizeOfLiveGCThings();
698         return n;
699     }
700 
addToTabSizesZoneStats701     void addToTabSizes(JS::TabSizes* sizes) const {
702         MOZ_ASSERT(isTotals);
703         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
704         unusedGCThings.addToTabSizes(sizes);
705         stringInfo.addToTabSizes(sizes);
706         shapeInfo.addToTabSizes(sizes);
707     }
708 
addToServoSizesZoneStats709     void addToServoSizes(JS::ServoSizes *sizes) const {
710         MOZ_ASSERT(isTotals);
711         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
712         unusedGCThings.addToServoSizes(sizes);
713         stringInfo.addToServoSizes(sizes);
714         shapeInfo.addToServoSizes(sizes);
715     }
716 
717     // These string measurements are initially for all strings.  At the end,
718     // if the measurement granularity is FineGrained, we subtract the
719     // measurements of the notable script sources and move them into
720     // |notableStrings|.
721     FOR_EACH_SIZE(DECL_SIZE)
722     UnusedGCThingSizes unusedGCThings;
723     StringInfo stringInfo;
724     ShapeInfo shapeInfo;
725     void* extra;    // This field can be used by embedders.
726 
727     typedef js::HashMap<JSString*, StringInfo,
728                         js::InefficientNonFlatteningStringHashPolicy,
729                         js::SystemAllocPolicy> StringsHashMap;
730 
731     // |allStrings| is only used transiently.  During the zone traversal it is
732     // filled with info about every string in the zone.  It's then used to fill
733     // in |notableStrings| (which actually gets reported), and immediately
734     // discarded afterwards.
735     StringsHashMap* allStrings;
736     js::Vector<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings;
737     bool isTotals;
738 
739 #undef FOR_EACH_SIZE
740 };
741 
742 struct CompartmentStats
743 {
744     // We assume that |objectsPrivate| is on the malloc heap, but it's not
745     // actually guaranteed. But for Servo, at least, it's a moot point because
746     // it doesn't provide an ObjectPrivateVisitor so the value will always be
747     // zero.
748 #define FOR_EACH_SIZE(macro) \
749     macro(Private, MallocHeap, objectsPrivate) \
750     macro(Other,   GCHeapUsed, scriptsGCHeap) \
751     macro(Other,   MallocHeap, scriptsMallocHeapData) \
752     macro(Other,   MallocHeap, baselineData) \
753     macro(Other,   MallocHeap, baselineStubsFallback) \
754     macro(Other,   MallocHeap, ionData) \
755     macro(Other,   MallocHeap, typeInferenceTypeScripts) \
756     macro(Other,   MallocHeap, typeInferenceAllocationSiteTables) \
757     macro(Other,   MallocHeap, typeInferenceArrayTypeTables) \
758     macro(Other,   MallocHeap, typeInferenceObjectTypeTables) \
759     macro(Other,   MallocHeap, compartmentObject) \
760     macro(Other,   MallocHeap, compartmentTables) \
761     macro(Other,   MallocHeap, innerViewsTable) \
762     macro(Other,   MallocHeap, lazyArrayBuffersTable) \
763     macro(Other,   MallocHeap, objectMetadataTable) \
764     macro(Other,   MallocHeap, crossCompartmentWrappersTable) \
765     macro(Other,   MallocHeap, regexpCompartment) \
766     macro(Other,   MallocHeap, savedStacksSet) \
767     macro(Other,   MallocHeap, varNamesSet) \
768     macro(Other,   MallocHeap, nonSyntacticLexicalScopesTable) \
769     macro(Other,   MallocHeap, jitCompartment) \
770     macro(Other,   MallocHeap, privateData)
771 
CompartmentStatsCompartmentStats772     CompartmentStats()
773       : FOR_EACH_SIZE(ZERO_SIZE)
774         classInfo(),
775         extra(),
776         allClasses(nullptr),
777         notableClasses(),
778         isTotals(true)
779     {}
780 
CompartmentStatsCompartmentStats781     CompartmentStats(CompartmentStats&& other)
782       : FOR_EACH_SIZE(COPY_OTHER_SIZE)
783         classInfo(mozilla::Move(other.classInfo)),
784         extra(other.extra),
785         allClasses(other.allClasses),
786         notableClasses(mozilla::Move(other.notableClasses)),
787         isTotals(other.isTotals)
788     {
789         other.allClasses = nullptr;
790         MOZ_ASSERT(!other.isTotals);
791     }
792 
793     CompartmentStats(const CompartmentStats&) = delete; // disallow copying
794 
~CompartmentStatsCompartmentStats795     ~CompartmentStats() {
796         // |allClasses| is usually deleted and set to nullptr before this
797         // destructor runs. But there are failure cases due to OOMs that may
798         // prevent that, so it doesn't hurt to try again here.
799         js_delete(allClasses);
800     }
801 
802     bool initClasses(JSRuntime* rt);
803 
addSizesCompartmentStats804     void addSizes(const CompartmentStats& other) {
805         MOZ_ASSERT(isTotals);
806         FOR_EACH_SIZE(ADD_OTHER_SIZE)
807         classInfo.add(other.classInfo);
808     }
809 
sizeOfLiveGCThingsCompartmentStats810     size_t sizeOfLiveGCThings() const {
811         MOZ_ASSERT(isTotals);
812         size_t n = 0;
813         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
814         n += classInfo.sizeOfLiveGCThings();
815         return n;
816     }
817 
addToTabSizesCompartmentStats818     void addToTabSizes(TabSizes* sizes) const {
819         MOZ_ASSERT(isTotals);
820         FOR_EACH_SIZE(ADD_TO_TAB_SIZES);
821         classInfo.addToTabSizes(sizes);
822     }
823 
addToServoSizesCompartmentStats824     void addToServoSizes(ServoSizes *sizes) const {
825         MOZ_ASSERT(isTotals);
826         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES);
827         classInfo.addToServoSizes(sizes);
828     }
829 
830     // The class measurements in |classInfo| are initially for all classes.  At
831     // the end, if the measurement granularity is FineGrained, we subtract the
832     // measurements of the notable classes and move them into |notableClasses|.
833     FOR_EACH_SIZE(DECL_SIZE)
834     ClassInfo classInfo;
835     void* extra;            // This field can be used by embedders.
836 
837     typedef js::HashMap<const char*, ClassInfo,
838                         js::CStringHashPolicy,
839                         js::SystemAllocPolicy> ClassesHashMap;
840 
841     // These are similar to |allStrings| and |notableStrings| in ZoneStats.
842     ClassesHashMap* allClasses;
843     js::Vector<NotableClassInfo, 0, js::SystemAllocPolicy> notableClasses;
844     bool isTotals;
845 
846 #undef FOR_EACH_SIZE
847 };
848 
849 typedef js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> CompartmentStatsVector;
850 typedef js::Vector<ZoneStats, 0, js::SystemAllocPolicy> ZoneStatsVector;
851 
852 struct RuntimeStats
853 {
854     // |gcHeapChunkTotal| is ignored because it's the sum of all the other
855     // values. |gcHeapGCThings| is ignored because it's the sum of some of the
856     // values from the zones and compartments. Both of those values are not
857     // reported directly, but are just present for sanity-checking other
858     // values.
859 #define FOR_EACH_SIZE(macro) \
860     macro(_, Ignore,            gcHeapChunkTotal) \
861     macro(_, GCHeapDecommitted, gcHeapDecommittedArenas) \
862     macro(_, GCHeapUnused,      gcHeapUnusedChunks) \
863     macro(_, GCHeapUnused,      gcHeapUnusedArenas) \
864     macro(_, GCHeapAdmin,       gcHeapChunkAdmin) \
865     macro(_, Ignore,            gcHeapGCThings)
866 
RuntimeStatsRuntimeStats867     explicit RuntimeStats(mozilla::MallocSizeOf mallocSizeOf)
868       : FOR_EACH_SIZE(ZERO_SIZE)
869         runtime(),
870         cTotals(),
871         zTotals(),
872         compartmentStatsVector(),
873         zoneStatsVector(),
874         currZoneStats(nullptr),
875         mallocSizeOf_(mallocSizeOf)
876     {}
877 
878     // Here's a useful breakdown of the GC heap.
879     //
880     // - rtStats.gcHeapChunkTotal
881     //   - decommitted bytes
882     //     - rtStats.gcHeapDecommittedArenas (decommitted arenas in non-empty chunks)
883     //   - unused bytes
884     //     - rtStats.gcHeapUnusedChunks (empty chunks)
885     //     - rtStats.gcHeapUnusedArenas (empty arenas within non-empty chunks)
886     //     - rtStats.zTotals.unusedGCThings.totalSize() (empty GC thing slots within non-empty arenas)
887     //   - used bytes
888     //     - rtStats.gcHeapChunkAdmin
889     //     - rtStats.zTotals.gcHeapArenaAdmin
890     //     - rtStats.gcHeapGCThings (in-use GC things)
891     //       == rtStats.zTotals.sizeOfLiveGCThings() + rtStats.cTotals.sizeOfLiveGCThings()
892     //
893     // It's possible that some arenas in empty chunks may be decommitted, but
894     // we don't count those under rtStats.gcHeapDecommittedArenas because (a)
895     // it's rare, and (b) this means that rtStats.gcHeapUnusedChunks is a
896     // multiple of the chunk size, which is good.
897 
addToServoSizesRuntimeStats898     void addToServoSizes(ServoSizes *sizes) const {
899         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
900         runtime.addToServoSizes(sizes);
901     }
902 
903     FOR_EACH_SIZE(DECL_SIZE)
904 
905     RuntimeSizes runtime;
906 
907     CompartmentStats cTotals;   // The sum of this runtime's compartments' measurements.
908     ZoneStats zTotals;          // The sum of this runtime's zones' measurements.
909 
910     CompartmentStatsVector compartmentStatsVector;
911     ZoneStatsVector zoneStatsVector;
912 
913     ZoneStats* currZoneStats;
914 
915     mozilla::MallocSizeOf mallocSizeOf_;
916 
917     virtual void initExtraCompartmentStats(JSCompartment* c, CompartmentStats* cstats) = 0;
918     virtual void initExtraZoneStats(JS::Zone* zone, ZoneStats* zstats) = 0;
919 
920 #undef FOR_EACH_SIZE
921 };
922 
923 class ObjectPrivateVisitor
924 {
925   public:
926     // Within CollectRuntimeStats, this method is called for each JS object
927     // that has an nsISupports pointer.
928     virtual size_t sizeOfIncludingThis(nsISupports* aSupports) = 0;
929 
930     // A callback that gets a JSObject's nsISupports pointer, if it has one.
931     // Note: this function does *not* addref |iface|.
932     typedef bool(*GetISupportsFun)(JSObject* obj, nsISupports** iface);
933     GetISupportsFun getISupports_;
934 
ObjectPrivateVisitor(GetISupportsFun getISupports)935     explicit ObjectPrivateVisitor(GetISupportsFun getISupports)
936       : getISupports_(getISupports)
937     {}
938 };
939 
940 extern JS_PUBLIC_API(bool)
941 CollectRuntimeStats(JSContext* cx, RuntimeStats* rtStats, ObjectPrivateVisitor* opv, bool anonymize);
942 
943 extern JS_PUBLIC_API(size_t)
944 SystemCompartmentCount(JSContext* cx);
945 
946 extern JS_PUBLIC_API(size_t)
947 UserCompartmentCount(JSContext* cx);
948 
949 extern JS_PUBLIC_API(size_t)
950 PeakSizeOfTemporary(const JSContext* cx);
951 
952 extern JS_PUBLIC_API(bool)
953 AddSizeOfTab(JSContext* cx, JS::HandleObject obj, mozilla::MallocSizeOf mallocSizeOf,
954              ObjectPrivateVisitor* opv, TabSizes* sizes);
955 
956 extern JS_PUBLIC_API(bool)
957 AddServoSizeOf(JSContext* cx, mozilla::MallocSizeOf mallocSizeOf,
958                ObjectPrivateVisitor *opv, ServoSizes *sizes);
959 
960 } // namespace JS
961 
962 #undef DECL_SIZE
963 #undef ZERO_SIZE
964 #undef COPY_OTHER_SIZE
965 #undef ADD_OTHER_SIZE
966 #undef SUB_OTHER_SIZE
967 #undef ADD_SIZE_TO_N
968 #undef ADD_SIZE_TO_N_IF_LIVE_GC_THING
969 #undef ADD_TO_TAB_SIZES
970 
971 #endif /* js_MemoryMetrics_h */
972