This source file includes following definitions.
- ProfilerMalloc
- ProfilerFree
- DoGetHeapProfileLocked
- GetHeapProfile
- DumpProfileLocked
- MaybeDumpProfileLocked
- RecordAlloc
- RecordFree
- NewHook
- DeleteHook
- RawInfoStackDumper
- MmapHook
- MremapHook
- MunmapHook
- SbrkHook
- HeapProfilerStart
- IsHeapProfilerRunning
- HeapProfilerStop
- HeapProfilerDump
- HeapProfilerInit
#include <config.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <algorithm>
#include <string>
#include <gperftools/heap-profiler.h>
#include "base/logging.h"
#include "base/basictypes.h"
#include "base/googleinit.h"
#include "base/commandlineflags.h"
#include "malloc_hook-inl.h"
#include "tcmalloc_guard.h"
#include <gperftools/malloc_hook.h>
#include <gperftools/malloc_extension.h>
#include "base/spinlock.h"
#include "base/low_level_alloc.h"
#include "base/sysinfo.h"
#include "heap-profile-table.h"
#include "memory_region_map.h"
#ifndef PATH_MAX
#ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN
#else
#define PATH_MAX 4096
#endif
#endif
using STL_NAMESPACE::string;
using STL_NAMESPACE::sort;
DEFINE_int64(heap_profile_allocation_interval,
EnvToInt64("HEAP_PROFILE_ALLOCATION_INTERVAL", 1 << 30 ),
"If non-zero, dump heap profiling information once every "
"specified number of bytes allocated by the program since "
"the last dump.");
DEFINE_int64(heap_profile_deallocation_interval,
EnvToInt64("HEAP_PROFILE_DEALLOCATION_INTERVAL", 0),
"If non-zero, dump heap profiling information once every "
"specified number of bytes deallocated by the program "
"since the last dump.");
DEFINE_int64(heap_profile_inuse_interval,
EnvToInt64("HEAP_PROFILE_INUSE_INTERVAL", 100 << 20 ),
"If non-zero, dump heap profiling information whenever "
"the high-water memory usage mark increases by the specified "
"number of bytes.");
DEFINE_bool(mmap_log,
EnvToBool("HEAP_PROFILE_MMAP_LOG", false),
"Should mmap/munmap calls be logged?");
DEFINE_bool(mmap_profile,
EnvToBool("HEAP_PROFILE_MMAP", false),
"If heap-profiling is on, also profile mmap, mremap, and sbrk)");
DEFINE_bool(only_mmap_profile,
EnvToBool("HEAP_PROFILE_ONLY_MMAP", false),
"If heap-profiling is on, only profile mmap, mremap, and sbrk; "
"do not profile malloc/new/etc");
static SpinLock heap_lock(SpinLock::LINKER_INITIALIZED);
static LowLevelAlloc::Arena *heap_profiler_memory;
static void* ProfilerMalloc(size_t bytes) {
return LowLevelAlloc::AllocWithArena(bytes, heap_profiler_memory);
}
static void ProfilerFree(void* p) {
LowLevelAlloc::Free(p);
}
static const int kProfileBufferSize = 1 << 20;
static char* global_profiler_buffer = NULL;
static bool is_on = false;
static bool dumping = false;
static char* filename_prefix = NULL;
static int dump_count = 0;
static int64 last_dump_alloc = 0;
static int64 last_dump_free = 0;
static int64 high_water_mark = 0;
static HeapProfileTable* heap_profile = NULL;
static char* DoGetHeapProfileLocked(char* buf, int buflen) {
if (buf == NULL || buflen < 1)
return NULL;
RAW_DCHECK(heap_lock.IsHeld(), "");
int bytes_written = 0;
if (is_on) {
if (FLAGS_mmap_profile) {
heap_profile->RefreshMMapData();
}
bytes_written = heap_profile->FillOrderedProfile(buf, buflen - 1);
if (FLAGS_mmap_profile) {
heap_profile->ClearMMapData();
}
}
buf[bytes_written] = '\0';
RAW_DCHECK(bytes_written == strlen(buf), "");
return buf;
}
extern "C" char* GetHeapProfile() {
char* buffer = reinterpret_cast<char*>(malloc(kProfileBufferSize));
SpinLockHolder l(&heap_lock);
return DoGetHeapProfileLocked(buffer, kProfileBufferSize);
}
static void NewHook(const void* ptr, size_t size);
static void DeleteHook(const void* ptr);
static void DumpProfileLocked(const char* reason) {
RAW_DCHECK(heap_lock.IsHeld(), "");
RAW_DCHECK(is_on, "");
RAW_DCHECK(!dumping, "");
if (filename_prefix == NULL) return;
dumping = true;
char file_name[1000];
dump_count++;
snprintf(file_name, sizeof(file_name), "%s.%04d%s",
filename_prefix, dump_count, HeapProfileTable::kFileExt);
RAW_VLOG(0, "Dumping heap profile to %s (%s)", file_name, reason);
RawFD fd = RawOpenForWriting(file_name);
if (fd == kIllegalRawFD) {
RAW_LOG(ERROR, "Failed dumping heap profile to %s", file_name);
dumping = false;
return;
}
if (global_profiler_buffer == NULL) {
global_profiler_buffer =
reinterpret_cast<char*>(ProfilerMalloc(kProfileBufferSize));
}
char* profile = DoGetHeapProfileLocked(global_profiler_buffer,
kProfileBufferSize);
RawWrite(fd, profile, strlen(profile));
RawClose(fd);
dumping = false;
}
static void MaybeDumpProfileLocked() {
if (!dumping) {
const HeapProfileTable::Stats& total = heap_profile->total();
const int64 inuse_bytes = total.alloc_size - total.free_size;
bool need_to_dump = false;
char buf[128];
if (FLAGS_heap_profile_allocation_interval > 0 &&
total.alloc_size >=
last_dump_alloc + FLAGS_heap_profile_allocation_interval) {
snprintf(buf, sizeof(buf), ("%"PRId64" MB allocated cumulatively, "
"%"PRId64" MB currently in use"),
total.alloc_size >> 20, inuse_bytes >> 20);
need_to_dump = true;
} else if (FLAGS_heap_profile_deallocation_interval > 0 &&
total.free_size >=
last_dump_free + FLAGS_heap_profile_deallocation_interval) {
snprintf(buf, sizeof(buf), ("%"PRId64" MB freed cumulatively, "
"%"PRId64" MB currently in use"),
total.free_size >> 20, inuse_bytes >> 20);
need_to_dump = true;
} else if (FLAGS_heap_profile_inuse_interval > 0 &&
inuse_bytes >
high_water_mark + FLAGS_heap_profile_inuse_interval) {
snprintf(buf, sizeof(buf), "%"PRId64" MB currently in use",
inuse_bytes >> 20);
need_to_dump = true;
}
if (need_to_dump) {
DumpProfileLocked(buf);
last_dump_alloc = total.alloc_size;
last_dump_free = total.free_size;
if (inuse_bytes > high_water_mark)
high_water_mark = inuse_bytes;
}
}
}
static void RecordAlloc(const void* ptr, size_t bytes, int skip_count) {
void* stack[HeapProfileTable::kMaxStackDepth];
int depth = HeapProfileTable::GetCallerStackTrace(skip_count + 1, stack);
SpinLockHolder l(&heap_lock);
if (is_on) {
heap_profile->RecordAlloc(ptr, bytes, depth, stack);
MaybeDumpProfileLocked();
}
}
static void RecordFree(const void* ptr) {
SpinLockHolder l(&heap_lock);
if (is_on) {
heap_profile->RecordFree(ptr);
MaybeDumpProfileLocked();
}
}
void NewHook(const void* ptr, size_t size) {
if (ptr != NULL) RecordAlloc(ptr, size, 0);
}
void DeleteHook(const void* ptr) {
if (ptr != NULL) RecordFree(ptr);
}
#ifdef TODO_REENABLE_STACK_TRACING
static void RawInfoStackDumper(const char* message, void*) {
RAW_LOG(INFO, "%.*s", static_cast<int>(strlen(message) - 1), message);
}
#endif
static void MmapHook(const void* result, const void* start, size_t size,
int prot, int flags, int fd, off_t offset) {
if (FLAGS_mmap_log) {
RAW_LOG(INFO,
"mmap(start=0x%"PRIxPTR", len=%"PRIuS", prot=0x%x, flags=0x%x, "
"fd=%d, offset=0x%x) = 0x%"PRIxPTR"",
(uintptr_t) start, size, prot, flags, fd, (unsigned int) offset,
(uintptr_t) result);
#ifdef TODO_REENABLE_STACK_TRACING
DumpStackTrace(1, RawInfoStackDumper, NULL);
#endif
}
}
static void MremapHook(const void* result, const void* old_addr,
size_t old_size, size_t new_size,
int flags, const void* new_addr) {
if (FLAGS_mmap_log) {
RAW_LOG(INFO,
"mremap(old_addr=0x%"PRIxPTR", old_size=%"PRIuS", "
"new_size=%"PRIuS", flags=0x%x, new_addr=0x%"PRIxPTR") = "
"0x%"PRIxPTR"",
(uintptr_t) old_addr, old_size, new_size, flags,
(uintptr_t) new_addr, (uintptr_t) result);
#ifdef TODO_REENABLE_STACK_TRACING
DumpStackTrace(1, RawInfoStackDumper, NULL);
#endif
}
}
static void MunmapHook(const void* ptr, size_t size) {
if (FLAGS_mmap_log) {
RAW_LOG(INFO, "munmap(start=0x%"PRIxPTR", len=%"PRIuS")",
(uintptr_t) ptr, size);
#ifdef TODO_REENABLE_STACK_TRACING
DumpStackTrace(1, RawInfoStackDumper, NULL);
#endif
}
}
static void SbrkHook(const void* result, ptrdiff_t increment) {
if (FLAGS_mmap_log) {
RAW_LOG(INFO, "sbrk(inc=%"PRIdS") = 0x%"PRIxPTR"",
increment, (uintptr_t) result);
#ifdef TODO_REENABLE_STACK_TRACING
DumpStackTrace(1, RawInfoStackDumper, NULL);
#endif
}
}
extern "C" void HeapProfilerStart(const char* prefix) {
SpinLockHolder l(&heap_lock);
if (is_on) return;
is_on = true;
RAW_VLOG(0, "Starting tracking the heap");
MallocExtension::Initialize();
if (FLAGS_only_mmap_profile) {
FLAGS_mmap_profile = true;
}
if (FLAGS_mmap_profile) {
MemoryRegionMap::Init(HeapProfileTable::kMaxStackDepth);
}
if (FLAGS_mmap_log) {
RAW_CHECK(MallocHook::AddMmapHook(&MmapHook), "");
RAW_CHECK(MallocHook::AddMremapHook(&MremapHook), "");
RAW_CHECK(MallocHook::AddMunmapHook(&MunmapHook), "");
RAW_CHECK(MallocHook::AddSbrkHook(&SbrkHook), "");
}
heap_profiler_memory =
LowLevelAlloc::NewArena(0, LowLevelAlloc::DefaultArena());
global_profiler_buffer =
reinterpret_cast<char*>(ProfilerMalloc(kProfileBufferSize));
heap_profile = new(ProfilerMalloc(sizeof(HeapProfileTable)))
HeapProfileTable(ProfilerMalloc, ProfilerFree);
last_dump_alloc = 0;
last_dump_free = 0;
high_water_mark = 0;
if (FLAGS_only_mmap_profile == false) {
RAW_CHECK(MallocHook::AddNewHook(&NewHook), "");
RAW_CHECK(MallocHook::AddDeleteHook(&DeleteHook), "");
}
RAW_DCHECK(filename_prefix == NULL, "");
const int prefix_length = strlen(prefix);
filename_prefix = reinterpret_cast<char*>(ProfilerMalloc(prefix_length + 1));
memcpy(filename_prefix, prefix, prefix_length);
filename_prefix[prefix_length] = '\0';
}
extern "C" int IsHeapProfilerRunning() {
SpinLockHolder l(&heap_lock);
return is_on ? 1 : 0;
}
extern "C" void HeapProfilerStop() {
SpinLockHolder l(&heap_lock);
if (!is_on) return;
if (FLAGS_only_mmap_profile == false) {
RAW_CHECK(MallocHook::RemoveNewHook(&NewHook), "");
RAW_CHECK(MallocHook::RemoveDeleteHook(&DeleteHook), "");
}
if (FLAGS_mmap_log) {
RAW_CHECK(MallocHook::RemoveMmapHook(&MmapHook), "");
RAW_CHECK(MallocHook::RemoveMremapHook(&MremapHook), "");
RAW_CHECK(MallocHook::RemoveSbrkHook(&SbrkHook), "");
RAW_CHECK(MallocHook::RemoveMunmapHook(&MunmapHook), "");
}
heap_profile->~HeapProfileTable();
ProfilerFree(heap_profile);
heap_profile = NULL;
ProfilerFree(global_profiler_buffer);
ProfilerFree(filename_prefix);
filename_prefix = NULL;
if (!LowLevelAlloc::DeleteArena(heap_profiler_memory)) {
RAW_LOG(FATAL, "Memory leak in HeapProfiler:");
}
if (FLAGS_mmap_profile) {
MemoryRegionMap::Shutdown();
}
is_on = false;
}
extern "C" void HeapProfilerDump(const char *reason) {
SpinLockHolder l(&heap_lock);
if (is_on && !dumping) {
DumpProfileLocked(reason);
}
}
static void HeapProfilerInit() {
char fname[PATH_MAX];
if (!GetUniquePathFromEnv("HEAPPROFILE", fname)) {
return;
}
#ifdef HAVE_GETEUID
if (getuid() != geteuid()) {
RAW_LOG(WARNING, ("HeapProfiler: ignoring HEAPPROFILE because "
"program seems to be setuid\n"));
return;
}
#endif
HeapProfileTable::CleanupOldProfiles(fname);
HeapProfilerStart(fname);
}
struct HeapProfileEndWriter {
~HeapProfileEndWriter() { HeapProfilerDump("Exiting"); }
};
static const TCMallocGuard tcmalloc_initializer;
REGISTER_MODULE_INITIALIZER(heapprofiler, HeapProfilerInit());
static HeapProfileEndWriter heap_profile_end_writer;