diff --git a/src/track/heaptrack.sh.cmake b/src/track/heaptrack.sh.cmake index 7ca00261..3027eaa6 100755 --- a/src/track/heaptrack.sh.cmake +++ b/src/track/heaptrack.sh.cmake @@ -36,6 +36,10 @@ usage() { echo echo "Optional arguments to heaptrack:" echo " -r, --raw Only record raw data, do not interpret it." + echo " --unwind-depth DEPTH" + echo " Limit backtraces to DEPTH frames per allocation (1..64, default: 64)." + echo " Smaller depths reduce the tracking overhead." + echo " Only takes effect for newly started processes, not with --pid." echo " -d, --debug Run the debuggee in GDB and heaptrack." echo " --use-inject Use the same heaptrack_inject symbol interception mechanism instead of relying on" echo " the dynamic linker and LD_PRELOAD. This is an experimental flag for now." @@ -72,6 +76,7 @@ pid= client= use_inject_lib= write_raw_data= +unwind_depth= record_only= asan= asan_ld_preload= @@ -169,6 +174,20 @@ while true; do write_raw_data=1 shift 1 ;; + "--unwind-depth") + case "$2" in + ''|*[!0-9]*) + echo "Invalid --unwind-depth argument: \"$2\" (expected 1..64)" + exit 1 + ;; + esac + if [ "$2" -lt 1 ] || [ "$2" -gt 64 ]; then + echo "Invalid --unwind-depth argument: \"$2\" (expected 1..64)" + exit 1 + fi + unwind_depth=$2 + shift 2 + ;; "--asan") asan=1 use_inject_lib=1 @@ -428,6 +447,10 @@ if [ -z ${quiet} ]; then echo "heaptrack output will be written to \"$output\"" fi +if [ ! -z "$unwind_depth" ]; then + export HEAPTRACK_UNWIND_DEPTH="$unwind_depth" +fi + if [ -z "$debug" ] && [ -z "$pid" ]; then if [ -z ${quiet} ]; then echo "starting application, this might take some time..." diff --git a/src/track/libheaptrack.cpp b/src/track/libheaptrack.cpp index f7576a2e..ed5bfb0b 100644 --- a/src/track/libheaptrack.cpp +++ b/src/track/libheaptrack.cpp @@ -304,6 +304,16 @@ class HeapTrack Trace::setup(); + if (const char* depthStr = getenv("HEAPTRACK_UNWIND_DEPTH")) { + const int depth = atoi(depthStr); + if (depth >= 1 && depth <= Trace::MAX_SIZE) { + Trace::setMaxDepth(depth); + } else { + fprintf(stderr, "heaptrack: ignoring invalid HEAPTRACK_UNWIND_DEPTH=%s (expected 1..%d)\n", + depthStr, Trace::MAX_SIZE); + } + } + // do not trace forked child processes // TODO: make this configurable pthread_atfork(&prepare_fork, &parent_fork, &child_fork); @@ -661,7 +671,8 @@ class HeapTrack }; /** - * To prevent deadlocks on shutdown, we try to lock from the timer thread + * To prevent deadlocks on shutdown, we periodically give up waiting for + * the lock to run the stopLockCheck. * * TODO: c++17 return std::optional */ @@ -669,11 +680,14 @@ class HeapTrack static LockStatus tryLock(StopLockCheck stopLockCheck) { debugLog("%s", "trying to acquire lock"); - while (!s_lock.try_lock()) { + // wait on the lock via a futex instead of polling with tiny sleeps: + // the kernel's default timer slack inflates a 1us sleep to ~50us, + // which heavily delays contended allocations. The timeout only + // bounds how long a pending shutdown can go unnoticed. + while (!s_lock.try_lock_for(chrono::milliseconds(1))) { if (stopLockCheck()) { return false; } - this_thread::sleep_for(chrono::microseconds(1)); } debugLog("%s", "lock acquired"); return true; @@ -791,14 +805,14 @@ class HeapTrack #endif }; - static std::mutex s_lock; + static std::timed_mutex s_lock; static LockedData* s_data; private: static std::atomic s_paused; }; -std::mutex HeapTrack::s_lock; +std::timed_mutex HeapTrack::s_lock; HeapTrack::LockedData* HeapTrack::s_data {nullptr}; std::atomic HeapTrack::s_paused {false}; } diff --git a/src/track/trace.h b/src/track/trace.h index f610bf26..551ddce8 100644 --- a/src/track/trace.h +++ b/src/track/trace.h @@ -71,9 +71,18 @@ struct Trace static void print(); + /// limit the number of frames captured per backtrace, must be in [1, MAX_SIZE] + static void setMaxDepth(int depth) + { + assert(depth >= 1 && depth <= MAX_SIZE); + s_maxDepth = depth; + } + private: static int unwind(void** data); + inline static int s_maxDepth = MAX_SIZE; + private: int m_size = 0; int m_skip = 0; diff --git a/src/track/trace_libunwind.cpp b/src/track/trace_libunwind.cpp index b52fda23..f1cd477d 100644 --- a/src/track/trace_libunwind.cpp +++ b/src/track/trace_libunwind.cpp @@ -64,5 +64,5 @@ void Trace::setup() int Trace::unwind(void** data) { - return unw_backtrace(data, MAX_SIZE); + return unw_backtrace(data, s_maxDepth); } diff --git a/src/track/trace_unwind_tables.cpp b/src/track/trace_unwind_tables.cpp index d35cb2be..1b7b399f 100644 --- a/src/track/trace_unwind_tables.cpp +++ b/src/track/trace_unwind_tables.cpp @@ -54,7 +54,7 @@ int Trace::unwind(void** data) { backtrace trace; trace.data = data; - trace.max_size = MAX_SIZE; + trace.max_size = s_maxDepth; _Unwind_Backtrace(unwind_backtrace_callback, &trace); return trace.ctr;