Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d964802cc2 |
10
.clang-tidy
Normal file
10
.clang-tidy
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Checks: '-*,modernize-*,cppcoreguidelines-*,-modernize-use-trailing-return-type'
|
||||||
|
WarningsAsErrors: 'modernize-use-nullptr'
|
||||||
|
HeaderFilterRegex: '.*(my_project/include|my_project/src)/.*'
|
||||||
|
CheckOptions:
|
||||||
|
- key: modernize-use-nullptr.NullMacros
|
||||||
|
value: NULL,CUSTOM_NULL
|
||||||
|
- key: cppcoreguidelines-pro-type-member-init.CheckNakedFields
|
||||||
|
value: true
|
||||||
|
- key: readability-braces-around-statements.ShortStatementLines
|
||||||
|
value: 1
|
||||||
@ -7,6 +7,9 @@ add_library(${PROJECT_NAME} SHARED)
|
|||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC include)
|
target_include_directories(${PROJECT_NAME} PUBLIC include)
|
||||||
|
|
||||||
|
set(BENCH ON)
|
||||||
|
set(TESTS ON)
|
||||||
|
|
||||||
file(GLOB prj_src src/*)
|
file(GLOB prj_src src/*)
|
||||||
|
|
||||||
target_sources(${PROJECT_NAME} PRIVATE ${prj_src})
|
target_sources(${PROJECT_NAME} PRIVATE ${prj_src})
|
||||||
@ -14,5 +17,14 @@ target_sources(${PROJECT_NAME} PRIVATE ${prj_src})
|
|||||||
# target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=thread)
|
# target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=thread)
|
||||||
# target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=thread)
|
# target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=thread)
|
||||||
|
|
||||||
add_subdirectory(bench)
|
if(DEFINED BENCH)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(bench)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(DEFINED TESTS)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
|
||||||
|
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/"
|
||||||
|
DESTINATION include)
|
||||||
@ -4,7 +4,6 @@
|
|||||||
"conan": {}
|
"conan": {}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"build/Release/generators/CMakePresets.json",
|
"build/CMakePresets.json"
|
||||||
"build/Debug/generators/CMakePresets.json"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# MetricsLogger
|
||||||
|
|
||||||
|
Simple implementation of metrics logger with next constraints:
|
||||||
|
1. no blocking on worker threads
|
||||||
|
2. metrics only of arithmetic types
|
||||||
|
3. one metrics is written only by one thread
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Benchmark Time CPU Iterations
|
||||||
|
---------------------------------------------------------------
|
||||||
|
BM_wo_logger 662 ns 662 ns 1061318
|
||||||
|
BM_taylor_logger 886 ns 886 ns 796535
|
||||||
|
BM_taylor_glog 4303 ns 4302 ns 162679
|
||||||
|
BM_taylor_atomic_upd 671 ns 671 ns 1049589
|
||||||
|
BM_taylor_mutex_upd 725 ns 725 ns 960519
|
||||||
|
BM_taylor_map_upd 944 ns 943 ns 744620
|
||||||
|
BM_caesar_logger 52965 ns 52963 ns 13206
|
||||||
|
BM_caesar_wo_logger 52636 ns 52635 ns 13284
|
||||||
|
DoNothing 4.74 ns 4.74 ns 112688754
|
||||||
|
```
|
||||||
@ -1,11 +1,10 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <metricsLogger.hh>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "benchmark/benchmark.h"
|
#include "benchmark/benchmark.h"
|
||||||
#include "glog/logging.h"
|
#include "glog/logging.h"
|
||||||
#include "logger.hh"
|
|
||||||
|
|
||||||
//! AI generated
|
//! AI generated
|
||||||
static double taylor_approximation(
|
static double taylor_approximation(
|
||||||
@ -38,16 +37,16 @@ static double calc_exp_taylor() {
|
|||||||
auto exp_deriv = [](int k, double a) -> double {
|
auto exp_deriv = [](int k, double a) -> double {
|
||||||
return std::exp(a); // k-th derivative of e^x is e^x
|
return std::exp(a); // k-th derivative of e^x is e^x
|
||||||
};
|
};
|
||||||
double x = 1.0;
|
static constexpr double x = 1.0;
|
||||||
double a_exp = 0.0;
|
static constexpr double a_exp = 0.0;
|
||||||
int terms = 10;
|
static constexpr int terms = 10;
|
||||||
return taylor_approximation(
|
return taylor_approximation(
|
||||||
x, a_exp, terms, [](double a) { return std::exp(a); }, // f(a)
|
x, a_exp, terms, [](double a) { return std::exp(a); }, // f(a)
|
||||||
exp_deriv);
|
exp_deriv);
|
||||||
}
|
}
|
||||||
|
|
||||||
vptyp::Logger& getLogger() {
|
vptyp::MetricsLogger& getLogger() {
|
||||||
static vptyp::Logger l(std::cout);
|
static vptyp::MetricsLogger l(std::cout);
|
||||||
if (!l.isConfigured()) {
|
if (!l.isConfigured()) {
|
||||||
l.configure({"size", "apprx", "garbage", "garbage2"});
|
l.configure({"size", "apprx", "garbage", "garbage2"});
|
||||||
}
|
}
|
||||||
@ -63,9 +62,10 @@ void initGlog() {
|
|||||||
|
|
||||||
static void BM_taylor_logger(benchmark::State& state) {
|
static void BM_taylor_logger(benchmark::State& state) {
|
||||||
auto& l = getLogger();
|
auto& l = getLogger();
|
||||||
|
std::string apprx = "apprx";
|
||||||
for (auto _ : state) {
|
for (auto _ : state) {
|
||||||
double res = calc_exp_taylor();
|
double res = calc_exp_taylor();
|
||||||
l.add("apprx", res);
|
l.add(apprx, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,18 +95,16 @@ static void BM_taylor_atomic_upd(benchmark::State& state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void updMapValue(double res, const std::string& key) {
|
void updMapValue(double res, const std::string& key) {
|
||||||
static std::unordered_map<std::string, double> d = {{"key", 10.0},
|
static std::unordered_map<std::string, double> d = {
|
||||||
{"assembly", 11.0},
|
{"key", 0}, {"assembly", 0}, {"draw", 0.0}, {"d2", 0.0}, {"d55", 0.0}};
|
||||||
{"draw", 123.3},
|
|
||||||
{"d2", 0.0},
|
|
||||||
{"d55", 0.23}};
|
|
||||||
d[key] = res;
|
d[key] = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BM_taylor_map_upd(benchmark::State& state) {
|
static void BM_taylor_map_upd(benchmark::State& state) {
|
||||||
|
std::string assembly = "assembly";
|
||||||
for (auto _ : state) {
|
for (auto _ : state) {
|
||||||
double res = calc_exp_taylor();
|
double res = calc_exp_taylor();
|
||||||
updMapValue(res, "assembly");
|
updMapValue(res, assembly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +134,13 @@ BENCHMARK(BM_taylor_map_upd);
|
|||||||
std::string caesar_encoder(const std::string& input) {
|
std::string caesar_encoder(const std::string& input) {
|
||||||
static constexpr int small = 'a';
|
static constexpr int small = 'a';
|
||||||
static constexpr int big = 'A';
|
static constexpr int big = 'A';
|
||||||
|
static constexpr int alphabetSize = 25;
|
||||||
std::string encoded;
|
std::string encoded;
|
||||||
encoded.reserve(input.size());
|
encoded.reserve(input.size());
|
||||||
for (auto& c : input) {
|
for (auto& c : input) {
|
||||||
assert((c - small < 25 && c - small >= 0) ||
|
assert((c - small < alphabetSize && c - small >= 0) ||
|
||||||
(c - big < 25 && c - big >= 0));
|
(c - big < alphabetSize && c - big >= 0));
|
||||||
int id, begin;
|
int id{0}, begin{0};
|
||||||
if (c - small < 0) {
|
if (c - small < 0) {
|
||||||
id = c - big;
|
id = c - big;
|
||||||
begin = big;
|
begin = big;
|
||||||
@ -149,12 +148,13 @@ std::string caesar_encoder(const std::string& input) {
|
|||||||
id = c - small;
|
id = c - small;
|
||||||
begin = small;
|
begin = small;
|
||||||
}
|
}
|
||||||
encoded += begin + (id + 3) % 25;
|
encoded += begin + (id + 3) % alphabetSize;
|
||||||
}
|
}
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
int caesar_base(int size = 1000) {
|
static constexpr int sizeDef = 1000;
|
||||||
|
int caesar_base(int size = sizeDef) {
|
||||||
std::string a(size, 'a');
|
std::string a(size, 'a');
|
||||||
auto res = caesar_encoder(a);
|
auto res = caesar_encoder(a);
|
||||||
return size;
|
return size;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
[requires]
|
[requires]
|
||||||
gtest/1.16.0
|
gtest/1.16.0
|
||||||
glog/0.7.1
|
rapidyaml/0.9.0
|
||||||
|
glog/0.7.1-unwindfix
|
||||||
benchmark/1.9.1
|
benchmark/1.9.1
|
||||||
boost/1.84.0
|
boost/1.84.0
|
||||||
|
|
||||||
@ -13,6 +14,4 @@ boost/*:without_* = True
|
|||||||
|
|
||||||
[generators]
|
[generators]
|
||||||
CMakeDeps
|
CMakeDeps
|
||||||
CMakeToolchain
|
CMakeToolchain
|
||||||
[layout]
|
|
||||||
cmake_layout
|
|
||||||
@ -13,29 +13,28 @@ namespace vptyp {
|
|||||||
static constexpr std::string_view configErrorMsg =
|
static constexpr std::string_view configErrorMsg =
|
||||||
"Bruh, incorrect configuration";
|
"Bruh, incorrect configuration";
|
||||||
|
|
||||||
class Logger {
|
class MetricsLogger {
|
||||||
// helper class for handling worker thread
|
|
||||||
class Worker;
|
|
||||||
|
|
||||||
using map_type =
|
using map_type =
|
||||||
std::unordered_map<std::string, std::variant<int64_t, double>>;
|
std::unordered_map<std::string, std::variant<int64_t, double>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~Logger();
|
virtual ~MetricsLogger();
|
||||||
|
MetricsLogger();
|
||||||
Logger();
|
explicit MetricsLogger(std::ostream& out);
|
||||||
explicit Logger(std::ostream& out);
|
MetricsLogger(MetricsLogger&) = delete;
|
||||||
Logger(Logger&) = delete;
|
MetricsLogger(MetricsLogger&&) = delete;
|
||||||
Logger(Logger&&) = delete;
|
MetricsLogger& operator=(MetricsLogger&) = delete;
|
||||||
Logger& operator=(Logger&) = delete;
|
MetricsLogger& operator=(MetricsLogger&&) = delete;
|
||||||
Logger& operator=(Logger&&) = delete;
|
|
||||||
|
|
||||||
|
/// @brief you should make logger configuration before logging
|
||||||
|
/// After logger will be ready for holding metrics.
|
||||||
/// @return success or not (already configured)
|
/// @return success or not (already configured)
|
||||||
bool configure(const std::vector<std::string>& d);
|
bool configure(const std::vector<std::string>& d);
|
||||||
|
|
||||||
template <typename Metric,
|
template <typename Metric>
|
||||||
typename = std::enable_if_t<std::is_arithmetic_v<Metric>>>
|
void add(const std::string& field, Metric metric)
|
||||||
void add(const std::string& field, Metric metric) {
|
requires(std::is_arithmetic_v<Metric>)
|
||||||
|
{
|
||||||
refs.fetch_add(1, std::memory_order_release);
|
refs.fetch_add(1, std::memory_order_release);
|
||||||
map_type* locked = active.load(std::memory_order_acquire);
|
map_type* locked = active.load(std::memory_order_acquire);
|
||||||
auto it = locked->find(field);
|
auto it = locked->find(field);
|
||||||
@ -50,14 +49,16 @@ class Logger {
|
|||||||
bool isConfigured() { return configured == CONFIGURED; }
|
bool isConfigured() { return configured == CONFIGURED; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// helper class for handling worker thread
|
||||||
|
class Worker;
|
||||||
friend Worker;
|
friend Worker;
|
||||||
enum Configuration { NOT_CONFIGURED, CONFIG_IN_PROGRESS, CONFIGURED };
|
enum Configuration { NOT_CONFIGURED, CONFIG_IN_PROGRESS, CONFIGURED };
|
||||||
std::atomic<int> configured{NOT_CONFIGURED};
|
std::atomic<int> configured{NOT_CONFIGURED};
|
||||||
std::unique_ptr<Worker> worker;
|
std::unique_ptr<Worker> worker;
|
||||||
std::unique_ptr<map_type> m1, m2;
|
std::unique_ptr<map_type> m1, m2;
|
||||||
std::atomic<map_type*> active; // impl may use mutex!
|
std::atomic<map_type*> active;
|
||||||
std::atomic<size_t> refs{0}; // degradation on worker side (waiting for no
|
std::atomic<size_t> refs{0}; // degradation on worker side (waiting for no
|
||||||
// one to be in refs section)
|
// one to be in refs section)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace vptyp
|
} // namespace vptyp
|
||||||
102
src/logger.cc
102
src/logger.cc
@ -1,102 +0,0 @@
|
|||||||
#include "logger.hh"
|
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#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.get());
|
|
||||||
configured.store(CONFIGURED);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger::Logger()
|
|
||||||
: m1(std::make_unique<map_type>()), m2(std::make_unique<map_type>()) {
|
|
||||||
worker = std::make_unique<Logger::Worker>(*this, std::cout);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger::Logger(std::ostream& out)
|
|
||||||
: m1(std::make_unique<map_type>()), m2(std::make_unique<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::thread([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::thread thread; // jthread not needed, as we anyway must wait for join
|
|
||||||
};
|
|
||||||
|
|
||||||
void Logger::Worker::unroll() {
|
|
||||||
if (!parent.isConfigured())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto tmp = parent.active.load(std::memory_order_acquire);
|
|
||||||
auto toBeActive = tmp == parent.m1.get() ? parent.m2.get() : parent.m1.get();
|
|
||||||
|
|
||||||
parent.active.store(toBeActive, std::memory_order_release);
|
|
||||||
|
|
||||||
// so we setting up happens before relation with counters
|
|
||||||
while (parent.refs.load(std::memory_order_acquire) > 0) {
|
|
||||||
std::this_thread::yield();
|
|
||||||
}
|
|
||||||
|
|
||||||
// // it's needed thread fence to guarantee use count change
|
|
||||||
// // __tsan_acquire(tmp.use_count());
|
|
||||||
// std::atomic_thread_fence(std::memory_order_acquire);
|
|
||||||
|
|
||||||
// at this place we are guarantee that tmp is only ours or not?
|
|
||||||
std::string output;
|
|
||||||
bool haveToPush{false};
|
|
||||||
for (auto& element : *tmp) {
|
|
||||||
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
|
|
||||||
147
src/metricsLogger.cc
Normal file
147
src/metricsLogger.cc
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <format>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <memory>
|
||||||
|
#include <metricsLogger.hh>
|
||||||
|
#include <thread>
|
||||||
|
namespace vptyp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief take yaml file and parse it
|
||||||
|
* @details
|
||||||
|
*/
|
||||||
|
class LoggerFormat {
|
||||||
|
public:
|
||||||
|
explicit LoggerFormat(std::string_view format_file = "logger-format.yaml") {
|
||||||
|
(void)format_file; // unused at the moment
|
||||||
|
}
|
||||||
|
|
||||||
|
void placeElement(std::string_view key, std::string_view value);
|
||||||
|
void finalize(std::ostream& out);
|
||||||
|
|
||||||
|
private:
|
||||||
|
pid_t pid = getpid();
|
||||||
|
std::string time_format{"%d-%m-%Y %H-%M-%S"};
|
||||||
|
std::string prefix{"[{}] {} "};
|
||||||
|
std::string elementFormat{"{}={}"};
|
||||||
|
std::string delimiter{";"};
|
||||||
|
std::string intermediateLine{};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool MetricsLogger::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.get());
|
||||||
|
configured.store(CONFIGURED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsLogger::MetricsLogger()
|
||||||
|
: m1(std::make_unique<map_type>()), m2(std::make_unique<map_type>()) {
|
||||||
|
worker = std::make_unique<MetricsLogger::Worker>(*this, std::cout);
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsLogger::MetricsLogger(std::ostream& out)
|
||||||
|
: m1(std::make_unique<map_type>()), m2(std::make_unique<map_type>()) {
|
||||||
|
worker = std::make_unique<MetricsLogger::Worker>(*this, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsLogger::~MetricsLogger() {
|
||||||
|
worker.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetricsLogger::Worker {
|
||||||
|
public:
|
||||||
|
explicit Worker(MetricsLogger& father, std::ostream& out)
|
||||||
|
: parent(father), out(out) {
|
||||||
|
thread = std::thread([this] { routine(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
~Worker() {
|
||||||
|
state = STOPPING;
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
Worker(const Worker&) = delete;
|
||||||
|
Worker(Worker&&) = delete;
|
||||||
|
Worker& operator=(const Worker&) = delete;
|
||||||
|
Worker& operator=(const Worker&&) = delete;
|
||||||
|
|
||||||
|
void routine() {
|
||||||
|
state = RUNNING;
|
||||||
|
while (state == RUNNING) {
|
||||||
|
unroll();
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unroll();
|
||||||
|
|
||||||
|
private:
|
||||||
|
LoggerFormat formatter;
|
||||||
|
enum State { UNDEF, RUNNING, STOPPING };
|
||||||
|
std::atomic<State> state;
|
||||||
|
MetricsLogger& parent;
|
||||||
|
std::ostream& out;
|
||||||
|
std::thread thread; // jthread not needed, as we anyway must wait for join
|
||||||
|
};
|
||||||
|
|
||||||
|
void MetricsLogger::Worker::unroll() {
|
||||||
|
if (!parent.isConfigured())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto tmp = parent.active.load(std::memory_order_acquire);
|
||||||
|
auto toBeActive = tmp == parent.m1.get() ? parent.m2.get() : parent.m1.get();
|
||||||
|
|
||||||
|
parent.active.store(toBeActive, std::memory_order_release);
|
||||||
|
|
||||||
|
// so we setting up happens before relation with counters
|
||||||
|
while (parent.refs.load(std::memory_order_acquire) > 0) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this place we are guarantee that tmp is only ours :)
|
||||||
|
std::string output;
|
||||||
|
bool haveToPush{false};
|
||||||
|
for (auto& element : *tmp) {
|
||||||
|
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);
|
||||||
|
formatter.placeElement(element.first, value);
|
||||||
|
element.second = 0l;
|
||||||
|
haveToPush = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveToPush)
|
||||||
|
formatter.finalize(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void LoggerFormat::placeElement(std::string_view key,
|
||||||
|
std::string_view value) {
|
||||||
|
if (!intermediateLine.empty())
|
||||||
|
intermediateLine += delimiter;
|
||||||
|
intermediateLine =
|
||||||
|
std::vformat(elementFormat, std::make_format_args(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void LoggerFormat::finalize(std::ostream& out) {
|
||||||
|
auto t = std::time(nullptr);
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::put_time(&tm, this->time_format.c_str());
|
||||||
|
auto timeString = ss.str();
|
||||||
|
out << std::vformat(prefix, std::make_format_args(timeString, pid)) << " "
|
||||||
|
<< intermediateLine << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace vptyp
|
||||||
@ -1,11 +1,11 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <boost/lockfree/queue.hpp>
|
#include <boost/lockfree/queue.hpp>
|
||||||
#include <logger.hh>
|
#include <metricsLogger.hh>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
TEST(SingleThread, Configuration) {
|
TEST(SingleThread, Configuration) {
|
||||||
vptyp::Logger l(std::cout);
|
vptyp::MetricsLogger l(std::cout);
|
||||||
EXPECT_NO_THROW(l.configure({"apprx", "size", "time"}));
|
EXPECT_NO_THROW(l.configure({"apprx", "size", "time"}));
|
||||||
EXPECT_NO_THROW(l.add("apprx", 123));
|
EXPECT_NO_THROW(l.add("apprx", 123));
|
||||||
EXPECT_ANY_THROW(l.add("not in configuration", 123));
|
EXPECT_ANY_THROW(l.add("not in configuration", 123));
|
||||||
@ -15,7 +15,7 @@ void outBufferCheck(std::ostringstream& s,
|
|||||||
std::queue<std::pair<std::string, int64_t>>& prev) {
|
std::queue<std::pair<std::string, int64_t>>& prev) {
|
||||||
std::string_view outBufferView = s.rdbuf()->view();
|
std::string_view outBufferView = s.rdbuf()->view();
|
||||||
std::string_view lineView;
|
std::string_view lineView;
|
||||||
auto startPos = 0;
|
size_t startPos = 0;
|
||||||
while (startPos < outBufferView.length() &&
|
while (startPos < outBufferView.length() &&
|
||||||
(lineView = outBufferView.substr(startPos, outBufferView.find('\n')),
|
(lineView = outBufferView.substr(startPos, outBufferView.find('\n')),
|
||||||
lineView.size() > 0)) {
|
lineView.size() > 0)) {
|
||||||
@ -44,19 +44,19 @@ TEST(SingleThread, Add) {
|
|||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
std::queue<std::pair<std::string, int64_t>> prev;
|
std::queue<std::pair<std::string, int64_t>> prev;
|
||||||
{
|
{
|
||||||
vptyp::Logger l(s);
|
vptyp::MetricsLogger l(s);
|
||||||
|
|
||||||
l.configure({"apprx", "size", "time"});
|
l.configure({"apprx", "size", "time"});
|
||||||
|
|
||||||
auto decorator = [&l, &prev](std::string field, int value) {
|
auto decorator = [&l, &prev](std::string field, int value) {
|
||||||
l.add(field, value);
|
l.add(field, value);
|
||||||
prev.push({field, value});
|
prev.emplace(field, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
decorator("size", 1);
|
decorator("size", 1);
|
||||||
decorator("time", 1);
|
decorator("time", 1);
|
||||||
sleep(2); // twice of logger sleep
|
sleep(2); // twice of logger sleep
|
||||||
decorator("apprx", 12);
|
decorator("apprx", 1);
|
||||||
decorator("size", 2);
|
decorator("size", 2);
|
||||||
sleep(2);
|
sleep(2);
|
||||||
}
|
}
|
||||||
@ -66,7 +66,8 @@ TEST(SingleThread, Add) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultiThread, Configure) {
|
TEST(MultiThread, Configure) {
|
||||||
vptyp::Logger l(std::cout);
|
static constexpr size_t threadsNumber = 10;
|
||||||
|
vptyp::MetricsLogger l(std::cout);
|
||||||
std::atomic<size_t> howManyConfigured;
|
std::atomic<size_t> howManyConfigured;
|
||||||
auto fConfig = [&l, &howManyConfigured] {
|
auto fConfig = [&l, &howManyConfigured] {
|
||||||
bool res{false};
|
bool res{false};
|
||||||
@ -74,7 +75,7 @@ TEST(MultiThread, Configure) {
|
|||||||
howManyConfigured.fetch_add(static_cast<size_t>(res));
|
howManyConfigured.fetch_add(static_cast<size_t>(res));
|
||||||
};
|
};
|
||||||
// simulate race condition configure
|
// simulate race condition configure
|
||||||
std::vector<std::jthread> threads(10);
|
std::vector<std::jthread> threads(threadsNumber);
|
||||||
for (auto& thread : threads) {
|
for (auto& thread : threads) {
|
||||||
thread = std::jthread(fConfig);
|
thread = std::jthread(fConfig);
|
||||||
}
|
}
|
||||||
@ -83,28 +84,30 @@ TEST(MultiThread, Configure) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultiThread, Add) {
|
TEST(MultiThread, Add) {
|
||||||
|
static constexpr size_t maxRandNumber = 100;
|
||||||
|
static constexpr size_t producerIterations = 5;
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
std::queue<std::pair<std::string, int64_t>> prev;
|
std::queue<std::pair<std::string, int64_t>> prev;
|
||||||
std::mutex mtx;
|
std::mutex mtx;
|
||||||
{
|
{
|
||||||
vptyp::Logger l(s);
|
vptyp::MetricsLogger l(s);
|
||||||
l.configure({"apprx", "size", "time"});
|
l.configure({"apprx", "size", "time"});
|
||||||
|
|
||||||
auto decorator = [&l, &prev, &mtx](std::string field, int value) {
|
auto decorator = [&l, &prev, &mtx](std::string field, int value) {
|
||||||
for (auto i = 0; i < 5; ++i) {
|
for (size_t i = 0; i < producerIterations; ++i) {
|
||||||
l.add(field, value);
|
l.add(field, value);
|
||||||
{
|
{
|
||||||
std::lock_guard g(mtx);
|
std::lock_guard g(mtx);
|
||||||
prev.push({field, value});
|
prev.emplace(field, value);
|
||||||
}
|
}
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::jthread> threads(3);
|
std::vector<std::jthread> threads(3);
|
||||||
threads[0] = std::jthread(decorator, "apprx", rand() % 100);
|
threads[0] = std::jthread(decorator, "apprx", rand() % maxRandNumber);
|
||||||
threads[1] = std::jthread(decorator, "size", rand() % 100);
|
threads[1] = std::jthread(decorator, "size", rand() % maxRandNumber);
|
||||||
threads[2] = std::jthread(decorator, "time", rand() % 100);
|
threads[2] = std::jthread(decorator, "time", rand() % maxRandNumber);
|
||||||
|
|
||||||
threads.clear();
|
threads.clear();
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user