|  | // Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/debug/thread_heap_usage_tracker.h" | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <algorithm> | 
|  | #include <limits> | 
|  | #include <new> | 
|  | #include <type_traits> | 
|  |  | 
|  | #include "base/allocator/allocator_shim.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/no_destructor.h" | 
|  | #include "base/threading/thread_local_storage.h" | 
|  | #include "build/build_config.h" | 
|  |  | 
|  | #if defined(OS_MACOSX) || defined(OS_IOS) | 
|  | #include <malloc/malloc.h> | 
|  | #else | 
|  | #include <malloc.h> | 
|  | #endif | 
|  |  | 
|  | namespace base { | 
|  | namespace debug { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using base::allocator::AllocatorDispatch; | 
|  |  | 
|  | const uintptr_t kSentinelMask = std::numeric_limits<uintptr_t>::max() - 1; | 
|  | ThreadHeapUsage* const kInitializationSentinel = | 
|  | reinterpret_cast<ThreadHeapUsage*>(kSentinelMask); | 
|  | ThreadHeapUsage* const kTeardownSentinel = | 
|  | reinterpret_cast<ThreadHeapUsage*>(kSentinelMask | 1); | 
|  |  | 
|  | ThreadLocalStorage::Slot& ThreadAllocationUsage() { | 
|  | static NoDestructor<ThreadLocalStorage::Slot> thread_allocator_usage( | 
|  | [](void* thread_heap_usage) { | 
|  | // This destructor will be called twice. Once to destroy the actual | 
|  | // ThreadHeapUsage instance and a second time, immediately after, for | 
|  | // the sentinel. Re-setting the TLS slow (below) does re-initialize the | 
|  | // TLS slot. The ThreadLocalStorage code is designed to deal with this | 
|  | // use case and will re-call the destructor with the kTeardownSentinel | 
|  | // as arg. | 
|  | if (thread_heap_usage == kTeardownSentinel) | 
|  | return; | 
|  | DCHECK_NE(thread_heap_usage, kInitializationSentinel); | 
|  |  | 
|  | // Deleting the ThreadHeapUsage TLS object will re-enter the shim and | 
|  | // hit RecordFree() (see below). The sentinel prevents RecordFree() from | 
|  | // re-creating another ThreadHeapUsage object. | 
|  | ThreadAllocationUsage().Set(kTeardownSentinel); | 
|  | delete static_cast<ThreadHeapUsage*>(thread_heap_usage); | 
|  | }); | 
|  | return *thread_allocator_usage; | 
|  | } | 
|  |  | 
|  | bool g_heap_tracking_enabled = false; | 
|  |  | 
|  | // Forward declared as it needs to delegate memory allocation to the next | 
|  | // lower shim. | 
|  | ThreadHeapUsage* GetOrCreateThreadUsage(); | 
|  |  | 
|  | size_t GetAllocSizeEstimate(const AllocatorDispatch* next, | 
|  | void* ptr, | 
|  | void* context) { | 
|  | if (ptr == nullptr) | 
|  | return 0U; | 
|  |  | 
|  | return next->get_size_estimate_function(next, ptr, context); | 
|  | } | 
|  |  | 
|  | void RecordAlloc(const AllocatorDispatch* next, | 
|  | void* ptr, | 
|  | size_t size, | 
|  | void* context) { | 
|  | ThreadHeapUsage* usage = GetOrCreateThreadUsage(); | 
|  | if (usage == nullptr) | 
|  | return; | 
|  |  | 
|  | usage->alloc_ops++; | 
|  | size_t estimate = GetAllocSizeEstimate(next, ptr, context); | 
|  | if (size && estimate) { | 
|  | // Only keep track of the net number of bytes allocated in the scope if the | 
|  | // size estimate function returns sane values, e.g. non-zero. | 
|  | usage->alloc_bytes += estimate; | 
|  | usage->alloc_overhead_bytes += estimate - size; | 
|  |  | 
|  | // Record the max outstanding number of bytes, but only if the difference | 
|  | // is net positive (e.g. more bytes allocated than freed in the scope). | 
|  | if (usage->alloc_bytes > usage->free_bytes) { | 
|  | uint64_t allocated_bytes = usage->alloc_bytes - usage->free_bytes; | 
|  | if (allocated_bytes > usage->max_allocated_bytes) | 
|  | usage->max_allocated_bytes = allocated_bytes; | 
|  | } | 
|  | } else { | 
|  | usage->alloc_bytes += size; | 
|  | } | 
|  | } | 
|  |  | 
|  | void RecordFree(const AllocatorDispatch* next, void* ptr, void* context) { | 
|  | ThreadHeapUsage* usage = GetOrCreateThreadUsage(); | 
|  | if (usage == nullptr) | 
|  | return; | 
|  |  | 
|  | size_t estimate = GetAllocSizeEstimate(next, ptr, context); | 
|  | usage->free_ops++; | 
|  | usage->free_bytes += estimate; | 
|  | } | 
|  |  | 
|  | void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) { | 
|  | void* ret = self->next->alloc_function(self->next, size, context); | 
|  | if (ret != nullptr) | 
|  | RecordAlloc(self->next, ret, size, context); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void* AllocZeroInitializedFn(const AllocatorDispatch* self, | 
|  | size_t n, | 
|  | size_t size, | 
|  | void* context) { | 
|  | void* ret = | 
|  | self->next->alloc_zero_initialized_function(self->next, n, size, context); | 
|  | if (ret != nullptr) | 
|  | RecordAlloc(self->next, ret, size, context); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void* AllocAlignedFn(const AllocatorDispatch* self, | 
|  | size_t alignment, | 
|  | size_t size, | 
|  | void* context) { | 
|  | void* ret = | 
|  | self->next->alloc_aligned_function(self->next, alignment, size, context); | 
|  | if (ret != nullptr) | 
|  | RecordAlloc(self->next, ret, size, context); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void* ReallocFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | size_t size, | 
|  | void* context) { | 
|  | if (address != nullptr) | 
|  | RecordFree(self->next, address, context); | 
|  |  | 
|  | void* ret = self->next->realloc_function(self->next, address, size, context); | 
|  | if (ret != nullptr && size != 0) | 
|  | RecordAlloc(self->next, ret, size, context); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void FreeFn(const AllocatorDispatch* self, void* address, void* context) { | 
|  | if (address != nullptr) | 
|  | RecordFree(self->next, address, context); | 
|  | self->next->free_function(self->next, address, context); | 
|  | } | 
|  |  | 
|  | size_t GetSizeEstimateFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | void* context) { | 
|  | return self->next->get_size_estimate_function(self->next, address, context); | 
|  | } | 
|  |  | 
|  | unsigned BatchMallocFn(const AllocatorDispatch* self, | 
|  | size_t size, | 
|  | void** results, | 
|  | unsigned num_requested, | 
|  | void* context) { | 
|  | unsigned count = self->next->batch_malloc_function(self->next, size, results, | 
|  | num_requested, context); | 
|  | for (unsigned i = 0; i < count; ++i) { | 
|  | RecordAlloc(self->next, results[i], size, context); | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | void BatchFreeFn(const AllocatorDispatch* self, | 
|  | void** to_be_freed, | 
|  | unsigned num_to_be_freed, | 
|  | void* context) { | 
|  | for (unsigned i = 0; i < num_to_be_freed; ++i) { | 
|  | if (to_be_freed[i] != nullptr) { | 
|  | RecordFree(self->next, to_be_freed[i], context); | 
|  | } | 
|  | } | 
|  | self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed, | 
|  | context); | 
|  | } | 
|  |  | 
|  | void FreeDefiniteSizeFn(const AllocatorDispatch* self, | 
|  | void* ptr, | 
|  | size_t size, | 
|  | void* context) { | 
|  | if (ptr != nullptr) | 
|  | RecordFree(self->next, ptr, context); | 
|  | self->next->free_definite_size_function(self->next, ptr, size, context); | 
|  | } | 
|  |  | 
|  | // The allocator dispatch used to intercept heap operations. | 
|  | AllocatorDispatch allocator_dispatch = {&AllocFn, | 
|  | &AllocZeroInitializedFn, | 
|  | &AllocAlignedFn, | 
|  | &ReallocFn, | 
|  | &FreeFn, | 
|  | &GetSizeEstimateFn, | 
|  | &BatchMallocFn, | 
|  | &BatchFreeFn, | 
|  | &FreeDefiniteSizeFn, | 
|  | nullptr}; | 
|  |  | 
|  | ThreadHeapUsage* GetOrCreateThreadUsage() { | 
|  | auto tls_ptr = reinterpret_cast<uintptr_t>(ThreadAllocationUsage().Get()); | 
|  | if ((tls_ptr & kSentinelMask) == kSentinelMask) | 
|  | return nullptr;  // Re-entrancy case. | 
|  |  | 
|  | auto* allocator_usage = reinterpret_cast<ThreadHeapUsage*>(tls_ptr); | 
|  | if (allocator_usage == nullptr) { | 
|  | // Prevent reentrancy due to the allocation below. | 
|  | ThreadAllocationUsage().Set(kInitializationSentinel); | 
|  |  | 
|  | allocator_usage = new ThreadHeapUsage(); | 
|  | static_assert(std::is_pod<ThreadHeapUsage>::value, | 
|  | "AllocatorDispatch must be POD"); | 
|  | memset(allocator_usage, 0, sizeof(*allocator_usage)); | 
|  | ThreadAllocationUsage().Set(allocator_usage); | 
|  | } | 
|  |  | 
|  | return allocator_usage; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | ThreadHeapUsageTracker::ThreadHeapUsageTracker() : thread_usage_(nullptr) { | 
|  | static_assert(std::is_pod<ThreadHeapUsage>::value, "Must be POD."); | 
|  | } | 
|  |  | 
|  | ThreadHeapUsageTracker::~ThreadHeapUsageTracker() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  |  | 
|  | if (thread_usage_ != nullptr) { | 
|  | // If this tracker wasn't stopped, make it inclusive so that the | 
|  | // usage isn't lost. | 
|  | Stop(false); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ThreadHeapUsageTracker::Start() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  |  | 
|  | thread_usage_ = GetOrCreateThreadUsage(); | 
|  | usage_ = *thread_usage_; | 
|  |  | 
|  | // Reset the stats for our current scope. | 
|  | // The per-thread usage instance now tracks this scope's usage, while this | 
|  | // instance persists the outer scope's usage stats. On destruction, this | 
|  | // instance will restore the outer scope's usage stats with this scope's | 
|  | // usage added. | 
|  | memset(thread_usage_, 0, sizeof(*thread_usage_)); | 
|  | } | 
|  |  | 
|  | void ThreadHeapUsageTracker::Stop(bool usage_is_exclusive) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | DCHECK_NE(nullptr, thread_usage_); | 
|  |  | 
|  | ThreadHeapUsage current = *thread_usage_; | 
|  | if (usage_is_exclusive) { | 
|  | // Restore the outer scope. | 
|  | *thread_usage_ = usage_; | 
|  | } else { | 
|  | // Update the outer scope with the accrued inner usage. | 
|  | if (thread_usage_->max_allocated_bytes) { | 
|  | uint64_t outer_net_alloc_bytes = usage_.alloc_bytes - usage_.free_bytes; | 
|  |  | 
|  | thread_usage_->max_allocated_bytes = | 
|  | std::max(usage_.max_allocated_bytes, | 
|  | outer_net_alloc_bytes + thread_usage_->max_allocated_bytes); | 
|  | } | 
|  |  | 
|  | thread_usage_->alloc_ops += usage_.alloc_ops; | 
|  | thread_usage_->alloc_bytes += usage_.alloc_bytes; | 
|  | thread_usage_->alloc_overhead_bytes += usage_.alloc_overhead_bytes; | 
|  | thread_usage_->free_ops += usage_.free_ops; | 
|  | thread_usage_->free_bytes += usage_.free_bytes; | 
|  | } | 
|  |  | 
|  | thread_usage_ = nullptr; | 
|  | usage_ = current; | 
|  | } | 
|  |  | 
|  | ThreadHeapUsage ThreadHeapUsageTracker::GetUsageSnapshot() { | 
|  | ThreadHeapUsage* usage = GetOrCreateThreadUsage(); | 
|  | DCHECK_NE(nullptr, usage); | 
|  | return *usage; | 
|  | } | 
|  |  | 
|  | void ThreadHeapUsageTracker::EnableHeapTracking() { | 
|  | EnsureTLSInitialized(); | 
|  |  | 
|  | CHECK_EQ(false, g_heap_tracking_enabled) << "No double-enabling."; | 
|  | g_heap_tracking_enabled = true; | 
|  | CHECK(false) << "Can't enable heap tracking without the shim."; | 
|  | } | 
|  |  | 
|  | bool ThreadHeapUsageTracker::IsHeapTrackingEnabled() { | 
|  | return g_heap_tracking_enabled; | 
|  | } | 
|  |  | 
|  | void ThreadHeapUsageTracker::DisableHeapTrackingForTesting() { | 
|  | CHECK(false) << "Can't disable heap tracking without the shim."; | 
|  | DCHECK_EQ(true, g_heap_tracking_enabled) << "Heap tracking not enabled."; | 
|  | g_heap_tracking_enabled = false; | 
|  | } | 
|  |  | 
|  | base::allocator::AllocatorDispatch* | 
|  | ThreadHeapUsageTracker::GetDispatchForTesting() { | 
|  | return &allocator_dispatch; | 
|  | } | 
|  |  | 
|  | void ThreadHeapUsageTracker::EnsureTLSInitialized() { | 
|  | ignore_result(ThreadAllocationUsage()); | 
|  | } | 
|  |  | 
|  | }  // namespace debug | 
|  | }  // namespace base |