diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml new file mode 100644 index 0000000..a1664ed --- /dev/null +++ b/.github/workflows/performance-tests.yml @@ -0,0 +1,76 @@ +name: performance-test + +on: + push: + pull_request: + +jobs: + test-linux: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + compiler: [g++-11, clang++-14] + target: [Debug] + std: [11, 20] + config: [ + "-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On", + "-DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF4=On", + "-DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On" + ] + exclude: + - config: -DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On + compiler: g++-11 + steps: + - uses: actions/checkout@v2 + - name: dependencies + run: sudo apt install gcc-11 g++-11 libgcc-11-dev + - name: build + run: | + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=${{matrix.target}} \ + -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + ${{matrix.config}} \ + -DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/include/backtrace.h \ + -DCPPTRACE_BUILD_SPEEDTEST=On \ + -DBUILD_SHARED_LIBS=On + make + - name: test + working-directory: build + run: | + ./speedtest | python3 ../test/speedtest.py ${{matrix.config}} + # TODO: For some reason this is slow on github's runner + #test-windows: + # runs-on: windows-2019 + # strategy: + # fail-fast: false + # matrix: + # compiler: [cl, clang++] + # target: [Debug] + # std: [11, 20] + # config: [ + # "-DCPPTRACE_GET_SYMBOLS_WITH_DBGHELP=On" + # ] + # steps: + # - uses: actions/checkout@v2 + # - name: Enable Developer Command Prompt + # uses: ilammy/msvc-dev-cmd@v1.10.0 + # - name: build + # run: | + # mkdir -p build + # cd build + # cmake .. ` + # -DCMAKE_BUILD_TYPE=Debug ` + # -DCMAKE_CXX_COMPILER=${{matrix.compiler}} ` + # -DCMAKE_CXX_STANDARD=${{matrix.std}} ` + # ${{matrix.config}} ` + # -DCPPTRACE_BUILD_SPEEDTEST=On ` + # -DBUILD_SHARED_LIBS=On + # msbuild .\cpptrace.sln + # - name: test + # working-directory: build + # run: | + # .\${{matrix.target}}\speedtest.exe | python3 ../test/speedtest.py ${{matrix.config}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 615c3b7..1f3d074 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,10 @@ option(CPPTRACE_DEMANGLE_WITH_NOTHING "" OFF) option(CPPTRACE_BUILD_TEST "" OFF) option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF) +option(CPPTRACE_BUILD_SPEEDTEST "" OFF) +option(CPPTRACE_BUILD_SPEEDTEST_DWARF4 "" OFF) +option(CPPTRACE_BUILD_SPEEDTEST_DWARF5 "" OFF) + set(CPPTRACE_BACKTRACE_PATH "" CACHE STRING "Path to backtrace.h, if the compiler doesn't already know it. Check /usr/lib/gcc/x86_64-linux-gnu/*/include.") set(CPPTRACE_HARD_MAX_FRAMES "" CACHE STRING "Hard limit on unwinding depth. Default is 100.") @@ -371,3 +375,47 @@ if(CPPTRACE_BUILD_TEST) set_property(TARGET test PROPERTY ENABLE_EXPORTS ON) endif() endif() + +if(CPPTRACE_BUILD_SPEEDTEST) + if(CPPTRACE_BUILD_SPEEDTEST_DWARF4) + check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4) + if(HAS_DWARF4) + add_compile_options("$<$:-gdwarf-4>") + #target_compile_options(speedtest PRIVATE "$<$:-gdwarf-4>") + #target_compile_options(googletest INTERFACE "$<$:-gdwarf-4>") + endif() + endif() + if(CPPTRACE_BUILD_SPEEDTEST_DWARF5) + check_cxx_compiler_flag("-gdwarf-5" HAS_DWARF5) + if(HAS_DWARF5) + add_compile_options("$<$:-gdwarf-5>") + #target_compile_options(speedtest PRIVATE "$<$:-gdwarf-4>") + #target_compile_options(googletest INTERFACE "$<$:-gdwarf-4>") + endif() + endif() + + include(FetchContent) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + + add_executable(speedtest test/speedtest.cpp) + target_link_libraries( + speedtest + PRIVATE + GTest::gtest_main + cpptrace + ) + + if(WIN32) + add_custom_command( + TARGET speedtest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND_EXPAND_LISTS + ) + endif() +endif() diff --git a/README.md b/README.md index 48c2c08..c750e11 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![build](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml) [![test](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml) +[![performance-test](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/performance-tests.yml/badge.svg)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/performance-tests.yml) 🚧 WIP 🏗️ diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp index 810631d..fd34378 100644 --- a/src/symbols/symbols_with_addr2line.cpp +++ b/src/symbols/symbols_with_addr2line.cpp @@ -44,6 +44,22 @@ namespace cpptrace { return frames; } + bool has_addr2line() { + // Detects if addr2line exists by trying to invoke addr2line --help + constexpr int magic = 42; + pid_t pid = fork(); + if(pid == -1) { return false; } + if(pid == 0) { // child + close(STDOUT_FILENO); + // TODO: path + execlp("addr2line", "addr2line", "--help", nullptr); + exit(magic); + } + int status; + waitpid(pid, &status, 0); + return WEXITSTATUS(status) == 0; + } + struct pipe_t { union { struct { @@ -93,51 +109,53 @@ namespace cpptrace { struct symbolizer::impl { std::vector resolve_frames(const std::vector& frames) { std::vector trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" }); - std::vector dlframes = backtrace_frames(frames); - std::unordered_map< - std::string, - std::vector>> - > entries; - for(size_t i = 0; i < dlframes.size(); i++) { - const auto& entry = dlframes[i]; - entries[entry.obj_path].push_back({ - to_hex(entry.raw_address - entry.obj_base), - trace[i] - }); - // Set what is known for now, and resolutions from addr2line should overwrite - trace[i].filename = entry.obj_path; - trace[i].symbol = entry.symbol; - } - for(const auto& entry : entries) { - const auto& object_name = entry.first; - const auto& entries_vec = entry.second; - std::string address_input; - for(const auto& pair : entries_vec) { - address_input += pair.first; - address_input += '\n'; + if(has_addr2line()) { + std::vector dlframes = backtrace_frames(frames); + std::unordered_map< + std::string, + std::vector>> + > entries; + for(size_t i = 0; i < dlframes.size(); i++) { + const auto& entry = dlframes[i]; + entries[entry.obj_path].push_back({ + to_hex(entry.raw_address - entry.obj_base), + trace[i] + }); + // Set what is known for now, and resolutions from addr2line should overwrite + trace[i].filename = entry.obj_path; + trace[i].symbol = entry.symbol; } - auto output = split(trim(resolve_addresses(address_input, object_name)), "\n"); - internal_verify(output.size() == entries_vec.size()); - for(size_t i = 0; i < output.size(); i++) { - // result will be of the form " at " path:line - // path may be ?? if addr2line cannot resolve, line may be ? - const auto& line = output[i]; - auto at_location = line.find(" at "); - internal_verify(at_location != std::string::npos); - auto symbol = line.substr(0, at_location); - auto colon = line.rfind(":"); - internal_verify(colon != std::string::npos); - internal_verify(colon > at_location); - auto filename = line.substr(at_location + 4, colon - at_location - 4); - auto line_number = line.substr(colon + 1); - if(line_number != "?") { - entries_vec[i].second.get().line = std::stoi(line_number); + for(const auto& entry : entries) { + const auto& object_name = entry.first; + const auto& entries_vec = entry.second; + std::string address_input; + for(const auto& pair : entries_vec) { + address_input += pair.first; + address_input += '\n'; } - if(filename != "??") { - entries_vec[i].second.get().filename = filename; - } - if(symbol != "") { - entries_vec[i].second.get().symbol = symbol; + auto output = split(trim(resolve_addresses(address_input, object_name)), "\n"); + internal_verify(output.size() == entries_vec.size()); + for(size_t i = 0; i < output.size(); i++) { + // result will be of the form " at " path:line + // path may be ?? if addr2line cannot resolve, line may be ? + const auto& line = output[i]; + auto at_location = line.find(" at "); + internal_verify(at_location != std::string::npos); + auto symbol = line.substr(0, at_location); + auto colon = line.rfind(":"); + internal_verify(colon != std::string::npos); + internal_verify(colon > at_location); + auto filename = line.substr(at_location + 4, colon - at_location - 4); + auto line_number = line.substr(colon + 1); + if(line_number != "?") { + entries_vec[i].second.get().line = std::stoi(line_number); + } + if(filename != "??") { + entries_vec[i].second.get().filename = filename; + } + if(symbol != "") { + entries_vec[i].second.get().symbol = symbol; + } } } } diff --git a/test/speedtest.cpp b/test/speedtest.cpp new file mode 100644 index 0000000..047bc7d --- /dev/null +++ b/test/speedtest.cpp @@ -0,0 +1,11 @@ +// https://github.com/jeremy-rifkin/libassert/issues/43 + +#include + +#include + +#include + +TEST(TraceTest, trace_test) { + ASSERT_THROW((cpptrace::print_trace(), false), std::logic_error); +} diff --git a/test/speedtest.py b/test/speedtest.py new file mode 100644 index 0000000..3980399 --- /dev/null +++ b/test/speedtest.py @@ -0,0 +1,33 @@ +import sys +import re + +def main(): + output = sys.stdin.read() + + print(output) + + print("-" * 50) + + time = int(re.search(r"\d+ tests? from \d+ test suites? ran. \((\d+) ms total\)", output).group(1)) + + dwarf4 = any(["DWARF4" in arg for arg in sys.argv[1:]]) + dwarf5 = any(["DWARF5" in arg for arg in sys.argv[1:]]) + expect_slow = dwarf4 + + threshold = 100 # ms + + if expect_slow: + if time > 100: + print(f"Success (expecting slow): Test program took {time} ms") + else: + print(f"Error (expecting slow): Test program took {time} ms") + sys.exit(1) + else: + if time > 100: + print(f"Error: Test program took {time} ms") + sys.exit(1) + else: + print(f"Success: Test program took {time} ms") + + +main()