blob: 26eb92319a23458cf2720248c5eafabb161bace5 [file] [log] [blame]
ssid07386852015-04-14 15:32:371// 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
avibd1ed052015-12-24 04:03:447#include <stddef.h>
8
9#include "base/allocator/allocator_extension.h"
primianofd9072162016-03-25 02:13:2810#include "base/allocator/allocator_shim.h"
11#include "base/allocator/features.h"
siggiba33ec02016-08-26 16:13:0712#include "base/debug/profiler.h"
primianofd9072162016-03-25 02:13:2813#include "base/trace_event/heap_profiler_allocation_context.h"
14#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
15#include "base/trace_event/heap_profiler_allocation_register.h"
16#include "base/trace_event/heap_profiler_heap_dump_writer.h"
avibd1ed052015-12-24 04:03:4417#include "base/trace_event/process_memory_dump.h"
primianofd9072162016-03-25 02:13:2818#include "base/trace_event/trace_event_argument.h"
avibd1ed052015-12-24 04:03:4419#include "build/build_config.h"
20
ssid3aa02fe2015-11-07 16:15:0721#if defined(OS_MACOSX)
22#include <malloc/malloc.h>
23#else
ssid07386852015-04-14 15:32:3724#include <malloc.h>
ssid3aa02fe2015-11-07 16:15:0725#endif
siggi7bec59a2016-08-25 20:22:2626#if defined(OS_WIN)
27#include <windows.h>
28#endif
ssid07386852015-04-14 15:32:3729
ssid07386852015-04-14 15:32:3730namespace base {
31namespace trace_event {
32
primianofd9072162016-03-25 02:13:2833namespace {
siggi7bec59a2016-08-25 20:22:2634#if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
primianofd9072162016-03-25 02:13:2835
36using allocator::AllocatorDispatch;
37
38void* HookAlloc(const AllocatorDispatch* self, size_t size) {
39 const AllocatorDispatch* const next = self->next;
40 void* ptr = next->alloc_function(next, size);
41 if (ptr)
42 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
43 return ptr;
44}
45
46void* HookZeroInitAlloc(const AllocatorDispatch* self, size_t n, size_t size) {
47 const AllocatorDispatch* const next = self->next;
48 void* ptr = next->alloc_zero_initialized_function(next, n, size);
49 if (ptr)
50 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, n * size);
51 return ptr;
52}
53
54void* HookllocAligned(const AllocatorDispatch* self,
55 size_t alignment,
56 size_t size) {
57 const AllocatorDispatch* const next = self->next;
58 void* ptr = next->alloc_aligned_function(next, alignment, size);
59 if (ptr)
60 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
61 return ptr;
62}
63
64void* HookRealloc(const AllocatorDispatch* self, void* address, size_t size) {
65 const AllocatorDispatch* const next = self->next;
66 void* ptr = next->realloc_function(next, address, size);
67 MallocDumpProvider::GetInstance()->RemoveAllocation(address);
68 if (size > 0) // realloc(size == 0) means free().
69 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
70 return ptr;
71}
72
73void HookFree(const AllocatorDispatch* self, void* address) {
74 if (address)
75 MallocDumpProvider::GetInstance()->RemoveAllocation(address);
76 const AllocatorDispatch* const next = self->next;
77 next->free_function(next, address);
78}
79
siggi46e1b072016-09-09 16:43:3180size_t HookGetSizeEstimate(const AllocatorDispatch* self, void* address) {
81 const AllocatorDispatch* const next = self->next;
82 return next->get_size_estimate_function(next, address);
83}
84
primianofd9072162016-03-25 02:13:2885AllocatorDispatch g_allocator_hooks = {
siggi46e1b072016-09-09 16:43:3186 &HookAlloc, /* alloc_function */
87 &HookZeroInitAlloc, /* alloc_zero_initialized_function */
88 &HookllocAligned, /* alloc_aligned_function */
89 &HookRealloc, /* realloc_function */
90 &HookFree, /* free_function */
91 &HookGetSizeEstimate, /* get_size_estimate_function */
92 nullptr, /* next */
primianofd9072162016-03-25 02:13:2893};
primianofd9072162016-03-25 02:13:2894#endif // BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
95
siggi7bec59a2016-08-25 20:22:2696#if defined(OS_WIN)
97// A structure containing some information about a given heap.
98struct WinHeapInfo {
99 HANDLE heap_id;
100 size_t committed_size;
101 size_t uncommitted_size;
102 size_t allocated_size;
103 size_t block_count;
104};
105
106bool GetHeapInformation(WinHeapInfo* heap_info,
107 const std::set<void*>& block_to_skip) {
108 CHECK(::HeapLock(heap_info->heap_id) == TRUE);
109 PROCESS_HEAP_ENTRY heap_entry;
110 heap_entry.lpData = nullptr;
111 // Walk over all the entries in this heap.
112 while (::HeapWalk(heap_info->heap_id, &heap_entry) != FALSE) {
113 if (block_to_skip.count(heap_entry.lpData) == 1)
114 continue;
115 if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
116 heap_info->allocated_size += heap_entry.cbData;
117 heap_info->block_count++;
118 } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
119 heap_info->committed_size += heap_entry.Region.dwCommittedSize;
120 heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
121 }
122 }
123 CHECK(::HeapUnlock(heap_info->heap_id) == TRUE);
124 return true;
125}
126
127void WinHeapMemoryDumpImpl(WinHeapInfo* all_heap_info) {
128// This method might be flaky for 2 reasons:
129// - GetProcessHeaps is racy by design. It returns a snapshot of the
130// available heaps, but there's no guarantee that that snapshot remains
131// valid. If a heap disappears between GetProcessHeaps() and HeapWalk()
132// then chaos should be assumed. This flakyness is acceptable for tracing.
133// - The MSDN page for HeapLock says: "If the HeapLock function is called on
134// a heap created with the HEAP_NO_SERIALIZATION flag, the results are
135// undefined."
136// - Note that multiple heaps occur on Windows primarily because system and
137// 3rd party DLLs will each create their own private heap. It's possible to
138// retrieve the heap the CRT allocates from and report specifically on that
139// heap. It's interesting to report all heaps, as e.g. loading or invoking
140// on a Windows API may consume memory from a private heap.
141#if defined(SYZYASAN)
142 if (base::debug::IsBinaryInstrumented())
143 return;
144#endif
145
146 // Retrieves the number of heaps in the current process.
147 DWORD number_of_heaps = ::GetProcessHeaps(0, NULL);
148
149 // Try to retrieve a handle to all the heaps owned by this process. Returns
150 // false if the number of heaps has changed.
151 //
152 // This is inherently racy as is, but it's not something that we observe a lot
153 // in Chrome, the heaps tend to be created at startup only.
154 std::unique_ptr<HANDLE[]> all_heaps(new HANDLE[number_of_heaps]);
155 if (::GetProcessHeaps(number_of_heaps, all_heaps.get()) != number_of_heaps)
156 return;
157
158 // Skip the pointer to the heap array to avoid accounting the memory used by
159 // this dump provider.
160 std::set<void*> block_to_skip;
161 block_to_skip.insert(all_heaps.get());
162
163 // Retrieves some metrics about each heap.
164 for (size_t i = 0; i < number_of_heaps; ++i) {
165 WinHeapInfo heap_info = {0};
166 heap_info.heap_id = all_heaps[i];
167 GetHeapInformation(&heap_info, block_to_skip);
168
169 all_heap_info->allocated_size += heap_info.allocated_size;
170 all_heap_info->committed_size += heap_info.committed_size;
171 all_heap_info->uncommitted_size += heap_info.uncommitted_size;
172 all_heap_info->block_count += heap_info.block_count;
173 }
174}
175#endif // defined(OS_WIN)
176} // namespace
177
ssid07386852015-04-14 15:32:37178// static
primianofadec05e2015-06-03 16:57:32179const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
180
181// static
ssid07386852015-04-14 15:32:37182MallocDumpProvider* MallocDumpProvider::GetInstance() {
183 return Singleton<MallocDumpProvider,
184 LeakySingletonTraits<MallocDumpProvider>>::get();
185}
186
primianofd9072162016-03-25 02:13:28187MallocDumpProvider::MallocDumpProvider()
188 : heap_profiler_enabled_(false), tid_dumping_heap_(kInvalidThreadId) {}
ssid07386852015-04-14 15:32:37189
ssid3aa02fe2015-11-07 16:15:07190MallocDumpProvider::~MallocDumpProvider() {}
ssid07386852015-04-14 15:32:37191
192// Called at trace dump point time. Creates a snapshot the memory counters for
193// the current process.
ssid90694aeec2015-08-06 13:01:30194bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
195 ProcessMemoryDump* pmd) {
ssid09434092015-10-26 23:05:04196 size_t total_virtual_size = 0;
197 size_t resident_size = 0;
198 size_t allocated_objects_size = 0;
siggi7bec59a2016-08-25 20:22:26199 size_t allocated_objects_count = 0;
ssid86f78c12015-12-21 11:45:32200#if defined(USE_TCMALLOC)
primianodda6c272015-12-07 16:51:04201 bool res =
202 allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
203 DCHECK(res);
204 res = allocator::GetNumericProperty("generic.total_physical_bytes",
205 &resident_size);
206 DCHECK(res);
207 res = allocator::GetNumericProperty("generic.current_allocated_bytes",
208 &allocated_objects_size);
209 DCHECK(res);
ssid86f78c12015-12-21 11:45:32210#elif defined(OS_MACOSX) || defined(OS_IOS)
211 malloc_statistics_t stats = {0};
212 malloc_zone_statistics(nullptr, &stats);
213 total_virtual_size = stats.size_allocated;
214 allocated_objects_size = stats.size_in_use;
215
216 // The resident size is approximated to the max size in use, which would count
217 // the total size of all regions other than the free bytes at the end of each
218 // region. In each allocation region the allocations are rounded off to a
219 // fixed quantum, so the excess region will not be resident.
220 // See crrev.com/1531463004 for detailed explanation.
221 resident_size = stats.max_size_in_use;
siggi7bec59a2016-08-25 20:22:26222#elif defined(OS_WIN)
223 WinHeapInfo all_heap_info = {};
224 WinHeapMemoryDumpImpl(&all_heap_info);
225 total_virtual_size =
226 all_heap_info.committed_size + all_heap_info.uncommitted_size;
227 // Resident size is approximated with committed heap size. Note that it is
228 // possible to do this with better accuracy on windows by intersecting the
229 // working set with the virtual memory ranges occuipied by the heap. It's not
230 // clear that this is worth it, as it's fairly expensive to do.
231 resident_size = all_heap_info.committed_size;
232 allocated_objects_size = all_heap_info.allocated_size;
233 allocated_objects_count = all_heap_info.block_count;
ssid3aa02fe2015-11-07 16:15:07234#else
primianodda6c272015-12-07 16:51:04235 struct mallinfo info = mallinfo();
236 DCHECK_GE(info.arena + info.hblkhd, info.uordblks);
ssid09434092015-10-26 23:05:04237
primianodda6c272015-12-07 16:51:04238 // In case of Android's jemalloc |arena| is 0 and the outer pages size is
239 // reported by |hblkhd|. In case of dlmalloc the total is given by
240 // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
241 total_virtual_size = info.arena + info.hblkhd;
242 resident_size = info.uordblks;
siggi7bec59a2016-08-25 20:22:26243
244 // Total allocated space is given by |uordblks|.
primianodda6c272015-12-07 16:51:04245 allocated_objects_size = info.uordblks;
ssid3aa02fe2015-11-07 16:15:07246#endif
ssid09434092015-10-26 23:05:04247
primianofadec05e2015-06-03 16:57:32248 MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
ssid09434092015-10-26 23:05:04249 outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
250 total_virtual_size);
251 outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
252 MemoryAllocatorDump::kUnitsBytes, resident_size);
ssid07386852015-04-14 15:32:37253
primianofadec05e2015-06-03 16:57:32254 MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
255 inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
ssid09434092015-10-26 23:05:04256 MemoryAllocatorDump::kUnitsBytes,
257 allocated_objects_size);
siggi7bec59a2016-08-25 20:22:26258 if (allocated_objects_count != 0) {
siggi52114f272016-08-31 23:51:30259 inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
siggi7bec59a2016-08-25 20:22:26260 MemoryAllocatorDump::kUnitsObjects,
261 allocated_objects_count);
262 }
ssid07386852015-04-14 15:32:37263
siggi52114f272016-08-31 23:51:30264 if (resident_size > allocated_objects_size) {
ssid86f78c12015-12-21 11:45:32265 // Explicitly specify why is extra memory resident. In tcmalloc it accounts
266 // for free lists and caches. In mac and ios it accounts for the
267 // fragmentation and metadata.
268 MemoryAllocatorDump* other_dump =
269 pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
270 other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
271 MemoryAllocatorDump::kUnitsBytes,
272 resident_size - allocated_objects_size);
273 }
274
primianofd9072162016-03-25 02:13:28275 // Heap profiler dumps.
276 if (!heap_profiler_enabled_)
277 return true;
278
279 // The dumps of the heap profiler should be created only when heap profiling
280 // was enabled (--enable-heap-profiling) AND a DETAILED dump is requested.
281 // However, when enabled, the overhead of the heap profiler should be always
282 // reported to avoid oscillations of the malloc total in LIGHT dumps.
283
284 tid_dumping_heap_ = PlatformThread::CurrentId();
285 // At this point the Insert/RemoveAllocation hooks will ignore this thread.
286 // Enclosing all the temporariy data structures in a scope, so that the heap
287 // profiler does not see unabalanced malloc/free calls from these containers.
288 {
289 TraceEventMemoryOverhead overhead;
ssid1eedc592016-04-15 01:56:44290 hash_map<AllocationContext, AllocationMetrics> metrics_by_context;
primianofd9072162016-03-25 02:13:28291 {
292 AutoLock lock(allocation_register_lock_);
293 if (allocation_register_) {
294 if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
ssid1eedc592016-04-15 01:56:44295 for (const auto& alloc_size : *allocation_register_) {
296 AllocationMetrics& metrics = metrics_by_context[alloc_size.context];
297 metrics.size += alloc_size.size;
298 metrics.count++;
299 }
primianofd9072162016-03-25 02:13:28300 }
301 allocation_register_->EstimateTraceMemoryOverhead(&overhead);
302 }
303 } // lock(allocation_register_lock_)
bashib873c0d42016-05-12 05:41:04304 pmd->DumpHeapUsage(metrics_by_context, overhead, "malloc");
primianofd9072162016-03-25 02:13:28305 }
306 tid_dumping_heap_ = kInvalidThreadId;
307
ssid07386852015-04-14 15:32:37308 return true;
309}
310
primianofd9072162016-03-25 02:13:28311void MallocDumpProvider::OnHeapProfilingEnabled(bool enabled) {
312#if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
313 if (enabled) {
314 {
315 AutoLock lock(allocation_register_lock_);
316 allocation_register_.reset(new AllocationRegister());
317 }
318 allocator::InsertAllocatorDispatch(&g_allocator_hooks);
319 } else {
320 AutoLock lock(allocation_register_lock_);
321 allocation_register_.reset();
322 // Insert/RemoveAllocation below will no-op if the register is torn down.
323 // Once disabled, heap profiling will not re-enabled anymore for the
324 // lifetime of the process.
325 }
326#endif
327 heap_profiler_enabled_ = enabled;
328}
329
330void MallocDumpProvider::InsertAllocation(void* address, size_t size) {
331 // CurrentId() can be a slow operation (crbug.com/497226). This apparently
332 // redundant condition short circuits the CurrentID() calls when unnecessary.
333 if (tid_dumping_heap_ != kInvalidThreadId &&
334 tid_dumping_heap_ == PlatformThread::CurrentId())
335 return;
336
337 // AllocationContextTracker will return nullptr when called re-reentrantly.
338 // This is the case of GetInstanceForCurrentThread() being called for the
339 // first time, which causes a new() inside the tracker which re-enters the
340 // heap profiler, in which case we just want to early out.
vmpstr5170bf92016-06-29 02:15:58341 auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
primianofd9072162016-03-25 02:13:28342 if (!tracker)
343 return;
344 AllocationContext context = tracker->GetContextSnapshot();
345
346 AutoLock lock(allocation_register_lock_);
347 if (!allocation_register_)
348 return;
349
350 allocation_register_->Insert(address, size, context);
351}
352
353void MallocDumpProvider::RemoveAllocation(void* address) {
354 // No re-entrancy is expected here as none of the calls below should
355 // cause a free()-s (|allocation_register_| does its own heap management).
356 if (tid_dumping_heap_ != kInvalidThreadId &&
357 tid_dumping_heap_ == PlatformThread::CurrentId())
358 return;
359 AutoLock lock(allocation_register_lock_);
360 if (!allocation_register_)
361 return;
362 allocation_register_->Remove(address);
363}
364
ssid07386852015-04-14 15:32:37365} // namespace trace_event
366} // namespace base