feat update
Some checks failed
linux-mips64-gcc / linux-gcc-mips64el (Debug) (push) Successful in 1m58s
linux-aarch64-cpu-gcc / linux-gcc-aarch64 (push) Successful in 2m8s
linux-x64-gcc / linux-gcc (Release) (push) Successful in 2m13s
linux-arm-gcc / linux-gcc-armhf (push) Failing after 2m22s
linux-mips64-gcc / linux-gcc-mips64el (Release) (push) Successful in 3m6s
linux-x64-gcc / linux-gcc (Debug) (push) Successful in 3m49s

This commit is contained in:
tqcq 2024-04-29 16:32:06 +08:00
parent 4a1d1c9aa1
commit a137ee1f1c
4 changed files with 365 additions and 348 deletions

View File

@ -55,11 +55,11 @@
/* Annoying stuff for windows; makes sure clients can import these functions */ /* Annoying stuff for windows; makes sure clients can import these functions */
#ifndef PERFTOOLS_DLL_DECL #ifndef PERFTOOLS_DLL_DECL
# ifdef _WIN32 #ifdef _WIN32
# define PERFTOOLS_DLL_DECL __declspec(dllimport) #define PERFTOOLS_DLL_DECL __declspec(dllimport)
# else #else
# define PERFTOOLS_DLL_DECL #define PERFTOOLS_DLL_DECL
# endif #endif
#endif #endif
/* All this code should be usable from within C apps. */ /* All this code should be usable from within C apps. */
@ -70,7 +70,7 @@ extern "C" {
/* Start profiling and arrange to write profile data to file names /* Start profiling and arrange to write profile data to file names
* of the form: "prefix.0000", "prefix.0001", ... * of the form: "prefix.0000", "prefix.0001", ...
*/ */
PERFTOOLS_DLL_DECL void HeapProfilerStart(const char* prefix); PERFTOOLS_DLL_DECL void HeapProfilerStart(const char *prefix);
/* Returns non-zero if we are currently profiling the heap. (Returns /* Returns non-zero if we are currently profiling the heap. (Returns
* an int rather than a bool so it's usable from C.) This is true * an int rather than a bool so it's usable from C.) This is true
@ -96,10 +96,10 @@ PERFTOOLS_DLL_DECL void HeapProfilerDump(const char *reason);
* The returned pointer is a '\0'-terminated string allocated using malloc() * The returned pointer is a '\0'-terminated string allocated using malloc()
* and should be free()-ed as soon as the caller does not need it anymore. * and should be free()-ed as soon as the caller does not need it anymore.
*/ */
PERFTOOLS_DLL_DECL char* GetHeapProfile(); PERFTOOLS_DLL_DECL char *GetHeapProfile();
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" }// extern "C"
#endif #endif
#endif /* BASE_HEAP_PROFILER_H_ */ #endif /* BASE_HEAP_PROFILER_H_ */

View File

@ -42,41 +42,41 @@
#endif #endif
#include <inttypes.h> #include <inttypes.h>
#ifdef HAVE_FCNTL_H #ifdef HAVE_FCNTL_H
#include <fcntl.h> // for open() #include <fcntl.h>// for open()
#endif #endif
#ifdef HAVE_MMAP #ifdef HAVE_MMAP
#include <sys/mman.h> #include <sys/mman.h>
#endif #endif
#include <errno.h>
#include <assert.h> #include <assert.h>
#include <sys/types.h> #include <errno.h>
#include <signal.h> #include <signal.h>
#include <sys/types.h>
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <gperftools/heap-profiler.h> #include <gperftools/heap-profiler.h>
#include "base/logging.h" #include "base/basictypes.h"// for PRId64, among other things
#include "base/basictypes.h" // for PRId64, among other things
#include "base/googleinit.h"
#include "base/commandlineflags.h" #include "base/commandlineflags.h"
#include "malloc_hook-inl.h" #include "base/googleinit.h"
#include "tcmalloc_guard.h" #include "base/logging.h"
#include <gperftools/malloc_hook.h>
#include <gperftools/malloc_extension.h>
#include "base/spinlock.h"
#include "base/low_level_alloc.h" #include "base/low_level_alloc.h"
#include "base/sysinfo.h" // for GetUniquePathFromEnv() #include "base/spinlock.h"
#include "base/sysinfo.h"// for GetUniquePathFromEnv()
#include "heap-profile-table.h" #include "heap-profile-table.h"
#include "malloc_hook-inl.h"
#include "memory_region_map.h" #include "memory_region_map.h"
#include "mmap_hook.h" #include "mmap_hook.h"
#include "tcmalloc_guard.h"
#include <gperftools/malloc_extension.h>
#include <gperftools/malloc_hook.h>
#ifndef PATH_MAX #ifndef PATH_MAX
#ifdef MAXPATHLEN #ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN #define PATH_MAX MAXPATHLEN
#else #else
#define PATH_MAX 4096 // seems conservative for max filename len! #define PATH_MAX 4096// seems conservative for max filename len!
#endif #endif
#endif #endif
@ -110,9 +110,7 @@ DEFINE_int64(heap_profile_time_interval,
EnvToInt64("HEAP_PROFILE_TIME_INTERVAL", 0), EnvToInt64("HEAP_PROFILE_TIME_INTERVAL", 0),
"If non-zero, dump heap profiling information once every " "If non-zero, dump heap profiling information once every "
"specified number of seconds since the last dump."); "specified number of seconds since the last dump.");
DEFINE_bool(mmap_log, DEFINE_bool(mmap_log, EnvToBool("HEAP_PROFILE_MMAP_LOG", false), "Should mmap/munmap calls be logged?");
EnvToBool("HEAP_PROFILE_MMAP_LOG", false),
"Should mmap/munmap calls be logged?");
DEFINE_bool(mmap_profile, DEFINE_bool(mmap_profile,
EnvToBool("HEAP_PROFILE_MMAP", false), EnvToBool("HEAP_PROFILE_MMAP", false),
"If heap-profiling is on, also profile mmap, mremap, and sbrk)"); "If heap-profiling is on, also profile mmap, mremap, and sbrk)");
@ -121,7 +119,6 @@ DEFINE_bool(only_mmap_profile,
"If heap-profiling is on, only profile mmap, mremap, and sbrk; " "If heap-profiling is on, only profile mmap, mremap, and sbrk; "
"do not profile malloc/new/etc"); "do not profile malloc/new/etc");
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Locking // Locking
//---------------------------------------------------------------------- //----------------------------------------------------------------------
@ -140,11 +137,16 @@ static SpinLock heap_lock(SpinLock::LINKER_INITIALIZED);
static LowLevelAlloc::Arena *heap_profiler_memory; static LowLevelAlloc::Arena *heap_profiler_memory;
static void* ProfilerMalloc(size_t bytes) { static void *
return LowLevelAlloc::AllocWithArena(bytes, heap_profiler_memory); ProfilerMalloc(size_t bytes)
{
return LowLevelAlloc::AllocWithArena(bytes, heap_profiler_memory);
} }
static void ProfilerFree(void* p) {
LowLevelAlloc::Free(p); static void
ProfilerFree(void *p)
{
LowLevelAlloc::Free(p);
} }
// We use buffers of this size in DoGetHeapProfile. // We use buffers of this size in DoGetHeapProfile.
@ -155,106 +157,107 @@ static const int kProfileBufferSize = 1 << 20;
// will be used by HeapProfileEndWriter when the application has to // will be used by HeapProfileEndWriter when the application has to
// exit due to out-of-memory. This buffer is allocated in // exit due to out-of-memory. This buffer is allocated in
// HeapProfilerStart. Access to this must be protected by heap_lock. // HeapProfilerStart. Access to this must be protected by heap_lock.
static char* global_profiler_buffer = NULL; static char *global_profiler_buffer = NULL;
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Profiling control/state data // Profiling control/state data
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Access to all of these is protected by heap_lock. // Access to all of these is protected by heap_lock.
static bool is_on = false; // If are on as a subsytem. static bool is_on = false;// If are on as a subsytem.
static bool dumping = false; // Dumping status to prevent recursion static bool dumping = false;// Dumping status to prevent recursion
static char* filename_prefix = NULL; // Prefix used for profile file names static char *filename_prefix = NULL; // Prefix used for profile file names
// (NULL if no need for dumping yet) // (NULL if no need for dumping yet)
static int dump_count = 0; // How many dumps so far static int dump_count = 0; // How many dumps so far
static int64 last_dump_alloc = 0; // alloc_size when did we last dump static int64 last_dump_alloc = 0; // alloc_size when did we last dump
static int64 last_dump_free = 0; // free_size when did we last dump static int64 last_dump_free = 0; // free_size when did we last dump
static int64 high_water_mark = 0; // In-use-bytes at last high-water dump static int64 high_water_mark = 0; // In-use-bytes at last high-water dump
static int64 last_dump_time = 0; // The time of the last dump static int64 last_dump_time = 0; // The time of the last dump
static HeapProfileTable* heap_profile = NULL; // the heap profile table static HeapProfileTable *heap_profile = NULL;// the heap profile table
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Profile generation // Profile generation
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Input must be a buffer of size at least 1MB. // Input must be a buffer of size at least 1MB.
static char* DoGetHeapProfileLocked(char* buf, int buflen) { static char *
// We used to be smarter about estimating the required memory and DoGetHeapProfileLocked(char *buf, int buflen)
// then capping it to 1MB and generating the profile into that. {
if (buf == NULL || buflen < 1) // We used to be smarter about estimating the required memory and
return NULL; // then capping it to 1MB and generating the profile into that.
if (buf == NULL || buflen < 1) return NULL;
RAW_DCHECK(heap_lock.IsHeld(), ""); RAW_DCHECK(heap_lock.IsHeld(), "");
int bytes_written = 0; int bytes_written = 0;
if (is_on) { if (is_on) {
HeapProfileTable::Stats const stats = heap_profile->total(); HeapProfileTable::Stats const stats = heap_profile->total();
(void)stats; // avoid an unused-variable warning in non-debug mode. (void) stats;// avoid an unused-variable warning in non-debug mode.
bytes_written = heap_profile->FillOrderedProfile(buf, buflen - 1); bytes_written = heap_profile->FillOrderedProfile(buf, buflen - 1);
// FillOrderedProfile should not reduce the set of active mmap-ed regions, // FillOrderedProfile should not reduce the set of active mmap-ed regions,
// hence MemoryRegionMap will let us remove everything we've added above: // hence MemoryRegionMap will let us remove everything we've added above:
RAW_DCHECK(stats.Equivalent(heap_profile->total()), ""); RAW_DCHECK(stats.Equivalent(heap_profile->total()), "");
// if this fails, we somehow removed by FillOrderedProfile // if this fails, we somehow removed by FillOrderedProfile
// more than we have added. // more than we have added.
} }
buf[bytes_written] = '\0'; buf[bytes_written] = '\0';
RAW_DCHECK(bytes_written == strlen(buf), ""); RAW_DCHECK(bytes_written == strlen(buf), "");
return buf; return buf;
} }
extern "C" char* GetHeapProfile() { extern "C" char *
// Use normal malloc: we return the profile to the user to free it: GetHeapProfile()
char* buffer = reinterpret_cast<char*>(malloc(kProfileBufferSize)); {
SpinLockHolder l(&heap_lock); // Use normal malloc: we return the profile to the user to free it:
return DoGetHeapProfileLocked(buffer, kProfileBufferSize); char *buffer = reinterpret_cast<char *>(malloc(kProfileBufferSize));
SpinLockHolder l(&heap_lock);
return DoGetHeapProfileLocked(buffer, kProfileBufferSize);
} }
// defined below // defined below
static void NewHook(const void* ptr, size_t size); static void NewHook(const void *ptr, size_t size);
static void DeleteHook(const void* ptr); static void DeleteHook(const void *ptr);
// Helper for HeapProfilerDump. // Helper for HeapProfilerDump.
static void DumpProfileLocked(const char* reason) { static void
RAW_DCHECK(heap_lock.IsHeld(), ""); DumpProfileLocked(const char *reason)
RAW_DCHECK(is_on, ""); {
RAW_DCHECK(!dumping, ""); RAW_DCHECK(heap_lock.IsHeld(), "");
RAW_DCHECK(is_on, "");
RAW_DCHECK(!dumping, "");
if (filename_prefix == NULL) return; // we do not yet need dumping if (filename_prefix == NULL) return;// we do not yet need dumping
dumping = true; dumping = true;
// Make file name // Make file name
char file_name[1000]; char file_name[1000];
dump_count++; dump_count++;
snprintf(file_name, sizeof(file_name), "%s.%04d%s", snprintf(file_name, sizeof(file_name), "%s.%04d%s", filename_prefix, dump_count, HeapProfileTable::kFileExt);
filename_prefix, dump_count, HeapProfileTable::kFileExt);
// Dump the profile
RAW_VLOG(0, "Dumping heap profile to %s (%s)", file_name, reason);
// We must use file routines that don't access memory, since we hold
// a memory lock now.
RawFD fd = RawOpenForWriting(file_name);
if (fd == kIllegalRawFD) {
RAW_LOG(ERROR, "Failed dumping heap profile to %s. Numeric errno is %d", file_name, errno);
dumping = false;
return;
}
// This case may be impossible, but it's best to be safe.
// It's safe to use the global buffer: we're protected by heap_lock.
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);
// Dump the profile
RAW_VLOG(0, "Dumping heap profile to %s (%s)", file_name, reason);
// We must use file routines that don't access memory, since we hold
// a memory lock now.
RawFD fd = RawOpenForWriting(file_name);
if (fd == kIllegalRawFD) {
RAW_LOG(ERROR, "Failed dumping heap profile to %s. Numeric errno is %d", file_name, errno);
dumping = false; dumping = false;
return;
}
// This case may be impossible, but it's best to be safe.
// It's safe to use the global buffer: we're protected by heap_lock.
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;
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
@ -263,73 +266,78 @@ static void DumpProfileLocked(const char* reason) {
// Dump a profile after either an allocation or deallocation, if // Dump a profile after either an allocation or deallocation, if
// the memory use has changed enough since the last dump. // the memory use has changed enough since the last dump.
static void MaybeDumpProfileLocked() { static void
if (!dumping) { MaybeDumpProfileLocked()
const HeapProfileTable::Stats& total = heap_profile->total(); {
const int64_t inuse_bytes = total.alloc_size - total.free_size; if (!dumping) {
bool need_to_dump = false; const HeapProfileTable::Stats &total = heap_profile->total();
char buf[128]; const int64_t inuse_bytes = total.alloc_size - total.free_size;
bool need_to_dump = false;
char buf[128];
if (FLAGS_heap_profile_allocation_interval > 0 && if (FLAGS_heap_profile_allocation_interval > 0
total.alloc_size >= && total.alloc_size >= last_dump_alloc + FLAGS_heap_profile_allocation_interval) {
last_dump_alloc + FLAGS_heap_profile_allocation_interval) { snprintf(buf,
snprintf(buf, sizeof(buf), ("%" PRId64 " MB allocated cumulatively, " sizeof(buf),
"%" PRId64 " MB currently in use"), ("%" PRId64 " MB allocated cumulatively, "
total.alloc_size >> 20, inuse_bytes >> 20); "%" PRId64 " MB currently in use"),
need_to_dump = true; total.alloc_size >> 20,
} else if (FLAGS_heap_profile_deallocation_interval > 0 && inuse_bytes >> 20);
total.free_size >= need_to_dump = true;
last_dump_free + FLAGS_heap_profile_deallocation_interval) { } else if (FLAGS_heap_profile_deallocation_interval > 0
snprintf(buf, sizeof(buf), ("%" PRId64 " MB freed cumulatively, " && total.free_size >= last_dump_free + FLAGS_heap_profile_deallocation_interval) {
"%" PRId64 " MB currently in use"), snprintf(buf,
total.free_size >> 20, inuse_bytes >> 20); sizeof(buf),
need_to_dump = true; ("%" PRId64 " MB freed cumulatively, "
} else if (FLAGS_heap_profile_inuse_interval > 0 && "%" PRId64 " MB currently in use"),
inuse_bytes > total.free_size >> 20,
high_water_mark + FLAGS_heap_profile_inuse_interval) { inuse_bytes >> 20);
snprintf(buf, sizeof(buf), "%" PRId64 " MB currently in use", need_to_dump = true;
inuse_bytes >> 20); } else if (FLAGS_heap_profile_inuse_interval > 0
need_to_dump = true; && inuse_bytes > high_water_mark + FLAGS_heap_profile_inuse_interval) {
} else if (FLAGS_heap_profile_time_interval > 0 ) { snprintf(buf, sizeof(buf), "%" PRId64 " MB currently in use", inuse_bytes >> 20);
int64 current_time = time(NULL); need_to_dump = true;
if (current_time - last_dump_time >= } else if (FLAGS_heap_profile_time_interval > 0) {
FLAGS_heap_profile_time_interval) { int64 current_time = time(NULL);
snprintf(buf, sizeof(buf), "%" PRId64 " sec since the last dump", if (current_time - last_dump_time >= FLAGS_heap_profile_time_interval) {
current_time - last_dump_time); snprintf(buf, sizeof(buf), "%" PRId64 " sec since the last dump", current_time - last_dump_time);
need_to_dump = true; need_to_dump = true;
last_dump_time = current_time; last_dump_time = current_time;
} }
} }
if (need_to_dump) { if (need_to_dump) {
DumpProfileLocked(buf); DumpProfileLocked(buf);
last_dump_alloc = total.alloc_size; last_dump_alloc = total.alloc_size;
last_dump_free = total.free_size; last_dump_free = total.free_size;
if (inuse_bytes > high_water_mark) if (inuse_bytes > high_water_mark) high_water_mark = inuse_bytes;
high_water_mark = inuse_bytes; }
} }
}
} }
// Record an allocation in the profile. // Record an allocation in the profile.
static void RecordAlloc(const void* ptr, size_t bytes, int skip_count) { static void
// Take the stack trace outside the critical section. RecordAlloc(const void *ptr, size_t bytes, int skip_count)
void* stack[HeapProfileTable::kMaxStackDepth]; {
int depth = HeapProfileTable::GetCallerStackTrace(skip_count + 1, stack); // Take the stack trace outside the critical section.
SpinLockHolder l(&heap_lock); void *stack[HeapProfileTable::kMaxStackDepth];
if (is_on) { int depth = HeapProfileTable::GetCallerStackTrace(skip_count + 1, stack);
heap_profile->RecordAlloc(ptr, bytes, depth, stack); SpinLockHolder l(&heap_lock);
MaybeDumpProfileLocked(); if (is_on) {
} heap_profile->RecordAlloc(ptr, bytes, depth, stack);
MaybeDumpProfileLocked();
}
} }
// Record a deallocation in the profile. // Record a deallocation in the profile.
static void RecordFree(const void* ptr) { static void
SpinLockHolder l(&heap_lock); RecordFree(const void *ptr)
if (is_on) { {
heap_profile->RecordFree(ptr); SpinLockHolder l(&heap_lock);
MaybeDumpProfileLocked(); if (is_on) {
} heap_profile->RecordFree(ptr);
MaybeDumpProfileLocked();
}
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
@ -337,256 +345,263 @@ static void RecordFree(const void* ptr) {
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// static // static
void NewHook(const void* ptr, size_t size) { void
if (ptr != NULL) RecordAlloc(ptr, size, 0); NewHook(const void *ptr, size_t size)
{
if (ptr != NULL) RecordAlloc(ptr, size, 0);
} }
// static // static
void DeleteHook(const void* ptr) { void
if (ptr != NULL) RecordFree(ptr); DeleteHook(const void *ptr)
{
if (ptr != NULL) RecordFree(ptr);
} }
static tcmalloc::MappingHookSpace mmap_logging_hook_space; static tcmalloc::MappingHookSpace mmap_logging_hook_space;
static void LogMappingEvent(const tcmalloc::MappingEvent& evt) { static void
if (!FLAGS_mmap_log) { LogMappingEvent(const tcmalloc::MappingEvent &evt)
return; {
} if (!FLAGS_mmap_log) { return; }
if (evt.file_valid) { if (evt.file_valid) {
// We use PRIxPTR not just '%p' to avoid deadlocks // We use PRIxPTR not just '%p' to avoid deadlocks
// in pretty-printing of NULL as "nil". // in pretty-printing of NULL as "nil".
// TODO(maxim): instead should use a safe snprintf reimplementation // TODO(maxim): instead should use a safe snprintf reimplementation
RAW_LOG(INFO, RAW_LOG(INFO,
"mmap(start=0x%" PRIxPTR ", len=%zu, prot=0x%x, flags=0x%x, " "mmap(start=0x%" PRIxPTR
"fd=%d, offset=0x%llx) = 0x%" PRIxPTR "", ", len=%zu, prot=0x%x, flags=0x%x, "
(uintptr_t) evt.before_address, evt.after_length, evt.prot, "fd=%d, offset=0x%llx) = 0x%" PRIxPTR "",
evt.flags, evt.file_fd, (unsigned long long) evt.file_off, (uintptr_t) evt.before_address,
(uintptr_t) evt.after_address); evt.after_length,
} else if (evt.after_valid && evt.before_valid) { evt.prot,
// We use PRIxPTR not just '%p' to avoid deadlocks evt.flags,
// in pretty-printing of NULL as "nil". evt.file_fd,
// TODO(maxim): instead should use a safe snprintf reimplementation (unsigned long long) evt.file_off,
RAW_LOG(INFO, (uintptr_t) evt.after_address);
"mremap(old_addr=0x%" PRIxPTR ", old_size=%zu, " } else if (evt.after_valid && evt.before_valid) {
"new_size=%zu, flags=0x%x, new_addr=0x%" PRIxPTR ") = " // We use PRIxPTR not just '%p' to avoid deadlocks
"0x%" PRIxPTR "", // in pretty-printing of NULL as "nil".
(uintptr_t) evt.before_address, evt.before_length, evt.after_length, evt.flags, // TODO(maxim): instead should use a safe snprintf reimplementation
(uintptr_t) evt.after_address, (uintptr_t) evt.after_address); RAW_LOG(INFO,
} else if (evt.is_sbrk) { "mremap(old_addr=0x%" PRIxPTR
intptr_t increment; ", old_size=%zu, "
uintptr_t result; "new_size=%zu, flags=0x%x, new_addr=0x%" PRIxPTR
if (evt.after_valid) { ") = "
increment = evt.after_length; "0x%" PRIxPTR "",
result = reinterpret_cast<uintptr_t>(evt.after_address) + evt.after_length; (uintptr_t) evt.before_address,
} else { evt.before_length,
increment = -static_cast<intptr_t>(evt.before_length); evt.after_length,
result = reinterpret_cast<uintptr_t>(evt.before_address); evt.flags,
(uintptr_t) evt.after_address,
(uintptr_t) evt.after_address);
} else if (evt.is_sbrk) {
intptr_t increment;
uintptr_t result;
if (evt.after_valid) {
increment = evt.after_length;
result = reinterpret_cast<uintptr_t>(evt.after_address) + evt.after_length;
} else {
increment = -static_cast<intptr_t>(evt.before_length);
result = reinterpret_cast<uintptr_t>(evt.before_address);
}
RAW_LOG(INFO, "sbrk(inc=%zd) = 0x%" PRIxPTR "", increment, (uintptr_t) result);
} else if (evt.before_valid) {
// We use PRIxPTR not just '%p' to avoid deadlocks
// in pretty-printing of NULL as "nil".
// TODO(maxim): instead should use a safe snprintf reimplementation
RAW_LOG(INFO, "munmap(start=0x%" PRIxPTR ", len=%zu)", (uintptr_t) evt.before_address, evt.before_length);
} }
RAW_LOG(INFO, "sbrk(inc=%zd) = 0x%" PRIxPTR "",
increment, (uintptr_t) result);
} else if (evt.before_valid) {
// We use PRIxPTR not just '%p' to avoid deadlocks
// in pretty-printing of NULL as "nil".
// TODO(maxim): instead should use a safe snprintf reimplementation
RAW_LOG(INFO, "munmap(start=0x%" PRIxPTR ", len=%zu)",
(uintptr_t) evt.before_address, evt.before_length);
}
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Starting/stopping/dumping // Starting/stopping/dumping
//---------------------------------------------------------------------- //----------------------------------------------------------------------
extern "C" void HeapProfilerStart(const char* prefix) { extern "C" void
SpinLockHolder l(&heap_lock); HeapProfilerStart(const char *prefix)
{
SpinLockHolder l(&heap_lock);
if (is_on) return; if (is_on) return;
is_on = true; is_on = true;
RAW_VLOG(0, "Starting tracking the heap"); RAW_VLOG(0, "Starting tracking the heap");
// This should be done before the hooks are set up, since it should // This should be done before the hooks are set up, since it should
// call new, and we want that to be accounted for correctly. // call new, and we want that to be accounted for correctly.
MallocExtension::Initialize(); MallocExtension::Initialize();
if (FLAGS_only_mmap_profile) { if (FLAGS_only_mmap_profile) { FLAGS_mmap_profile = true; }
FLAGS_mmap_profile = true;
}
if (FLAGS_mmap_profile) { if (FLAGS_mmap_profile) {
// Ask MemoryRegionMap to record all mmap, mremap, and sbrk // Ask MemoryRegionMap to record all mmap, mremap, and sbrk
// call stack traces of at least size kMaxStackDepth: // call stack traces of at least size kMaxStackDepth:
MemoryRegionMap::Init(HeapProfileTable::kMaxStackDepth, MemoryRegionMap::Init(HeapProfileTable::kMaxStackDepth,
/* use_buckets */ true); /* use_buckets */ true);
} }
if (FLAGS_mmap_log) { if (FLAGS_mmap_log) {
// Install our hooks to do the logging: // Install our hooks to do the logging:
tcmalloc::HookMMapEvents(&mmap_logging_hook_space, LogMappingEvent); tcmalloc::HookMMapEvents(&mmap_logging_hook_space, LogMappingEvent);
} }
heap_profiler_memory = heap_profiler_memory = LowLevelAlloc::NewArena(0, LowLevelAlloc::DefaultArena());
LowLevelAlloc::NewArena(0, LowLevelAlloc::DefaultArena());
// Reserve space now for the heap profiler, so we can still write a // Reserve space now for the heap profiler, so we can still write a
// heap profile even if the application runs out of memory. // heap profile even if the application runs out of memory.
global_profiler_buffer = global_profiler_buffer = reinterpret_cast<char *>(ProfilerMalloc(kProfileBufferSize));
reinterpret_cast<char*>(ProfilerMalloc(kProfileBufferSize));
heap_profile = new(ProfilerMalloc(sizeof(HeapProfileTable))) heap_profile = new (ProfilerMalloc(sizeof(HeapProfileTable)))
HeapProfileTable(ProfilerMalloc, ProfilerFree, FLAGS_mmap_profile); HeapProfileTable(ProfilerMalloc, ProfilerFree, FLAGS_mmap_profile);
last_dump_alloc = 0; last_dump_alloc = 0;
last_dump_free = 0; last_dump_free = 0;
high_water_mark = 0; high_water_mark = 0;
last_dump_time = 0; last_dump_time = 0;
// We do not reset dump_count so if the user does a sequence of // We do not reset dump_count so if the user does a sequence of
// HeapProfilerStart/HeapProfileStop, we will get a continuous // HeapProfilerStart/HeapProfileStop, we will get a continuous
// sequence of profiles. // sequence of profiles.
if (FLAGS_only_mmap_profile == false) { if (FLAGS_only_mmap_profile == false) {
// Now set the hooks that capture new/delete and malloc/free. // Now set the hooks that capture new/delete and malloc/free.
RAW_CHECK(MallocHook::AddNewHook(&NewHook), ""); RAW_CHECK(MallocHook::AddNewHook(&NewHook), "");
RAW_CHECK(MallocHook::AddDeleteHook(&DeleteHook), ""); RAW_CHECK(MallocHook::AddDeleteHook(&DeleteHook), "");
} }
// Copy filename prefix // Copy filename prefix
RAW_DCHECK(filename_prefix == NULL, ""); RAW_DCHECK(filename_prefix == NULL, "");
const int prefix_length = strlen(prefix); const int prefix_length = strlen(prefix);
filename_prefix = reinterpret_cast<char*>(ProfilerMalloc(prefix_length + 1)); filename_prefix = reinterpret_cast<char *>(ProfilerMalloc(prefix_length + 1));
memcpy(filename_prefix, prefix, prefix_length); memcpy(filename_prefix, prefix, prefix_length);
filename_prefix[prefix_length] = '\0'; filename_prefix[prefix_length] = '\0';
} }
extern "C" int IsHeapProfilerRunning() { extern "C" int
SpinLockHolder l(&heap_lock); IsHeapProfilerRunning()
return is_on ? 1 : 0; // return an int, because C code doesn't have bool {
SpinLockHolder l(&heap_lock);
return is_on ? 1 : 0;// return an int, because C code doesn't have bool
} }
extern "C" void HeapProfilerStop() { extern "C" void
SpinLockHolder l(&heap_lock); HeapProfilerStop()
{
SpinLockHolder l(&heap_lock);
if (!is_on) return; if (!is_on) return;
if (FLAGS_only_mmap_profile == false) { if (FLAGS_only_mmap_profile == false) {
// Unset our new/delete hooks, checking they were set: // Unset our new/delete hooks, checking they were set:
RAW_CHECK(MallocHook::RemoveNewHook(&NewHook), ""); RAW_CHECK(MallocHook::RemoveNewHook(&NewHook), "");
RAW_CHECK(MallocHook::RemoveDeleteHook(&DeleteHook), ""); RAW_CHECK(MallocHook::RemoveDeleteHook(&DeleteHook), "");
} }
if (FLAGS_mmap_log) { if (FLAGS_mmap_log) {
// Restore mmap/sbrk hooks, checking that our hooks were set: // Restore mmap/sbrk hooks, checking that our hooks were set:
tcmalloc::UnHookMMapEvents(&mmap_logging_hook_space); tcmalloc::UnHookMMapEvents(&mmap_logging_hook_space);
} }
// free profile // free profile
heap_profile->~HeapProfileTable(); heap_profile->~HeapProfileTable();
ProfilerFree(heap_profile); ProfilerFree(heap_profile);
heap_profile = NULL; heap_profile = NULL;
// free output-buffer memory // free output-buffer memory
ProfilerFree(global_profiler_buffer); ProfilerFree(global_profiler_buffer);
// free prefix // free prefix
ProfilerFree(filename_prefix); ProfilerFree(filename_prefix);
filename_prefix = NULL; filename_prefix = NULL;
if (!LowLevelAlloc::DeleteArena(heap_profiler_memory)) { if (!LowLevelAlloc::DeleteArena(heap_profiler_memory)) { RAW_LOG(FATAL, "Memory leak in HeapProfiler:"); }
RAW_LOG(FATAL, "Memory leak in HeapProfiler:");
}
if (FLAGS_mmap_profile) { if (FLAGS_mmap_profile) { MemoryRegionMap::Shutdown(); }
MemoryRegionMap::Shutdown();
}
is_on = false; is_on = false;
} }
extern "C" void HeapProfilerDump(const char *reason) { extern "C" void
SpinLockHolder l(&heap_lock); HeapProfilerDump(const char *reason)
if (is_on && !dumping) { {
DumpProfileLocked(reason); SpinLockHolder l(&heap_lock);
} if (is_on && !dumping) { DumpProfileLocked(reason); }
} }
// Signal handler that is registered when a user selectable signal // Signal handler that is registered when a user selectable signal
// number is defined in the environment variable HEAPPROFILESIGNAL. // number is defined in the environment variable HEAPPROFILESIGNAL.
static void HeapProfilerDumpSignal(int signal_number) { static void
(void)signal_number; HeapProfilerDumpSignal(int signal_number)
if (!heap_lock.TryLock()) { {
return; (void) signal_number;
} if (!heap_lock.TryLock()) { return; }
if (is_on && !dumping) { if (is_on && !dumping) { DumpProfileLocked("signal"); }
DumpProfileLocked("signal"); heap_lock.Unlock();
}
heap_lock.Unlock();
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Initialization/finalization code // Initialization/finalization code
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Initialization code // Initialization code
static void HeapProfilerInit() { static void
// Everything after this point is for setting up the profiler based on envvar HeapProfilerInit()
char fname[PATH_MAX]; {
if (!GetUniquePathFromEnv("HEAPPROFILE", fname)) { // Everything after this point is for setting up the profiler based on envvar
return; char fname[PATH_MAX];
} if (!GetUniquePathFromEnv("HEAPPROFILE", fname)) { return; }
// We do a uid check so we don't write out files in a setuid executable. // We do a uid check so we don't write out files in a setuid executable.
#ifdef HAVE_GETEUID #ifdef HAVE_GETEUID
if (getuid() != geteuid()) { if (getuid() != geteuid()) {
RAW_LOG(WARNING, ("HeapProfiler: ignoring HEAPPROFILE because " RAW_LOG(WARNING,
"program seems to be setuid\n")); ("HeapProfiler: ignoring HEAPPROFILE because "
return; "program seems to be setuid\n"));
} return;
}
#endif #endif
char *signal_number_str = getenv("HEAPPROFILESIGNAL"); char *signal_number_str = getenv("HEAPPROFILESIGNAL");
if (signal_number_str != NULL) { if (signal_number_str != NULL) {
long int signal_number = strtol(signal_number_str, NULL, 10); long int signal_number = strtol(signal_number_str, NULL, 10);
intptr_t old_signal_handler = reinterpret_cast<intptr_t>(signal(signal_number, HeapProfilerDumpSignal)); intptr_t old_signal_handler = reinterpret_cast<intptr_t>(signal(signal_number, HeapProfilerDumpSignal));
if (old_signal_handler == reinterpret_cast<intptr_t>(SIG_ERR)) { if (old_signal_handler == reinterpret_cast<intptr_t>(SIG_ERR)) {
RAW_LOG(FATAL, "Failed to set signal. Perhaps signal number %s is invalid\n", signal_number_str); RAW_LOG(FATAL, "Failed to set signal. Perhaps signal number %s is invalid\n", signal_number_str);
} else if (old_signal_handler == 0) { } else if (old_signal_handler == 0) {
RAW_LOG(INFO,"Using signal %d as heap profiling switch", signal_number); RAW_LOG(INFO, "Using signal %d as heap profiling switch", signal_number);
} else { } else {
RAW_LOG(FATAL, "Signal %d already in use\n", signal_number); RAW_LOG(FATAL, "Signal %d already in use\n", signal_number);
}
} }
}
HeapProfileTable::CleanupOldProfiles(fname); HeapProfileTable::CleanupOldProfiles(fname);
HeapProfilerStart(fname); HeapProfilerStart(fname);
} }
// class used for finalization -- dumps the heap-profile at program exit // class used for finalization -- dumps the heap-profile at program exit
struct HeapProfileEndWriter { struct HeapProfileEndWriter {
~HeapProfileEndWriter() { ~HeapProfileEndWriter()
char buf[128]; {
if (heap_profile) { char buf[128];
const HeapProfileTable::Stats& total = heap_profile->total(); if (heap_profile) {
const int64_t inuse_bytes = total.alloc_size - total.free_size; const HeapProfileTable::Stats &total = heap_profile->total();
const int64_t inuse_bytes = total.alloc_size - total.free_size;
if ((inuse_bytes >> 20) > 0) { if ((inuse_bytes >> 20) > 0) {
snprintf(buf, sizeof(buf), ("Exiting, %" PRId64 " MB in use"), snprintf(buf, sizeof(buf), ("Exiting, %" PRId64 " MB in use"), inuse_bytes >> 20);
inuse_bytes >> 20); } else if ((inuse_bytes >> 10) > 0) {
} else if ((inuse_bytes >> 10) > 0) { snprintf(buf, sizeof(buf), ("Exiting, %" PRId64 " kB in use"), inuse_bytes >> 10);
snprintf(buf, sizeof(buf), ("Exiting, %" PRId64 " kB in use"), } else {
inuse_bytes >> 10); snprintf(buf, sizeof(buf), ("Exiting, %" PRId64 " bytes in use"), inuse_bytes);
} else { }
snprintf(buf, sizeof(buf), ("Exiting, %" PRId64 " bytes in use"), } else {
inuse_bytes); snprintf(buf, sizeof(buf), ("Exiting"));
} }
} else { HeapProfilerDump(buf);
snprintf(buf, sizeof(buf), ("Exiting"));
} }
HeapProfilerDump(buf);
}
}; };
// We want to make sure tcmalloc is up and running before starting the profiler // We want to make sure tcmalloc is up and running before starting the profiler

View File

@ -104,7 +104,7 @@ target_sources(
target_link_libraries( target_link_libraries(
sled sled
PUBLIC rpc_core fmt marl Async++ minilua tcmalloc_and_profiler_static PUBLIC rpc_core fmt marl Async++ minilua
# protobuf::libprotoc # protobuf::libprotoc
PRIVATE dl) PRIVATE dl)
if(SLED_WITH_PROTOBUF) if(SLED_WITH_PROTOBUF)

View File

@ -108,5 +108,7 @@
// testing // testing
#include "sled/testing/test.h" #include "sled/testing/test.h"
// debugging // debugging
#endif// SLED_SLED_H #endif// SLED_SLED_H