Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(111)

Unified Diff: third_party/tcmalloc/chromium/src/leak_detector.cc

Issue 986503002: components/metrics: Add runtime memory leak detector (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Remove Author label from new files; Check type in LeakDetectorValueType comparators; Add missing fi… Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/tcmalloc/chromium/src/leak_detector.cc
diff --git a/third_party/tcmalloc/chromium/src/leak_detector.cc b/third_party/tcmalloc/chromium/src/leak_detector.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cc1d106f62e29af57b07c2cec1578654056b73cb
--- /dev/null
+++ b/third_party/tcmalloc/chromium/src/leak_detector.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2015 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 <unistd.h>
+#include <link.h>
+
+#include "base/basictypes.h"
+#include "base/custom_allocator.h"
+#include "base/googleinit.h"
+#include "base/logging.h"
+#include "base/spinlock.h"
+#include <gperftools/malloc_extension.h>
+#include <gperftools/malloc_hook.h>
+#include "leak_detector_impl.h"
+#include "tcmalloc_guard.h"
+
+DEFINE_int32(sampling_factor,
+ EnvToInt("LEAK_DETECTOR_SAMPLING_FACTOR", 1),
+ "Used for sampling allocs and frees. Randomly samples "
+ "|sampling_factor|/256 of the pointers being allocated and "
+ "freed.");
+DEFINE_int32(stack_depth,
+ EnvToInt("LEAK_DETECTOR_STACK_DEPTH", 4),
+ "The number of call stack levels to unwind when profiling "
+ "allocations by call stack.");
+DEFINE_uint64(dump_interval_bytes,
+ EnvToInt("LEAK_DETECTOR_DUMP_INTERVAL_KB", 32768) * 1024,
+ "Dump allocation stats and check for memory leaks after this "
+ "many bytes have been allocated since the last dump/check. Does "
+ "not get affected by sampling.");
+DEFINE_bool(dump_leak_analysis,
+ EnvToInt("LEAK_DETECTOR_VERBOSE", false),
+ "Dump all leak analysis data, not just suspected leak reports.");
+
+DEFINE_int32(size_suspicion_threshold,
+ EnvToInt("LEAK_DETECTOR_SIZE_SUSPICION_THRESHOLD", 4),
+ "The number of times an allocation size must be suspected as a "
+ "leak before it gets reported.");
+DEFINE_int32(call_stack_suspicion_threshold,
+ EnvToInt("LEAK_DETECTOR_CALL_STACK_SUSPICION_THRESHOLD", 4),
+ "The number of times a call stack for a particular allocation "
+ "size must be suspected as a leak before it gets reported.");
+
+namespace leak_detector {
+
+namespace {
+
+// We strip out different number of stack frames in debug mode
+// because less inlining happens in that case
+#ifdef NDEBUG
+static const int kStripFrames = 2;
+#else
+static const int kStripFrames = 3;
+#endif
+
+// Use a simple spinlock for locking. Don't use a mutex, which can call malloc
+// and cause infinite recursion.
+SpinLock heap_lock(SpinLock::LINKER_INITIALIZED);
+
+// For storing the address range of the Chrome binary in memory.
+struct MappingInfo {
+ uint64 addr;
+ uint64 size;
+} chrome_mapping;
+
+// For making allocations within the leak detector that it should not track.
+LowLevelAlloc::Arena *leak_detector_arena;
+
+// Points to the active instance of the leak detector.
+// Modify this only when locked.
+LeakDetectorImpl* leak_detector = NULL;
+
+// Keep track of the total number of bytes allocated.
+// TODO(sque): This might render LeakDetectorImpl::stats redundant.
+// Modify this only when locked.
+uint64 total_alloc_size = 0;
+
+// Keep track of the total alloc size when the last dump occurred.
+// Modify this only when locked.
+uint64 last_alloc_dump_size = 0;
+
+// Dump allocation stats and check for leaks after |FLAGS_dump_interval_bytes|
+// have been allocated since the last time that was done. Should be called with
+// a lock since it modifies the global variable |last_alloc_dump_size|.
+inline void MaybeDumpStatsAndCheckForLeaks() {
+ if (total_alloc_size > last_alloc_dump_size + FLAGS_dump_interval_bytes) {
+ leak_detector->DumpStats();
+ last_alloc_dump_size = total_alloc_size;
+ leak_detector->TestForLeaks();
+ }
+}
+
+// Convert a pointer to a hash value. Returns only the upper eight bits.
+inline unsigned int PointerToHash(const void* ptr) {
+ // The input data is the pointer address, not the location in memory pointed
+ // to by the pointer.
+ const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL;
+ uint64_t value = reinterpret_cast<uint64_t>(ptr) * kMultiplier;
+ return value >> 56;
+}
+
+// Uses PointerToHash() to pseudorandomly sample |ptr|.
+inline bool ShouldSample(const void* ptr) {
+ return PointerToHash(ptr) < FLAGS_sampling_factor;
+}
+
+// Allocation/deallocation hooks for MallocHook.
+void NewHook(const void* ptr, size_t size) {
+ {
+ SpinLockHolder l(&heap_lock);
+ total_alloc_size += size;
+ }
+
+ if (!ShouldSample(ptr) || !ptr || !leak_detector)
+ return;
+
+ // Take the stack trace outside the critical section.
+ // leak_detector->ShouldGetStackTraceForSize() is const; there is no need for
+ // a lock.
+ void* stack[FLAGS_stack_depth];
+ int depth = 0;
+ if (leak_detector->ShouldGetStackTraceForSize(size)) {
+ depth = MallocHook::GetCallerStackTrace(
+ stack, FLAGS_stack_depth, kStripFrames + 1);
+ }
+
+ SpinLockHolder l(&heap_lock);
+ leak_detector->RecordAlloc(ptr, size, depth, stack);
+ MaybeDumpStatsAndCheckForLeaks();
+}
+
+void DeleteHook(const void* ptr) {
+ if (!ShouldSample(ptr) || !ptr || !leak_detector)
+ return;
+
+ SpinLockHolder l(&heap_lock);
+ leak_detector->RecordFree(ptr);
+}
+
+// Callback for dl_iterate_phdr() to find the Chrome binary mapping.
+int IterateLoadedObjects(struct dl_phdr_info *info,
+ size_t /* size */,
+ void *data) {
+ uint64 base_addr = info->dlpi_addr;
+ if (FLAGS_dump_leak_analysis) {
+ RAW_LOG(0, "%d: name=%s addr=%lx (%d segments)", getpid(),
+ info->dlpi_name, base_addr, info->dlpi_phnum);
+ }
+ for (int i = 0; i < info->dlpi_phnum; i++) {
+ const ElfW(Phdr)& header = info->dlpi_phdr[i];
+ RAW_LOG(0, "%d: \t\t header %2d: type=%x, offset=%lx, vaddr=%lx, "
+ "paddr=%lx, filesz=%lx, memsz=%lx", getpid(), i,
+ header.p_type,
+ header.p_offset,
+ header.p_vaddr,
+ header.p_paddr,
+ header.p_filesz,
+ header.p_memsz);
+ if (header.p_type == SHT_PROGBITS && header.p_offset == 0 && data) {
+ MappingInfo* mapping = static_cast<MappingInfo*>(data);
+ mapping->addr = info->dlpi_addr + header.p_offset;
+ mapping->size = header.p_memsz;
+ if (FLAGS_dump_leak_analysis) {
+ RAW_LOG(0, "%d: Chrome mapped from %lx to %lx", getpid(),
+ mapping->addr, mapping->addr + mapping->size);
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+} // namespace
+
+//----------------------------------------------------------------------
+// Starting/stopping
+//----------------------------------------------------------------------
+
+void LeakDetectorStart() {
+ // If the sampling factor is too low, don't bother enabling the leak detector.
+ if (FLAGS_sampling_factor < 1) {
+ RAW_LOG(0, "%d: Not enabling leak detector because sampling_factor = %d",
+ getpid(), FLAGS_sampling_factor);
+ return;
+ }
+
+ // Locate the Chrome binary mapping before doing anything else.
+ dl_iterate_phdr(IterateLoadedObjects, &chrome_mapping);
+
+ SpinLockHolder l(&heap_lock);
+ if (leak_detector)
+ return;
+
+ RAW_LOG(0, "%d: Starting leak detector. Sampling factor: %d", getpid(),
+ FLAGS_sampling_factor);
+
+ // This should be done before the hooks are set up, since it should
+ // call new, and we want that to be accounted for correctly.
+ MallocExtension::Initialize();
+
+ leak_detector_arena =
+ LowLevelAlloc::NewArena(0, LowLevelAlloc::DefaultArena());
+ CustomAllocator::SetArena(leak_detector_arena);
+
+ leak_detector = new(CustomAllocator::Allocate(sizeof(LeakDetectorImpl)))
+ LeakDetectorImpl(chrome_mapping.addr, chrome_mapping.size);
+
+ // We do not reset dump_count so if the user does a sequence of
+ // HeapProfilerStart/HeapProfileStop, we will get a continuous
+ // sequence of profiles.
+
+ // Now set the hooks that capture new/delete and malloc/free.
+ RAW_CHECK(MallocHook::SetNewHook(&NewHook) == NULL, "");
+ RAW_CHECK(MallocHook::SetDeleteHook(&DeleteHook) == NULL, "");
+}
+
+void LeakDetectorStop() {
+ SpinLockHolder l(&heap_lock);
+
+ if (!leak_detector)
+ return;
+
+ // Unset our new/delete hooks, checking they were set:
+ RAW_CHECK(MallocHook::SetNewHook(NULL) == &NewHook, "");
+ RAW_CHECK(MallocHook::SetDeleteHook(NULL) == &DeleteHook, "");
+
+ leak_detector->~LeakDetectorImpl();
+ CustomAllocator::Free(leak_detector, sizeof(LeakDetectorImpl));
+ leak_detector = NULL;
+
+ CustomAllocator::SetArena(NULL);
+ if (!LowLevelAlloc::DeleteArena(leak_detector_arena)) {
+ RAW_LOG(FATAL, "Memory leak in LeakDetector.");
+ }
+}
+
+} // namespace leak_detector
+
+#if defined(ENABLE_LEAK_DETECTOR)
+
+// We want to make sure tcmalloc is up and running before starting the profiler
+static const TCMallocGuard tcmalloc_initializer;
+REGISTER_MODULE_INITIALIZER(leakdetector, leak_detector::LeakDetectorStart());
+REGISTER_MODULE_DESTRUCTOR(leakdetector, leak_detector::LeakDetectorStop());
+
+#endif // defined(ENABLE_LEAK_DETECTOR)

Powered by Google App Engine
This is Rietveld 408576698