Try to add basic performance testing (#8)

This commit is contained in:
Jeremy Rifkin 2023-07-13 21:54:17 -04:00 committed by GitHub
parent 6b55222a4b
commit d955c61cd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 230 additions and 43 deletions

76
.github/workflows/performance-tests.yml vendored Normal file
View File

@ -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}}

View File

@ -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("$<$<CONFIG:Debug>:-gdwarf-4>")
#target_compile_options(speedtest PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
#target_compile_options(googletest INTERFACE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
endif()
if(CPPTRACE_BUILD_SPEEDTEST_DWARF5)
check_cxx_compiler_flag("-gdwarf-5" HAS_DWARF5)
if(HAS_DWARF5)
add_compile_options("$<$<CONFIG:Debug>:-gdwarf-5>")
#target_compile_options(speedtest PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
#target_compile_options(googletest INTERFACE "$<$<CONFIG:Debug>:-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 $<TARGET_RUNTIME_DLLS:speedtest> $<TARGET_FILE_DIR:speedtest>
COMMAND_EXPAND_LISTS
)
endif()
endif()

View File

@ -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 🏗️

View File

@ -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<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
std::vector<dlframe> dlframes = backtrace_frames(frames);
std::unordered_map<
std::string,
std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>
> 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<dlframe> dlframes = backtrace_frames(frames);
std::unordered_map<
std::string,
std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>
> 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 <identifier> " 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 <identifier> " 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;
}
}
}
}

11
test/speedtest.cpp Normal file
View File

@ -0,0 +1,11 @@
// https://github.com/jeremy-rifkin/libassert/issues/43
#include <cpptrace/cpptrace.hpp>
#include <gtest/gtest.h>
#include <exception>
TEST(TraceTest, trace_test) {
ASSERT_THROW((cpptrace::print_trace(), false), std::logic_error);
}

33
test/speedtest.py Normal file
View File

@ -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()