Home | History | Annotate | Download | only in trace_event
      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/debug/profiler.h"
     13 #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"
     17 #include "base/trace_event/process_memory_dump.h"
     18 #include "base/trace_event/trace_event_argument.h"
     19 #include "build/build_config.h"
     20 
     21 #if defined(OS_MACOSX)
     22 #include <malloc/malloc.h>
     23 #else
     24 #include <malloc.h>
     25 #endif
     26 #if defined(OS_WIN)
     27 #include <windows.h>
     28 #endif
     29 
     30 namespace base {
     31 namespace trace_event {
     32 
     33 namespace {
     34 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
     35 
     36 using allocator::AllocatorDispatch;
     37 
     38 void* HookAlloc(const AllocatorDispatch* self, size_t size, void* context) {
     39   const AllocatorDispatch* const next = self->next;
     40   void* ptr = next->alloc_function(next, size, context);
     41   if (ptr)
     42     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
     43   return ptr;
     44 }
     45 
     46 void* HookZeroInitAlloc(const AllocatorDispatch* self,
     47                         size_t n,
     48                         size_t size,
     49                         void* context) {
     50   const AllocatorDispatch* const next = self->next;
     51   void* ptr = next->alloc_zero_initialized_function(next, n, size, context);
     52   if (ptr)
     53     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, n * size);
     54   return ptr;
     55 }
     56 
     57 void* HookAllocAligned(const AllocatorDispatch* self,
     58                        size_t alignment,
     59                        size_t size,
     60                        void* context) {
     61   const AllocatorDispatch* const next = self->next;
     62   void* ptr = next->alloc_aligned_function(next, alignment, size, context);
     63   if (ptr)
     64     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
     65   return ptr;
     66 }
     67 
     68 void* HookRealloc(const AllocatorDispatch* self,
     69                   void* address,
     70                   size_t size,
     71                   void* context) {
     72   const AllocatorDispatch* const next = self->next;
     73   void* ptr = next->realloc_function(next, address, size, context);
     74   MallocDumpProvider::GetInstance()->RemoveAllocation(address);
     75   if (size > 0)  // realloc(size == 0) means free().
     76     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
     77   return ptr;
     78 }
     79 
     80 void HookFree(const AllocatorDispatch* self, void* address, void* context) {
     81   if (address)
     82     MallocDumpProvider::GetInstance()->RemoveAllocation(address);
     83   const AllocatorDispatch* const next = self->next;
     84   next->free_function(next, address, context);
     85 }
     86 
     87 size_t HookGetSizeEstimate(const AllocatorDispatch* self,
     88                            void* address,
     89                            void* context) {
     90   const AllocatorDispatch* const next = self->next;
     91   return next->get_size_estimate_function(next, address, context);
     92 }
     93 
     94 unsigned HookBatchMalloc(const AllocatorDispatch* self,
     95                          size_t size,
     96                          void** results,
     97                          unsigned num_requested,
     98                          void* context) {
     99   const AllocatorDispatch* const next = self->next;
    100   unsigned count =
    101       next->batch_malloc_function(next, size, results, num_requested, context);
    102   for (unsigned i = 0; i < count; ++i) {
    103     MallocDumpProvider::GetInstance()->InsertAllocation(results[i], size);
    104   }
    105   return count;
    106 }
    107 
    108 void HookBatchFree(const AllocatorDispatch* self,
    109                    void** to_be_freed,
    110                    unsigned num_to_be_freed,
    111                    void* context) {
    112   const AllocatorDispatch* const next = self->next;
    113   for (unsigned i = 0; i < num_to_be_freed; ++i) {
    114     MallocDumpProvider::GetInstance()->RemoveAllocation(to_be_freed[i]);
    115   }
    116   next->batch_free_function(next, to_be_freed, num_to_be_freed, context);
    117 }
    118 
    119 void HookFreeDefiniteSize(const AllocatorDispatch* self,
    120                           void* ptr,
    121                           size_t size,
    122                           void* context) {
    123   if (ptr)
    124     MallocDumpProvider::GetInstance()->RemoveAllocation(ptr);
    125   const AllocatorDispatch* const next = self->next;
    126   next->free_definite_size_function(next, ptr, size, context);
    127 }
    128 
    129 AllocatorDispatch g_allocator_hooks = {
    130     &HookAlloc,            /* alloc_function */
    131     &HookZeroInitAlloc,    /* alloc_zero_initialized_function */
    132     &HookAllocAligned,     /* alloc_aligned_function */
    133     &HookRealloc,          /* realloc_function */
    134     &HookFree,             /* free_function */
    135     &HookGetSizeEstimate,  /* get_size_estimate_function */
    136     &HookBatchMalloc,      /* batch_malloc_function */
    137     &HookBatchFree,        /* batch_free_function */
    138     &HookFreeDefiniteSize, /* free_definite_size_function */
    139     nullptr,               /* next */
    140 };
    141 #endif  // BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
    142 
    143 #if defined(OS_WIN)
    144 // A structure containing some information about a given heap.
    145 struct WinHeapInfo {
    146   size_t committed_size;
    147   size_t uncommitted_size;
    148   size_t allocated_size;
    149   size_t block_count;
    150 };
    151 
    152 // NOTE: crbug.com/665516
    153 // Unfortunately, there is no safe way to collect information from secondary
    154 // heaps due to limitations and racy nature of this piece of WinAPI.
    155 void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) {
    156 #if defined(SYZYASAN)
    157   if (base::debug::IsBinaryInstrumented())
    158     return;
    159 #endif
    160 
    161   // Iterate through whichever heap our CRT is using.
    162   HANDLE crt_heap = reinterpret_cast<HANDLE>(_get_heap_handle());
    163   ::HeapLock(crt_heap);
    164   PROCESS_HEAP_ENTRY heap_entry;
    165   heap_entry.lpData = nullptr;
    166   // Walk over all the entries in the main heap.
    167   while (::HeapWalk(crt_heap, &heap_entry) != FALSE) {
    168     if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
    169       crt_heap_info->allocated_size += heap_entry.cbData;
    170       crt_heap_info->block_count++;
    171     } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
    172       crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize;
    173       crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
    174     }
    175   }
    176   CHECK(::HeapUnlock(crt_heap) == TRUE);
    177 }
    178 #endif  // defined(OS_WIN)
    179 }  // namespace
    180 
    181 // static
    182 const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
    183 
    184 // static
    185 MallocDumpProvider* MallocDumpProvider::GetInstance() {
    186   return Singleton<MallocDumpProvider,
    187                    LeakySingletonTraits<MallocDumpProvider>>::get();
    188 }
    189 
    190 MallocDumpProvider::MallocDumpProvider()
    191     : heap_profiler_enabled_(false), tid_dumping_heap_(kInvalidThreadId) {}
    192 
    193 MallocDumpProvider::~MallocDumpProvider() {}
    194 
    195 // Called at trace dump point time. Creates a snapshot the memory counters for
    196 // the current process.
    197 bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
    198                                       ProcessMemoryDump* pmd) {
    199   size_t total_virtual_size = 0;
    200   size_t resident_size = 0;
    201   size_t allocated_objects_size = 0;
    202   size_t allocated_objects_count = 0;
    203 #if defined(USE_TCMALLOC)
    204   bool res =
    205       allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
    206   DCHECK(res);
    207   res = allocator::GetNumericProperty("generic.total_physical_bytes",
    208                                       &resident_size);
    209   DCHECK(res);
    210   res = allocator::GetNumericProperty("generic.current_allocated_bytes",
    211                                       &allocated_objects_size);
    212   DCHECK(res);
    213 #elif defined(OS_MACOSX) || defined(OS_IOS)
    214   malloc_statistics_t stats = {0};
    215   malloc_zone_statistics(nullptr, &stats);
    216   total_virtual_size = stats.size_allocated;
    217   allocated_objects_size = stats.size_in_use;
    218 
    219   // Resident size is approximated pretty well by stats.max_size_in_use.
    220   // However, on macOS, freed blocks are both resident and reusable, which is
    221   // semantically equivalent to deallocated. The implementation of libmalloc
    222   // will also only hold a fixed number of freed regions before actually
    223   // starting to deallocate them, so stats.max_size_in_use is also not
    224   // representative of the peak size. As a result, stats.max_size_in_use is
    225   // typically somewhere between actually resident [non-reusable] pages, and
    226   // peak size. This is not very useful, so we just use stats.size_in_use for
    227   // resident_size, even though it's an underestimate and fails to account for
    228   // fragmentation. See
    229   // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1.
    230   resident_size = stats.size_in_use;
    231 #elif defined(OS_WIN)
    232   WinHeapInfo main_heap_info = {};
    233   WinHeapMemoryDumpImpl(&main_heap_info);
    234   total_virtual_size =
    235       main_heap_info.committed_size + main_heap_info.uncommitted_size;
    236   // Resident size is approximated with committed heap size. Note that it is
    237   // possible to do this with better accuracy on windows by intersecting the
    238   // working set with the virtual memory ranges occuipied by the heap. It's not
    239   // clear that this is worth it, as it's fairly expensive to do.
    240   resident_size = main_heap_info.committed_size;
    241   allocated_objects_size = main_heap_info.allocated_size;
    242   allocated_objects_count = main_heap_info.block_count;
    243 #else
    244   struct mallinfo info = mallinfo();
    245   DCHECK_GE(info.arena + info.hblkhd, info.uordblks);
    246 
    247   // In case of Android's jemalloc |arena| is 0 and the outer pages size is
    248   // reported by |hblkhd|. In case of dlmalloc the total is given by
    249   // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
    250   total_virtual_size = info.arena + info.hblkhd;
    251   resident_size = info.uordblks;
    252 
    253   // Total allocated space is given by |uordblks|.
    254   allocated_objects_size = info.uordblks;
    255 #endif
    256 
    257   MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
    258   outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
    259                         total_virtual_size);
    260   outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
    261                         MemoryAllocatorDump::kUnitsBytes, resident_size);
    262 
    263   MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
    264   inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
    265                         MemoryAllocatorDump::kUnitsBytes,
    266                         allocated_objects_size);
    267   if (allocated_objects_count != 0) {
    268     inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
    269                           MemoryAllocatorDump::kUnitsObjects,
    270                           allocated_objects_count);
    271   }
    272 
    273   if (resident_size > allocated_objects_size) {
    274     // Explicitly specify why is extra memory resident. In tcmalloc it accounts
    275     // for free lists and caches. In mac and ios it accounts for the
    276     // fragmentation and metadata.
    277     MemoryAllocatorDump* other_dump =
    278         pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
    279     other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
    280                           MemoryAllocatorDump::kUnitsBytes,
    281                           resident_size - allocated_objects_size);
    282   }
    283 
    284   // Heap profiler dumps.
    285   if (!heap_profiler_enabled_)
    286     return true;
    287 
    288   // The dumps of the heap profiler should be created only when heap profiling
    289   // was enabled (--enable-heap-profiling) AND a DETAILED dump is requested.
    290   // However, when enabled, the overhead of the heap profiler should be always
    291   // reported to avoid oscillations of the malloc total in LIGHT dumps.
    292 
    293   tid_dumping_heap_ = PlatformThread::CurrentId();
    294   // At this point the Insert/RemoveAllocation hooks will ignore this thread.
    295   // Enclosing all the temporariy data structures in a scope, so that the heap
    296   // profiler does not see unabalanced malloc/free calls from these containers.
    297   {
    298     TraceEventMemoryOverhead overhead;
    299     hash_map<AllocationContext, AllocationMetrics> metrics_by_context;
    300     {
    301       AutoLock lock(allocation_register_lock_);
    302       if (allocation_register_) {
    303         if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
    304           for (const auto& alloc_size : *allocation_register_) {
    305             AllocationMetrics& metrics = metrics_by_context[alloc_size.context];
    306             metrics.size += alloc_size.size;
    307             metrics.count++;
    308           }
    309         }
    310         allocation_register_->EstimateTraceMemoryOverhead(&overhead);
    311       }
    312     }  // lock(allocation_register_lock_)
    313     pmd->DumpHeapUsage(metrics_by_context, overhead, "malloc");
    314   }
    315   tid_dumping_heap_ = kInvalidThreadId;
    316 
    317   return true;
    318 }
    319 
    320 void MallocDumpProvider::OnHeapProfilingEnabled(bool enabled) {
    321 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
    322   if (enabled) {
    323     {
    324       AutoLock lock(allocation_register_lock_);
    325       allocation_register_.reset(new AllocationRegister());
    326     }
    327     allocator::InsertAllocatorDispatch(&g_allocator_hooks);
    328   } else {
    329     AutoLock lock(allocation_register_lock_);
    330     allocation_register_.reset();
    331     // Insert/RemoveAllocation below will no-op if the register is torn down.
    332     // Once disabled, heap profiling will not re-enabled anymore for the
    333     // lifetime of the process.
    334   }
    335 #endif
    336   heap_profiler_enabled_ = enabled;
    337 }
    338 
    339 void MallocDumpProvider::InsertAllocation(void* address, size_t size) {
    340   // CurrentId() can be a slow operation (crbug.com/497226). This apparently
    341   // redundant condition short circuits the CurrentID() calls when unnecessary.
    342   if (tid_dumping_heap_ != kInvalidThreadId &&
    343       tid_dumping_heap_ == PlatformThread::CurrentId())
    344     return;
    345 
    346   // AllocationContextTracker will return nullptr when called re-reentrantly.
    347   // This is the case of GetInstanceForCurrentThread() being called for the
    348   // first time, which causes a new() inside the tracker which re-enters the
    349   // heap profiler, in which case we just want to early out.
    350   auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
    351   if (!tracker)
    352     return;
    353 
    354   AllocationContext context;
    355   if (!tracker->GetContextSnapshot(&context))
    356     return;
    357 
    358   AutoLock lock(allocation_register_lock_);
    359   if (!allocation_register_)
    360     return;
    361 
    362   allocation_register_->Insert(address, size, context);
    363 }
    364 
    365 void MallocDumpProvider::RemoveAllocation(void* address) {
    366   // No re-entrancy is expected here as none of the calls below should
    367   // cause a free()-s (|allocation_register_| does its own heap management).
    368   if (tid_dumping_heap_ != kInvalidThreadId &&
    369       tid_dumping_heap_ == PlatformThread::CurrentId())
    370     return;
    371   AutoLock lock(allocation_register_lock_);
    372   if (!allocation_register_)
    373     return;
    374   allocation_register_->Remove(address);
    375 }
    376 
    377 }  // namespace trace_event
    378 }  // namespace base
    379