1 // Copyright 2015 The Chromium Authors. All rights reserved.
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 "base/allocator/allocator_extension.h"
10 #include "base/allocator/allocator_shim.h"
11 #include "base/allocator/features.h"
12 #include "base/trace_event/heap_profiler_allocation_context.h"
13 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
14 #include "base/trace_event/heap_profiler_allocation_register.h"
15 #include "base/trace_event/heap_profiler_heap_dump_writer.h"
16 #include "base/trace_event/process_memory_dump.h"
17 #include "base/trace_event/trace_event_argument.h"
18 #include "build/build_config.h"
19
20 #if defined(OS_MACOSX)
21 #include <malloc/malloc.h>
22 #else
23 #include <malloc.h>
24 #endif
25
26 namespace base {
27 namespace trace_event {
28
29 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
30 namespace {
31
32 using allocator::AllocatorDispatch;
33
HookAlloc(const AllocatorDispatch * self,size_t size)34 void* HookAlloc(const AllocatorDispatch* self, size_t size) {
35 const AllocatorDispatch* const next = self->next;
36 void* ptr = next->alloc_function(next, size);
37 if (ptr)
38 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
39 return ptr;
40 }
41
HookZeroInitAlloc(const AllocatorDispatch * self,size_t n,size_t size)42 void* HookZeroInitAlloc(const AllocatorDispatch* self, size_t n, size_t size) {
43 const AllocatorDispatch* const next = self->next;
44 void* ptr = next->alloc_zero_initialized_function(next, n, size);
45 if (ptr)
46 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, n * size);
47 return ptr;
48 }
49
HookllocAligned(const AllocatorDispatch * self,size_t alignment,size_t size)50 void* HookllocAligned(const AllocatorDispatch* self,
51 size_t alignment,
52 size_t size) {
53 const AllocatorDispatch* const next = self->next;
54 void* ptr = next->alloc_aligned_function(next, alignment, size);
55 if (ptr)
56 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
57 return ptr;
58 }
59
HookRealloc(const AllocatorDispatch * self,void * address,size_t size)60 void* HookRealloc(const AllocatorDispatch* self, void* address, size_t size) {
61 const AllocatorDispatch* const next = self->next;
62 void* ptr = next->realloc_function(next, address, size);
63 MallocDumpProvider::GetInstance()->RemoveAllocation(address);
64 if (size > 0) // realloc(size == 0) means free().
65 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
66 return ptr;
67 }
68
HookFree(const AllocatorDispatch * self,void * address)69 void HookFree(const AllocatorDispatch* self, void* address) {
70 if (address)
71 MallocDumpProvider::GetInstance()->RemoveAllocation(address);
72 const AllocatorDispatch* const next = self->next;
73 next->free_function(next, address);
74 }
75
76 AllocatorDispatch g_allocator_hooks = {
77 &HookAlloc, /* alloc_function */
78 &HookZeroInitAlloc, /* alloc_zero_initialized_function */
79 &HookllocAligned, /* alloc_aligned_function */
80 &HookRealloc, /* realloc_function */
81 &HookFree, /* free_function */
82 nullptr, /* next */
83 };
84
85 } // namespace
86 #endif // BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
87
88 // static
89 const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
90
91 // static
GetInstance()92 MallocDumpProvider* MallocDumpProvider::GetInstance() {
93 return Singleton<MallocDumpProvider,
94 LeakySingletonTraits<MallocDumpProvider>>::get();
95 }
96
MallocDumpProvider()97 MallocDumpProvider::MallocDumpProvider()
98 : heap_profiler_enabled_(false), tid_dumping_heap_(kInvalidThreadId) {}
99
~MallocDumpProvider()100 MallocDumpProvider::~MallocDumpProvider() {}
101
102 // Called at trace dump point time. Creates a snapshot the memory counters for
103 // the current process.
OnMemoryDump(const MemoryDumpArgs & args,ProcessMemoryDump * pmd)104 bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
105 ProcessMemoryDump* pmd) {
106 size_t total_virtual_size = 0;
107 size_t resident_size = 0;
108 size_t allocated_objects_size = 0;
109 #if defined(USE_TCMALLOC)
110 bool res =
111 allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
112 DCHECK(res);
113 res = allocator::GetNumericProperty("generic.total_physical_bytes",
114 &resident_size);
115 DCHECK(res);
116 res = allocator::GetNumericProperty("generic.current_allocated_bytes",
117 &allocated_objects_size);
118 DCHECK(res);
119 #elif defined(OS_MACOSX) || defined(OS_IOS)
120 malloc_statistics_t stats;
121 memset(&stats, 0, sizeof(stats));
122 malloc_zone_statistics(nullptr, &stats);
123 total_virtual_size = stats.size_allocated;
124 allocated_objects_size = stats.size_in_use;
125
126 // The resident size is approximated to the max size in use, which would count
127 // the total size of all regions other than the free bytes at the end of each
128 // region. In each allocation region the allocations are rounded off to a
129 // fixed quantum, so the excess region will not be resident.
130 // See crrev.com/1531463004 for detailed explanation.
131 resident_size = stats.max_size_in_use;
132 #else
133 struct mallinfo info = mallinfo();
134 DCHECK_GE(info.arena + info.hblkhd, info.uordblks);
135
136 // In case of Android's jemalloc |arena| is 0 and the outer pages size is
137 // reported by |hblkhd|. In case of dlmalloc the total is given by
138 // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
139 total_virtual_size = info.arena + info.hblkhd;
140 resident_size = info.uordblks;
141 allocated_objects_size = info.uordblks;
142 #endif
143
144 MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
145 outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
146 total_virtual_size);
147 outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
148 MemoryAllocatorDump::kUnitsBytes, resident_size);
149
150 // Total allocated space is given by |uordblks|.
151 MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
152 inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
153 MemoryAllocatorDump::kUnitsBytes,
154 allocated_objects_size);
155
156 if (resident_size - allocated_objects_size > 0) {
157 // Explicitly specify why is extra memory resident. In tcmalloc it accounts
158 // for free lists and caches. In mac and ios it accounts for the
159 // fragmentation and metadata.
160 MemoryAllocatorDump* other_dump =
161 pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
162 other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
163 MemoryAllocatorDump::kUnitsBytes,
164 resident_size - allocated_objects_size);
165 }
166
167 // Heap profiler dumps.
168 if (!heap_profiler_enabled_)
169 return true;
170
171 // The dumps of the heap profiler should be created only when heap profiling
172 // was enabled (--enable-heap-profiling) AND a DETAILED dump is requested.
173 // However, when enabled, the overhead of the heap profiler should be always
174 // reported to avoid oscillations of the malloc total in LIGHT dumps.
175
176 tid_dumping_heap_ = PlatformThread::CurrentId();
177 // At this point the Insert/RemoveAllocation hooks will ignore this thread.
178 // Enclosing all the temporariy data structures in a scope, so that the heap
179 // profiler does not see unabalanced malloc/free calls from these containers.
180 {
181 TraceEventMemoryOverhead overhead;
182 hash_map<AllocationContext, AllocationMetrics> metrics_by_context;
183 {
184 AutoLock lock(allocation_register_lock_);
185 if (allocation_register_) {
186 if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
187 for (const auto& alloc_size : *allocation_register_) {
188 AllocationMetrics& metrics = metrics_by_context[alloc_size.context];
189 metrics.size += alloc_size.size;
190 metrics.count++;
191 }
192 }
193 allocation_register_->EstimateTraceMemoryOverhead(&overhead);
194 }
195 } // lock(allocation_register_lock_)
196 pmd->DumpHeapUsage(metrics_by_context, overhead, "malloc");
197 }
198 tid_dumping_heap_ = kInvalidThreadId;
199
200 return true;
201 }
202
OnHeapProfilingEnabled(bool enabled)203 void MallocDumpProvider::OnHeapProfilingEnabled(bool enabled) {
204 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
205 if (enabled) {
206 {
207 AutoLock lock(allocation_register_lock_);
208 allocation_register_.reset(new AllocationRegister());
209 }
210 allocator::InsertAllocatorDispatch(&g_allocator_hooks);
211 } else {
212 AutoLock lock(allocation_register_lock_);
213 allocation_register_.reset();
214 // Insert/RemoveAllocation below will no-op if the register is torn down.
215 // Once disabled, heap profiling will not re-enabled anymore for the
216 // lifetime of the process.
217 }
218 #endif
219 heap_profiler_enabled_ = enabled;
220 }
221
InsertAllocation(void * address,size_t size)222 void MallocDumpProvider::InsertAllocation(void* address, size_t size) {
223 // CurrentId() can be a slow operation (crbug.com/497226). This apparently
224 // redundant condition short circuits the CurrentID() calls when unnecessary.
225 if (tid_dumping_heap_ != kInvalidThreadId &&
226 tid_dumping_heap_ == PlatformThread::CurrentId())
227 return;
228
229 // AllocationContextTracker will return nullptr when called re-reentrantly.
230 // This is the case of GetInstanceForCurrentThread() being called for the
231 // first time, which causes a new() inside the tracker which re-enters the
232 // heap profiler, in which case we just want to early out.
233 auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
234 if (!tracker)
235 return;
236 AllocationContext context = tracker->GetContextSnapshot();
237
238 AutoLock lock(allocation_register_lock_);
239 if (!allocation_register_)
240 return;
241
242 allocation_register_->Insert(address, size, context);
243 }
244
RemoveAllocation(void * address)245 void MallocDumpProvider::RemoveAllocation(void* address) {
246 // No re-entrancy is expected here as none of the calls below should
247 // cause a free()-s (|allocation_register_| does its own heap management).
248 if (tid_dumping_heap_ != kInvalidThreadId &&
249 tid_dumping_heap_ == PlatformThread::CurrentId())
250 return;
251 AutoLock lock(allocation_register_lock_);
252 if (!allocation_register_)
253 return;
254 allocation_register_->Remove(address);
255 }
256
257 } // namespace trace_event
258 } // namespace base
259