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