Line data Source code
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 : #include "js/MemoryMetrics.h"
8 :
9 : #include "mozilla/DebugOnly.h"
10 :
11 : #include "jsapi.h"
12 : #include "jscompartment.h"
13 : #include "jsgc.h"
14 : #include "jsobj.h"
15 : #include "jsscript.h"
16 :
17 : #include "gc/Heap.h"
18 : #include "jit/BaselineJIT.h"
19 : #include "jit/Ion.h"
20 : #include "vm/ArrayObject.h"
21 : #include "vm/Runtime.h"
22 : #include "vm/Shape.h"
23 : #include "vm/String.h"
24 : #include "vm/Symbol.h"
25 : #include "vm/WrapperObject.h"
26 : #include "wasm/WasmInstance.h"
27 : #include "wasm/WasmJS.h"
28 : #include "wasm/WasmModule.h"
29 :
30 : using mozilla::DebugOnly;
31 : using mozilla::MallocSizeOf;
32 : using mozilla::Move;
33 : using mozilla::PodCopy;
34 : using mozilla::PodEqual;
35 :
36 : using namespace js;
37 :
38 : using JS::RuntimeStats;
39 : using JS::ObjectPrivateVisitor;
40 : using JS::ZoneStats;
41 : using JS::CompartmentStats;
42 :
43 : namespace js {
44 :
45 : JS_FRIEND_API(size_t)
46 0 : MemoryReportingSundriesThreshold()
47 : {
48 0 : return 8 * 1024;
49 : }
50 :
51 : template <typename CharT>
52 : static uint32_t
53 0 : HashStringChars(JSString* s)
54 : {
55 0 : ScopedJSFreePtr<CharT> ownedChars;
56 : const CharT* chars;
57 0 : JS::AutoCheckCannotGC nogc;
58 0 : if (s->isLinear()) {
59 0 : chars = s->asLinear().chars<CharT>(nogc);
60 : } else {
61 : // Slowest hash function evar!
62 0 : if (!s->asRope().copyChars<CharT>(/* tcx */ nullptr, ownedChars))
63 0 : MOZ_CRASH("oom");
64 0 : chars = ownedChars;
65 : }
66 :
67 0 : return mozilla::HashString(chars, s->length());
68 : }
69 :
70 : /* static */ HashNumber
71 0 : InefficientNonFlatteningStringHashPolicy::hash(const Lookup& l)
72 : {
73 0 : return l->hasLatin1Chars()
74 0 : ? HashStringChars<Latin1Char>(l)
75 0 : : HashStringChars<char16_t>(l);
76 : }
77 :
78 : template <typename Char1, typename Char2>
79 : static bool
80 0 : EqualStringsPure(JSString* s1, JSString* s2)
81 : {
82 0 : if (s1->length() != s2->length())
83 0 : return false;
84 :
85 : const Char1* c1;
86 0 : ScopedJSFreePtr<Char1> ownedChars1;
87 0 : JS::AutoCheckCannotGC nogc;
88 0 : if (s1->isLinear()) {
89 0 : c1 = s1->asLinear().chars<Char1>(nogc);
90 : } else {
91 0 : if (!s1->asRope().copyChars<Char1>(/* tcx */ nullptr, ownedChars1))
92 0 : MOZ_CRASH("oom");
93 0 : c1 = ownedChars1;
94 : }
95 :
96 : const Char2* c2;
97 0 : ScopedJSFreePtr<Char2> ownedChars2;
98 0 : if (s2->isLinear()) {
99 0 : c2 = s2->asLinear().chars<Char2>(nogc);
100 : } else {
101 0 : if (!s2->asRope().copyChars<Char2>(/* tcx */ nullptr, ownedChars2))
102 0 : MOZ_CRASH("oom");
103 0 : c2 = ownedChars2;
104 : }
105 :
106 0 : return EqualChars(c1, c2, s1->length());
107 : }
108 :
109 : /* static */ bool
110 0 : InefficientNonFlatteningStringHashPolicy::match(const JSString* const& k, const Lookup& l)
111 : {
112 : // We can't use js::EqualStrings, because that flattens our strings.
113 0 : JSString* s1 = const_cast<JSString*>(k);
114 0 : if (k->hasLatin1Chars()) {
115 0 : return l->hasLatin1Chars()
116 0 : ? EqualStringsPure<Latin1Char, Latin1Char>(s1, l)
117 0 : : EqualStringsPure<Latin1Char, char16_t>(s1, l);
118 : }
119 :
120 0 : return l->hasLatin1Chars()
121 0 : ? EqualStringsPure<char16_t, Latin1Char>(s1, l)
122 0 : : EqualStringsPure<char16_t, char16_t>(s1, l);
123 : }
124 :
125 : /* static */ HashNumber
126 0 : CStringHashPolicy::hash(const Lookup& l)
127 : {
128 0 : return mozilla::HashString(l);
129 : }
130 :
131 : /* static */ bool
132 0 : CStringHashPolicy::match(const char* const& k, const Lookup& l)
133 : {
134 0 : return strcmp(k, l) == 0;
135 : }
136 :
137 : } // namespace js
138 :
139 : namespace JS {
140 :
141 0 : NotableStringInfo::NotableStringInfo()
142 : : StringInfo(),
143 : buffer(0),
144 0 : length(0)
145 : {
146 0 : }
147 :
148 : template <typename CharT>
149 : static void
150 0 : StoreStringChars(char* buffer, size_t bufferSize, JSString* str)
151 : {
152 : const CharT* chars;
153 0 : ScopedJSFreePtr<CharT> ownedChars;
154 0 : JS::AutoCheckCannotGC nogc;
155 0 : if (str->isLinear()) {
156 0 : chars = str->asLinear().chars<CharT>(nogc);
157 : } else {
158 0 : if (!str->asRope().copyChars<CharT>(/* tcx */ nullptr, ownedChars))
159 0 : MOZ_CRASH("oom");
160 0 : chars = ownedChars;
161 : }
162 :
163 : // We might truncate |str| even if it's much shorter than 1024 chars, if
164 : // |str| contains unicode chars. Since this is just for a memory reporter,
165 : // we don't care.
166 0 : PutEscapedString(buffer, bufferSize, chars, str->length(), /* quote */ 0);
167 0 : }
168 :
169 0 : NotableStringInfo::NotableStringInfo(JSString* str, const StringInfo& info)
170 : : StringInfo(info),
171 0 : length(str->length())
172 : {
173 0 : size_t bufferSize = Min(str->length() + 1, size_t(MAX_SAVED_CHARS));
174 0 : buffer = js_pod_malloc<char>(bufferSize);
175 0 : if (!buffer) {
176 0 : MOZ_CRASH("oom");
177 : }
178 :
179 0 : if (str->hasLatin1Chars())
180 0 : StoreStringChars<Latin1Char>(buffer, bufferSize, str);
181 : else
182 0 : StoreStringChars<char16_t>(buffer, bufferSize, str);
183 0 : }
184 :
185 0 : NotableStringInfo::NotableStringInfo(NotableStringInfo&& info)
186 0 : : StringInfo(Move(info)),
187 0 : length(info.length)
188 : {
189 0 : buffer = info.buffer;
190 0 : info.buffer = nullptr;
191 0 : }
192 :
193 0 : NotableStringInfo& NotableStringInfo::operator=(NotableStringInfo&& info)
194 : {
195 0 : MOZ_ASSERT(this != &info, "self-move assignment is prohibited");
196 0 : this->~NotableStringInfo();
197 0 : new (this) NotableStringInfo(Move(info));
198 0 : return *this;
199 : }
200 :
201 0 : NotableClassInfo::NotableClassInfo()
202 : : ClassInfo(),
203 0 : className_(nullptr)
204 : {
205 0 : }
206 :
207 0 : NotableClassInfo::NotableClassInfo(const char* className, const ClassInfo& info)
208 0 : : ClassInfo(info)
209 : {
210 0 : size_t bytes = strlen(className) + 1;
211 0 : className_ = js_pod_malloc<char>(bytes);
212 0 : if (!className_)
213 0 : MOZ_CRASH("oom");
214 0 : PodCopy(className_, className, bytes);
215 0 : }
216 :
217 0 : NotableClassInfo::NotableClassInfo(NotableClassInfo&& info)
218 0 : : ClassInfo(Move(info))
219 : {
220 0 : className_ = info.className_;
221 0 : info.className_ = nullptr;
222 0 : }
223 :
224 0 : NotableClassInfo& NotableClassInfo::operator=(NotableClassInfo&& info)
225 : {
226 0 : MOZ_ASSERT(this != &info, "self-move assignment is prohibited");
227 0 : this->~NotableClassInfo();
228 0 : new (this) NotableClassInfo(Move(info));
229 0 : return *this;
230 : }
231 :
232 0 : NotableScriptSourceInfo::NotableScriptSourceInfo()
233 : : ScriptSourceInfo(),
234 0 : filename_(nullptr)
235 : {
236 0 : }
237 :
238 0 : NotableScriptSourceInfo::NotableScriptSourceInfo(const char* filename, const ScriptSourceInfo& info)
239 0 : : ScriptSourceInfo(info)
240 : {
241 0 : size_t bytes = strlen(filename) + 1;
242 0 : filename_ = js_pod_malloc<char>(bytes);
243 0 : if (!filename_)
244 0 : MOZ_CRASH("oom");
245 0 : PodCopy(filename_, filename, bytes);
246 0 : }
247 :
248 0 : NotableScriptSourceInfo::NotableScriptSourceInfo(NotableScriptSourceInfo&& info)
249 0 : : ScriptSourceInfo(Move(info))
250 : {
251 0 : filename_ = info.filename_;
252 0 : info.filename_ = nullptr;
253 0 : }
254 :
255 0 : NotableScriptSourceInfo& NotableScriptSourceInfo::operator=(NotableScriptSourceInfo&& info)
256 : {
257 0 : MOZ_ASSERT(this != &info, "self-move assignment is prohibited");
258 0 : this->~NotableScriptSourceInfo();
259 0 : new (this) NotableScriptSourceInfo(Move(info));
260 0 : return *this;
261 : }
262 :
263 :
264 : } // namespace JS
265 :
266 : typedef HashSet<ScriptSource*, DefaultHasher<ScriptSource*>, SystemAllocPolicy> SourceSet;
267 :
268 0 : struct StatsClosure
269 : {
270 : RuntimeStats* rtStats;
271 : ObjectPrivateVisitor* opv;
272 : SourceSet seenSources;
273 : wasm::Metadata::SeenSet wasmSeenMetadata;
274 : wasm::ShareableBytes::SeenSet wasmSeenBytes;
275 : wasm::Code::SeenSet wasmSeenCode;
276 : wasm::Table::SeenSet wasmSeenTables;
277 : bool anonymize;
278 :
279 0 : StatsClosure(RuntimeStats* rt, ObjectPrivateVisitor* v, bool anon)
280 0 : : rtStats(rt),
281 : opv(v),
282 0 : anonymize(anon)
283 0 : {}
284 :
285 0 : bool init() {
286 0 : return seenSources.init() &&
287 0 : wasmSeenMetadata.init() &&
288 0 : wasmSeenBytes.init() &&
289 0 : wasmSeenCode.init() &&
290 0 : wasmSeenTables.init();
291 : }
292 : };
293 :
294 : static void
295 0 : DecommittedArenasChunkCallback(JSRuntime* rt, void* data, gc::Chunk* chunk)
296 : {
297 : // This case is common and fast to check. Do it first.
298 0 : if (chunk->decommittedArenas.isAllClear())
299 0 : return;
300 :
301 0 : size_t n = 0;
302 0 : for (size_t i = 0; i < gc::ArenasPerChunk; i++) {
303 0 : if (chunk->decommittedArenas.get(i))
304 0 : n += gc::ArenaSize;
305 : }
306 0 : MOZ_ASSERT(n > 0);
307 0 : *static_cast<size_t*>(data) += n;
308 : }
309 :
310 : static void
311 0 : StatsZoneCallback(JSRuntime* rt, void* data, Zone* zone)
312 : {
313 : // Append a new CompartmentStats to the vector.
314 0 : RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
315 :
316 : // CollectRuntimeStats reserves enough space.
317 0 : MOZ_ALWAYS_TRUE(rtStats->zoneStatsVector.growBy(1));
318 0 : ZoneStats& zStats = rtStats->zoneStatsVector.back();
319 0 : if (!zStats.initStrings(rt))
320 0 : MOZ_CRASH("oom");
321 0 : rtStats->initExtraZoneStats(zone, &zStats);
322 0 : rtStats->currZoneStats = &zStats;
323 :
324 0 : zone->addSizeOfIncludingThis(rtStats->mallocSizeOf_,
325 : &zStats.typePool,
326 : &zStats.regexpZone,
327 : &zStats.jitZone,
328 : &zStats.baselineStubsOptimized,
329 : &zStats.cachedCFG,
330 : &zStats.uniqueIdMap,
331 : &zStats.shapeTables,
332 0 : &rtStats->runtime.atomsMarkBitmaps);
333 0 : }
334 :
335 : static void
336 0 : StatsCompartmentCallback(JSContext* cx, void* data, JSCompartment* compartment)
337 : {
338 : // Append a new CompartmentStats to the vector.
339 0 : RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
340 :
341 : // CollectRuntimeStats reserves enough space.
342 0 : MOZ_ALWAYS_TRUE(rtStats->compartmentStatsVector.growBy(1));
343 0 : CompartmentStats& cStats = rtStats->compartmentStatsVector.back();
344 0 : if (!cStats.initClasses(cx->runtime()))
345 0 : MOZ_CRASH("oom");
346 0 : rtStats->initExtraCompartmentStats(compartment, &cStats);
347 :
348 0 : compartment->setCompartmentStats(&cStats);
349 :
350 : // Measure the compartment object itself, and things hanging off it.
351 0 : compartment->addSizeOfIncludingThis(rtStats->mallocSizeOf_,
352 : &cStats.typeInferenceAllocationSiteTables,
353 : &cStats.typeInferenceArrayTypeTables,
354 : &cStats.typeInferenceObjectTypeTables,
355 : &cStats.compartmentObject,
356 : &cStats.compartmentTables,
357 : &cStats.innerViewsTable,
358 : &cStats.lazyArrayBuffersTable,
359 : &cStats.objectMetadataTable,
360 : &cStats.crossCompartmentWrappersTable,
361 : &cStats.savedStacksSet,
362 : &cStats.varNamesSet,
363 : &cStats.nonSyntacticLexicalScopesTable,
364 : &cStats.templateLiteralMap,
365 : &cStats.jitCompartment,
366 0 : &cStats.privateData);
367 0 : }
368 :
369 : static void
370 0 : StatsArenaCallback(JSRuntime* rt, void* data, gc::Arena* arena,
371 : JS::TraceKind traceKind, size_t thingSize)
372 : {
373 0 : RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
374 :
375 : // The admin space includes (a) the header fields and (b) the padding
376 : // between the end of the header fields and the first GC thing.
377 0 : size_t allocationSpace = gc::Arena::thingsSpan(arena->getAllocKind());
378 0 : rtStats->currZoneStats->gcHeapArenaAdmin += gc::ArenaSize - allocationSpace;
379 :
380 : // We don't call the callback on unused things. So we compute the
381 : // unused space like this: arenaUnused = maxArenaUnused - arenaUsed.
382 : // We do this by setting arenaUnused to maxArenaUnused here, and then
383 : // subtracting thingSize for every used cell, in StatsCellCallback().
384 0 : rtStats->currZoneStats->unusedGCThings.addToKind(traceKind, allocationSpace);
385 0 : }
386 :
387 : // FineGrained is used for normal memory reporting. CoarseGrained is used by
388 : // AddSizeOfTab(), which aggregates all the measurements into a handful of
389 : // high-level numbers, which means that fine-grained reporting would be a waste
390 : // of effort.
391 : enum Granularity {
392 : FineGrained,
393 : CoarseGrained
394 : };
395 :
396 : static void
397 0 : AddClassInfo(Granularity granularity, CompartmentStats& cStats, const char* className,
398 : JS::ClassInfo& info)
399 : {
400 0 : if (granularity == FineGrained) {
401 0 : if (!className)
402 0 : className = "<no class name>";
403 0 : CompartmentStats::ClassesHashMap::AddPtr p = cStats.allClasses->lookupForAdd(className);
404 0 : if (!p) {
405 0 : bool ok = cStats.allClasses->add(p, className, info);
406 : // Ignore failure -- we just won't record the
407 : // object/shape/base-shape as notable.
408 : (void)ok;
409 : } else {
410 0 : p->value().add(info);
411 : }
412 : }
413 0 : }
414 :
415 : template <Granularity granularity>
416 : static void
417 0 : CollectScriptSourceStats(StatsClosure* closure, ScriptSource* ss)
418 : {
419 0 : RuntimeStats* rtStats = closure->rtStats;
420 :
421 0 : SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss);
422 0 : if (entry)
423 0 : return;
424 :
425 0 : bool ok = closure->seenSources.add(entry, ss);
426 : (void)ok; // Not much to be done on failure.
427 :
428 0 : JS::ScriptSourceInfo info; // This zeroes all the sizes.
429 0 : ss->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &info);
430 :
431 0 : rtStats->runtime.scriptSourceInfo.add(info);
432 :
433 : if (granularity == FineGrained) {
434 0 : const char* filename = ss->filename();
435 0 : if (!filename)
436 0 : filename = "<no filename>";
437 :
438 : JS::RuntimeSizes::ScriptSourcesHashMap::AddPtr p =
439 0 : rtStats->runtime.allScriptSources->lookupForAdd(filename);
440 0 : if (!p) {
441 0 : bool ok = rtStats->runtime.allScriptSources->add(p, filename, info);
442 : // Ignore failure -- we just won't record the script source as notable.
443 : (void)ok;
444 : } else {
445 0 : p->value().add(info);
446 : }
447 : }
448 : }
449 :
450 :
451 : // The various kinds of hashing are expensive, and the results are unused when
452 : // doing coarse-grained measurements. Skipping them more than doubles the
453 : // profile speed for complex pages such as gmail.com.
454 : template <Granularity granularity>
455 : static void
456 0 : StatsCellCallback(JSRuntime* rt, void* data, void* thing, JS::TraceKind traceKind,
457 : size_t thingSize)
458 : {
459 0 : StatsClosure* closure = static_cast<StatsClosure*>(data);
460 0 : RuntimeStats* rtStats = closure->rtStats;
461 0 : ZoneStats* zStats = rtStats->currZoneStats;
462 0 : switch (traceKind) {
463 : case JS::TraceKind::Object: {
464 0 : JSObject* obj = static_cast<JSObject*>(thing);
465 0 : CompartmentStats& cStats = obj->compartment()->compartmentStats();
466 0 : JS::ClassInfo info; // This zeroes all the sizes.
467 0 : info.objectsGCHeap += thingSize;
468 :
469 0 : obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);
470 :
471 : // These classes require special handling due to shared resources which
472 : // we must be careful not to report twice.
473 0 : if (obj->is<WasmModuleObject>()) {
474 0 : wasm::Module& module = obj->as<WasmModuleObject>().module();
475 0 : if (ScriptSource* ss = module.metadata().maybeScriptSource())
476 0 : CollectScriptSourceStats<granularity>(closure, ss);
477 0 : module.addSizeOfMisc(rtStats->mallocSizeOf_,
478 : &closure->wasmSeenMetadata,
479 : &closure->wasmSeenBytes,
480 : &closure->wasmSeenCode,
481 : &info.objectsNonHeapCodeWasm,
482 : &info.objectsMallocHeapMisc);
483 0 : } else if (obj->is<WasmInstanceObject>()) {
484 0 : wasm::Instance& instance = obj->as<WasmInstanceObject>().instance();
485 0 : if (ScriptSource* ss = instance.metadata().maybeScriptSource())
486 0 : CollectScriptSourceStats<granularity>(closure, ss);
487 0 : instance.addSizeOfMisc(rtStats->mallocSizeOf_,
488 : &closure->wasmSeenMetadata,
489 : &closure->wasmSeenBytes,
490 : &closure->wasmSeenCode,
491 : &closure->wasmSeenTables,
492 : &info.objectsNonHeapCodeWasm,
493 : &info.objectsMallocHeapMisc);
494 : }
495 :
496 0 : cStats.classInfo.add(info);
497 :
498 0 : const Class* clasp = obj->getClass();
499 0 : const char* className = clasp->name;
500 0 : AddClassInfo(granularity, cStats, className, info);
501 :
502 0 : if (ObjectPrivateVisitor* opv = closure->opv) {
503 : nsISupports* iface;
504 0 : if (opv->getISupports_(obj, &iface) && iface)
505 0 : cStats.objectsPrivate += opv->sizeOfIncludingThis(iface);
506 : }
507 0 : break;
508 : }
509 :
510 : case JS::TraceKind::Script: {
511 0 : JSScript* script = static_cast<JSScript*>(thing);
512 0 : CompartmentStats& cStats = script->compartment()->compartmentStats();
513 0 : cStats.scriptsGCHeap += thingSize;
514 0 : cStats.scriptsMallocHeapData += script->sizeOfData(rtStats->mallocSizeOf_);
515 0 : cStats.typeInferenceTypeScripts += script->sizeOfTypeScript(rtStats->mallocSizeOf_);
516 0 : jit::AddSizeOfBaselineData(script, rtStats->mallocSizeOf_, &cStats.baselineData,
517 : &cStats.baselineStubsFallback);
518 0 : cStats.ionData += jit::SizeOfIonData(script, rtStats->mallocSizeOf_);
519 0 : CollectScriptSourceStats<granularity>(closure, script->scriptSource());
520 0 : break;
521 : }
522 :
523 : case JS::TraceKind::String: {
524 0 : JSString* str = static_cast<JSString*>(thing);
525 :
526 0 : JS::StringInfo info;
527 0 : if (str->hasLatin1Chars()) {
528 0 : info.gcHeapLatin1 = thingSize;
529 0 : info.mallocHeapLatin1 = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
530 : } else {
531 0 : info.gcHeapTwoByte = thingSize;
532 0 : info.mallocHeapTwoByte = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
533 : }
534 0 : info.numCopies = 1;
535 :
536 0 : zStats->stringInfo.add(info);
537 :
538 : // The primary use case for anonymization is automated crash submission
539 : // (to help detect OOM crashes). In that case, we don't want to pay the
540 : // memory cost required to do notable string detection.
541 0 : if (granularity == FineGrained && !closure->anonymize) {
542 0 : ZoneStats::StringsHashMap::AddPtr p = zStats->allStrings->lookupForAdd(str);
543 0 : if (!p) {
544 0 : bool ok = zStats->allStrings->add(p, str, info);
545 : // Ignore failure -- we just won't record the string as notable.
546 : (void)ok;
547 : } else {
548 0 : p->value().add(info);
549 : }
550 : }
551 0 : break;
552 : }
553 :
554 : case JS::TraceKind::Symbol:
555 0 : zStats->symbolsGCHeap += thingSize;
556 0 : break;
557 :
558 : case JS::TraceKind::BaseShape: {
559 0 : JS::ShapeInfo info; // This zeroes all the sizes.
560 0 : info.shapesGCHeapBase += thingSize;
561 : // No malloc-heap measurements.
562 :
563 0 : zStats->shapeInfo.add(info);
564 0 : break;
565 : }
566 :
567 : case JS::TraceKind::JitCode: {
568 0 : zStats->jitCodesGCHeap += thingSize;
569 : // The code for a script is counted in ExecutableAllocator::sizeOfCode().
570 0 : break;
571 : }
572 :
573 : case JS::TraceKind::LazyScript: {
574 0 : LazyScript* lazy = static_cast<LazyScript*>(thing);
575 0 : zStats->lazyScriptsGCHeap += thingSize;
576 0 : zStats->lazyScriptsMallocHeap += lazy->sizeOfExcludingThis(rtStats->mallocSizeOf_);
577 0 : break;
578 : }
579 :
580 : case JS::TraceKind::Shape: {
581 0 : Shape* shape = static_cast<Shape*>(thing);
582 :
583 0 : JS::ShapeInfo info; // This zeroes all the sizes.
584 0 : if (shape->inDictionary())
585 0 : info.shapesGCHeapDict += thingSize;
586 : else
587 0 : info.shapesGCHeapTree += thingSize;
588 0 : shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);
589 0 : zStats->shapeInfo.add(info);
590 0 : break;
591 : }
592 :
593 : case JS::TraceKind::ObjectGroup: {
594 0 : ObjectGroup* group = static_cast<ObjectGroup*>(thing);
595 0 : zStats->objectGroupsGCHeap += thingSize;
596 0 : zStats->objectGroupsMallocHeap += group->sizeOfExcludingThis(rtStats->mallocSizeOf_);
597 0 : break;
598 : }
599 :
600 : case JS::TraceKind::Scope: {
601 0 : Scope* scope = static_cast<Scope*>(thing);
602 0 : zStats->scopesGCHeap += thingSize;
603 0 : zStats->scopesMallocHeap += scope->sizeOfExcludingThis(rtStats->mallocSizeOf_);
604 0 : break;
605 : }
606 :
607 : case JS::TraceKind::RegExpShared: {
608 0 : auto regexp = static_cast<RegExpShared*>(thing);
609 0 : zStats->regExpSharedsGCHeap += thingSize;
610 0 : zStats->regExpSharedsMallocHeap += regexp->sizeOfExcludingThis(rtStats->mallocSizeOf_);
611 0 : break;
612 : }
613 :
614 : default:
615 0 : MOZ_CRASH("invalid traceKind in StatsCellCallback");
616 : }
617 :
618 : // Yes, this is a subtraction: see StatsArenaCallback() for details.
619 0 : zStats->unusedGCThings.addToKind(traceKind, -thingSize);
620 0 : }
621 :
622 : bool
623 0 : ZoneStats::initStrings(JSRuntime* rt)
624 : {
625 0 : isTotals = false;
626 0 : allStrings = rt->new_<StringsHashMap>();
627 0 : if (!allStrings || !allStrings->init()) {
628 0 : js_delete(allStrings);
629 0 : allStrings = nullptr;
630 0 : return false;
631 : }
632 0 : return true;
633 : }
634 :
635 : bool
636 0 : CompartmentStats::initClasses(JSRuntime* rt)
637 : {
638 0 : isTotals = false;
639 0 : allClasses = rt->new_<ClassesHashMap>();
640 0 : if (!allClasses || !allClasses->init()) {
641 0 : js_delete(allClasses);
642 0 : allClasses = nullptr;
643 0 : return false;
644 : }
645 0 : return true;
646 : }
647 :
648 : static bool
649 0 : FindNotableStrings(ZoneStats& zStats)
650 : {
651 : using namespace JS;
652 :
653 : // We should only run FindNotableStrings once per ZoneStats object.
654 0 : MOZ_ASSERT(zStats.notableStrings.empty());
655 :
656 0 : for (ZoneStats::StringsHashMap::Range r = zStats.allStrings->all(); !r.empty(); r.popFront()) {
657 :
658 0 : JSString* str = r.front().key();
659 0 : StringInfo& info = r.front().value();
660 :
661 0 : if (!info.isNotable())
662 0 : continue;
663 :
664 0 : if (!zStats.notableStrings.growBy(1))
665 0 : return false;
666 :
667 0 : zStats.notableStrings.back() = NotableStringInfo(str, info);
668 :
669 : // We're moving this string from a non-notable to a notable bucket, so
670 : // subtract it out of the non-notable tallies.
671 0 : zStats.stringInfo.subtract(info);
672 : }
673 : // Delete |allStrings| now, rather than waiting for zStats's destruction,
674 : // to reduce peak memory consumption during reporting.
675 0 : js_delete(zStats.allStrings);
676 0 : zStats.allStrings = nullptr;
677 0 : return true;
678 : }
679 :
680 : static bool
681 0 : FindNotableClasses(CompartmentStats& cStats)
682 : {
683 : using namespace JS;
684 :
685 : // We should only run FindNotableClasses once per ZoneStats object.
686 0 : MOZ_ASSERT(cStats.notableClasses.empty());
687 :
688 0 : for (CompartmentStats::ClassesHashMap::Range r = cStats.allClasses->all();
689 0 : !r.empty();
690 0 : r.popFront())
691 : {
692 0 : const char* className = r.front().key();
693 0 : ClassInfo& info = r.front().value();
694 :
695 : // If this class isn't notable, or if we can't grow the notableStrings
696 : // vector, skip this string.
697 0 : if (!info.isNotable())
698 0 : continue;
699 :
700 0 : if (!cStats.notableClasses.growBy(1))
701 0 : return false;
702 :
703 0 : cStats.notableClasses.back() = NotableClassInfo(className, info);
704 :
705 : // We're moving this class from a non-notable to a notable bucket, so
706 : // subtract it out of the non-notable tallies.
707 0 : cStats.classInfo.subtract(info);
708 : }
709 : // Delete |allClasses| now, rather than waiting for zStats's destruction,
710 : // to reduce peak memory consumption during reporting.
711 0 : js_delete(cStats.allClasses);
712 0 : cStats.allClasses = nullptr;
713 0 : return true;
714 : }
715 :
716 : static bool
717 0 : FindNotableScriptSources(JS::RuntimeSizes& runtime)
718 : {
719 : using namespace JS;
720 :
721 : // We should only run FindNotableScriptSources once per RuntimeSizes.
722 0 : MOZ_ASSERT(runtime.notableScriptSources.empty());
723 :
724 0 : for (RuntimeSizes::ScriptSourcesHashMap::Range r = runtime.allScriptSources->all();
725 0 : !r.empty();
726 0 : r.popFront())
727 : {
728 0 : const char* filename = r.front().key();
729 0 : ScriptSourceInfo& info = r.front().value();
730 :
731 0 : if (!info.isNotable())
732 0 : continue;
733 :
734 0 : if (!runtime.notableScriptSources.growBy(1))
735 0 : return false;
736 :
737 0 : runtime.notableScriptSources.back() = NotableScriptSourceInfo(filename, info);
738 :
739 : // We're moving this script source from a non-notable to a notable
740 : // bucket, so subtract its sizes from the non-notable tallies.
741 0 : runtime.scriptSourceInfo.subtract(info);
742 : }
743 : // Delete |allScriptSources| now, rather than waiting for zStats's
744 : // destruction, to reduce peak memory consumption during reporting.
745 0 : js_delete(runtime.allScriptSources);
746 0 : runtime.allScriptSources = nullptr;
747 0 : return true;
748 : }
749 :
750 : static bool
751 0 : CollectRuntimeStatsHelper(JSContext* cx, RuntimeStats* rtStats, ObjectPrivateVisitor* opv,
752 : bool anonymize, IterateCellCallback statsCellCallback)
753 : {
754 0 : JSRuntime* rt = cx->runtime();
755 0 : if (!rtStats->compartmentStatsVector.reserve(rt->numCompartments))
756 0 : return false;
757 :
758 0 : size_t totalZones = 1; // For the atoms zone.
759 0 : for (ZoneGroupsIter group(rt); !group.done(); group.next())
760 0 : totalZones += group->zones().length();
761 0 : if (!rtStats->zoneStatsVector.reserve(totalZones))
762 0 : return false;
763 :
764 0 : rtStats->gcHeapChunkTotal =
765 0 : size_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
766 :
767 0 : rtStats->gcHeapUnusedChunks =
768 0 : size_t(JS_GetGCParameter(cx, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
769 :
770 0 : IterateChunks(cx, &rtStats->gcHeapDecommittedArenas,
771 0 : DecommittedArenasChunkCallback);
772 :
773 : // Take the per-compartment measurements.
774 0 : StatsClosure closure(rtStats, opv, anonymize);
775 0 : if (!closure.init())
776 0 : return false;
777 : IterateHeapUnbarriered(cx, &closure,
778 : StatsZoneCallback,
779 : StatsCompartmentCallback,
780 : StatsArenaCallback,
781 0 : statsCellCallback);
782 :
783 : // Take the "explicit/js/runtime/" measurements.
784 0 : rt->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &rtStats->runtime);
785 :
786 0 : if (!FindNotableScriptSources(rtStats->runtime))
787 0 : return false;
788 :
789 0 : JS::ZoneStatsVector& zs = rtStats->zoneStatsVector;
790 0 : ZoneStats& zTotals = rtStats->zTotals;
791 :
792 : // We don't look for notable strings for zTotals. So we first sum all the
793 : // zones' measurements to get the totals. Then we find the notable strings
794 : // within each zone.
795 0 : for (size_t i = 0; i < zs.length(); i++)
796 0 : zTotals.addSizes(zs[i]);
797 :
798 0 : for (size_t i = 0; i < zs.length(); i++)
799 0 : if (!FindNotableStrings(zs[i]))
800 0 : return false;
801 :
802 0 : MOZ_ASSERT(!zTotals.allStrings);
803 :
804 0 : JS::CompartmentStatsVector& cs = rtStats->compartmentStatsVector;
805 0 : CompartmentStats& cTotals = rtStats->cTotals;
806 :
807 : // As with the zones, we sum all compartments first, and then get the
808 : // notable classes within each zone.
809 0 : for (size_t i = 0; i < cs.length(); i++)
810 0 : cTotals.addSizes(cs[i]);
811 :
812 0 : for (size_t i = 0; i < cs.length(); i++) {
813 0 : if (!FindNotableClasses(cs[i]))
814 0 : return false;
815 : }
816 :
817 0 : MOZ_ASSERT(!cTotals.allClasses);
818 :
819 0 : rtStats->gcHeapGCThings = rtStats->zTotals.sizeOfLiveGCThings() +
820 0 : rtStats->cTotals.sizeOfLiveGCThings();
821 :
822 : #ifdef DEBUG
823 : // Check that the in-arena measurements look ok.
824 0 : size_t totalArenaSize = rtStats->zTotals.gcHeapArenaAdmin +
825 0 : rtStats->zTotals.unusedGCThings.totalSize() +
826 0 : rtStats->gcHeapGCThings;
827 0 : MOZ_ASSERT(totalArenaSize % gc::ArenaSize == 0);
828 : #endif
829 :
830 0 : for (CompartmentsIter comp(rt, WithAtoms); !comp.done(); comp.next())
831 0 : comp->nullCompartmentStats();
832 :
833 : size_t numDirtyChunks =
834 0 : (rtStats->gcHeapChunkTotal - rtStats->gcHeapUnusedChunks) / gc::ChunkSize;
835 : size_t perChunkAdmin =
836 0 : sizeof(gc::Chunk) - (sizeof(gc::Arena) * gc::ArenasPerChunk);
837 0 : rtStats->gcHeapChunkAdmin = numDirtyChunks * perChunkAdmin;
838 :
839 : // |gcHeapUnusedArenas| is the only thing left. Compute it in terms of
840 : // all the others. See the comment in RuntimeStats for explanation.
841 0 : rtStats->gcHeapUnusedArenas = rtStats->gcHeapChunkTotal -
842 0 : rtStats->gcHeapDecommittedArenas -
843 0 : rtStats->gcHeapUnusedChunks -
844 0 : rtStats->zTotals.unusedGCThings.totalSize() -
845 0 : rtStats->gcHeapChunkAdmin -
846 0 : rtStats->zTotals.gcHeapArenaAdmin -
847 0 : rtStats->gcHeapGCThings;
848 0 : return true;
849 : }
850 :
851 : JS_PUBLIC_API(bool)
852 0 : JS::CollectRuntimeStats(JSContext* cx, RuntimeStats *rtStats, ObjectPrivateVisitor *opv,
853 : bool anonymize)
854 : {
855 0 : return CollectRuntimeStatsHelper(cx, rtStats, opv, anonymize, StatsCellCallback<FineGrained>);
856 : }
857 :
858 : JS_PUBLIC_API(size_t)
859 0 : JS::SystemCompartmentCount(JSContext* cx)
860 : {
861 0 : size_t n = 0;
862 0 : for (CompartmentsIter comp(cx->runtime(), WithAtoms); !comp.done(); comp.next()) {
863 0 : if (comp->isSystem())
864 0 : ++n;
865 : }
866 0 : return n;
867 : }
868 :
869 : JS_PUBLIC_API(size_t)
870 0 : JS::UserCompartmentCount(JSContext* cx)
871 : {
872 0 : size_t n = 0;
873 0 : for (CompartmentsIter comp(cx->runtime(), WithAtoms); !comp.done(); comp.next()) {
874 0 : if (!comp->isSystem())
875 0 : ++n;
876 : }
877 0 : return n;
878 : }
879 :
880 : JS_PUBLIC_API(size_t)
881 0 : JS::PeakSizeOfTemporary(const JSContext* cx)
882 : {
883 0 : return cx->tempLifoAlloc().peakSizeOfExcludingThis();
884 : }
885 :
886 : JS_PUBLIC_API(void)
887 0 : JS::CollectTraceLoggerStateStats(RuntimeStats* rtStats)
888 : {
889 : #ifdef JS_TRACE_LOGGING
890 0 : rtStats->runtime.tracelogger += SizeOfTraceLogState(rtStats->mallocSizeOf_);
891 0 : rtStats->runtime.tracelogger += SizeOfTraceLogGraphState(rtStats->mallocSizeOf_);
892 : #endif
893 0 : }
894 :
895 : namespace JS {
896 :
897 0 : class SimpleJSRuntimeStats : public JS::RuntimeStats
898 : {
899 : public:
900 0 : explicit SimpleJSRuntimeStats(MallocSizeOf mallocSizeOf)
901 0 : : JS::RuntimeStats(mallocSizeOf)
902 0 : {}
903 :
904 0 : virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats)
905 : override
906 0 : {}
907 :
908 0 : virtual void initExtraCompartmentStats(
909 : JSCompartment* c, JS::CompartmentStats* cStats) override
910 0 : {}
911 : };
912 :
913 : JS_PUBLIC_API(bool)
914 0 : AddSizeOfTab(JSContext* cx, HandleObject obj, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor* opv,
915 : TabSizes* sizes)
916 : {
917 0 : SimpleJSRuntimeStats rtStats(mallocSizeOf);
918 :
919 0 : JS::Zone* zone = GetObjectZone(obj);
920 :
921 0 : if (!rtStats.compartmentStatsVector.reserve(zone->compartments().length()))
922 0 : return false;
923 :
924 0 : if (!rtStats.zoneStatsVector.reserve(1))
925 0 : return false;
926 :
927 : // Take the per-compartment measurements. No need to anonymize because
928 : // these measurements will be aggregated.
929 0 : StatsClosure closure(&rtStats, opv, /* anonymize = */ false);
930 0 : if (!closure.init())
931 0 : return false;
932 : IterateHeapUnbarrieredForZone(cx, zone, &closure,
933 : StatsZoneCallback,
934 : StatsCompartmentCallback,
935 : StatsArenaCallback,
936 0 : StatsCellCallback<CoarseGrained>);
937 :
938 0 : MOZ_ASSERT(rtStats.zoneStatsVector.length() == 1);
939 0 : rtStats.zTotals.addSizes(rtStats.zoneStatsVector[0]);
940 :
941 0 : for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++)
942 0 : rtStats.cTotals.addSizes(rtStats.compartmentStatsVector[i]);
943 :
944 0 : for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
945 0 : comp->nullCompartmentStats();
946 :
947 0 : rtStats.zTotals.addToTabSizes(sizes);
948 0 : rtStats.cTotals.addToTabSizes(sizes);
949 :
950 0 : return true;
951 : }
952 :
953 : JS_PUBLIC_API(bool)
954 0 : AddServoSizeOf(JSContext* cx, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor* opv,
955 : ServoSizes* sizes)
956 : {
957 0 : SimpleJSRuntimeStats rtStats(mallocSizeOf);
958 :
959 : // No need to anonymize because the results will be aggregated.
960 0 : if (!CollectRuntimeStatsHelper(cx, &rtStats, opv, /* anonymize = */ false,
961 : StatsCellCallback<CoarseGrained>))
962 0 : return false;
963 :
964 : #ifdef DEBUG
965 0 : size_t gcHeapTotalOriginal = sizes->gcHeapUsed +
966 0 : sizes->gcHeapUnused +
967 0 : sizes->gcHeapAdmin +
968 0 : sizes->gcHeapDecommitted;
969 : #endif
970 :
971 0 : rtStats.addToServoSizes(sizes);
972 0 : rtStats.zTotals.addToServoSizes(sizes);
973 0 : rtStats.cTotals.addToServoSizes(sizes);
974 :
975 : #ifdef DEBUG
976 0 : size_t gcHeapTotal = sizes->gcHeapUsed +
977 0 : sizes->gcHeapUnused +
978 0 : sizes->gcHeapAdmin +
979 0 : sizes->gcHeapDecommitted;
980 0 : MOZ_ASSERT(rtStats.gcHeapChunkTotal == gcHeapTotal - gcHeapTotalOriginal);
981 : #endif
982 :
983 0 : return true;
984 : }
985 :
986 : } // namespace JS
|