This commit is contained in:
Artur Mukhamadiev 2025-06-22 18:11:55 +00:00
commit caa87478b5
11 changed files with 443 additions and 0 deletions

1
.clang-format Normal file
View File

@ -0,0 +1 @@
BasedOnStyle: Chromium

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.cache/*
build/*
compile_commands.json

18
CMakeLists.txt Normal file
View File

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

9
CMakeUserPresets.json Normal file
View File

@ -0,0 +1,9 @@
{
"version": 4,
"vendor": {
"conan": {}
},
"include": [
"build/Release/generators/CMakePresets.json"
]
}

11
bench/CMakeLists.txt Normal file
View File

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

106
bench/main.cc Normal file
View File

@ -0,0 +1,106 @@
#include <cmath>
#include <functional>
#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<double(double)>& func, // f(a)
const std::function<double(int, double)>& 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();

18
conanfile.txt Normal file
View File

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

56
include/logger.hh Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include <atomic>
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <variant>
#include <vector>
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<std::string, std::variant<int64_t, double>>;
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<std::string>& d);
template <typename Metric,
typename = std::enable_if_t<std::is_arithmetic_v<Metric>>>
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<int> configured{NOT_CONFIGURED};
std::unique_ptr<Worker> worker;
std::shared_ptr<map_type> m1, m2;
std::atomic<std::shared_ptr<map_type>> active; // impl using mutexes!
};
} // namespace vptyp

86
src/logger.cc Normal file
View File

@ -0,0 +1,86 @@
#include "logger.hh"
#include <thread>
namespace vptyp {
bool Logger::configure(const std::vector<std::string>& 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<map_type>()), m2(std::make_shared<map_type>()) {
worker = std::make_unique<Logger::Worker>(*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

16
tests/CMakeLists.txt Normal file
View File

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

119
tests/main.cc Normal file
View File

@ -0,0 +1,119 @@
#include <gtest/gtest.h>
#include <boost/lockfree/queue.hpp>
#include <logger.hh>
#include <queue>
#include <thread>
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<std::pair<std::string, int64_t>> 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<size_t> howManyConfigured;
auto fConfig = [&l, &howManyConfigured] {
bool res{false};
EXPECT_NO_THROW(res = l.configure({"apprx", "size", "time"}));
howManyConfigured.fetch_add(static_cast<size_t>(res));
};
// simulate race condition configure
std::vector<std::jthread> 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<std::pair<std::string, int64_t>> 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<std::jthread> 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();
}