1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/trace_event/malloc_dump_provider.h"
6
7 #include <stddef.h>
8
9 #include <unordered_map>
10
11 #include "base/allocator/allocator_extension.h"
12 #include "base/allocator/buildflags.h"
13 #include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
14 #include "base/allocator/partition_allocator/partition_alloc_config.h"
15 #include "base/allocator/partition_allocator/partition_bucket_lookup.h"
16 #include "base/debug/profiler.h"
17 #include "base/format_macros.h"
18 #include "base/memory/nonscannable_memory.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/trace_event/process_memory_dump.h"
23 #include "base/trace_event/traced_value.h"
24 #include "build/build_config.h"
25
26 #if BUILDFLAG(IS_APPLE)
27 #include <malloc/malloc.h>
28 #else
29 #include <malloc.h>
30 #endif
31 #if BUILDFLAG(IS_WIN)
32 #include <windows.h>
33 #endif
34
35 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
36 #include <features.h>
37 #endif
38
39 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
40 #include "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_partition_alloc.h"
41 #endif
42
43 #if PA_CONFIG(THREAD_CACHE_ALLOC_STATS)
44 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
45 #endif
46
47 namespace base {
48 namespace trace_event {
49
50 namespace {
51 #if BUILDFLAG(IS_WIN)
52 // A structure containing some information about a given heap.
53 struct WinHeapInfo {
54 size_t committed_size;
55 size_t uncommitted_size;
56 size_t allocated_size;
57 size_t block_count;
58 };
59
60 // NOTE: crbug.com/665516
61 // Unfortunately, there is no safe way to collect information from secondary
62 // heaps due to limitations and racy nature of this piece of WinAPI.
WinHeapMemoryDumpImpl(WinHeapInfo * crt_heap_info)63 void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) {
64 // Iterate through whichever heap our CRT is using.
65 HANDLE crt_heap = reinterpret_cast<HANDLE>(_get_heap_handle());
66 ::HeapLock(crt_heap);
67 PROCESS_HEAP_ENTRY heap_entry;
68 heap_entry.lpData = nullptr;
69 // Walk over all the entries in the main heap.
70 while (::HeapWalk(crt_heap, &heap_entry) != FALSE) {
71 if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
72 crt_heap_info->allocated_size += heap_entry.cbData;
73 crt_heap_info->block_count++;
74 } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
75 crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize;
76 crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
77 }
78 }
79 CHECK(::HeapUnlock(crt_heap) == TRUE);
80 }
81
ReportWinHeapStats(MemoryDumpLevelOfDetail level_of_detail,ProcessMemoryDump * pmd,size_t * total_virtual_size,size_t * resident_size,size_t * allocated_objects_size,size_t * allocated_objects_count)82 void ReportWinHeapStats(MemoryDumpLevelOfDetail level_of_detail,
83 ProcessMemoryDump* pmd,
84 size_t* total_virtual_size,
85 size_t* resident_size,
86 size_t* allocated_objects_size,
87 size_t* allocated_objects_count) {
88 // This is too expensive on Windows, crbug.com/780735.
89 if (level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
90 WinHeapInfo main_heap_info = {};
91 WinHeapMemoryDumpImpl(&main_heap_info);
92 *total_virtual_size +=
93 main_heap_info.committed_size + main_heap_info.uncommitted_size;
94 // Resident size is approximated with committed heap size. Note that it is
95 // possible to do this with better accuracy on windows by intersecting the
96 // working set with the virtual memory ranges occuipied by the heap. It's
97 // not clear that this is worth it, as it's fairly expensive to do.
98 *resident_size += main_heap_info.committed_size;
99 *allocated_objects_size += main_heap_info.allocated_size;
100 *allocated_objects_count += main_heap_info.block_count;
101
102 if (pmd) {
103 MemoryAllocatorDump* win_heap_dump =
104 pmd->CreateAllocatorDump("malloc/win_heap");
105 win_heap_dump->AddScalar(MemoryAllocatorDump::kNameSize,
106 MemoryAllocatorDump::kUnitsBytes,
107 main_heap_info.allocated_size);
108 }
109 }
110 }
111 #endif // BUILDFLAG(IS_WIN)
112
113 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
ReportPartitionAllocStats(ProcessMemoryDump * pmd,MemoryDumpLevelOfDetail level_of_detail,size_t * total_virtual_size,size_t * resident_size,size_t * allocated_objects_size,size_t * allocated_objects_count,uint64_t * syscall_count,size_t * cumulative_brp_quarantined_size,size_t * cumulative_brp_quarantined_count)114 void ReportPartitionAllocStats(ProcessMemoryDump* pmd,
115 MemoryDumpLevelOfDetail level_of_detail,
116 size_t* total_virtual_size,
117 size_t* resident_size,
118 size_t* allocated_objects_size,
119 size_t* allocated_objects_count,
120 uint64_t* syscall_count,
121 size_t* cumulative_brp_quarantined_size,
122 size_t* cumulative_brp_quarantined_count) {
123 MemoryDumpPartitionStatsDumper partition_stats_dumper("malloc", pmd,
124 level_of_detail);
125 bool is_light_dump = level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND;
126
127 auto* allocator = allocator_shim::internal::PartitionAllocMalloc::Allocator();
128 allocator->DumpStats("allocator", is_light_dump, &partition_stats_dumper);
129
130 auto* original_allocator =
131 allocator_shim::internal::PartitionAllocMalloc::OriginalAllocator();
132 if (original_allocator) {
133 original_allocator->DumpStats("original", is_light_dump,
134 &partition_stats_dumper);
135 }
136 auto* aligned_allocator =
137 allocator_shim::internal::PartitionAllocMalloc::AlignedAllocator();
138 if (aligned_allocator != allocator) {
139 aligned_allocator->DumpStats("aligned", is_light_dump,
140 &partition_stats_dumper);
141 }
142 auto& nonscannable_allocator = internal::NonScannableAllocator::Instance();
143 if (auto* root = nonscannable_allocator.root())
144 root->DumpStats("nonscannable", is_light_dump, &partition_stats_dumper);
145 auto& nonquarantinable_allocator =
146 internal::NonQuarantinableAllocator::Instance();
147 if (auto* root = nonquarantinable_allocator.root())
148 root->DumpStats("nonquarantinable", is_light_dump, &partition_stats_dumper);
149
150 *total_virtual_size += partition_stats_dumper.total_resident_bytes();
151 *resident_size += partition_stats_dumper.total_resident_bytes();
152 *allocated_objects_size += partition_stats_dumper.total_active_bytes();
153 *allocated_objects_count += partition_stats_dumper.total_active_count();
154 *syscall_count += partition_stats_dumper.syscall_count();
155 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
156 *cumulative_brp_quarantined_size +=
157 partition_stats_dumper.cumulative_brp_quarantined_bytes();
158 *cumulative_brp_quarantined_count +=
159 partition_stats_dumper.cumulative_brp_quarantined_count();
160 #endif // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
161 }
162 #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
163
164 #if !BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(IS_APPLE)
ReportAppleAllocStats(size_t * total_virtual_size,size_t * resident_size,size_t * allocated_objects_size)165 void ReportAppleAllocStats(size_t* total_virtual_size,
166 size_t* resident_size,
167 size_t* allocated_objects_size) {
168 malloc_statistics_t stats = {0};
169 malloc_zone_statistics(nullptr, &stats);
170 *total_virtual_size += stats.size_allocated;
171 *allocated_objects_size += stats.size_in_use;
172
173 // Resident size is approximated pretty well by stats.max_size_in_use.
174 // However, on macOS, freed blocks are both resident and reusable, which is
175 // semantically equivalent to deallocated. The implementation of libmalloc
176 // will also only hold a fixed number of freed regions before actually
177 // starting to deallocate them, so stats.max_size_in_use is also not
178 // representative of the peak size. As a result, stats.max_size_in_use is
179 // typically somewhere between actually resident [non-reusable] pages, and
180 // peak size. This is not very useful, so we just use stats.size_in_use for
181 // resident_size, even though it's an underestimate and fails to account for
182 // fragmentation. See
183 // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1.
184 *resident_size += stats.size_in_use;
185 }
186 #endif
187
188 #if (BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(IS_ANDROID)) || \
189 (!BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && !BUILDFLAG(IS_WIN) && \
190 !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_FUCHSIA))
ReportMallinfoStats(ProcessMemoryDump * pmd,size_t * total_virtual_size,size_t * resident_size,size_t * allocated_objects_size,size_t * allocated_objects_count)191 void ReportMallinfoStats(ProcessMemoryDump* pmd,
192 size_t* total_virtual_size,
193 size_t* resident_size,
194 size_t* allocated_objects_size,
195 size_t* allocated_objects_count) {
196 #if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
197 #if __GLIBC_PREREQ(2, 33)
198 #define MALLINFO2_FOUND_IN_LIBC
199 struct mallinfo2 info = mallinfo2();
200 #endif
201 #endif // defined(__GLIBC__) && defined(__GLIBC_PREREQ)
202 #if !defined(MALLINFO2_FOUND_IN_LIBC)
203 struct mallinfo info = mallinfo();
204 #endif
205 #undef MALLINFO2_FOUND_IN_LIBC
206 // In case of Android's jemalloc |arena| is 0 and the outer pages size is
207 // reported by |hblkhd|. In case of dlmalloc the total is given by
208 // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
209 *total_virtual_size += checked_cast<size_t>(info.arena + info.hblkhd);
210 size_t total_allocated_size = checked_cast<size_t>(info.uordblks);
211 *resident_size += total_allocated_size;
212
213 // Total allocated space is given by |uordblks|.
214 *allocated_objects_size += total_allocated_size;
215
216 if (pmd) {
217 MemoryAllocatorDump* sys_alloc_dump =
218 pmd->CreateAllocatorDump("malloc/sys_malloc");
219 sys_alloc_dump->AddScalar(MemoryAllocatorDump::kNameSize,
220 MemoryAllocatorDump::kUnitsBytes,
221 total_allocated_size);
222 }
223 }
224 #endif
225
226 #if BUILDFLAG(USE_PARTITION_ALLOC)
ReportPartitionAllocThreadCacheStats(ProcessMemoryDump * pmd,MemoryAllocatorDump * dump,const partition_alloc::ThreadCacheStats & stats,const std::string & metrics_suffix,bool detailed)227 void ReportPartitionAllocThreadCacheStats(
228 ProcessMemoryDump* pmd,
229 MemoryAllocatorDump* dump,
230 const partition_alloc::ThreadCacheStats& stats,
231 const std::string& metrics_suffix,
232 bool detailed) {
233 dump->AddScalar("alloc_count", MemoryAllocatorDump::kTypeScalar,
234 stats.alloc_count);
235 dump->AddScalar("alloc_hits", MemoryAllocatorDump::kTypeScalar,
236 stats.alloc_hits);
237 dump->AddScalar("alloc_misses", MemoryAllocatorDump::kTypeScalar,
238 stats.alloc_misses);
239
240 dump->AddScalar("alloc_miss_empty", MemoryAllocatorDump::kTypeScalar,
241 stats.alloc_miss_empty);
242 dump->AddScalar("alloc_miss_too_large", MemoryAllocatorDump::kTypeScalar,
243 stats.alloc_miss_too_large);
244
245 dump->AddScalar("cache_fill_count", MemoryAllocatorDump::kTypeScalar,
246 stats.cache_fill_count);
247 dump->AddScalar("cache_fill_hits", MemoryAllocatorDump::kTypeScalar,
248 stats.cache_fill_hits);
249 dump->AddScalar("cache_fill_misses", MemoryAllocatorDump::kTypeScalar,
250 stats.cache_fill_misses);
251
252 dump->AddScalar("batch_fill_count", MemoryAllocatorDump::kTypeScalar,
253 stats.batch_fill_count);
254
255 dump->AddScalar(MemoryAllocatorDump::kNameSize,
256 MemoryAllocatorDump::kUnitsBytes, stats.bucket_total_memory);
257 dump->AddScalar("metadata_overhead", MemoryAllocatorDump::kUnitsBytes,
258 stats.metadata_overhead);
259
260 if (stats.alloc_count) {
261 int hit_rate_percent =
262 static_cast<int>((100 * stats.alloc_hits) / stats.alloc_count);
263 base::UmaHistogramPercentage(
264 "Memory.PartitionAlloc.ThreadCache.HitRate" + metrics_suffix,
265 hit_rate_percent);
266 int batch_fill_rate_percent =
267 static_cast<int>((100 * stats.batch_fill_count) / stats.alloc_count);
268 base::UmaHistogramPercentage(
269 "Memory.PartitionAlloc.ThreadCache.BatchFillRate" + metrics_suffix,
270 batch_fill_rate_percent);
271
272 #if PA_CONFIG(THREAD_CACHE_ALLOC_STATS)
273 if (detailed) {
274 partition_alloc::internal::BucketIndexLookup lookup{};
275 std::string name = dump->absolute_name();
276 for (size_t i = 0; i < partition_alloc::kNumBuckets; i++) {
277 size_t bucket_size = lookup.bucket_sizes()[i];
278 if (bucket_size == partition_alloc::kInvalidBucketSize)
279 continue;
280 // Covers all normal buckets, that is up to ~1MiB, so 7 digits.
281 std::string dump_name =
282 base::StringPrintf("%s/buckets_alloc/%07d", name.c_str(),
283 static_cast<int>(bucket_size));
284 auto* buckets_alloc_dump = pmd->CreateAllocatorDump(dump_name);
285 buckets_alloc_dump->AddScalar("count",
286 MemoryAllocatorDump::kUnitsObjects,
287 stats.allocs_per_bucket_[i]);
288 }
289 }
290 #endif // PA_CONFIG(THREAD_CACHE_ALLOC_STATS)
291 }
292 }
293 #endif // BUILDFLAG(USE_PARTITION_ALLOC)
294
295 } // namespace
296
297 // static
298 const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
299
300 // static
GetInstance()301 MallocDumpProvider* MallocDumpProvider::GetInstance() {
302 return Singleton<MallocDumpProvider,
303 LeakySingletonTraits<MallocDumpProvider>>::get();
304 }
305
306 MallocDumpProvider::MallocDumpProvider() = default;
307 MallocDumpProvider::~MallocDumpProvider() = default;
308
309 // Called at trace dump point time. Creates a snapshot the memory counters for
310 // the current process.
OnMemoryDump(const MemoryDumpArgs & args,ProcessMemoryDump * pmd)311 bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
312 ProcessMemoryDump* pmd) {
313 {
314 base::AutoLock auto_lock(emit_metrics_on_memory_dump_lock_);
315 if (!emit_metrics_on_memory_dump_) {
316 return true;
317 }
318 }
319
320 size_t total_virtual_size = 0;
321 size_t resident_size = 0;
322 size_t allocated_objects_size = 0;
323 size_t allocated_objects_count = 0;
324 uint64_t syscall_count = 0;
325 size_t cumulative_brp_quarantined_size = 0;
326 size_t cumulative_brp_quarantined_count = 0;
327 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
328 uint64_t pa_only_resident_size;
329 uint64_t pa_only_allocated_objects_size;
330 #endif
331
332 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
333 ReportPartitionAllocStats(
334 pmd, args.level_of_detail, &total_virtual_size, &resident_size,
335 &allocated_objects_size, &allocated_objects_count, &syscall_count,
336 &cumulative_brp_quarantined_size, &cumulative_brp_quarantined_count);
337
338 pa_only_resident_size = resident_size;
339 pa_only_allocated_objects_size = allocated_objects_size;
340
341 // Even when PartitionAlloc is used, WinHeap / System malloc is still used as
342 // well, report its statistics.
343 #if BUILDFLAG(IS_ANDROID)
344 ReportMallinfoStats(pmd, &total_virtual_size, &resident_size,
345 &allocated_objects_size, &allocated_objects_count);
346 #elif BUILDFLAG(IS_WIN)
347 ReportWinHeapStats(args.level_of_detail, pmd, &total_virtual_size,
348 &resident_size, &allocated_objects_size,
349 &allocated_objects_count);
350 #endif // BUILDFLAG(IS_ANDROID), BUILDFLAG(IS_WIN)
351
352 #elif BUILDFLAG(IS_APPLE)
353 ReportAppleAllocStats(&total_virtual_size, &resident_size,
354 &allocated_objects_size);
355 #elif BUILDFLAG(IS_WIN)
356 ReportWinHeapStats(args.level_of_detail, nullptr, &total_virtual_size,
357 &resident_size, &allocated_objects_size,
358 &allocated_objects_count);
359 #elif BUILDFLAG(IS_FUCHSIA)
360 // TODO(fuchsia): Port, see https://crbug.com/706592.
361 #else
362 ReportMallinfoStats(/*pmd=*/nullptr, &total_virtual_size, &resident_size,
363 &allocated_objects_size, &allocated_objects_count);
364 #endif
365
366 MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
367 outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
368 total_virtual_size);
369 outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
370 MemoryAllocatorDump::kUnitsBytes, resident_size);
371
372 MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
373 inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
374 MemoryAllocatorDump::kUnitsBytes,
375 allocated_objects_size);
376 if (allocated_objects_count != 0) {
377 inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
378 MemoryAllocatorDump::kUnitsObjects,
379 allocated_objects_count);
380 }
381
382 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
383 base::trace_event::MemoryAllocatorDump* partitions_dump =
384 pmd->CreateAllocatorDump("malloc/partitions");
385 pmd->AddOwnershipEdge(inner_dump->guid(), partitions_dump->guid());
386 #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
387
388 int64_t waste = static_cast<int64_t>(resident_size - allocated_objects_size);
389
390 // With PartitionAlloc, reported size under malloc/partitions is the resident
391 // size, so it already includes fragmentation. Meaning that "malloc/"'s size
392 // would double-count fragmentation if we report it under
393 // "malloc/metadata_fragmentation_caches" as well.
394 //
395 // Still report waste, as on some platforms, PartitionAlloc doesn't capture
396 // all of malloc()'s memory footprint.
397 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
398 int64_t pa_waste = static_cast<int64_t>(pa_only_resident_size -
399 pa_only_allocated_objects_size);
400 waste -= pa_waste;
401 #endif
402
403 if (waste > 0) {
404 // Explicitly specify why is extra memory resident. In mac and ios it
405 // accounts for the fragmentation and metadata.
406 MemoryAllocatorDump* other_dump =
407 pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
408 other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
409 MemoryAllocatorDump::kUnitsBytes,
410 static_cast<uint64_t>(waste));
411 }
412
413 ReportPerMinuteStats(syscall_count, cumulative_brp_quarantined_size,
414 cumulative_brp_quarantined_count, outer_dump,
415 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
416 partitions_dump
417 #else
418 nullptr
419 #endif
420 );
421
422 return true;
423 }
424
ReportPerMinuteStats(uint64_t syscall_count,size_t cumulative_brp_quarantined_bytes,size_t cumulative_brp_quarantined_count,MemoryAllocatorDump * malloc_dump,MemoryAllocatorDump * partition_alloc_dump)425 void MallocDumpProvider::ReportPerMinuteStats(
426 uint64_t syscall_count,
427 size_t cumulative_brp_quarantined_bytes,
428 size_t cumulative_brp_quarantined_count,
429 MemoryAllocatorDump* malloc_dump,
430 MemoryAllocatorDump* partition_alloc_dump) {
431 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
432 uint64_t new_syscalls = syscall_count - last_syscall_count_;
433 size_t new_brp_quarantined_bytes =
434 cumulative_brp_quarantined_bytes - last_cumulative_brp_quarantined_bytes_;
435 size_t new_brp_quarantined_count =
436 cumulative_brp_quarantined_count - last_cumulative_brp_quarantined_count_;
437 base::TimeDelta time_since_last_dump =
438 base::TimeTicks::Now() - last_memory_dump_time_;
439 uint64_t syscalls_per_minute = static_cast<uint64_t>(
440 (60 * new_syscalls) / time_since_last_dump.InSecondsF());
441 malloc_dump->AddScalar("syscalls_per_minute", "count", syscalls_per_minute);
442 if (partition_alloc_dump) {
443 size_t brp_quarantined_bytes_per_minute =
444 (60 * new_brp_quarantined_bytes) / time_since_last_dump.InSecondsF();
445 size_t brp_quarantined_count_per_minute =
446 (60 * new_brp_quarantined_count) / time_since_last_dump.InSecondsF();
447 partition_alloc_dump->AddScalar("brp_quarantined_bytes_per_minute",
448 MemoryAllocatorDump::kUnitsBytes,
449 brp_quarantined_bytes_per_minute);
450 partition_alloc_dump->AddScalar("brp_quarantined_count_per_minute",
451 MemoryAllocatorDump::kNameObjectCount,
452 brp_quarantined_count_per_minute);
453 }
454
455 last_memory_dump_time_ = base::TimeTicks::Now();
456 last_syscall_count_ = syscall_count;
457 last_cumulative_brp_quarantined_bytes_ = cumulative_brp_quarantined_bytes;
458 last_cumulative_brp_quarantined_count_ = cumulative_brp_quarantined_count;
459 #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
460 }
461
462 #if BUILDFLAG(USE_PARTITION_ALLOC)
463 // static
464 const char* MemoryDumpPartitionStatsDumper::kPartitionsDumpName = "partitions";
465
GetPartitionDumpName(const char * root_name,const char * partition_name)466 std::string GetPartitionDumpName(const char* root_name,
467 const char* partition_name) {
468 return base::StringPrintf("%s/%s/%s", root_name,
469 MemoryDumpPartitionStatsDumper::kPartitionsDumpName,
470 partition_name);
471 }
472
MemoryDumpPartitionStatsDumper(const char * root_name,ProcessMemoryDump * memory_dump,MemoryDumpLevelOfDetail level_of_detail)473 MemoryDumpPartitionStatsDumper::MemoryDumpPartitionStatsDumper(
474 const char* root_name,
475 ProcessMemoryDump* memory_dump,
476 MemoryDumpLevelOfDetail level_of_detail)
477 : root_name_(root_name),
478 memory_dump_(memory_dump),
479 detailed_(level_of_detail != MemoryDumpLevelOfDetail::BACKGROUND) {}
480
PartitionDumpTotals(const char * partition_name,const partition_alloc::PartitionMemoryStats * memory_stats)481 void MemoryDumpPartitionStatsDumper::PartitionDumpTotals(
482 const char* partition_name,
483 const partition_alloc::PartitionMemoryStats* memory_stats) {
484 total_mmapped_bytes_ += memory_stats->total_mmapped_bytes;
485 total_resident_bytes_ += memory_stats->total_resident_bytes;
486 total_active_bytes_ += memory_stats->total_active_bytes;
487 total_active_count_ += memory_stats->total_active_count;
488 syscall_count_ += memory_stats->syscall_count;
489 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
490 cumulative_brp_quarantined_bytes_ +=
491 memory_stats->cumulative_brp_quarantined_bytes;
492 cumulative_brp_quarantined_count_ +=
493 memory_stats->cumulative_brp_quarantined_count;
494 #endif // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
495
496 std::string dump_name = GetPartitionDumpName(root_name_, partition_name);
497 MemoryAllocatorDump* allocator_dump =
498 memory_dump_->CreateAllocatorDump(dump_name);
499
500 auto total_committed_bytes = memory_stats->total_committed_bytes;
501 auto total_active_bytes = memory_stats->total_active_bytes;
502 size_t wasted = total_committed_bytes - total_active_bytes;
503 DCHECK_GE(total_committed_bytes, total_active_bytes);
504 size_t fragmentation =
505 total_committed_bytes == 0 ? 0 : 100 * wasted / total_committed_bytes;
506
507 allocator_dump->AddScalar(MemoryAllocatorDump::kNameSize,
508 MemoryAllocatorDump::kUnitsBytes,
509 memory_stats->total_resident_bytes);
510 allocator_dump->AddScalar("allocated_objects_size",
511 MemoryAllocatorDump::kUnitsBytes,
512 memory_stats->total_active_bytes);
513 allocator_dump->AddScalar("allocated_objects_count", "count",
514 memory_stats->total_active_count);
515 allocator_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
516 memory_stats->total_mmapped_bytes);
517 allocator_dump->AddScalar("virtual_committed_size",
518 MemoryAllocatorDump::kUnitsBytes,
519 memory_stats->total_committed_bytes);
520 allocator_dump->AddScalar("max_committed_size",
521 MemoryAllocatorDump::kUnitsBytes,
522 memory_stats->max_committed_bytes);
523 allocator_dump->AddScalar("allocated_size", MemoryAllocatorDump::kUnitsBytes,
524 memory_stats->total_allocated_bytes);
525 allocator_dump->AddScalar("max_allocated_size",
526 MemoryAllocatorDump::kUnitsBytes,
527 memory_stats->max_allocated_bytes);
528 allocator_dump->AddScalar("decommittable_size",
529 MemoryAllocatorDump::kUnitsBytes,
530 memory_stats->total_decommittable_bytes);
531 allocator_dump->AddScalar("discardable_size",
532 MemoryAllocatorDump::kUnitsBytes,
533 memory_stats->total_discardable_bytes);
534 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
535 allocator_dump->AddScalar("brp_quarantined_size",
536 MemoryAllocatorDump::kUnitsBytes,
537 memory_stats->total_brp_quarantined_bytes);
538 allocator_dump->AddScalar("brp_quarantined_count", "count",
539 memory_stats->total_brp_quarantined_count);
540 #endif // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
541 allocator_dump->AddScalar("syscall_count", "count",
542 memory_stats->syscall_count);
543 allocator_dump->AddScalar("syscall_total_time_ms", "ms",
544 memory_stats->syscall_total_time_ns / 1e6);
545 allocator_dump->AddScalar("fragmentation", "percent", fragmentation);
546 allocator_dump->AddScalar("wasted", MemoryAllocatorDump::kUnitsBytes, wasted);
547
548 if (memory_stats->has_thread_cache) {
549 const auto& thread_cache_stats = memory_stats->current_thread_cache_stats;
550 auto* thread_cache_dump = memory_dump_->CreateAllocatorDump(
551 dump_name + "/thread_cache/main_thread");
552 ReportPartitionAllocThreadCacheStats(memory_dump_, thread_cache_dump,
553 thread_cache_stats, ".MainThread",
554 detailed_);
555
556 const auto& all_thread_caches_stats = memory_stats->all_thread_caches_stats;
557 auto* all_thread_caches_dump =
558 memory_dump_->CreateAllocatorDump(dump_name + "/thread_cache");
559 ReportPartitionAllocThreadCacheStats(memory_dump_, all_thread_caches_dump,
560 all_thread_caches_stats, "",
561 detailed_);
562 }
563 }
564
PartitionsDumpBucketStats(const char * partition_name,const partition_alloc::PartitionBucketMemoryStats * memory_stats)565 void MemoryDumpPartitionStatsDumper::PartitionsDumpBucketStats(
566 const char* partition_name,
567 const partition_alloc::PartitionBucketMemoryStats* memory_stats) {
568 DCHECK(memory_stats->is_valid);
569 std::string dump_name = GetPartitionDumpName(root_name_, partition_name);
570 if (memory_stats->is_direct_map) {
571 dump_name.append(base::StringPrintf("/buckets/directMap_%" PRIu64, ++uid_));
572 } else {
573 // Normal buckets go up to ~1MiB, 7 digits.
574 dump_name.append(base::StringPrintf("/buckets/bucket_%07" PRIu32,
575 memory_stats->bucket_slot_size));
576 }
577
578 MemoryAllocatorDump* allocator_dump =
579 memory_dump_->CreateAllocatorDump(dump_name);
580 allocator_dump->AddScalar(MemoryAllocatorDump::kNameSize,
581 MemoryAllocatorDump::kUnitsBytes,
582 memory_stats->resident_bytes);
583 allocator_dump->AddScalar("allocated_objects_size",
584 MemoryAllocatorDump::kUnitsBytes,
585 memory_stats->active_bytes);
586 allocator_dump->AddScalar("slot_size", MemoryAllocatorDump::kUnitsBytes,
587 memory_stats->bucket_slot_size);
588 allocator_dump->AddScalar("decommittable_size",
589 MemoryAllocatorDump::kUnitsBytes,
590 memory_stats->decommittable_bytes);
591 allocator_dump->AddScalar("discardable_size",
592 MemoryAllocatorDump::kUnitsBytes,
593 memory_stats->discardable_bytes);
594 // TODO(bartekn): Rename the scalar names.
595 allocator_dump->AddScalar("total_slot_span_size",
596 MemoryAllocatorDump::kUnitsBytes,
597 memory_stats->allocated_slot_span_size);
598 allocator_dump->AddScalar("active_slot_spans",
599 MemoryAllocatorDump::kUnitsObjects,
600 memory_stats->num_active_slot_spans);
601 allocator_dump->AddScalar("full_slot_spans",
602 MemoryAllocatorDump::kUnitsObjects,
603 memory_stats->num_full_slot_spans);
604 allocator_dump->AddScalar("empty_slot_spans",
605 MemoryAllocatorDump::kUnitsObjects,
606 memory_stats->num_empty_slot_spans);
607 allocator_dump->AddScalar("decommitted_slot_spans",
608 MemoryAllocatorDump::kUnitsObjects,
609 memory_stats->num_decommitted_slot_spans);
610 }
611 #endif // BUILDFLAG(USE_PARTITION_ALLOC)
612
613 } // namespace trace_event
614 } // namespace base
615