commit 82652df4511c2c5112bb59ff906ea57e29e18a64 Author: Artur Mukhamadiev Date: Thu Jun 19 08:44:34 2025 +0000 init diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..34fe704 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: Chromium \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b9ed2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/* +build/* +compile_commands.json \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..126aa25 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.20) +project(mLogger) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_library(${PROJECT_NAME} SHARED) + +target_include_directories(${PROJECT_NAME} PUBLIC include) + +file(GLOB prj_src src/*) + +target_sources(${PROJECT_NAME} PRIVATE ${prj_src}) + +add_subdirectory(bench) +add_subdirectory(tests) \ No newline at end of file diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 0000000..f07b4a0 --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,18 @@ +{ + "version": 4, + "vendor": { + "conan": {} + }, + "include": [ + "build/Release/generators/CMakePresets.json" + ], + "configurePresets": [ + { + "name": "Build default", + "inherits": "conan-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + } + ] +} \ No newline at end of file diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 0000000..5055e24 --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1,11 @@ + +find_package(benchmark REQUIRED) +set(NAME ${PROJECT_NAME}_bench) + +add_executable(${NAME} main.cc) + +target_link_libraries(${NAME} PRIVATE + benchmark::benchmark_main + benchmark::benchmark + ${PROJECT_NAME} +) \ No newline at end of file diff --git a/bench/main.cc b/bench/main.cc new file mode 100644 index 0000000..0632efa --- /dev/null +++ b/bench/main.cc @@ -0,0 +1,194 @@ +#include +#include +#include "benchmark/benchmark.h" +#include "logger.hh" + +//! AI generated +static double taylor_approximation( + double x, // Point to evaluate + double a, // Expansion point + int n, // Number of terms + const std::function& func, // f(a) + const std::function& derivative // f^(k)(a) +) { + double result = 0.0; + double power = 1.0; // (x - a)^0 + double factorial = 1.0; // 0! + + // Zeroth derivative term (k=0) + result += func(a) * power / factorial; + + // Subsequent terms (k=1 to n-1) + for (int k = 1; k < n; k++) { + factorial *= k; // k! + power *= (x - a); // (x - a)^k + + double deriv = derivative(k, a); + result += deriv * power / factorial; + } + + return result; +} + +static double calc_exp_taylor() { + auto exp_deriv = [](int k, double a) -> double { + return std::exp(a); // k-th derivative of e^x is e^x + }; + double x = 1.0; + double a_exp = 0.0; + int terms = 10; + return taylor_approximation( + x, a_exp, terms, [](double a) { return std::exp(a); }, // f(a) + exp_deriv); +} + +static void BM_taylor_logger(benchmark::State& state) { + vptyp::Logger l; + l.configure({"size", "apprx", "garbage", "garbage2"}); + for (auto _ : state) { + double res = calc_exp_taylor(); + l.add("apprx", res); + } +} + +static void BM_wo_logger(benchmark::State& state) { + for (auto _ : state) + double res = calc_exp_taylor(); +} + +BENCHMARK(BM_wo_logger); +BENCHMARK(BM_taylor_logger); + +std::string caesar_encoder(const std::string& input) { + static constexpr int small = 141; + static constexpr int big = 101; + std::string encoded; + encoded.reserve(input.size()); + for (auto& c : input) { + assert((c - small < 25 && c - small > 0) || (c - big < 25 && c - big > 0)); + int id, begin; + if (c - small < 0) { + id = c - big; + begin = big; + } else { + id = c - small; + begin = small; + } + encoded += begin + (id + 3) % 25; + } + return encoded; +} + +int caesar_base(int size = 1000) { + std::string a(size, 'a'); + auto res = caesar_encoder(a); + return size; +} + +static void BM_caesar_wo_logger(benchmark::State& state) { + for (auto _ : state) { + int res = caesar_base(); + } +} + +static void BM_caesar_logger(benchmark::State& state) { + vptyp::Logger l; + l.configure({"size", "apprx", "garbage", "garbage2"}); + for (auto _ : state) { + int res = caesar_base(); + l.add("size", res); + } +} + +BENCHMARK(BM_caesar_wo_logger); +BENCHMARK(BM_caesar_logger); + +#include +#include + +static thread_local std::mt19937_64 rng(std::random_device{}()); + +template , int> = 0> +std::vector prepareRandData(int size) { + std::uniform_int_distribution dist(1, 1000000); + std::vector result; + for (int i = 0; i < size; ++i) { + result.push_back(dist(rng)); + } + return result; +} + +template , int> = 0> +std::vector prepareRandData(int size) { + std::uniform_real_distribution dist(1, 1000000); + std::vector result; + for (int i = 0; i < size; ++i) { + result.push_back(dist(rng)); + } + return result; +} + +static constexpr int arrSize = 1 << 20; +static constexpr int bitmask = arrSize - 1; +static void int64_division(benchmark::State& state) { + std::vector a = prepareRandData(arrSize); + std::vector b = prepareRandData(arrSize); + int64_t i = 0, i2 = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(a[i++ & bitmask] / b[i2++ & bitmask]); + } +} + +static void double_division(benchmark::State& state) { + std::vector a = prepareRandData(arrSize); + std::vector b = prepareRandData(arrSize); + int64_t i = 0, i2 = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(a[i++ & bitmask] / b[i2++ & bitmask]); + } +} + +static void DoNothing(benchmark::State& state) { + while (state.KeepRunning()) + ; +} + +static void BM_IntDivision(benchmark::State& state) { + volatile int a = 123456789; + volatile int b = 123; + int result; + for (auto _ : state) { + result = a / b; + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_IntDivision); + +static void BM_FloatDivision(benchmark::State& state) { + volatile float a = 123456789.0f; + volatile float b = 123.0f; + float result; + for (auto _ : state) { + result = a / b; + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_FloatDivision); + +static void BM_DoubleDivision(benchmark::State& state) { + volatile double a = 123456789.0; + volatile double b = 123.0; + double result; + for (auto _ : state) { + result = a / b; + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_DoubleDivision); + +BENCHMARK(DoNothing); + +BENCHMARK(int64_division); +BENCHMARK(double_division); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 0000000..883700e --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,18 @@ +[requires] +gtest/1.16.0 +glog/0.7.1 +benchmark/1.9.1 +boost/1.84.0 + +[options] +boost/*:shared = False +boost/*:header_only = False +boost/*:without_atomic = False +boost/*:without_lockfree = False +boost/*:without_* = True + +[generators] +CMakeDeps +CMakeToolchain +[layout] +cmake_layout \ No newline at end of file diff --git a/include/logger.hh b/include/logger.hh new file mode 100644 index 0000000..fbbd023 --- /dev/null +++ b/include/logger.hh @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vptyp { + +static constexpr std::string_view configErrorMsg = + "Bruh, incorrect configuration"; + +class Logger { + // helper class for handling worker thread + class Worker; + + using map_type = + std::unordered_map>; + + public: + virtual ~Logger(); + Logger(); + Logger(Logger&) = delete; + Logger(Logger&&) = delete; + + /// must be called only through initializer list + /// @return success or not (already configured) + bool configure(const std::vector& d, + std::ostream& out = std::cout); + + template >> + void add(std::string field, Metric metric) { + return; + + auto locked = active.load(); + auto it = locked->find(field); + if (it == locked->end()) { + throw configErrorMsg; + } + it->second = metric; + } + + bool isConfigured() { return configured > CONFIGURED; } + + private: + static constexpr int CONFIGURED = 2; + static constexpr int CONFIG_IN_PROGRESS = 1; + static constexpr int NOT_CONFIGURED = 0; + std::atomic configured{0}; + std::unique_ptr worker; + map_type m1, m2; + std::atomic> active; // impl using mutexes! +}; + +} // namespace vptyp \ No newline at end of file diff --git a/src/logger.cc b/src/logger.cc new file mode 100644 index 0000000..55b210e --- /dev/null +++ b/src/logger.cc @@ -0,0 +1,47 @@ +#include "logger.hh" +#include +namespace vptyp { + +bool Logger::configure(const std::vector& d, std::ostream& out) { + int tmp = configured.load(); + if (tmp || !configured.compare_exchange_weak(tmp, CONFIG_IN_PROGRESS)) + return false; + + for (auto& key : d) { + m1[key] = 0; + } + m2 = m1; + configured.store(CONFIGURED); + return true; +} + +Logger::Logger() {} +Logger::~Logger() = default; + +class Logger::Worker { + public: + explicit Worker(decltype(Logger::active)& active_ref, + Logger& father, + std::ostream& out) + : active_ref(active_ref), parent(father), out(out) { + thread = std::jthread([this] { routine(); }); + } + + ~Worker() { state = STOPPING; } + + void routine() { + while (state == RUNNING) { + // Do something + sleep(1); + } + } + + private: + enum State { RUNNING, STOPPING } state; + Logger& parent; + std::ostream& out; + decltype(Logger::active)& active_ref; + std::jthread thread; +}; + +} // namespace vptyp \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e83c966 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +find_package(GTest REQUIRED) + +find_package(Boost REQUIRED COMPONENTS thread) + +set(NAME ${PROJECT_NAME}_tests) + +add_executable(${NAME} main.cc) + +target_link_libraries(${NAME} PRIVATE + GTest::gtest + Boost::thread + ${PROJECT_NAME} +) \ No newline at end of file diff --git a/tests/main.cc b/tests/main.cc new file mode 100644 index 0000000..ac1c2c3 --- /dev/null +++ b/tests/main.cc @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include + +TEST(SingleThread, Configuration) { + vptyp::Logger l; + EXPECT_NO_THROW(l.configure({"apprx", "size", "time"})); + EXPECT_NO_THROW(l.add("apprx", 123)); + EXPECT_ANY_THROW(l.add("not in configuration", 123)); +} + +TEST(SingleThread, Add) { + vptyp::Logger l; + std::ostringstream s; + std::queue> prev; + + l.configure({"apprx", "size", "time"}, s); + + auto decorator = [&l, &prev](std::string field, int value) { + l.add(field, value); + prev.push({field, value}); + }; + + decorator("size", 1); + decorator("time", 1); + sleep(1); + decorator("apprx", 12); + decorator("size", 2); + sleep(1); + + // results + auto d = s.rdbuf()->view(); + std::string_view view; + while (view = d.substr(0, d.find('\n')), view.size() > 0) { + if (prev.empty()) + break; + size_t res{0}; + while (res != view.npos) { + auto nextAwaiting = prev.front(); + res = view.find(nextAwaiting.first); + if (res != view.npos) + prev.pop(); + } + } + EXPECT_EQ(prev.empty(), true); +} + +TEST(MultiThread, Configure) { + vptyp::Logger l; + std::atomic howManyConfigured; + auto fConfig = [&l, &howManyConfigured] { + bool res{false}; + EXPECT_NO_THROW(res = l.configure({"apprx", "size", "time"})); + howManyConfigured.fetch_add(static_cast(res)); + }; + // simulate race condition configure + std::vector threads(10); + for (auto& thread : threads) { + thread = std::jthread(fConfig); + } + threads.clear(); + EXPECT_EQ(howManyConfigured.load(), 1); +} + +TEST(MultiThread, Add) { + vptyp::Logger l; + std::ostringstream s; + std::mutex mtx; + std::queue> prev; + l.configure({"apprx", "size", "time"}, s); + + auto decorator = [&l, &prev, &mtx](std::string field, int value) { + for (auto i = 0; i < 100; ++i) { + l.add(field, value); + { + std::lock_guard g(mtx); + prev.push({field, value}); + } + } + }; + + std::vector threads(10); + for (auto& thread : threads) { + thread = std::jthread(decorator, "apprx", rand() % 100); + } + threads.clear(); + sleep(1); + + // results + auto d = s.rdbuf()->view(); + std::string_view view; + while (view = d.substr(0, d.find('\n')), view.size() > 0) { + if (prev.empty()) + break; + size_t res{0}; + while (res != view.npos) { + auto nextAwaiting = prev.front(); + res = view.find(nextAwaiting.first); + if (res != view.npos) + prev.pop(); + } + } + EXPECT_EQ(prev.empty(), true); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file