Skip to content

Commit

Permalink
Introduce new command: thread backtrace unique
Browse files Browse the repository at this point in the history
This patch introduces a new thread backtrace command "unique".
The command is based off of "thread backtrace all" but will instead
find all threads which share matching call stacks and de-duplicate
their output, listing call stack and all the threads which share it.
This is especially useful for apps which use thread/task pools
sitting around waiting for work and cause excessive duplicate output.
I needed this behavior recently when debugging a core with 700+ threads.

Differential Revision: https://reviews.llvm.org/D33426

Reviewers: clayborg, jingham
Patch by Brian Gianforcaro <b.gianfo@gmail.com>

llvm-svn: 305197
  • Loading branch information
labath committed Jun 12, 2017
1 parent 0c69d6e commit 7f1c121
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 86 deletions.
2 changes: 2 additions & 0 deletions lldb/include/lldb/Core/Debugger.h
Expand Up @@ -249,6 +249,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,

const FormatEntity::Entry *GetFrameFormat() const;

const FormatEntity::Entry *GetFrameFormatUnique() const;

const FormatEntity::Entry *GetThreadFormat() const;

const FormatEntity::Entry *GetThreadStopFormat() const;
Expand Down
11 changes: 9 additions & 2 deletions lldb/include/lldb/Target/StackFrame.h
Expand Up @@ -342,10 +342,13 @@ class StackFrame : public ExecutionContextScope,
/// @param [in] strm
/// The Stream to print the description to.
///
/// @param [in] show_unique
/// Whether to print the function arguments or not for backtrace unique.
///
/// @param [in] frame_marker
/// Optional string that will be prepended to the frame output description.
//------------------------------------------------------------------
void DumpUsingSettingsFormat(Stream *strm,
void DumpUsingSettingsFormat(Stream *strm, bool show_unique = false,
const char *frame_marker = nullptr);

//------------------------------------------------------------------
Expand Down Expand Up @@ -375,14 +378,18 @@ class StackFrame : public ExecutionContextScope,
/// @param[in] show_source
/// If true, print source or disassembly as per the user's settings.
///
/// @param[in] show_unique
/// If true, print using backtrace unique style, without function
/// arguments as per the user's settings.
///
/// @param[in] frame_marker
/// Passed to DumpUsingSettingsFormat() for the frame info printing.
///
/// @return
/// Returns true if successful.
//------------------------------------------------------------------
bool GetStatus(Stream &strm, bool show_frame_info, bool show_source,
const char *frame_marker = nullptr);
bool show_unique = false, const char *frame_marker = nullptr);

//------------------------------------------------------------------
/// Query whether this frame is a concrete frame on the call stack,
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/Target/StackFrameList.h
Expand Up @@ -70,6 +70,7 @@ class StackFrameList {

size_t GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames,
bool show_frame_info, uint32_t num_frames_with_source,
bool show_unique = false,
const char *frame_marker = nullptr);

protected:
Expand Down
4 changes: 2 additions & 2 deletions lldb/include/lldb/Target/Thread.h
Expand Up @@ -1163,8 +1163,8 @@ class Thread : public std::enable_shared_from_this<Thread>,
GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr);

size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames,
uint32_t num_frames_with_source,
bool stop_format);
uint32_t num_frames_with_source, bool stop_format,
bool only_stacks = false);

size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame,
uint32_t num_frames, bool show_frame_info,
Expand Down
Expand Up @@ -19,26 +19,27 @@ class NumberOfThreadsTestCase(TestBase):
def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
# Find the line number to break inside main().
self.line = line_number('main.cpp', '// Set break point at this line.')
# Find the line numbers for our break points.
self.thread3_notify_all_line = line_number('main.cpp', '// Set thread3 break point on notify_all at this line.')
self.thread3_before_lock_line = line_number('main.cpp', '// Set thread3 break point on lock at this line.')

def test(self):
def test_number_of_threads(self):
"""Test number of threads."""
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)

# This should create a breakpoint with 1 location.
lldbutil.run_break_set_by_file_and_line(
self, "main.cpp", self.line, num_expected_locations=1)
self, "main.cpp", self.thread3_notify_all_line, num_expected_locations=1)

