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, objectsMallocHeapElementsNonAsmJS) \
170     macro(Objects, MallocHeap, objectsMallocHeapElementsAsmJS) \
171     macro(Objects, NonHeap,    objectsNonHeapElementsAsmJS) \
172     macro(Objects, NonHeap,    objectsNonHeapElementsMapped) \
173     macro(Objects, NonHeap,    objectsNonHeapCodeAsmJS) \
174     macro(Objects, MallocHeap, objectsMallocHeapMisc) \
175     \
176     macro(Other,   GCHeapUsed, shapesGCHeapTree) \
177     macro(Other,   GCHeapUsed, shapesGCHeapDict) \
178     macro(Other,   GCHeapUsed, shapesGCHeapBase) \
179     macro(Other,   MallocHeap, shapesMallocHeapTreeTables) \
180     macro(Other,   MallocHeap, shapesMallocHeapDictTables) \
181     macro(Other,   MallocHeap, shapesMallocHeapTreeKids)
182 
ClassInfoClassInfo183     ClassInfo()
184       : FOR_EACH_SIZE(ZERO_SIZE)
185         dummy()
186     {}
187 
addClassInfo188     void add(const ClassInfo& other) {
189         FOR_EACH_SIZE(ADD_OTHER_SIZE)
190     }
191 
subtractClassInfo192     void subtract(const ClassInfo& other) {
193         FOR_EACH_SIZE(SUB_OTHER_SIZE)
194     }
195 
sizeOfAllThingsClassInfo196     size_t sizeOfAllThings() const {
197         size_t n = 0;
198         FOR_EACH_SIZE(ADD_SIZE_TO_N)
199         return n;
200     }
201 
isNotableClassInfo202     bool isNotable() const {
203         static const size_t NotabilityThreshold = 16 * 1024;
204         return sizeOfAllThings() >= NotabilityThreshold;
205     }
206 
sizeOfLiveGCThingsClassInfo207     size_t sizeOfLiveGCThings() const {
208         size_t n = 0;
209         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
210         return n;
211     }
212 
addToTabSizesClassInfo213     void addToTabSizes(TabSizes* sizes) const {
214         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
215     }
216 
addToServoSizesClassInfo217     void addToServoSizes(ServoSizes *sizes) const {
218         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
219     }
220 
221     FOR_EACH_SIZE(DECL_SIZE)
222     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
223 
224 #undef FOR_EACH_SIZE
225 };
226 
227 /**
228  * Holds data about a notable class (one whose combined object and shape
229  * instances use more than a certain amount of memory) so we can report it
230  * individually.
231  *
232  * The only difference between this class and ClassInfo is that this class
233  * holds a copy of the filename.
234  */
235 struct NotableClassInfo : public ClassInfo
236 {
237     NotableClassInfo();
238     NotableClassInfo(const char* className, const ClassInfo& info);
239     NotableClassInfo(NotableClassInfo&& info);
240     NotableClassInfo& operator=(NotableClassInfo&& info);
241 
~NotableClassInfoNotableClassInfo242     ~NotableClassInfo() {
243         js_free(className_);
244     }
245 
246     char* className_;
247 
248   private:
249     NotableClassInfo(const NotableClassInfo& info) = delete;
250 };
251 
252 /** Data for tracking JIT-code memory usage. */
253 struct CodeSizes
254 {
255 #define FOR_EACH_SIZE(macro) \
256     macro(_, NonHeap, ion) \
257     macro(_, NonHeap, baseline) \
258     macro(_, NonHeap, regexp) \
259     macro(_, NonHeap, other) \
260     macro(_, NonHeap, unused)
261 
CodeSizesCodeSizes262     CodeSizes()
263       : FOR_EACH_SIZE(ZERO_SIZE)
264         dummy()
265     {}
266 
addToServoSizesCodeSizes267     void addToServoSizes(ServoSizes *sizes) const {
268         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
269     }
270 
271     FOR_EACH_SIZE(DECL_SIZE)
272     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
273 
274 #undef FOR_EACH_SIZE
275 };
276 
277 /** Data for tracking GC memory usage. */
278 struct GCSizes
279 {
280     // |nurseryDecommitted| is marked as NonHeap rather than GCHeapDecommitted
281     // because we don't consider the nursery to be part of the GC heap.
282 #define FOR_EACH_SIZE(macro) \
283     macro(_, MallocHeap, marker) \
284     macro(_, NonHeap,    nurseryCommitted) \
285     macro(_, NonHeap,    nurseryDecommitted) \
286     macro(_, MallocHeap, nurseryMallocedBuffers) \
287     macro(_, MallocHeap, storeBufferVals) \
288     macro(_, MallocHeap, storeBufferCells) \
289     macro(_, MallocHeap, storeBufferSlots) \
290     macro(_, MallocHeap, storeBufferWholeCells) \
291     macro(_, MallocHeap, storeBufferGenerics)
292 
GCSizesGCSizes293     GCSizes()
294       : FOR_EACH_SIZE(ZERO_SIZE)
295         dummy()
296     {}
297 
addToServoSizesGCSizes298     void addToServoSizes(ServoSizes *sizes) const {
299         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
300     }
301 
302     FOR_EACH_SIZE(DECL_SIZE)
303     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
304 
305 #undef FOR_EACH_SIZE
306 };
307 
308 /**
309  * This class holds information about the memory taken up by identical copies of
310  * a particular string.  Multiple JSStrings may have their sizes aggregated
311  * together into one StringInfo object.  Note that two strings with identical
312  * chars will not be aggregated together if one is a short string and the other
313  * is not.
314  */
315 struct StringInfo
316 {
317 #define FOR_EACH_SIZE(macro) \
318     macro(Strings, GCHeapUsed, gcHeapLatin1) \
319     macro(Strings, GCHeapUsed, gcHeapTwoByte) \
320     macro(Strings, MallocHeap, mallocHeapLatin1) \
321     macro(Strings, MallocHeap, mallocHeapTwoByte)
322 
StringInfoStringInfo323     StringInfo()
324       : FOR_EACH_SIZE(ZERO_SIZE)
325         numCopies(0)
326     {}
327 
addStringInfo328     void add(const StringInfo& other) {
329         FOR_EACH_SIZE(ADD_OTHER_SIZE);
330         numCopies++;
331     }
332 
subtractStringInfo333     void subtract(const StringInfo& other) {
334         FOR_EACH_SIZE(SUB_OTHER_SIZE);
335         numCopies--;
336     }
337 
isNotableStringInfo338     bool isNotable() const {
339         static const size_t NotabilityThreshold = 16 * 1024;
340         size_t n = 0;
341         FOR_EACH_SIZE(ADD_SIZE_TO_N)
342         return n >= NotabilityThreshold;
343     }
344 
sizeOfLiveGCThingsStringInfo345     size_t sizeOfLiveGCThings() const {
346         size_t n = 0;
347         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
348         return n;
349     }
350 
addToTabSizesStringInfo351     void addToTabSizes(TabSizes* sizes) const {
352         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
353     }
354 
addToServoSizesStringInfo355     void addToServoSizes(ServoSizes *sizes) const {
356         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
357     }
358 
359     FOR_EACH_SIZE(DECL_SIZE)
360     uint32_t numCopies;     // How many copies of the string have we seen?
361 
362 #undef FOR_EACH_SIZE
363 };
364 
365 /**
366  * Holds data about a notable string (one which, counting all duplicates, uses
367  * more than a certain amount of memory) so we can report it individually.
368  *
369  * The only difference between this class and StringInfo is that
370  * NotableStringInfo holds a copy of some or all of the string's chars.
371  */
372 struct NotableStringInfo : public StringInfo
373 {
374     static const size_t MAX_SAVED_CHARS = 1024;
375 
376     NotableStringInfo();
377     NotableStringInfo(JSString* str, const StringInfo& info);
378     NotableStringInfo(NotableStringInfo&& info);
379     NotableStringInfo& operator=(NotableStringInfo&& info);
380 
~NotableStringInfoNotableStringInfo381     ~NotableStringInfo() {
382         js_free(buffer);
383     }
384 
385     char* buffer;
386     size_t length;
387 
388   private:
389     NotableStringInfo(const NotableStringInfo& info) = delete;
390 };
391 
392 /**
393  * This class holds information about the memory taken up by script sources
394  * from a particular file.
395  */
396 struct ScriptSourceInfo
397 {
398 #define FOR_EACH_SIZE(macro) \
399     macro(_, MallocHeap, compressed) \
400     macro(_, MallocHeap, uncompressed) \
401     macro(_, MallocHeap, misc)
402 
ScriptSourceInfoScriptSourceInfo403     ScriptSourceInfo()
404       : FOR_EACH_SIZE(ZERO_SIZE)
405         numScripts(0)
406     {}
407 
addScriptSourceInfo408     void add(const ScriptSourceInfo& other) {
409         FOR_EACH_SIZE(ADD_OTHER_SIZE)
410         numScripts++;
411     }
412 
subtractScriptSourceInfo413     void subtract(const ScriptSourceInfo& other) {
414         FOR_EACH_SIZE(SUB_OTHER_SIZE)
415         numScripts--;
416     }
417 
addToServoSizesScriptSourceInfo418     void addToServoSizes(ServoSizes *sizes) const {
419         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
420     }
421 
isNotableScriptSourceInfo422     bool isNotable() const {
423         static const size_t NotabilityThreshold = 16 * 1024;
424         size_t n = 0;
425         FOR_EACH_SIZE(ADD_SIZE_TO_N)
426         return n >= NotabilityThreshold;
427     }
428 
429     FOR_EACH_SIZE(DECL_SIZE)
430     uint32_t numScripts;    // How many ScriptSources come from this file? (It
431                             // can be more than one in XML files that have
432                             // multiple scripts in CDATA sections.)
433 #undef FOR_EACH_SIZE
434 };
435 
436 /**
437  * Holds data about a notable script source file (one whose combined
438  * script sources use more than a certain amount of memory) so we can report it
439  * individually.
440  *
441  * The only difference between this class and ScriptSourceInfo is that this
442  * class holds a copy of the filename.
443  */
444 struct NotableScriptSourceInfo : public ScriptSourceInfo
445 {
446     NotableScriptSourceInfo();
447     NotableScriptSourceInfo(const char* filename, const ScriptSourceInfo& info);
448     NotableScriptSourceInfo(NotableScriptSourceInfo&& info);
449     NotableScriptSourceInfo& operator=(NotableScriptSourceInfo&& info);
450 
~NotableScriptSourceInfoNotableScriptSourceInfo451     ~NotableScriptSourceInfo() {
452         js_free(filename_);
453     }
454 
455     char* filename_;
456 
457   private:
458     NotableScriptSourceInfo(const NotableScriptSourceInfo& info) = delete;
459 };
460 
461 /**
462  * These measurements relate directly to the JSRuntime, and not to zones and
463  * compartments within it.
464  */
465 struct RuntimeSizes
466 {
467 #define FOR_EACH_SIZE(macro) \
468     macro(_, MallocHeap, object) \
469     macro(_, MallocHeap, atomsTable) \
470     macro(_, MallocHeap, contexts) \
471     macro(_, MallocHeap, dtoa) \
472     macro(_, MallocHeap, temporary) \
473     macro(_, MallocHeap, interpreterStack) \
474     macro(_, MallocHeap, mathCache) \
475     macro(_, MallocHeap, uncompressedSourceCache) \
476     macro(_, MallocHeap, compressedSourceSet) \
477     macro(_, MallocHeap, scriptData)
478 
RuntimeSizesRuntimeSizes479     RuntimeSizes()
480       : FOR_EACH_SIZE(ZERO_SIZE)
481         scriptSourceInfo(),
482         code(),
483         gc(),
484         notableScriptSources()
485     {
486         allScriptSources = js_new<ScriptSourcesHashMap>();
487         if (!allScriptSources || !allScriptSources->init())
488             MOZ_CRASH("oom");
489     }
490 
~RuntimeSizesRuntimeSizes491     ~RuntimeSizes() {
492         // |allScriptSources| is usually deleted and set to nullptr before this
493         // destructor runs. But there are failure cases due to OOMs that may
494         // prevent that, so it doesn't hurt to try again here.
495         js_delete(allScriptSources);
496     }
497 
addToServoSizesRuntimeSizes498     void addToServoSizes(ServoSizes *sizes) const {
499         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
500         scriptSourceInfo.addToServoSizes(sizes);
501         code.addToServoSizes(sizes);
502         gc.addToServoSizes(sizes);
503     }
504 
505     // The script source measurements in |scriptSourceInfo| are initially for
506     // all script sources.  At the end, if the measurement granularity is
507     // FineGrained, we subtract the measurements of the notable script sources
508     // and move them into |notableScriptSources|.
509     FOR_EACH_SIZE(DECL_SIZE)
510     ScriptSourceInfo scriptSourceInfo;
511     CodeSizes code;
512     GCSizes gc;
513 
514     typedef js::HashMap<const char*, ScriptSourceInfo,
515                         js::CStringHashPolicy,
516                         js::SystemAllocPolicy> ScriptSourcesHashMap;
517 
518     // |allScriptSources| is only used transiently.  During the reporting phase
519     // it is filled with info about every script source in the runtime.  It's
520     // then used to fill in |notableScriptSources| (which actually gets
521     // reported), and immediately discarded afterwards.
522     ScriptSourcesHashMap* allScriptSources;
523     js::Vector<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> notableScriptSources;
524 
525 #undef FOR_EACH_SIZE
526 };
527 
528 struct UnusedGCThingSizes
529 {
530 #define FOR_EACH_SIZE(macro) \
531     macro(Other, GCHeapUnused, object) \
532     macro(Other, GCHeapUnused, script) \
533     macro(Other, GCHeapUnused, lazyScript) \
534     macro(Other, GCHeapUnused, shape) \
535     macro(Other, GCHeapUnused, baseShape) \
536     macro(Other, GCHeapUnused, objectGroup) \
537     macro(Other, GCHeapUnused, string) \
538     macro(Other, GCHeapUnused, symbol) \
539     macro(Other, GCHeapUnused, jitcode) \
540 
UnusedGCThingSizesUnusedGCThingSizes541     UnusedGCThingSizes()
542       : FOR_EACH_SIZE(ZERO_SIZE)
543         dummy()
544     {}
545 
UnusedGCThingSizesUnusedGCThingSizes546     UnusedGCThingSizes(UnusedGCThingSizes&& other)
547       : FOR_EACH_SIZE(COPY_OTHER_SIZE)
548         dummy()
549     {}
550 
addToKindUnusedGCThingSizes551     void addToKind(JS::TraceKind kind, intptr_t n) {
552         switch (kind) {
553           case JS::TraceKind::Object:       object += n;      break;
554           case JS::TraceKind::String:       string += n;      break;
555           case JS::TraceKind::Symbol:       symbol += n;      break;
556           case JS::TraceKind::Script:       script += n;      break;
557           case JS::TraceKind::Shape:        shape += n;       break;
558           case JS::TraceKind::BaseShape:    baseShape += n;   break;
559           case JS::TraceKind::JitCode:      jitcode += n;     break;
560           case JS::TraceKind::LazyScript:   lazyScript += n;  break;
561           case JS::TraceKind::ObjectGroup:  objectGroup += n; break;
562           default:
563             MOZ_CRASH("Bad trace kind for UnusedGCThingSizes");
564         }
565     }
566 
addSizesUnusedGCThingSizes567     void addSizes(const UnusedGCThingSizes& other) {
568         FOR_EACH_SIZE(ADD_OTHER_SIZE)
569     }
570 
totalSizeUnusedGCThingSizes571     size_t totalSize() const {
572         size_t n = 0;
573         FOR_EACH_SIZE(ADD_SIZE_TO_N)
574         return n;
575     }
576 
addToTabSizesUnusedGCThingSizes577     void addToTabSizes(JS::TabSizes *sizes) const {
578         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
579     }
580 
addToServoSizesUnusedGCThingSizes581     void addToServoSizes(JS::ServoSizes *sizes) const {
582         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
583     }
584 
585     FOR_EACH_SIZE(DECL_SIZE)
586     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
587 
588 #undef FOR_EACH_SIZE
589 };
590 
591 struct ZoneStats
592 {
593 #define FOR_EACH_SIZE(macro) \
594     macro(Other,   GCHeapUsed,  symbolsGCHeap) \
595     macro(Other,   GCHeapAdmin, gcHeapArenaAdmin) \
596     macro(Other,   GCHeapUsed,  lazyScriptsGCHeap) \
597     macro(Other,   MallocHeap,  lazyScriptsMallocHeap) \
598     macro(Other,   GCHeapUsed,  jitCodesGCHeap) \
599     macro(Other,   GCHeapUsed,  objectGroupsGCHeap) \
600     macro(Other,   MallocHeap,  objectGroupsMallocHeap) \
601     macro(Other,   MallocHeap,  typePool) \
602     macro(Other,   MallocHeap,  baselineStubsOptimized) \
603     macro(Other,   MallocHeap,  uniqueIdMap)
604 
ZoneStatsZoneStats605     ZoneStats()
606       : FOR_EACH_SIZE(ZERO_SIZE)
607         unusedGCThings(),
608         stringInfo(),
609         extra(),
610         allStrings(nullptr),
611         notableStrings(),
612         isTotals(true)
613     {}
614 
ZoneStatsZoneStats615     ZoneStats(ZoneStats&& other)
616       : FOR_EACH_SIZE(COPY_OTHER_SIZE)
617         unusedGCThings(mozilla::Move(other.unusedGCThings)),
618         stringInfo(mozilla::Move(other.stringInfo)),
619         extra(other.extra),
620         allStrings(other.allStrings),
621         notableStrings(mozilla::Move(other.notableStrings)),
622         isTotals(other.isTotals)
623     {
624         other.allStrings = nullptr;
625         MOZ_ASSERT(!other.isTotals);
626     }
627 
~ZoneStatsZoneStats628     ~ZoneStats() {
629         // |allStrings| is usually deleted and set to nullptr before this
630         // destructor runs. But there are failure cases due to OOMs that may
631         // prevent that, so it doesn't hurt to try again here.
632         js_delete(allStrings);
633     }
634 
635     bool initStrings(JSRuntime* rt);
636 
addSizesZoneStats637     void addSizes(const ZoneStats& other) {
638         MOZ_ASSERT(isTotals);
639         FOR_EACH_SIZE(ADD_OTHER_SIZE)
640         unusedGCThings.addSizes(other.unusedGCThings);
641         stringInfo.add(other.stringInfo);
642     }
643 
sizeOfLiveGCThingsZoneStats644     size_t sizeOfLiveGCThings() const {
645         MOZ_ASSERT(isTotals);
646         size_t n = 0;
647         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
648         n += stringInfo.sizeOfLiveGCThings();
649         return n;
650     }
651 
addToTabSizesZoneStats652     void addToTabSizes(JS::TabSizes* sizes) const {
653         MOZ_ASSERT(isTotals);
654         FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
655         unusedGCThings.addToTabSizes(sizes);
656         stringInfo.addToTabSizes(sizes);
657     }
658 
addToServoSizesZoneStats659     void addToServoSizes(JS::ServoSizes *sizes) const {
660         MOZ_ASSERT(isTotals);
661         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
662         unusedGCThings.addToServoSizes(sizes);
663         stringInfo.addToServoSizes(sizes);
664     }
665 
666     // These string measurements are initially for all strings.  At the end,
667     // if the measurement granularity is FineGrained, we subtract the
668     // measurements of the notable script sources and move them into
669     // |notableStrings|.
670     FOR_EACH_SIZE(DECL_SIZE)
671     UnusedGCThingSizes unusedGCThings;
672     StringInfo stringInfo;
673     void* extra;    // This field can be used by embedders.
674 
675     typedef js::HashMap<JSString*, StringInfo,
676                         js::InefficientNonFlatteningStringHashPolicy,
677                         js::SystemAllocPolicy> StringsHashMap;
678 
679     // |allStrings| is only used transiently.  During the zone traversal it is
680     // filled with info about every string in the zone.  It's then used to fill
681     // in |notableStrings| (which actually gets reported), and immediately
682     // discarded afterwards.
683     StringsHashMap* allStrings;
684     js::Vector<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings;
685     bool isTotals;
686 
687 #undef FOR_EACH_SIZE
688 };
689 
690 struct CompartmentStats
691 {
692     // We assume that |objectsPrivate| is on the malloc heap, but it's not
693     // actually guaranteed. But for Servo, at least, it's a moot point because
694     // it doesn't provide an ObjectPrivateVisitor so the value will always be
695     // zero.
696 #define FOR_EACH_SIZE(macro) \
697     macro(Private, MallocHeap, objectsPrivate) \
698     macro(Other,   GCHeapUsed, scriptsGCHeap) \
699     macro(Other,   MallocHeap, scriptsMallocHeapData) \
700     macro(Other,   MallocHeap, baselineData) \
701     macro(Other,   MallocHeap, baselineStubsFallback) \
702     macro(Other,   MallocHeap, ionData) \
703     macro(Other,   MallocHeap, typeInferenceTypeScripts) \
704     macro(Other,   MallocHeap, typeInferenceAllocationSiteTables) \
705     macro(Other,   MallocHeap, typeInferenceArrayTypeTables) \
706     macro(Other,   MallocHeap, typeInferenceObjectTypeTables) \
707     macro(Other,   MallocHeap, compartmentObject) \
708     macro(Other,   MallocHeap, compartmentTables) \
709     macro(Other,   MallocHeap, innerViewsTable) \
710     macro(Other,   MallocHeap, lazyArrayBuffersTable) \
711     macro(Other,   MallocHeap, objectMetadataTable) \
712     macro(Other,   MallocHeap, crossCompartmentWrappersTable) \
713     macro(Other,   MallocHeap, regexpCompartment) \
714     macro(Other,   MallocHeap, savedStacksSet) \
715     macro(Other,   MallocHeap, nonSyntacticLexicalScopesTable)
716 
CompartmentStatsCompartmentStats717     CompartmentStats()
718       : FOR_EACH_SIZE(ZERO_SIZE)
719         classInfo(),
720         extra(),
721         allClasses(nullptr),
722         notableClasses(),
723         isTotals(true)
724     {}
725 
CompartmentStatsCompartmentStats726     CompartmentStats(CompartmentStats&& other)
727       : FOR_EACH_SIZE(COPY_OTHER_SIZE)
728         classInfo(mozilla::Move(other.classInfo)),
729         extra(other.extra),
730         allClasses(other.allClasses),
731         notableClasses(mozilla::Move(other.notableClasses)),
732         isTotals(other.isTotals)
733     {
734         other.allClasses = nullptr;
735         MOZ_ASSERT(!other.isTotals);
736     }
737 
~CompartmentStatsCompartmentStats738     ~CompartmentStats() {
739         // |allClasses| is usually deleted and set to nullptr before this
740         // destructor runs. But there are failure cases due to OOMs that may
741         // prevent that, so it doesn't hurt to try again here.
742         js_delete(allClasses);
743     }
744 
745     bool initClasses(JSRuntime* rt);
746 
addSizesCompartmentStats747     void addSizes(const CompartmentStats& other) {
748         MOZ_ASSERT(isTotals);
749         FOR_EACH_SIZE(ADD_OTHER_SIZE)
750         classInfo.add(other.classInfo);
751     }
752 
sizeOfLiveGCThingsCompartmentStats753     size_t sizeOfLiveGCThings() const {
754         MOZ_ASSERT(isTotals);
755         size_t n = 0;
756         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
757         n += classInfo.sizeOfLiveGCThings();
758         return n;
759     }
760 
addToTabSizesCompartmentStats761     void addToTabSizes(TabSizes* sizes) const {
762         MOZ_ASSERT(isTotals);
763         FOR_EACH_SIZE(ADD_TO_TAB_SIZES);
764         classInfo.addToTabSizes(sizes);
765     }
766 
addToServoSizesCompartmentStats767     void addToServoSizes(ServoSizes *sizes) const {
768         MOZ_ASSERT(isTotals);
769         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES);
770         classInfo.addToServoSizes(sizes);
771     }
772 
773     // The class measurements in |classInfo| are initially for all classes.  At
774     // the end, if the measurement granularity is FineGrained, we subtract the
775     // measurements of the notable classes and move them into |notableClasses|.
776     FOR_EACH_SIZE(DECL_SIZE)
777     ClassInfo classInfo;
778     void* extra;            // This field can be used by embedders.
779 
780     typedef js::HashMap<const char*, ClassInfo,
781                         js::CStringHashPolicy,
782                         js::SystemAllocPolicy> ClassesHashMap;
783 
784     // These are similar to |allStrings| and |notableStrings| in ZoneStats.
785     ClassesHashMap* allClasses;
786     js::Vector<NotableClassInfo, 0, js::SystemAllocPolicy> notableClasses;
787     bool isTotals;
788 
789 #undef FOR_EACH_SIZE
790 };
791 
792 typedef js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> CompartmentStatsVector;
793 typedef js::Vector<ZoneStats, 0, js::SystemAllocPolicy> ZoneStatsVector;
794 
795 struct RuntimeStats
796 {
797     // |gcHeapChunkTotal| is ignored because it's the sum of all the other
798     // values. |gcHeapGCThings| is ignored because it's the sum of some of the
799     // values from the zones and compartments. Both of those values are not
800     // reported directly, but are just present for sanity-checking other
801     // values.
802 #define FOR_EACH_SIZE(macro) \
803     macro(_, Ignore,            gcHeapChunkTotal) \
804     macro(_, GCHeapDecommitted, gcHeapDecommittedArenas) \
805     macro(_, GCHeapUnused,      gcHeapUnusedChunks) \
806     macro(_, GCHeapUnused,      gcHeapUnusedArenas) \
807     macro(_, GCHeapAdmin,       gcHeapChunkAdmin) \
808     macro(_, Ignore,            gcHeapGCThings)
809 
RuntimeStatsRuntimeStats810     explicit RuntimeStats(mozilla::MallocSizeOf mallocSizeOf)
811       : FOR_EACH_SIZE(ZERO_SIZE)
812         runtime(),
813         cTotals(),
814         zTotals(),
815         compartmentStatsVector(),
816         zoneStatsVector(),
817         currZoneStats(nullptr),
818         mallocSizeOf_(mallocSizeOf)
819     {}
820 
821     // Here's a useful breakdown of the GC heap.
822     //
823     // - rtStats.gcHeapChunkTotal
824     //   - decommitted bytes
825     //     - rtStats.gcHeapDecommittedArenas (decommitted arenas in non-empty chunks)
826     //   - unused bytes
827     //     - rtStats.gcHeapUnusedChunks (empty chunks)
828     //     - rtStats.gcHeapUnusedArenas (empty arenas within non-empty chunks)
829     //     - rtStats.zTotals.unusedGCThings.totalSize() (empty GC thing slots within non-empty arenas)
830     //   - used bytes
831     //     - rtStats.gcHeapChunkAdmin
832     //     - rtStats.zTotals.gcHeapArenaAdmin
833     //     - rtStats.gcHeapGCThings (in-use GC things)
834     //       == rtStats.zTotals.sizeOfLiveGCThings() + rtStats.cTotals.sizeOfLiveGCThings()
835     //
836     // It's possible that some arenas in empty chunks may be decommitted, but
837     // we don't count those under rtStats.gcHeapDecommittedArenas because (a)
838     // it's rare, and (b) this means that rtStats.gcHeapUnusedChunks is a
839     // multiple of the chunk size, which is good.
840 
addToServoSizesRuntimeStats841     void addToServoSizes(ServoSizes *sizes) const {
842         FOR_EACH_SIZE(ADD_TO_SERVO_SIZES)
843         runtime.addToServoSizes(sizes);
844     }
845 
846     FOR_EACH_SIZE(DECL_SIZE)
847 
848     RuntimeSizes runtime;
849 
850     CompartmentStats cTotals;   // The sum of this runtime's compartments' measurements.
851     ZoneStats zTotals;          // The sum of this runtime's zones' measurements.
852 
853     CompartmentStatsVector compartmentStatsVector;
854     ZoneStatsVector zoneStatsVector;
855 
856     ZoneStats* currZoneStats;
857 
858     mozilla::MallocSizeOf mallocSizeOf_;
859 
860     virtual void initExtraCompartmentStats(JSCompartment* c, CompartmentStats* cstats) = 0;
861     virtual void initExtraZoneStats(JS::Zone* zone, ZoneStats* zstats) = 0;
862 
863 #undef FOR_EACH_SIZE
864 };
865 
866 class ObjectPrivateVisitor
867 {
868   public:
869     // Within CollectRuntimeStats, this method is called for each JS object
870     // that has an nsISupports pointer.
871     virtual size_t sizeOfIncludingThis(nsISupports* aSupports) = 0;
872 
873     // A callback that gets a JSObject's nsISupports pointer, if it has one.
874     // Note: this function does *not* addref |iface|.
875     typedef bool(*GetISupportsFun)(JSObject* obj, nsISupports** iface);
876     GetISupportsFun getISupports_;
877 
ObjectPrivateVisitor(GetISupportsFun getISupports)878     explicit ObjectPrivateVisitor(GetISupportsFun getISupports)
879       : getISupports_(getISupports)
880     {}
881 };
882 
883 extern JS_PUBLIC_API(bool)
884 CollectRuntimeStats(JSRuntime* rt, RuntimeStats* rtStats, ObjectPrivateVisitor* opv, bool anonymize);
885 
886 extern JS_PUBLIC_API(size_t)
887 SystemCompartmentCount(JSRuntime* rt);
888 
889 extern JS_PUBLIC_API(size_t)
890 UserCompartmentCount(JSRuntime* rt);
891 
892 extern JS_PUBLIC_API(size_t)
893 PeakSizeOfTemporary(const JSRuntime* rt);
894 
895 extern JS_PUBLIC_API(bool)
896 AddSizeOfTab(JSRuntime* rt, JS::HandleObject obj, mozilla::MallocSizeOf mallocSizeOf,
897              ObjectPrivateVisitor* opv, TabSizes* sizes);
898 
899 extern JS_PUBLIC_API(bool)
900 AddServoSizeOf(JSRuntime *rt, mozilla::MallocSizeOf mallocSizeOf,
901                ObjectPrivateVisitor *opv, ServoSizes *sizes);
902 
903 } // namespace JS
904 
905 #undef DECL_SIZE
906 #undef ZERO_SIZE
907 #undef COPY_OTHER_SIZE
908 #undef ADD_OTHER_SIZE
909 #undef SUB_OTHER_SIZE
910 #undef ADD_SIZE_TO_N
911 #undef ADD_SIZE_TO_N_IF_LIVE_GC_THING
912 #undef ADD_TO_TAB_SIZES
913 
914 #endif /* js_MemoryMetrics_h */
915