commit caa87478b54b846bb581fac36ae1414ed2b7fa10 Author: Artur Mukhamadiev Date: Sun Jun 22 18:11:55 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..1d2533d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +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}) + +target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=thread) +target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=thread) + +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..71aeace --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,9 @@ +{ + "version": 4, + "vendor": { + "conan": {} + }, + "include": [ + "build/Release/generators/CMakePresets.json" + ] +} \ 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..13b6fb6 --- /dev/null +++ b/bench/main.cc @@ -0,0 +1,106 @@ +#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); + +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..2c2547e --- /dev/null +++ b/include/logger.hh @@ -0,0 +1,56 @@ +#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(); + explicit Logger(std::ostream& out = std::cout); + Logger(Logger&) = delete; + Logger(Logger&&) = delete; + + /// @return success or not (already configured) + bool configure(const std::vector& d); + + template >> + void add(std::string field, Metric metric) { + auto locked = active.load(); + auto it = locked->find(field); + if (it == locked->end()) { + throw configErrorMsg; + } + it->second = metric; + } + + bool isConfigured() { return configured == CONFIGURED; } + + private: + friend Worker; + static constexpr int CONFIGURED = 2; + static constexpr int CONFIG_IN_PROGRESS = 1; + static constexpr int NOT_CONFIGURED = 0; + std::atomic configured{NOT_CONFIGURED}; + std::unique_ptr worker; + std::shared_ptr 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..8dfe77b --- /dev/null +++ b/src/logger.cc @@ -0,0 +1,86 @@ +#include "logger.hh" +#include +namespace vptyp { + +bool Logger::configure(const std::vector& d) { + int tmp = configured.load(); + if (tmp || !configured.compare_exchange_weak(tmp, CONFIG_IN_PROGRESS)) + return false; + + auto& m1_ref = *m1.get(); + auto& m2_ref = *m2.get(); + for (auto& key : d) { + m1_ref[key] = 0; + } + m2_ref = m1_ref; + active.store(m1); + configured.store(CONFIGURED); + return true; +} + +Logger::Logger(std::ostream& out) + : m1(std::make_shared()), m2(std::make_shared()) { + worker = std::make_unique(*this, out); +} + +Logger::~Logger() { + worker.reset(); +} + +class Logger::Worker { + public: + explicit Worker(Logger& father, std::ostream& out) + : parent(father), out(out) { + thread = std::jthread([this] { routine(); }); + } + + ~Worker() { + state = STOPPING; + thread.join(); + } + + void routine() { + state = RUNNING; + while (state == RUNNING) { + unroll(); + sleep(1); + } + } + + void unroll(); + + private: + enum State { UNDEF, RUNNING, STOPPING } state{UNDEF}; + Logger& parent; + std::ostream& out; + std::jthread thread; +}; + +void Logger::Worker::unroll() { + if (!parent.isConfigured()) + return; + + auto tmp = parent.active.load(); + auto toBeActive = tmp == parent.m1 ? parent.m2 : parent.m1; + while (!parent.active.compare_exchange_weak(tmp, toBeActive)) { + std::this_thread::yield(); + } + + // at this place we are guarantee that tmp is only ours or not? + std::string output; + bool haveToPush{false}; + for (auto& element : *tmp.get()) { + if (!std::visit([](auto&& i) -> bool { return i; }, element.second)) + continue; + std::string value = + std::visit([](auto&& i) { return std::to_string(i); }, element.second); + output += "[" + element.first + "=" + value + "] "; + element.second = 0l; + haveToPush = true; + } + + if (haveToPush) + out << output << std::endl; +} + +} // namespace vptyp \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..53d7714 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +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} +) + +target_compile_options(${NAME} PRIVATE -fsanitize=thread) +target_link_options(${NAME} PRIVATE -fsanitize=thread) \ No newline at end of file diff --git a/tests/main.cc b/tests/main.cc new file mode 100644 index 0000000..41821e3 --- /dev/null +++ b/tests/main.cc @@ -0,0 +1,119 @@ +#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) { + std::ostringstream s; + vptyp::Logger l(s); + std::queue> prev; + + l.configure({"apprx", "size", "time"}); + + 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 + std::string_view outBufferView = s.rdbuf()->view(); + std::string_view lineView; + auto startPos = 0; + while (startPos < outBufferView.length() && + (lineView = outBufferView.substr(startPos, outBufferView.find('\n')), + lineView.size() > 0)) { + std::cout << lineView << std::endl; + if (prev.empty()) + break; + startPos = outBufferView.find('\n') + 1; + size_t res{0}; + while (res != lineView.npos && !prev.empty()) { + auto nextAwaiting = prev.front(); + res = lineView.find(nextAwaiting.first); + if (res != lineView.npos) + prev.pop(); + std::cout << nextAwaiting.first << std::endl; + } + std::cout << "n" << std::endl; + } + 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) { + std::ostringstream s; + vptyp::Logger l(s); + std::mutex mtx; + std::queue> prev; + l.configure({"apprx", "size", "time"}); + + auto decorator = [&l, &prev, &mtx](std::string field, int value) { + for (auto i = 0; i < 5; ++i) { + l.add(field, value); + { + std::lock_guard g(mtx); + prev.push({field, value}); + } + } + }; + + std::vector threads(3); + threads[0] = std::jthread(decorator, "apprx", rand() % 100); + threads[1] = std::jthread(decorator, "size", rand() % 100); + threads[2] = std::jthread(decorator, "time", 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