# The breakpoint list should show 3 locations.
# The breakpoint list should show 1 location.
self.expect(
"breakpoint list -f",
"Breakpoint location shown correctly",
substrs=[
"1: file = 'main.cpp', line = %d, exact_match = 0, locations = 1" %
self.line])
self.thread3_notify_all_line])

# Run the program.
self.runCmd("run", RUN_SUCCEEDED)
Expand All @@ -57,5 +58,64 @@ def test(self):
# Using std::thread may involve extra threads, so we assert that there are
# at least 4 rather than exactly 4.
self.assertTrue(
num_threads >= 4,
num_threads >= 13,
'Number of expected threads and actual threads do not match.')

def test_unique_stacks(self):
"""Test backtrace unique with multiple threads executing the same stack."""
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)

# Set a break point on the thread3 notify all (should get hit on threads 4-13).
lldbutil.run_break_set_by_file_and_line(
self, "main.cpp", self.thread3_before_lock_line, num_expected_locations=1)

# Run the program.
self.runCmd("run", RUN_SUCCEEDED)

# Stopped once.
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=["stop reason = breakpoint 1."])

process = self.process()

# Get the number of threads
num_threads = process.GetNumThreads()

# Using std::thread may involve extra threads, so we assert that there are
# at least 10 thread3's rather than exactly 10.
self.assertTrue(
num_threads >= 10,
'Number of expected threads and actual threads do not match.')

# Attempt to walk each of the thread's executing the thread3 function to
# the same breakpoint.
def is_thread3(thread):
for frame in thread:
if "thread3" in frame.GetFunctionName(): return True
return False

expect_threads = ""
for i in range(num_threads):
thread = process.GetThreadAtIndex(i)
self.assertTrue(thread.IsValid())
if not is_thread3(thread):
continue

# If we aren't stopped out the thread breakpoint try to resume.
if thread.GetStopReason() != lldb.eStopReasonBreakpoint:
self.runCmd("thread continue %d"%(i+1))
self.assertEqual(thread.GetStopReason(), lldb.eStopReasonBreakpoint)

expect_threads += " #%d"%(i+1)

# Construct our expected back trace string
expect_string = "10 thread(s)%s" % (expect_threads)

# Now that we are stopped, we should have 10 threads waiting in the
# thread3 function. All of these threads should show as one stack.
self.expect("thread backtrace unique",
"Backtrace with unique stack shown correctly",
substrs=[expect_string,
"main.cpp:%d"%self.thread3_before_lock_line])
@@ -1,23 +1,27 @@
#include "pseudo_barrier.h"
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>

std::mutex mutex;
std::condition_variable cond;
pseudo_barrier_t thread3_barrier;

void *
thread3(void *input)
{
std::unique_lock<std::mutex> lock(mutex);
cond.notify_all(); // Set break point at this line.
pseudo_barrier_wait(thread3_barrier);
std::unique_lock<std::mutex> lock(mutex); // Set thread3 break point on lock at this line.
cond.notify_all(); // Set thread3 break point on notify_all at this line.
return NULL;
}

void *
thread2(void *input)
{
std::unique_lock<std::mutex> lock(mutex);
cond.notify_all();
cond.notify_all(); // release main thread
cond.wait(lock);
return NULL;
}
Expand All @@ -36,15 +40,23 @@ int main()
std::unique_lock<std::mutex> lock(mutex);

std::thread thread_1(thread1, nullptr);
cond.wait(lock);
cond.wait(lock); // wait for thread2

std::thread thread_3(thread3, nullptr);
cond.wait(lock);
pseudo_barrier_init(thread3_barrier, 10);

std::vector<std::thread> thread_3s;
for (int i = 0; i < 10; i++) {
thread_3s.push_back(std::thread(thread3, nullptr));
}

cond.wait(lock); // wait for thread_3s

lock.unlock();

thread_1.join();
thread_3.join();
for (auto &t : thread_3s){
t.join();
}

return 0;
}

0 comments on commit 7f1c121

Please sign in to comment.