Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/track/heaptrack.sh.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -72,6 +76,7 @@ pid=
client=
use_inject_lib=
write_raw_data=
unwind_depth=
record_only=
asan=
asan_ld_preload=
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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..."
Expand Down
24 changes: 19 additions & 5 deletions src/track/libheaptrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -661,19 +671,23 @@ 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<HeapTrack>
*/
template <typename StopLockCheck>
static LockStatus tryLock(StopLockCheck stopLockCheck)
{
debugLog<VeryVerboseOutput>("%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<VeryVerboseOutput>("%s", "lock acquired");
return true;
Expand Down Expand Up @@ -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<bool> s_paused;
};

std::mutex HeapTrack::s_lock;
std::timed_mutex HeapTrack::s_lock;
HeapTrack::LockedData* HeapTrack::s_data {nullptr};
std::atomic<bool> HeapTrack::s_paused {false};
}
Expand Down
9 changes: 9 additions & 0 deletions src/track/trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/track/trace_libunwind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ void Trace::setup()

int Trace::unwind(void** data)
{
return unw_backtrace(data, MAX_SIZE);
return unw_backtrace(data, s_maxDepth);
}
2 changes: 1 addition & 1 deletion src/track/trace_unwind_tables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down