init
This commit is contained in:
commit
85f0f924f5
1
.clang-format
Normal file
1
.clang-format
Normal file
@ -0,0 +1 @@
|
||||
BasedOnStyle: Chromium
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.cache/*
|
||||
build/*
|
||||
compile_commands.json
|
||||
18
CMakeLists.txt
Normal file
18
CMakeLists.txt
Normal 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)
|
||||
10
CMakeUserPresets.json
Normal file
10
CMakeUserPresets.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 4,
|
||||
"vendor": {
|
||||
"conan": {}
|
||||
},
|
||||
"include": [
|
||||
"build/Release/generators/CMakePresets.json",
|
||||
"build/Debug/generators/CMakePresets.json"
|
||||
]
|
||||
}
|
||||
14
bench/CMakeLists.txt
Normal file
14
bench/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
# target_compile_options(${NAME} PRIVATE -fsanitize=thread)
|
||||
# target_link_options(${NAME} PRIVATE -fsanitize=thread)
|
||||
121
bench/main.cc
Normal file
121
bench/main.cc
Normal file
@ -0,0 +1,121 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
vptyp::Logger& getLogger() {
|
||||
static vptyp::Logger l(std::cout);
|
||||
if (!l.isConfigured()) {
|
||||
l.configure({"size", "apprx", "garbage", "garbage2"});
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
static void BM_taylor_logger(benchmark::State& state) {
|
||||
auto& l = getLogger();
|
||||
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 = 'a';
|
||||
static constexpr int big = 'A';
|
||||
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) {
|
||||
auto& l = getLogger();
|
||||
|
||||
for (auto _ : state) {
|
||||
int res = caesar_base();
|
||||
l.add("size", res);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_caesar_logger);
|
||||
BENCHMARK(BM_caesar_wo_logger);
|
||||
|
||||
static void DoNothing(benchmark::State& state) {
|
||||
while (state.KeepRunning())
|
||||
;
|
||||
}
|
||||
BENCHMARK(DoNothing);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
18
conanfile.txt
Normal file
18
conanfile.txt
Normal 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
|
||||
55
include/logger.hh
Normal file
55
include/logger.hh
Normal file
@ -0,0 +1,55 @@
|
||||
#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);
|
||||
Logger() = delete;
|
||||
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(const 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;
|
||||
enum Configuration { NOT_CONFIGURED, CONFIG_IN_PROGRESS, CONFIGURED };
|
||||
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 may use mutex!
|
||||
};
|
||||
|
||||
} // namespace vptyp
|
||||
91
src/logger.cc
Normal file
91
src/logger.cc
Normal file
@ -0,0 +1,91 @@
|
||||
#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 = 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 };
|
||||
std::atomic<State> state;
|
||||
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();
|
||||
}
|
||||
|
||||
while (tmp.use_count() > 2) {
|
||||
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
16
tests/CMakeLists.txt
Normal 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)
|
||||
120
tests/main.cc
Normal file
120
tests/main.cc
Normal file
@ -0,0 +1,120 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <boost/lockfree/queue.hpp>
|
||||
#include <logger.hh>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
|
||||
TEST(SingleThread, Configuration) {
|
||||
vptyp::Logger l(std::cout);
|
||||
EXPECT_NO_THROW(l.configure({"apprx", "size", "time"}));
|
||||
EXPECT_NO_THROW(l.add("apprx", 123));
|
||||
EXPECT_ANY_THROW(l.add("not in configuration", 123));
|
||||
}
|
||||
|
||||
void outBufferCheck(std::ostringstream& s,
|
||||
std::queue<std::pair<std::string, int64_t>>& prev) {
|
||||
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};
|
||||
std::set<std::string_view> s;
|
||||
while (res != lineView.npos && !prev.empty()) {
|
||||
auto nextAwaiting = prev.front();
|
||||
|
||||
res = lineView.find(nextAwaiting.first);
|
||||
if (res != lineView.npos) {
|
||||
s.emplace(nextAwaiting.first);
|
||||
prev.pop();
|
||||
}
|
||||
std::cout << nextAwaiting.first << std::endl;
|
||||
}
|
||||
std::cout << "n" << std::endl;
|
||||
}
|
||||
EXPECT_EQ(prev.empty(), true);
|
||||
}
|
||||
|
||||
TEST(SingleThread, Add) {
|
||||
std::ostringstream s;
|
||||
std::queue<std::pair<std::string, int64_t>> prev;
|
||||
{
|
||||
vptyp::Logger l(s);
|
||||
|
||||
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(2); // twice of logger sleep
|
||||
decorator("apprx", 12);
|
||||
decorator("size", 2);
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
// results
|
||||
outBufferCheck(s, prev);
|
||||
}
|
||||
|
||||
TEST(MultiThread, Configure) {
|
||||
vptyp::Logger l(std::cout);
|
||||
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;
|
||||
std::queue<std::pair<std::string, int64_t>> prev;
|
||||
std::mutex mtx;
|
||||
{
|
||||
vptyp::Logger l(s);
|
||||
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});
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
outBufferCheck(s, prev);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user