目录
1.项目介绍
2.整体框架设计
3.⽇志输出格式化类设计
4.⽇志落地(LogSink)类设计
5.⽇志器类(Logger)设计(建造者模式)
6.双缓冲区异步任务处理器(AsyncLooper)设计
7.⽇志宏&全局接⼝设计(代理模式)
8.性能测试
9.扩展
10.参考资料
pattern = "[%d{%H:%M:%S}] %m%n"
items = {
{OtherFormatItem(), "["},
{TimeFormatItem(), "%H:%M:%S"},
{OtherFormatItem(), "]"},
{MsgFormatItem (), ""},
{NLineFormatItem (), ""}
}
LogMsg msg = {
size_t _line = 22;
size_t _ctime = 12345678;
std::thread::id _tid = 0x12345678;
std::string _name = "logger";
std::string _file = "main.cpp";
std::string _payload = "创建套接字失败";
LogLevel::value _level = ERROR;
};
#ifndef __M_FMT_H__
#define __M_FMT_H__
#include "util.hpp"
#include "message.hpp"
#include "level.hpp"
#include
#include
#include
namespace bitlog{
class FormatItem{
public:
using ptr = std::shared_ptr;
virtual ~FormatItem() {}
virtual void format(std::ostream &os, const LogMsg &msg) = 0;
};
class MsgFormatItem : public FormatItem {
public:
MsgFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << msg._payload;
}
};
class LevelFormatItem : public FormatItem {
public:
LevelFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << LogLevel::toString(msg._level);
}
};
class NameFormatItem : public FormatItem {
public:
NameFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << msg._name;
}
};
class ThreadFormatItem : public FormatItem {
public:
ThreadFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << msg._tid;
}
};
class TimeFormatItem : public FormatItem {
private:
std::string _format;
public:
TimeFormatItem(const std::string &format = "%H:%M:%S"):_format(format){
if (format.empty()) _format = "%H:%M:%S";
}
virtual void format(std::ostream &os, const LogMsg &msg) {
time_t t = msg._ctime;
struct tm lt;
localtime_r(&t, <);
char tmp[128];
strftime(tmp, 127, _format.c_str(), <);
os << tmp;
}
};
class CFileFormatItem : public FormatItem {
public:
CFileFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << msg._file;
}
};
class CLineFormatItem : public FormatItem {
public:
CLineFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << msg._line;
}
};
class TabFormatItem : public FormatItem {
public:
TabFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << "\t";
}
};
class NLineFormatItem : public FormatItem {
public:
NLineFormatItem(const std::string &str = ""){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << "\n";
}
};
class OtherFormatItem : public FormatItem {
private:
std::string _str;
public:
OtherFormatItem(const std::string &str = ""):_str(str){}
virtual void format(std::ostream &os, const LogMsg &msg) {
os << _str;
}
};
class Formatter {
public:
using ptr = std::shared_ptr;
/*
%d ⽇期
%T 缩进
%t 线程id
%p ⽇志级别
%c ⽇志器名称
%f ⽂件名
%l ⾏号
%m ⽇志消息
%n 换⾏
*/
Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%p][%c]
[%f:%l] %m%n"):
_pattern(pattern){
assert(parsePattern());
}
const std::string pattern() { return _pattern; }
std::string format(const LogMsg &msg) {
std::stringstream ss;
for (auto &it : _items) {
it->format(ss, msg);
}
return ss.str();
}
std::ostream& format(std::ostream &os, const LogMsg &msg) {
for (auto &it : _items) {
it->format(os, msg);
}
return os;
}
FormatItem::ptr createItem(const std::string &fc, const std::string
&subfmt) {
if (fc == "m") return FormatItem::ptr(new MsgFormatItem(subfmt));
if (fc == "p") return FormatItem::ptr(new LevelFormatItem(subfmt));
if (fc == "c") return FormatItem::ptr(new NameFormatItem(subfmt));
if (fc == "t") return FormatItem::ptr(new
ThreadFormatItem(subfmt));
if (fc == "n") return FormatItem::ptr(new NLineFormatItem(subfmt));
if (fc == "d") return FormatItem::ptr(new TimeFormatItem(subfmt));
if (fc == "f") return FormatItem::ptr(new CFileFormatItem(subfmt));
if (fc == "l") return FormatItem::ptr(new CLineFormatItem(subfmt));
if (fc == "T") return FormatItem::ptr(new TabFormatItem(subfmt));
return FormatItem::ptr();
}
//pattern解析
bool parsePattern() {
//std::string _pattern
="sg{}fsg%d{%H:%M:%S}%Tsdf%t%T[%p]%T[%c]%T%f:%l%T%m%n";
//std::cout << _pattern << std::endl;
//每个要素分为三部分:
// 格式化字符 : %d %T %p...
// 对应的输出⼦格式 : {%H:%M:%S}
// 对应数据的类型 : 0-表⽰原始字符串,也就是⾮格式化字符,1-表⽰格式化数据
类型
// 默认格式 "%d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n"
std::vector> arry;
std::string format_key;//存放%后的格式化字符
std::string format_val;//存放格式化字符后边 {} 中的⼦格式字符串
std::string string_row;//存放原始的⾮格式化字符
bool sub_format_error = false;
int pos = 0;
while (pos < _pattern.size()) {
if (_pattern[pos] != '%') {
string_row.append(1, _pattern[pos++]);
continue;
}
if (pos+1 < _pattern.size() && _pattern[pos+1] == '%') {
string_row.append(1, '%');
pos += 2;
continue;
}
if (string_row.empty() == false) {
arry.push_back(std::make_tuple(string_row, "", 0));
string_row.clear();
}
//当前位置是%字符位置
pos += 1;//pos指向格式化字符位置
if (pos < _pattern.size() && isalpha(_pattern[pos])) {
format_key = _pattern[pos];//保存格式化字符
}else {
std::cout << &_pattern[pos-1] << "位置附近格式错误!\n";
return false;
}
//pos指向格式化字符的下⼀个位置,判断是否包含有⼦格式 %d{%Y-%m-%d}
pos += 1;
if (pos < _pattern.size() && _pattern[pos] == '{') {
sub_format_error = true;
pos += 1;//pos指向花括号下⼀个字符处
while(pos < _pattern.size()) {
if (_pattern[pos] == '}') {
sub_format_error = false;
pos += 1;//让pos指向}的下⼀个字符处
break;
}
format_val.append(1, _pattern[pos++]);
}
}
arry.push_back(std::make_tuple(format_key, format_val, 1));
format_key.clear();
format_val.clear();
}
if (sub_format_error) {
std::cout << "{}对应出错\n";
return false;
}
if (string_row.empty() == false)
arry.push_back(std::make_tuple(string_row, "", 0));
if (format_key.empty() == false)
arry.push_back(std::make_tuple(format_key, format_val, 1));
for (auto &it : arry) {
if (std::get<2>(it) == 0) {
FormatItem::ptr fi(new OtherFormatItem(std::get<0>(it)));
_items.push_back(fi);
}else {
FormatItem::ptr fi = createItem(std::get<0>(it),
std::get<1>(it));
if (fi.get() == nullptr) {
std::cout <<"没有对应的格式化字符: %"<(it) <<
std::endl;
return false;
}
_items.push_back(fi);
}
}
return true;
}
private:
std::string _pattern;
std::vector _items;
};
}
#endif
#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include "message.hpp"
#include "formatter.hpp"
#include
#include
namespace bitlog{
class LogSink {
public:
using ptr = std::shared_ptr;
LogSink() {}
virtual ~LogSink() {}
virtual void log(const char *data, size_t len) = 0;
};
class StdoutSink : public LogSink {
public:
using ptr = std::shared_ptr;
StdoutSink() = default;
void log(const char *data, size_t len) {
std::cout.write(data, len);
}
};
class FileSink : public LogSink {
public:
using ptr = std::shared_ptr;
FileSink(const std::string &filename):_filename(filename) {
util::file::create_directory(util::file::path(filename));
_ofs.open(_filename, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
const std::string &file() {return _filename; }
void log(const char *data, size_t len) {
_ofs.write((const char*)data, len);
if (_ofs.good() == false) {
std::cout << "⽇志输出⽂件失败!\n";
}
}
private:
std::string _filename;
std::ofstream _ofs;
};
class RollSink : public LogSink {
public:
using ptr = std::shared_ptr;
RollSink(const std::string &basename, size_t max_fsize):
_basename(basename), _max_fsize(max_fsize), _cur_fsize(0){
util::file::create_directory(util::file::path(basename));
}
void log(const char *data, size_t len) {
initLogFile();
_ofs.write(data, len);
if (_ofs.good() == false) {
std::cout << "⽇志输出⽂件失败!\n";
}
_cur_fsize += len;
}
private:
void initLogFile() {
if (_ofs.is_open() == false || _cur_fsize >= _max_fsize) {
_ofs.close();
std::string name = createFilename();
_ofs.open(name, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
_cur_fsize = 0;
return;
}
return;
}
std::string createFilename() {
time_t t = time(NULL);
struct tm lt;
localtime_r(&t, <);
std::stringstream ss;
ss << _basename;
ss << lt.tm_year + 1900;
ss << lt.tm_mon + 1;
ss << lt.tm_mday;
ss << lt.tm_hour;
ss << lt.tm_min;
ss << lt.tm_sec;
ss << ".log";
return ss.str();
}
private:
std::string _basename;
std::ofstream _ofs;
size_t _max_fsize;
size_t _cur_fsize;
};
class SinkFactory {
public:
template
static LogSink::ptr create(Args &&...args) {
return std::make_shared(std::forward(args)...);
}
};
}
#endif
#ifndef __M_LOG_H__
#define __M_LOG_H__
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "formatter.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include
#include
#include
#include
#include
#include
namespace bitlog{
class Logger {
public:
enum class Type {
LOGGER_SYNC = 0,
LOGGER_ASYNC
};
using ptr = std::shared_ptr;
Logger(const std::string &name,
Formatter::ptr formatter,
std::vector &sinks,
LogLevel::value level = LogLevel::value::DEBUG):
_name(name), _level(level), _formatter(formatter),
_sinks(sinks.begin(), sinks.end()){
}
std::string loggerName() { return _name; }
LogLevel::value loggerLevel() { return _level; }
void debug(const char *file, size_t line, const char *fmt, ...) {
if (shouldLog(LogLevel::value::DEBUG) == false) {
return ;
}
va_list al;
va_start(al, fmt);
log(LogLevel::value::DEBUG, file, line, fmt, al);
va_end(al);
}
void info(const char *file, size_t line, const char *fmt, ...) {
if (shouldLog(LogLevel::value::INFO) == false) return ;
va_list al;
va_start(al, fmt);
log(LogLevel::value::INFO, file, line, fmt, al);
va_end(al);
}
void warn(const char *file, size_t line, const char *fmt, ...) {
if (shouldLog(LogLevel::value::WARN) == false) return ;
va_list al;
va_start(al, fmt);
log(LogLevel::value::WARN, file, line, fmt, al);
va_end(al);
}
void error(const char *file, size_t line, const char *fmt, ...) {
if (shouldLog(LogLevel::value::ERROR) == false) return ;
va_list al;
va_start(al, fmt);
log(LogLevel::value::ERROR, file, line, fmt, al);
va_end(al);
}
void fatal(const char *file, size_t line, const char *fmt, ...) {
if (shouldLog(LogLevel::value::FATAL) == false) return ;
va_list al;
va_start(al, fmt);
log(LogLevel::value::FATAL, file, line, fmt, al);
va_end(al);
}
public:
class Builder {
public:
using ptr = std::shared_ptr;
Builder():_level(LogLevel::value::DEBUG),
_logger_type(Logger::Type::LOGGER_SYNC) {}
void buildLoggerName(const std::string &name) { _logger_name =
name; }
void buildLoggerLevel(LogLevel::value level) { _level = level;
}
void buildLoggerType(Logger::Type type) { _logger_type = type;
}
void buildFormatter(const std::string pattern) {
_formatter = std::make_shared(pattern);
}
void buildFormatter(const Formatter::ptr &formatter) {
_formatter = formatter;
}
template
void buildSink(Args &&...args) {
auto sink = SinkFactory::create
(std::forward(args)...);
_sinks.push_back(sink);
}
virtual Logger::ptr build() = 0;
protected:
Logger::Type _logger_type;
std::string _logger_name;
LogLevel::value _level;
Formatter::ptr _formatter;
std::vector _sinks;
};
protected:
bool shouldLog(LogLevel::value level) { return level >= _level; }
void log(LogLevel::value level,const char *file,
size_t line, const char *fmt, va_list al) {
char *buf;
std::string msg;
int len = vasprintf(&buf, fmt, al);
if (len < 0) {
msg = "格式化⽇志消息失败!!";
}else {
msg.assign(buf, len);
free(buf);
}
//LogMsg(name, file, line, payload, level)
LogMsg lm(_name, file, line, std::move(msg), level);
std::stringstream ss;
_formatter->format(ss, lm);
logIt(std::move(ss.str()));
}
virtual void logIt(const std::string &msg) = 0;
protected:
std::mutex _mutex;
std::string _name;
Formatter::ptr _formatter;
std::atomic _level;
std::vector _sinks;
};
class SyncLogger : public Logger {
public:
using ptr = std::shared_ptr;
SyncLogger(const std::string &name,
Formatter::ptr formatter,
std::vector &sinks,
LogLevel::value level = LogLevel::value::DEBUG):
Logger(name, formatter, sinks, level){
std::cout << LogLevel::toString(level)<<" 同步⽇志器: "<< name<< "创
建成功...\n";
}
private:
virtual void logIt(const std::string &msg) {
std::unique_lock lock(_mutex);
if (_sinks.empty()) { return ; }
for (auto &it : _sinks) {
it->log(msg.c_str(), msg.size());
}
}
};
class LocalLoggerBuilder: public Logger::Builder {
public:
virtual Logger::ptr build() {
if (_logger_name.empty()) {
std::cout << "⽇志器名称不能为空!!";
abort();
}
if (_formatter.get() == nullptr) {
std::cout<<"当前⽇志器:" << _logger_name;
std::cout<<" 未检测到⽇志格式,默认设置为: ";
std::cout<<" %d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n\n";
_formatter = std::make_shared();
}
if (_sinks.empty()) {
std::cout<<"当前⽇志器:"<<_logger_name<<" 未检测到落地⽅向,默认为
标准输出!\n";
_sinks.push_back(std::make_shared());
}
Logger::ptr lp;
if (_logger_type == Logger::Type::LOGGER_ASYNC) {
lp = std::make_shared(_logger_name,_formatter,
_sinks, _level);
}else {
lp = std::make_shared(_logger_name, _formatter,
_sinks, _level);
}
return lp;
}
};
}
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace bitlog{
#define BUFFER_DEFAULT_SIZE (1*1024*1024)
#define BUFFER_INCREMENT_SIZE (1*1024*1024)
#define BUFFER_THRESHOLD_SIZE (10*1024*1024)
class Buffer {
public:
Buffer(): _reader_idx(0), _writer_idx(0), _v(BUFFER_DEFAULT_SIZE){}
bool empty() { return _reader_idx == _writer_idx; }
size_t readAbleSize() { return _writer_idx - _reader_idx; }
size_t writeAbleSize() { return _v.size() - _writer_idx; }
void reset() { _reader_idx = _writer_idx = 0; }
void swap(Buffer &buf) {
_v.swap(buf._v);
std::swap(_reader_idx, buf._reader_idx);
std::swap(_writer_idx, buf._writer_idx);
}
void push(const char *data, size_t len) {
assert(len <= writeAbleSize());
ensureEnoughSpace(len);
std::copy(data, data+len, &_v[_writer_idx]);
_writer_idx += len;
}
const char*begin() { return &_v[_reader_idx]; }
void pop(size_t len) {
_reader_idx += len;
assert(_reader_idx <= _writer_idx);
}
protected:
void ensureEnoughSpace(size_t len) {
if (len <= writeAbleSize()) return;
/*每次增⼤1M⼤⼩*/
size_t new_capacity;
if (_v.size() < BUFFER_THRESHOLD_SIZE)
new_capacity = _v.size() * 2 + len;
else
new_capacity = _v.size() + BUFFER_INCREMENT_SIZE + len;
_v.resize(new_capacity);
}
private:
size_t _reader_idx;
size_t _writer_idx;
std::vector _v;
};
}
#ifndef __M_LOOP_H__
#define __M_LOOP_H__
#include "util.hpp"
#include
#include
#include
#include
#include
#include
#include "buffer.hpp"
namespace bitlog{
class AsyncLooper {
public:
using Functor = std::function;
using ptr = std::shared_ptr;
AsyncLooper(const Functor &cb): _running(true),
_looper_callback(cb),
_thread(std::thread(&AsyncLooper::worker_loop, this)) {
}
~AsyncLooper() { stop(); }
void stop(){
_running = false;
_pop_cond.notify_all();
_thread.join();
}
void push(const std::string &msg){
if (_running == false) return;
{
std::unique_lock lock(_mutex);
_push_cond.wait(lock,
[&]{ return _tasks_push.writeAbleSize() >= msg.size();
});
_tasks_push.push(msg.c_str(), msg.size());
}
_pop_cond.notify_all();
}
private:
void worker_loop(){
while(1){
{
std::unique_lock lock(_mutex);
if (_running == false && _tasks_push.empty()) {
return; }
_pop_cond.wait(lock,[&]{ return
!_tasks_push.empty()||!_running; });
_tasks_push.swap(_tasks_pop);
}
_push_cond.notify_all();
_looper_callback(_tasks_pop);
_tasks_pop.reset();
}
return;
}
private:
Functor _looper_callback;
private:
std::mutex _mutex;
std::atomic _running;
std::condition_variable _push_cond;
std::condition_variable _pop_cond;
Buffer _tasks_push;
Buffer _tasks_pop;
std::thread _thread;
};
}
#endif
#ifndef __M_BIT_H__
#define __M_BIT_H__
#include "logger.hpp"
namespace bitlog {
Logger::ptr getLogger(const std::string &name) {
return loggerManager::getInstance().getLogger(name);
}
Logger::ptr rootLogger() {
return loggerManager::getInstance().rootLogger();
}
#define debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define warn(fmt, ...) warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define LOG_DEBUG(logger, fmt, ...) (logger)->debug(fmt, ##__VA_ARGS__)
#define LOG_INFO(logger, fmt, ...) (logger)->info(fmt, ##__VA_ARGS__)
#define LOG_WARN(logger, fmt, ...) (logger)->warn(fmt, ##__VA_ARGS__)
#define LOG_ERROR(logger, fmt, ...) (logger)->error(fmt, ##__VA_ARGS__)
#define LOG_FATAL(logger, fmt, ...) (logger)->fatal(fmt, ##__VA_ARGS__)
#define LOGD(fmt, ...) LOG_DEBUG(bitlog::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGI(fmt, ...) LOG_INFO(bitlog::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGW(fmt, ...) LOG_WARN(bitlog::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGE(fmt, ...) LOG_ERROR(bitlog::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGF(fmt, ...) LOG_FATAL(bitlog::rootLogger(), fmt, ##__VA_ARGS__)
}#endif
#ifndef __M_BENCH_H__
#define __M_BENCH_H__
#include "bitlog.h"
#include
namespace bitlog {
void bench(const std::string &loger_name, size_t thread_num,
size_t msglen, size_t msg_count)
{
Logger::ptr lp = getLogger(loger_name);
if (lp.get() == nullptr) return;
std::string msg(msglen, '1');
size_t msg_count_per_thread = msg_count / thread_num;
std::vector cost_time(thread_num);
std::vector threads;
std::cout << "输⼊线程数量: " << thread_num << std::endl;
std::cout << "输出⽇志数量: " << msg_count << std::endl;
std::cout << "输出⽇志⼤⼩: " << msglen * msg_count / 1024 << "KB" <<
std::endl;
for (int i = 0; i < thread_num; i++) {
threads.emplace_back([&, i](){
auto start = std::chrono::high_resolution_clock::now();
for(size_t j = 0; j < msg_count_per_thread; j++) {
lp->fatal("%s", msg.c_str());
}
auto end = std::chrono::high_resolution_clock::now();
auto cost=std::chrono::duration_cast>
(end-start);
cost_time[i] = cost.count();
auto avg = msg_count_per_thread / cost_time[i];
std::cout << "线程" << i << "耗时: " << cost.count() << "s";
std::cout << " 平均:" << (size_t)avg << "/s\n";
});
}
for(auto &thr : threads) {
thr.join();
}
double max_cost = 0;
for (auto cost : cost_time) max_cost = max_cost < cost ? cost : max_cost;
std::cout << "总消耗时间: " << max_cost << std::endl;
std::cout << "平均每秒输出: " << (size_t)(msg_count / max_cost) << std::endl;
}
}
#endif
#include "bitlog.h"
#include "bench.h"
#include
void sync_bench_thread_log(size_t thread_count, size_t msg_count, size_t
msglen)
{
static int num = 1;
std::string logger_name = "sync_bench_logger" + std::to_string(num++);
LOGI("************************************************");
LOGI("同步⽇志测试: %d threads, %d messages", thread_count, msg_count);
bitlog::GlobalLoggerBuilder::ptr lbp(new bitlog::GlobalLoggerBuilder);
lbp->buildLoggerName(logger_name);
lbp->buildFormatter("%m%n");
lbp->buildSink("./logs/sync.log");
lbp->buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
lbp->build();
bitlog::bench(logger_name, thread_count, msglen, msg_count);
LOGI("************************************************");
}
void async_bench_thread_log(size_t thread_count, size_t msg_count, size_t
msglen)
{
static int num = 1;
std::string logger_name = "async_bench_logger" + std::to_string(num++);
LOGI("************************************************");
LOGI("异步⽇志测试: %d threads, %d messages", thread_count, msg_count);
bitlog::GlobalLoggerBuilder::ptr lbp(new bitlog::GlobalLoggerBuilder);
lbp->buildLoggerName(logger_name);
lbp->buildFormatter("%m");
lbp->buildSink("./logs/async.log");
lbp->buildLoggerType(bitlog::Logger::Type::LOGGER_ASYNC);
lbp->build();
bitlog::bench(logger_name, thread_count, msglen, msg_count);
LOGI("************************************************");
}
void bench_test() {
// 同步写⽇志
sync_bench_thread_log(1, 1000000, 100);
sync_bench_thread_log(5, 1000000, 100);
/*异步⽇志输出,为了避免因为等待落地影响时间所以⽇志数量降低为⼩于缓冲区⼤⼩进⾏测试*/
async_bench_thread_log(1, 100000, 100);
async_bench_thread_log(5, 100000, 100);
}
int main(int argc, char *argv[])
{
bench_test();
return 0;
}