Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,39 @@ on:
workflow_dispatch:

jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Cache build
uses: actions/cache@v3
with:
path: core/build-tests
key: ${{ runner.os }}-build-tests-${{ hashFiles('**/CMakeLists.txt', '**/examples/google_benchmark/**') }}

- name: Create build directory
run: mkdir -p core/build-tests

- name: Build tests
run: |
cd core/build-tests
cmake .. -DENABLE_TESTS=ON
make -j

- name: Run tests
run: |
cd core/build-tests
GTEST_OUTPUT=json:test-results/ ctest

- name: Upload test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test_results
path: ${{runner.workspace}}/core/build-tests/test/test-results/**/*.json

instrumentation:
runs-on: ubuntu-latest

Expand Down
8 changes: 7 additions & 1 deletion core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
include_directories(include)

# Add the library
add_library(codspeed src/codspeed.cpp src/walltime.cpp)
add_library(codspeed src/codspeed.cpp src/walltime.cpp src/uri.cpp)

# Version
add_compile_definitions(CODSPEED_VERSION="${CODSPEED_VERSION}")
Expand All @@ -28,3 +28,9 @@ target_include_directories(
codspeed
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)

option(ENABLE_TESTS "Enable building the unit tests which depend on gtest" OFF)
if(ENABLE_TESTS)
enable_testing()
add_subdirectory(test)
endif()
3 changes: 3 additions & 0 deletions core/include/codspeed.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ struct RawWalltimeBenchmark {
void generate_codspeed_walltime_report(
const std::vector<RawWalltimeBenchmark> &walltime_data_list);

std::string extract_lambda_namespace(const std::string &pretty_func);
std::string sanitize_bench_args(std::string &text);

#endif // CODSPEED_H
31 changes: 31 additions & 0 deletions core/src/codspeed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@
#include <string>
#include <vector>

// Remove any `::` between brackets at the end to not mess with the URI
// parsing
// FIXME: Remove this bandaid when we migrate to structured benchmark metadata
std::string sanitize_bench_args(std::string &text) {
std::string search = "::";
std::string replace = "\\:\\:";

if (text.back() == ']') {
size_t pos_open = text.rfind('[');
if (pos_open != std::string::npos) {
// Extract the substring between '[' and ']'
size_t pos_close = text.size() - 1;
std::string substring =
text.substr(pos_open + 1, pos_close - pos_open - 1);

// Perform the search and replace within the substring
size_t pos = substring.find(search);
while (pos != std::string::npos) {
substring.replace(pos, search.length(), replace);
pos = substring.find(search, pos + replace.length());
}

// Replace the original substring with the modified one
text.replace(pos_open + 1, pos_close - pos_open - 1, substring);
}
}
return text;
}

std::string join(const std::vector<std::string> &elements,
const std::string &delimiter) {
std::string result;
Expand Down Expand Up @@ -40,6 +69,8 @@ void CodSpeed::pop_group() {
void CodSpeed::start_benchmark(const std::string &name) {
std::string uri = name;

uri = sanitize_bench_args(uri);

// Sanity check URI and add a placeholder if format is wrong
if (name.find("::") == std::string::npos) {
std::string uri = "unknown_file::" + name;
Expand Down
48 changes: 48 additions & 0 deletions core/src/uri.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "codspeed.h"
#include <string>
#include <iostream>

// Example: auto outer::test12::(anonymous class)::operator()() const
// Returns: outer::test12::
std::string extract_namespace_clang(const std::string& pretty_func) {
std::size_t anon_class_pos = pretty_func.find("::(anonymous class)");
std::size_t space_pos = pretty_func.find(' ');

if (space_pos == std::string::npos || anon_class_pos == std::string::npos) {
return {};
}
space_pos += 1; // Skip the space

return pretty_func.substr(space_pos, anon_class_pos - space_pos) + "::";
}

// Example: outer::test12::<lambda()>
// Returns: outer::test12::
std::string extract_namespace_gcc(const std::string& pretty_func) {
auto lambda_pos = pretty_func.find("::<lambda()>");
if (lambda_pos == std::string::npos) {
return {};
}

return pretty_func.substr(0, lambda_pos) + "::";
}

// Has to pass the pretty function from a lambda:
// (([]() { return __PRETTY_FUNCTION__; })())
//
// Returns: An empty string if the namespace could not be extracted,
// otherwise the namespace with a trailing "::"
std::string extract_lambda_namespace(const std::string& pretty_func) {
if (pretty_func.find("(anonymous namespace)") != std::string::npos) {
std::cerr << "[ERROR] Anonymous namespace not supported in " << pretty_func << std::endl;
return {};
}

#ifdef __clang__
return extract_namespace_clang(pretty_func);
#elif __GNUC__
return extract_namespace_gcc(pretty_func);
#else
#error "Unsupported compiler"
#endif
}
16 changes: 14 additions & 2 deletions core/src/walltime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ void compute_iqr_and_outliers(const std::vector<double> &times_ns, double mean,
});
}

std::string escapeBackslashes(const std::string &input) {
std::string output;
for (char c : input) {
if (c == '\\') {
output += "\\\\";
} else {
output += c;
}
}
return output;
}

void write_codspeed_benchmarks_to_json(
const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
std::ostringstream oss;
Expand All @@ -113,8 +125,8 @@ void write_codspeed_benchmarks_to_json(
const auto &metadata = benchmark.metadata;

oss << " {\n";
oss << " \"name\": \"" << metadata.name << "\",\n";
oss << " \"uri\": \"" << metadata.uri << "\",\n";
oss << " \"name\": \"" << escapeBackslashes(metadata.name) << "\",\n";
oss << " \"uri\": \"" << escapeBackslashes(metadata.uri) << "\",\n";
// TODO: Manage config fields from actual configuration
oss << " \"config\": {\n";
oss << " \"warmup_time_ns\": null,\n";
Expand Down
22 changes: 22 additions & 0 deletions core/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.16.0
)
FetchContent_MakeAvailable(googletest)

add_executable(unit_tests
uri.cpp
codspeed.cpp
)

target_link_libraries(unit_tests
PRIVATE
codspeed
GTest::gtest
GTest::gtest_main
)

include(GoogleTest)
gtest_discover_tests(unit_tests)
51 changes: 51 additions & 0 deletions core/test/codspeed.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "codspeed.h"
#include <gtest/gtest.h>

// Manual definition (to avoid including it in the public header):

TEST(CodSpeedTest, TestSearchAndReplaceBetweenBracketsNamespace) {
std::string no_brackets_input =
"examples/google_benchmark/main.cpp::BM_rand_vector";
std::string no_brackets_output =
"examples/google_benchmark/main.cpp::BM_rand_vector";
EXPECT_EQ(sanitize_bench_args(no_brackets_input), no_brackets_output);

std::string brackets_and_no_escaped_type_input =
"examples/google_benchmark/"
"template_bench.hpp::BM_Template1_Capture[two_type_test, int, double]";
std::string brackets_and_no_escaped_type_output =
"examples/google_benchmark/"
"template_bench.hpp::BM_Template1_Capture[two_type_test, int, double]";
EXPECT_EQ(sanitize_bench_args(brackets_and_no_escaped_type_input),
brackets_and_no_escaped_type_output);

std::string brackets_and_escaped_type_input =
"examples/google_benchmark/"
"template_bench.hpp::test::BM_Template[std::string]";
std::string brackets_and_escaped_type_output =
"examples/google_benchmark/"
"template_bench.hpp::test::BM_Template[std\\:\\:string]";

EXPECT_EQ(sanitize_bench_args(brackets_and_escaped_type_input),
brackets_and_escaped_type_output);

std::string brackets_and_escaped_types_input =
"examples/google_benchmark/"
"template_bench.hpp::test::BM_Template[std::string, std::string]";
std::string brackets_and_escaped_types_output =
"examples/google_benchmark/"
"template_bench.hpp::test::BM_Template[std\\:\\:string, std\\:\\:string]";

EXPECT_EQ(sanitize_bench_args(brackets_and_escaped_types_input),
brackets_and_escaped_types_output);

std::string brackets_and_multiple_types_input =
"examples/google_benchmark/"
"template_bench.hpp::test::BM_Template[std::string, int, double]";
std::string brackets_and_multiple_types_output =
"examples/google_benchmark/"
"template_bench.hpp::test::BM_Template[std\\:\\:string, int, double]";

EXPECT_EQ(sanitize_bench_args(brackets_and_multiple_types_input),
brackets_and_multiple_types_output);
}
29 changes: 29 additions & 0 deletions core/test/uri.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <gtest/gtest.h>
#include "codspeed.h"

// Manual definition (to avoid including it in the public header):
std::string extract_namespace_clang(const std::string& func_str);
std::string extract_namespace_gcc(const std::string& func_str);

TEST(UriTest, TestExtractNamespaceClang) {
EXPECT_EQ(extract_namespace_clang("auto outer::test12::(anonymous class)::operator()() const"), "outer::test12::");
EXPECT_EQ(extract_namespace_clang("auto outer::(anonymous namespace)::test12::(anonymous class)::operator()() const"), "outer::(anonymous namespace)::test12::");
}

TEST(UriTest, TestExtractNamespaceGcc) {
EXPECT_EQ(extract_namespace_gcc("outer::test12::<lambda()>"), "outer::test12::");
EXPECT_EQ(extract_namespace_gcc("outer::(anonymous namespace)::test12::<lambda()>"), "outer::(anonymous namespace)::test12::");
}


namespace a {
namespace b {
namespace c {
static std::string pretty_func = ([]() { return __PRETTY_FUNCTION__; })();

TEST(UriTest, TestExtractNamespace) {
EXPECT_EQ(extract_lambda_namespace(pretty_func), "a::b::c::");
}
}
}
}
2 changes: 1 addition & 1 deletion examples/google_benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ include(FetchContent)

project(codspeed_picobench_compat VERSION 0.0.0 LANGUAGES CXX)

set(BENCHMARK_DOWNLOAD_DEPENDENCIES ON)
option(BENCHMARK_ENABLE_GTEST_TESTS OFF)

FetchContent_Declare(
google_benchmark
Expand Down
10 changes: 10 additions & 0 deletions examples/google_benchmark/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
#include "template_bench.hpp"
#include <benchmark/benchmark.h>
#include <cstring>

template <class... Args>
void BM_Capture(benchmark::State &state, Args &&...args) {
auto args_tuple = std::make_tuple(std::move(args)...);
for (auto _ : state) {
}
}
BENCHMARK_CAPTURE(BM_Capture, int_string_test, 42, std::string("abc"));
BENCHMARK_CAPTURE(BM_Capture, int_test, 42, 43);

// Function to benchmark
static void BM_rand_vector(benchmark::State &state) {
std::vector<int> v;
Expand Down
56 changes: 56 additions & 0 deletions examples/google_benchmark/template_bench.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#ifndef TEMPLATE_BENCH_HPP
#define TEMPLATE_BENCH_HPP

#include <benchmark/benchmark.h>
#include <cstring>
#include <string>

namespace test {
template <class T> void BM_Template(benchmark::State &state) {
std::vector<T> v;
for (auto _ : state) {
v.push_back(T());
}
}
BENCHMARK_TEMPLATE(BM_Template, int);
BENCHMARK_TEMPLATE(BM_Template, std::string);
} // namespace test

//
//

template <typename T> void BM_Template1(benchmark::State &state) {
T val = T();
for (auto _ : state) {
benchmark::DoNotOptimize(val++);
}
}
BENCHMARK_TEMPLATE1(BM_Template1, int);

//
//

template <typename T, typename U> void BM_Template2(benchmark::State &state) {
T t = T();
U u = U();
for (auto _ : state) {
benchmark::DoNotOptimize(t + u);
}
}
BENCHMARK_TEMPLATE2(BM_Template2, int, double);

//
//

template <typename T, class... ExtraArgs>
void BM_Template1_Capture(benchmark::State &state, ExtraArgs &&...extra_args) {
auto args_tuple = std::make_tuple(std::move(extra_args)...);
for (auto _ : state) {
}
}
BENCHMARK_TEMPLATE1_CAPTURE(BM_Template1_Capture, void, int_string_test, 42,
std::string("abc"));
BENCHMARK_TEMPLATE2_CAPTURE(BM_Template1_Capture, int, double, two_type_test,
42, std::string("abc"));

#endif
4 changes: 4 additions & 0 deletions google_benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,8 @@ $ ./my-bench
Checked: main.cpp::BM_memcpy[8192]
```

### Not supported

- Declaring benches within anonymous namespaces

For more information, please checkout the [codspeed documentation](https://docs.codspeed.io/benchmarks/cpp)
Loading