├── .gitignore ├── README.md ├── example ├── makefile ├── multi-thread-example.cc └── single-thread-example.cc ├── makefile ├── pictures ├── init.png ├── mainstructor.png ├── step1.5.png ├── step1.png ├── step2.png ├── step3.png └── step4.png ├── src ├── rlog.cc └── rlog.h └── test ├── echo server QPS ├── echo_cli.cc ├── echo_serv.cc └── makefile ├── multi thread ├── makefile └── mtest.cc └── single thread ├── makefile └── test.cc /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | lib 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ring Log 2 | 3 | ### **简介** 4 | Ring Log是一个适用于C++的异步日志, 其特点是**效率高(每秒支持125+万日志写入)、易拓展**,尤其适用于**频繁写日志的场景** 5 | 6 | **效率高**:建立日志缓冲区、优化UTC日志时间生成策略 7 | 8 | **易拓展**:基于双向循环链表构建日志缓冲区,其中每个节点是一个小的日志缓冲区 9 | 10 | 而: 11 | 传统日志:直接走磁盘 12 | 而“基于队列的异步日志”:每写一条日志就需要通知一次后台线程,在频繁写日志的场景下通知过多,且队列内存不易拓展 13 | 14 | > 对应博文见我的CSDN: 15 | > http://blog.csdn.net/linkedin_38454662/article/details/72921025 16 | 17 | ### **工作原理** 18 | #### **数据结构** 19 | Ring Log的缓冲区是若干个`cell_buffer`以双向、循环的链表组成 20 | `cell_buffer`是简单的一段缓冲区,日志追加于此,带状态: 21 | - `FREE`:表示还有空间可追加日志 22 | - `FULL`:表示暂时无法追加日志,正在、或即将被持久化到磁盘; 23 | 24 | Ring Log有两个指针: 25 | - `Producer Ptr`:生产者产生的日志向这个指针指向的`cell_buffer`里追加,写满后指针向前移动,指向下一个`cell_buffer`;`Producer Ptr`永远表示当前日志写入哪个`cell_buffer`,**被多个生产者线程共同持有** 26 | - `Consumer Ptr`:消费者把这个指针指向的`cell_buffer`里的日志持久化到磁盘,完成后执行向前移动,指向下一个`cell_buffer`;`Consumer Ptr`永远表示哪个`cell_buffer`正要被持久化,仅被**一个后台消费者线程持有** 27 | 28 | ![Alt text](pictures/mainstructor.png) 29 | 30 | 起始时刻,每个`cell_buffer`状态均为`FREE` 31 | `Producer Ptr`与`Consumer Ptr`指向同一个`cell_buffer` 32 | 33 | 整个Ring Log被一个互斥锁`mutex`保护 34 | 35 | #### **大致原理** 36 | 37 | **消费者** 38 | 39 | 后台线程(消费者)forever loop: 40 | 1. 上锁,检查当前`Consumer Ptr`: 41 | - 如果对应`cell_buffer`状态为`FULL`,释放锁,去*STEP 4*; 42 | - 否则,以1秒超时时间等待条件变量`cond`; 43 | 2. 再次检查当前`Consumer Ptr`: 44 | - 若`cell_buffer`状态为`FULL`,释放锁,去*STEP 4*; 45 | - 否则,如果`cell_buffer`无内容,则释放锁,回到*STEP 1*; 46 | - 如果`cell_buffer`有内容,将其标记为`FULL`,同时`Producer Ptr`前进一位; 47 | 3. 释放锁 48 | 4. 持久化`cell_buffer` 49 | 5. 重新上锁,将`cell_buffer`状态标记为`FREE`,并清空其内容;`Consumer Ptr`前进一位; 50 | 6. 释放锁 51 | 52 | **生产者** 53 | 54 | 1. 上锁,检查当前`Producer Ptr`对应`cell_buffer`状态: 55 | 如果`cell_buffer`状态为`FREE`,且生剩余空间足以写入本次日志,则追加日志到`cell_buffer`,去*STEP X*; 56 | 2. 如果`cell_buffer`状态为`FREE`但是剩余空间不足了,标记其状态为`FULL`,然后进一步探测下一位的`next_cell_buffer`: 57 | - 如果`next_cell_buffer`状态为`FREE`,`Producer Ptr`前进一位,去*STEP X*; 58 | - 如果`next_cell_buffer`状态为`FULL`,说明`Consumer Ptr` = `next_cell_buffer`,Ring Log缓冲区使用完了;则我们继续申请一个`new_cell_buffer`,将其插入到`cell_buffer`与`next_cell_buffer`之间,并使得`Producer Ptr`指向此`new_cell_buffer`,去*STEP X*; 59 | 3. 如果`cell_buffer`状态为`FULL`,说明此时`Consumer Ptr` = `cell_buffer`,丢弃日志; 60 | 4. 释放锁,如果本线程将`cell_buffer`状态改为`FULL`则通知条件变量`cond` 61 | 62 | >在大量日志产生的场景下,Ring Log有一定的内存拓展能力;实际使用中,为防止Ring Log缓冲区无限拓展,会限制内存总大小,当超过此内存限制时不再申请新`cell_buffer`而是丢弃日志 63 | 64 | #### **图解各场景** 65 | 初始时候,`Consumer Ptr`与`Producer Ptr`均指向同一个空闲`cell_buffer1` 66 | 67 | ![Alt text](pictures/init.png) 68 | 69 | 然后生产者在1s内写满了`cell_buffer1`,`Producer Ptr`前进,通知后台消费者线程持久化 70 | 71 | ![Alt text](pictures/step1.png) 72 | 73 | 消费者持久化完成,重置`cell_buffer1`,`Consumer Ptr`前进一位,发现指向的`cell_buffer2`未满,等待 74 | 75 | ![Alt text](pictures/step1.5.png) 76 | 77 | 超过一秒后`cell_buffer2`虽有日志,但依然未满:消费者将此`cell_buffer2`标记为`FULL`强行持久化,并将`Producer Ptr`前进一位到`cell_buffer3` 78 | 79 | ![Alt text](pictures/step2.png) 80 | 81 | 消费者在`cell_buffer2`的持久化上延迟过大,结果生产者都写满`cell_buffer3\4\5\6`,已经正在写`cell_buffer1`了 82 | 83 | ![Alt text](pictures/step3.png) 84 | 85 | 生产者写满写`cell_buffer1`,发现下一位`cell_buffer2`是`FULL`,则拓展换冲区,新增`new_cell_buffer` 86 | 87 | ![Alt text](pictures/step4.png) 88 | 89 | 90 | 91 | ### **UTC时间优化** 92 | 93 | 每条日志往往都需要UTC时间:`yyyy-mm-dd hh:mm:ss`(PS:Ring Log提供了毫秒级别的精度) 94 | Linux系统下本地UTC时间的获取需要调用`localtime`函数获取年月日时分秒 95 | 在`localtime`调用次数较少时不会出现什么性能问题,但是写日志是一个大批量的工作,如果每条日志都调用`localtime`获取UTC时间,性能无法接受 96 | >在实际测试中,对于1亿条100字节日志的写入,未优化`locatime`函数时 RingLog写内存耗时`245.41s`,仅比传统日志写磁盘耗时`292.58s`快将近一分钟; 97 | >而在优化`locatime`函数后,RingLog写内存耗时`79.39s`,速度好几倍提升 98 | 99 | #### **策略** 100 | 为了减少对`localtime`的调用,使用以下策略 101 | 102 | RingLog使用变量`_sys_acc_sec`记录写上一条日志时,系统经过的秒数(从1970年起算)、使用变量`_sys_acc_min`记录写上一条日志时,系统经过的分钟数,并缓存写上一条日志时的年月日时分秒year、mon、day、hour、min、sec,并缓存UTC日志格式字符串 103 | 104 | 每当准备写一条日志: 105 | 1. 调用`gettimeofday`获取系统经过的秒`tv.tv_sec`,与`_sys_acc_sec`比较; 106 | 2. 如果`tv.tv_sec` 与 `_sys_acc_sec`相等,说明此日志与上一条日志在同一秒内产生,故**年月日时分秒**是一样的,直接使用缓存即可; 107 | 3. 否则,说明此日志与上一条日志不在同一秒内产生,继续检查:`tv.tv_sec/60`即系统经过的分钟数与`_sys_acc_min`比较; 108 | 4. 如果`tv.tv_sec/60`与`_sys_acc_min`相等,说明此日志与上一条日志在同一分钟内产生,故**年月日时分**是一样的,年月日时分 使用缓存即可,而秒`sec` = `tv.tv_sec%60`,更新缓存的秒sec,重组UTC日志格式字符串的秒部分; 109 | 5. 否则,说明此日志与上一条日志不在同一分钟内产生,调用`localtime`**重新获取UTC时间**,并更新缓存的年月日时分秒,重组UTC日志格式字符串 110 | 111 | >**小结**:如此一来,`localtime`一分钟才会调用一次,频繁写日志几乎不会有性能损耗 112 | 113 | ### **性能测试** 114 | 115 | 对比传统同步日志、与RingLog日志的效率(为了方便,传统同步日志以sync log表示) 116 | 117 | #### **1. 单线程连续写1亿条日志的效率** 118 | 分别使用`Sync log`与`Ring log`写1亿条日志(每条日志长度为100字节)测试调用总耗时,测5次,结果如下: 119 | 120 | | 方式 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 | 速度/s | 121 | |:----: |:----: |:----: |:----: |:----: |:----: |:----:|:----:| 122 | | Sync Log |290.134s|298.466s|287.727s|285.087s|301.499s|292.583s|34.18万/s| 123 | | Ring Log |79.816s| 78.694s|79.489s|79.731s|79.220s|79.39s|125.96万/s| 124 | 125 | >单线程运行下,`Ring Log`写日志效率是传统同步日志的近`3.7`倍,可以达到**每秒127万条**长为*100字节*的日志的写入 126 | 127 | #### **2、多线程各写1千万条日志的效率** 128 | 分别使用`Sync log`与`Ring log`开5个线程各写1千万条日志(每条日志长度为100字节)测试调用总耗时,测5次,结果如下: 129 | 130 | | 方式 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 | 速度/s | 131 | |:----: |:----: |:----: |:----: |:----: |:----: |:----:|:----:| 132 | | Sync Log |141.727s|144.720s|142.653s|138.304|143.818s|142.24s|35.15万/s| 133 | | Ring Log |36.896s|37.011s|38.524s|37.197s|38.034s|37.532s|133.22万/s| 134 | 135 | >多线程(5线程)运行下,`Ring Log`写日志效率是传统同步日志的近`3.8`倍,可以达到**每秒135.5万条**长为*100字节*的日志的写入 136 | 137 | 138 | 139 | #### **2. 对server QPS的影响** 140 | 现有一个Reactor模式实现的echo Server,其纯净的QPS大致为`19.32万/s` 141 | 现在分别使用`Sync Log`、`Ring Log`来测试:echo Server在每收到一个数据就调用一次日志打印下的QPS表现 142 | 143 | 对于两种方式,分别采集12次实时QPS,统计后大致结果如下: 144 | 145 | | 方式 | 最低QPS | 最高QPS | 平均QPS | QPS损失比 | 146 | |:----: |:----: |:----: |:----: | :----:| 147 | | `Sync Log` |96891次|130068次|114251次| 40.89%| 148 | | `Ring Log` |154979次 |178697次 |167198次|13.46% | 149 | 150 | >传统同步日志`sync log`使得echo Server QPS从19.32w万/s降低至`11.42万/s`,损失了`40.89%` 151 | >`RingLog`使得echo Server QPS从19.32w万/s降低至`16.72万/s`,损失了`13.46%` 152 | 153 | ### **USAGE** 154 | 155 | 156 | >LOG_INIT("logdir", "myapp"); 157 | > 158 | >LOG_ERROR("my name is %s, my number is %d", "leechanx", 3); 159 | 160 | 最后会在目录logdir下生成myapp.yyyy-mm-dd.pid.log.[n]文件名的日志 161 | 162 | 日志格式为: 163 | >[ERROR][yyyy-mm-dd hh:mm:ss.ms][pid]code.cc:line_no(function_name): my name is leechanx, my number is 3 164 | 165 | 166 | ### **TODO** 167 | - 程序正常退出、异常退出,此时在buffer中缓存的日志会丢失(通过把堆内存替换为共享内存来解决,见分支NoLoseData) 168 | - 第N天23:59:59秒产生的日志有时会被刷写到第N+1天的日志文件中 169 | -------------------------------------------------------------------------------- /example/makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -o multi-thread-example multi-thread-example.cc -I../src -lpthread -L../lib/ -lringlog 3 | g++ -o single-thread-example single-thread-example.cc -I../src -lpthread -L../lib/ -lringlog 4 | -------------------------------------------------------------------------------- /example/multi-thread-example.cc: -------------------------------------------------------------------------------- 1 | #include "rlog.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int64_t get_current_millis(void) { 9 | struct timeval tv; 10 | gettimeofday(&tv, NULL); 11 | return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; 12 | } 13 | 14 | void* thdo(void* args) 15 | { 16 | for (int i = 0;i < 1e7; ++i) 17 | { 18 | LOG_ERROR("my number is number my number is my number is my number is my number is my number is my number is %d", i); 19 | } 20 | } 21 | 22 | int main(int argc, char** argv) 23 | { 24 | LOG_INIT("log", "myname", 3); 25 | pthread_t tids[5]; 26 | for (int i = 0;i < 5; ++i) 27 | pthread_create(&tids[i], NULL, thdo, NULL); 28 | 29 | for (int i = 0;i < 5; ++i) 30 | pthread_join(tids[i], NULL); 31 | } 32 | -------------------------------------------------------------------------------- /example/single-thread-example.cc: -------------------------------------------------------------------------------- 1 | #include "rlog.h" 2 | #include 3 | 4 | int main(int argc, char** argv) 5 | { 6 | LOG_INIT("log", "myname", 3); 7 | for (int i = 0;i < 1e3; ++i) 8 | { 9 | LOG_ERROR("my number is number my number is my number is my number is my number is my number is my number is %d", i); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | TARGET=lib/libringlog.a 2 | CXX=g++ 3 | CFLAGS=-g -O2 -Wall -fPIC 4 | SRC=src 5 | INC=-Isrc 6 | OBJS = $(addsuffix .o, $(basename $(wildcard $(SRC)/*.cc))) 7 | 8 | $(TARGET): $(OBJS) 9 | -mkdir -p lib 10 | ar cqs $@ $^ 11 | 12 | -include $(OBJS:.o=.d) 13 | 14 | %.o: %.cc 15 | $(CXX) $(CFLAGS) -c -o $@ $< $(INC) 16 | @$(CXX) -MM $*.cc $(INC) > $*.d 17 | @mv -f $*.d $*.d.tmp 18 | @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d 19 | @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | \ 20 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d 21 | rm -f $*.d.tmp 22 | 23 | .PHONY: clean 24 | 25 | clean: 26 | -rm -f src/*.o $(TARGET) 27 | -------------------------------------------------------------------------------- /pictures/init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/init.png -------------------------------------------------------------------------------- /pictures/mainstructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/mainstructor.png -------------------------------------------------------------------------------- /pictures/step1.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/step1.5.png -------------------------------------------------------------------------------- /pictures/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/step1.png -------------------------------------------------------------------------------- /pictures/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/step2.png -------------------------------------------------------------------------------- /pictures/step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/step3.png -------------------------------------------------------------------------------- /pictures/step4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeechanX/Ring-Log/b2284ca92d64968c4d6c7de5de66edd205602cd6/pictures/step4.png -------------------------------------------------------------------------------- /src/rlog.cc: -------------------------------------------------------------------------------- 1 | #include "rlog.h" 2 | 3 | #include 4 | #include //access, getpid 5 | #include //assert 6 | #include //va_list 7 | #include //mkdir 8 | #include //system call 9 | 10 | #define MEM_USE_LIMIT (3u * 1024 * 1024 * 1024)//3GB 11 | #define LOG_USE_LIMIT (1u * 1024 * 1024 * 1024)//1GB 12 | #define LOG_LEN_LIMIT (4 * 1024)//4K 13 | #define RELOG_THRESOLD 5 14 | #define BUFF_WAIT_TIME 1 15 | 16 | pid_t gettid() 17 | { 18 | return syscall(__NR_gettid); 19 | } 20 | 21 | pthread_mutex_t ring_log::_mutex = PTHREAD_MUTEX_INITIALIZER; 22 | pthread_cond_t ring_log::_cond = PTHREAD_COND_INITIALIZER; 23 | 24 | ring_log* ring_log::_ins = NULL; 25 | pthread_once_t ring_log::_once = PTHREAD_ONCE_INIT; 26 | uint32_t ring_log::_one_buff_len = 30*1024*1024;//30MB 27 | 28 | ring_log::ring_log(): 29 | _buff_cnt(3), 30 | _curr_buf(NULL), 31 | _prst_buf(NULL), 32 | _fp(NULL), 33 | _log_cnt(0), 34 | _env_ok(false), 35 | _level(INFO), 36 | _lst_lts(0), 37 | _tm() 38 | { 39 | //create double linked list 40 | cell_buffer* head = new cell_buffer(_one_buff_len); 41 | if (!head) 42 | { 43 | fprintf(stderr, "no space to allocate cell_buffer\n"); 44 | exit(1); 45 | } 46 | cell_buffer* current; 47 | cell_buffer* prev = head; 48 | for (int i = 1;i < _buff_cnt; ++i) 49 | { 50 | current = new cell_buffer(_one_buff_len); 51 | if (!current) 52 | { 53 | fprintf(stderr, "no space to allocate cell_buffer\n"); 54 | exit(1); 55 | } 56 | current->prev = prev; 57 | prev->next = current; 58 | prev = current; 59 | } 60 | prev->next = head; 61 | head->prev = prev; 62 | 63 | _curr_buf = head; 64 | _prst_buf = head; 65 | 66 | _pid = getpid(); 67 | } 68 | 69 | void ring_log::init_path(const char* log_dir, const char* prog_name, int level) 70 | { 71 | pthread_mutex_lock(&_mutex); 72 | 73 | strncpy(_log_dir, log_dir, 512); 74 | //name format: name_year-mon-day-t[tid].log.n 75 | strncpy(_prog_name, prog_name, 128); 76 | 77 | mkdir(_log_dir, 0777); 78 | //查看是否存在此目录、目录下是否允许创建文件 79 | if (access(_log_dir, F_OK | W_OK) == -1) 80 | { 81 | fprintf(stderr, "logdir: %s error: %s\n", _log_dir, strerror(errno)); 82 | } 83 | else 84 | { 85 | _env_ok = true; 86 | } 87 | if (level > TRACE) 88 | level = TRACE; 89 | if (level < FATAL) 90 | level = FATAL; 91 | _level = level; 92 | 93 | pthread_mutex_unlock(&_mutex); 94 | } 95 | 96 | void ring_log::persist() 97 | { 98 | while (true) 99 | { 100 | //check if _prst_buf need to be persist 101 | pthread_mutex_lock(&_mutex); 102 | if (_prst_buf->status == cell_buffer::FREE) 103 | { 104 | struct timespec tsp; 105 | struct timeval now; 106 | gettimeofday(&now, NULL); 107 | tsp.tv_sec = now.tv_sec; 108 | tsp.tv_nsec = now.tv_usec * 1000;//nanoseconds 109 | tsp.tv_sec += BUFF_WAIT_TIME;//wait for 1 seconds 110 | pthread_cond_timedwait(&_cond, &_mutex, &tsp); 111 | } 112 | if (_prst_buf->empty()) 113 | { 114 | //give up, go to next turn 115 | pthread_mutex_unlock(&_mutex); 116 | continue; 117 | } 118 | 119 | if (_prst_buf->status == cell_buffer::FREE) 120 | { 121 | assert(_curr_buf == _prst_buf);//to test 122 | _curr_buf->status = cell_buffer::FULL; 123 | _curr_buf = _curr_buf->next; 124 | } 125 | 126 | int year = _tm.year, mon = _tm.mon, day = _tm.day; 127 | pthread_mutex_unlock(&_mutex); 128 | 129 | //decision which file to write 130 | if (!decis_file(year, mon, day)) 131 | continue; 132 | //write 133 | _prst_buf->persist(_fp); 134 | fflush(_fp); 135 | 136 | pthread_mutex_lock(&_mutex); 137 | _prst_buf->clear(); 138 | _prst_buf = _prst_buf->next; 139 | pthread_mutex_unlock(&_mutex); 140 | } 141 | } 142 | 143 | void ring_log::try_append(const char* lvl, const char* format, ...) 144 | { 145 | int ms; 146 | uint64_t curr_sec = _tm.get_curr_time(&ms); 147 | if (_lst_lts && curr_sec - _lst_lts < RELOG_THRESOLD) 148 | return ; 149 | 150 | char log_line[LOG_LEN_LIMIT]; 151 | //int prev_len = snprintf(log_line, LOG_LEN_LIMIT, "%s[%d-%02d-%02d %02d:%02d:%02d.%03d]", lvl, _tm.year, _tm.mon, _tm.day, _tm.hour, _tm.min, _tm.sec, ms); 152 | int prev_len = snprintf(log_line, LOG_LEN_LIMIT, "%s[%s.%03d]", lvl, _tm.utc_fmt, ms); 153 | 154 | va_list arg_ptr; 155 | va_start(arg_ptr, format); 156 | 157 | //TO OPTIMIZE IN THE FUTURE: performance too low here! 158 | int main_len = vsnprintf(log_line + prev_len, LOG_LEN_LIMIT - prev_len, format, arg_ptr); 159 | 160 | va_end(arg_ptr); 161 | 162 | uint32_t len = prev_len + main_len; 163 | 164 | _lst_lts = 0; 165 | bool tell_back = false; 166 | 167 | pthread_mutex_lock(&_mutex); 168 | if (_curr_buf->status == cell_buffer::FREE && _curr_buf->avail_len() >= len) 169 | { 170 | _curr_buf->append(log_line, len); 171 | } 172 | else 173 | { 174 | //1. _curr_buf->status = cell_buffer::FREE but _curr_buf->avail_len() < len 175 | //2. _curr_buf->status = cell_buffer::FULL 176 | if (_curr_buf->status == cell_buffer::FREE) 177 | { 178 | _curr_buf->status = cell_buffer::FULL;//set to FULL 179 | cell_buffer* next_buf = _curr_buf->next; 180 | //tell backend thread 181 | tell_back = true; 182 | 183 | //it suggest that this buffer is under the persist job 184 | if (next_buf->status == cell_buffer::FULL) 185 | { 186 | //if mem use < MEM_USE_LIMIT, allocate new cell_buffer 187 | if (_one_buff_len * (_buff_cnt + 1) > MEM_USE_LIMIT) 188 | { 189 | fprintf(stderr, "no more log space can use\n"); 190 | _curr_buf = next_buf; 191 | _lst_lts = curr_sec; 192 | } 193 | else 194 | { 195 | cell_buffer* new_buffer = new cell_buffer(_one_buff_len); 196 | _buff_cnt += 1; 197 | new_buffer->prev = _curr_buf; 198 | _curr_buf->next = new_buffer; 199 | new_buffer->next = next_buf; 200 | next_buf->prev = new_buffer; 201 | _curr_buf = new_buffer; 202 | } 203 | } 204 | else 205 | { 206 | //next buffer is free, we can use it 207 | _curr_buf = next_buf; 208 | } 209 | if (!_lst_lts) 210 | _curr_buf->append(log_line, len); 211 | } 212 | else//_curr_buf->status == cell_buffer::FULL, assert persist is on here too! 213 | { 214 | _lst_lts = curr_sec; 215 | } 216 | } 217 | pthread_mutex_unlock(&_mutex); 218 | if (tell_back) 219 | { 220 | pthread_cond_signal(&_cond); 221 | } 222 | } 223 | 224 | bool ring_log::decis_file(int year, int mon, int day) 225 | { 226 | //TODO: 是根据日志消息的时间写时间?还是自主写时间? I select 自主写时间 227 | if (!_env_ok) 228 | { 229 | if (_fp) 230 | fclose(_fp); 231 | _fp = fopen("/dev/null", "w"); 232 | return _fp != NULL; 233 | } 234 | if (!_fp) 235 | { 236 | _year = year, _mon = mon, _day = day; 237 | char log_path[1024] = {}; 238 | sprintf(log_path, "%s/%s.%d%02d%02d.%u.log", _log_dir, _prog_name, _year, _mon, _day, _pid); 239 | _fp = fopen(log_path, "w"); 240 | if (_fp) 241 | _log_cnt += 1; 242 | } 243 | else if (_day != day) 244 | { 245 | fclose(_fp); 246 | char log_path[1024] = {}; 247 | _year = year, _mon = mon, _day = day; 248 | sprintf(log_path, "%s/%s.%d%02d%02d.%u.log", _log_dir, _prog_name, _year, _mon, _day, _pid); 249 | _fp = fopen(log_path, "w"); 250 | if (_fp) 251 | _log_cnt = 1; 252 | } 253 | else if (ftell(_fp) >= LOG_USE_LIMIT) 254 | { 255 | fclose(_fp); 256 | char old_path[1024] = {}; 257 | char new_path[1024] = {}; 258 | //mv xxx.log.[i] xxx.log.[i + 1] 259 | for (int i = _log_cnt - 1;i > 0; --i) 260 | { 261 | sprintf(old_path, "%s/%s.%d%02d%02d.%u.log.%d", _log_dir, _prog_name, _year, _mon, _day, _pid, i); 262 | sprintf(new_path, "%s/%s.%d%02d%02d.%u.log.%d", _log_dir, _prog_name, _year, _mon, _day, _pid, i + 1); 263 | rename(old_path, new_path); 264 | } 265 | //mv xxx.log xxx.log.1 266 | sprintf(old_path, "%s/%s.%d%02d%02d.%u.log", _log_dir, _prog_name, _year, _mon, _day, _pid); 267 | sprintf(new_path, "%s/%s.%d%02d%02d.%u.log.1", _log_dir, _prog_name, _year, _mon, _day, _pid); 268 | rename(old_path, new_path); 269 | _fp = fopen(old_path, "w"); 270 | if (_fp) 271 | _log_cnt += 1; 272 | } 273 | return _fp != NULL; 274 | } 275 | 276 | void* be_thdo(void* args) 277 | { 278 | ring_log::ins()->persist(); 279 | return NULL; 280 | } 281 | -------------------------------------------------------------------------------- /src/rlog.h: -------------------------------------------------------------------------------- 1 | #ifndef __RING_LOG_H__ 2 | #define __RING_LOG_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include //getpid, gettid 12 | 13 | enum LOG_LEVEL 14 | { 15 | FATAL = 1, 16 | ERROR, 17 | WARN, 18 | INFO, 19 | DEBUG, 20 | TRACE, 21 | }; 22 | 23 | extern pid_t gettid(); 24 | 25 | struct utc_timer 26 | { 27 | utc_timer() 28 | { 29 | struct timeval tv; 30 | gettimeofday(&tv, NULL); 31 | //set _sys_acc_sec, _sys_acc_min 32 | _sys_acc_sec = tv.tv_sec; 33 | _sys_acc_min = _sys_acc_sec / 60; 34 | //use _sys_acc_sec calc year, mon, day, hour, min, sec 35 | struct tm cur_tm; 36 | localtime_r((time_t*)&_sys_acc_sec, &cur_tm); 37 | year = cur_tm.tm_year + 1900; 38 | mon = cur_tm.tm_mon + 1; 39 | day = cur_tm.tm_mday; 40 | hour = cur_tm.tm_hour; 41 | min = cur_tm.tm_min; 42 | sec = cur_tm.tm_sec; 43 | reset_utc_fmt(); 44 | } 45 | 46 | uint64_t get_curr_time(int* p_msec = NULL) 47 | { 48 | struct timeval tv; 49 | //get current ts 50 | gettimeofday(&tv, NULL); 51 | if (p_msec) 52 | *p_msec = tv.tv_usec / 1000; 53 | //if not in same seconds 54 | if ((uint32_t)tv.tv_sec != _sys_acc_sec) 55 | { 56 | sec = tv.tv_sec % 60; 57 | _sys_acc_sec = tv.tv_sec; 58 | //or if not in same minutes 59 | if (_sys_acc_sec / 60 != _sys_acc_min) 60 | { 61 | //use _sys_acc_sec update year, mon, day, hour, min, sec 62 | _sys_acc_min = _sys_acc_sec / 60; 63 | struct tm cur_tm; 64 | localtime_r((time_t*)&_sys_acc_sec, &cur_tm); 65 | year = cur_tm.tm_year + 1900; 66 | mon = cur_tm.tm_mon + 1; 67 | day = cur_tm.tm_mday; 68 | hour = cur_tm.tm_hour; 69 | min = cur_tm.tm_min; 70 | //reformat utc format 71 | reset_utc_fmt(); 72 | } 73 | else 74 | { 75 | //reformat utc format only sec 76 | reset_utc_fmt_sec(); 77 | } 78 | } 79 | return tv.tv_sec; 80 | } 81 | 82 | int year, mon, day, hour, min, sec; 83 | char utc_fmt[20]; 84 | 85 | private: 86 | void reset_utc_fmt() 87 | { 88 | snprintf(utc_fmt, 20, "%d-%02d-%02d %02d:%02d:%02d", year, mon, day, hour, min, sec); 89 | } 90 | 91 | void reset_utc_fmt_sec() 92 | { 93 | snprintf(utc_fmt + 17, 3, "%02d", sec); 94 | } 95 | 96 | uint64_t _sys_acc_min; 97 | uint64_t _sys_acc_sec; 98 | }; 99 | 100 | class cell_buffer 101 | { 102 | public: 103 | enum buffer_status 104 | { 105 | FREE, 106 | FULL 107 | }; 108 | 109 | cell_buffer(uint32_t len): 110 | status(FREE), 111 | prev(NULL), 112 | next(NULL), 113 | _total_len(len), 114 | _used_len(0) 115 | { 116 | _data = new char[len]; 117 | if (!_data) 118 | { 119 | fprintf(stderr, "no space to allocate _data\n"); 120 | exit(1); 121 | } 122 | } 123 | 124 | uint32_t avail_len() const { return _total_len - _used_len; } 125 | 126 | bool empty() const { return _used_len == 0; } 127 | 128 | void append(const char* log_line, uint32_t len) 129 | { 130 | if (avail_len() < len) 131 | return ; 132 | memcpy(_data + _used_len, log_line, len); 133 | _used_len += len; 134 | } 135 | 136 | void clear() 137 | { 138 | _used_len = 0; 139 | status = FREE; 140 | } 141 | 142 | void persist(FILE* fp) 143 | { 144 | uint32_t wt_len = fwrite(_data, 1, _used_len, fp); 145 | if (wt_len != _used_len) 146 | { 147 | fprintf(stderr, "write log to disk error, wt_len %u\n", wt_len); 148 | } 149 | } 150 | 151 | buffer_status status; 152 | 153 | cell_buffer* prev; 154 | cell_buffer* next; 155 | 156 | private: 157 | cell_buffer(const cell_buffer&); 158 | cell_buffer& operator=(const cell_buffer&); 159 | 160 | uint32_t _total_len; 161 | uint32_t _used_len; 162 | char* _data; 163 | }; 164 | 165 | class ring_log 166 | { 167 | public: 168 | //for thread-safe singleton 169 | static ring_log* ins() 170 | { 171 | pthread_once(&_once, ring_log::init); 172 | return _ins; 173 | } 174 | 175 | static void init() 176 | { 177 | while (!_ins) _ins = new ring_log(); 178 | } 179 | 180 | void init_path(const char* log_dir, const char* prog_name, int level); 181 | 182 | int get_level() const { return _level; } 183 | 184 | void persist(); 185 | 186 | void try_append(const char* lvl, const char* format, ...); 187 | 188 | private: 189 | ring_log(); 190 | 191 | bool decis_file(int year, int mon, int day); 192 | 193 | ring_log(const ring_log&); 194 | const ring_log& operator=(const ring_log&); 195 | 196 | int _buff_cnt; 197 | 198 | cell_buffer* _curr_buf; 199 | cell_buffer* _prst_buf; 200 | 201 | cell_buffer* last_buf; 202 | 203 | FILE* _fp; 204 | pid_t _pid; 205 | int _year, _mon, _day, _log_cnt; 206 | char _prog_name[128]; 207 | char _log_dir[512]; 208 | 209 | bool _env_ok;//if log dir ok 210 | int _level; 211 | uint64_t _lst_lts;//last can't log error time(s) if value != 0, log error happened last time 212 | 213 | utc_timer _tm; 214 | 215 | static pthread_mutex_t _mutex; 216 | static pthread_cond_t _cond; 217 | 218 | static uint32_t _one_buff_len; 219 | 220 | //singleton 221 | static ring_log* _ins; 222 | static pthread_once_t _once; 223 | }; 224 | 225 | void* be_thdo(void* args); 226 | 227 | #define LOG_MEM_SET(mem_lmt) \ 228 | do \ 229 | { \ 230 | if (mem_lmt < 90 * 1024 * 1024) \ 231 | { \ 232 | mem_lmt = 90 * 1024 * 1024; \ 233 | } \ 234 | else if (mem_lmt > 1024 * 1024 * 1024) \ 235 | { \ 236 | mem_lmt = 1024 * 1024 * 1024; \ 237 | } \ 238 | ring_log::_one_buff_len = mem_lmt; \ 239 | } while (0) 240 | 241 | #define LOG_INIT(log_dir, prog_name, level) \ 242 | do \ 243 | { \ 244 | ring_log::ins()->init_path(log_dir, prog_name, level); \ 245 | pthread_t tid; \ 246 | pthread_create(&tid, NULL, be_thdo, NULL); \ 247 | pthread_detach(tid); \ 248 | } while (0) 249 | 250 | //format: [LEVEL][yy-mm-dd h:m:s.ms][tid]file_name:line_no(func_name):content 251 | #define LOG_TRACE(fmt, args...) \ 252 | do \ 253 | { \ 254 | if (ring_log::ins()->get_level() >= TRACE) \ 255 | { \ 256 | ring_log::ins()->try_append("[TRACE]", "[%u]%s:%d(%s): " fmt "\n", \ 257 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 258 | } \ 259 | } while (0) 260 | 261 | #define LOG_DEBUG(fmt, args...) \ 262 | do \ 263 | { \ 264 | if (ring_log::ins()->get_level() >= DEBUG) \ 265 | { \ 266 | ring_log::ins()->try_append("[DEBUG]", "[%u]%s:%d(%s): " fmt "\n", \ 267 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 268 | } \ 269 | } while (0) 270 | 271 | #define LOG_INFO(fmt, args...) \ 272 | do \ 273 | { \ 274 | if (ring_log::ins()->get_level() >= INFO) \ 275 | { \ 276 | ring_log::ins()->try_append("[INFO]", "[%u]%s:%d(%s): " fmt "\n", \ 277 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 278 | } \ 279 | } while (0) 280 | 281 | #define LOG_NORMAL(fmt, args...) \ 282 | do \ 283 | { \ 284 | if (ring_log::ins()->get_level() >= INFO) \ 285 | { \ 286 | ring_log::ins()->try_append("[INFO]", "[%u]%s:%d(%s): " fmt "\n", \ 287 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 288 | } \ 289 | } while (0) 290 | 291 | #define LOG_WARN(fmt, args...) \ 292 | do \ 293 | { \ 294 | if (ring_log::ins()->get_level() >= WARN) \ 295 | { \ 296 | ring_log::ins()->try_append("[WARN]", "[%u]%s:%d(%s): " fmt "\n", \ 297 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 298 | } \ 299 | } while (0) 300 | 301 | #define LOG_ERROR(fmt, args...) \ 302 | do \ 303 | { \ 304 | if (ring_log::ins()->get_level() >= ERROR) \ 305 | { \ 306 | ring_log::ins()->try_append("[ERROR]", "[%u]%s:%d(%s): " fmt "\n", \ 307 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 308 | } \ 309 | } while (0) 310 | 311 | #define LOG_FATAL(fmt, args...) \ 312 | do \ 313 | { \ 314 | ring_log::ins()->try_append("[FATAL]", "[%u]%s:%d(%s): " fmt "\n", \ 315 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 316 | } while (0) 317 | 318 | #define TRACE(fmt, args...) \ 319 | do \ 320 | { \ 321 | if (ring_log::ins()->get_level() >= TRACE) \ 322 | { \ 323 | ring_log::ins()->try_append("[TRACE]", "[%u]%s:%d(%s): " fmt "\n", \ 324 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 325 | } \ 326 | } while (0) 327 | 328 | #define DEBUG(fmt, args...) \ 329 | do \ 330 | { \ 331 | if (ring_log::ins()->get_level() >= DEBUG) \ 332 | { \ 333 | ring_log::ins()->try_append("[DEBUG]", "[%u]%s:%d(%s): " fmt "\n", \ 334 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 335 | } \ 336 | } while (0) 337 | 338 | #define INFO(fmt, args...) \ 339 | do \ 340 | { \ 341 | if (ring_log::ins()->get_level() >= INFO) \ 342 | { \ 343 | ring_log::ins()->try_append("[INFO]", "[%u]%s:%d(%s): " fmt "\n", \ 344 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 345 | } \ 346 | } while (0) 347 | 348 | #define NORMAL(fmt, args...) \ 349 | do \ 350 | { \ 351 | if (ring_log::ins()->get_level() >= INFO) \ 352 | { \ 353 | ring_log::ins()->try_append("[INFO]", "[%u]%s:%d(%s): " fmt "\n", \ 354 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 355 | } \ 356 | } while (0) 357 | 358 | #define WARN(fmt, args...) \ 359 | do \ 360 | { \ 361 | if (ring_log::ins()->get_level() >= WARN) \ 362 | { \ 363 | ring_log::ins()->try_append("[WARN]", "[%u]%s:%d(%s): " fmt "\n", \ 364 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 365 | } \ 366 | } while (0) 367 | 368 | #define ERROR(fmt, args...) \ 369 | do \ 370 | { \ 371 | if (ring_log::ins()->get_level() >= ERROR) \ 372 | { \ 373 | ring_log::ins()->try_append("[ERROR]", "[%u]%s:%d(%s): " fmt "\n", \ 374 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 375 | } \ 376 | } while (0) 377 | 378 | #define FATAL(fmt, args...) \ 379 | do \ 380 | { \ 381 | ring_log::ins()->try_append("[FATAL]", "[%u]%s:%d(%s): " fmt "\n", \ 382 | gettid(), __FILE__, __LINE__, __FUNCTION__, ##args); \ 383 | } while (0) 384 | 385 | #endif 386 | -------------------------------------------------------------------------------- /test/echo server QPS/echo_cli.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main(int argc, char const *argv[]) 12 | { 13 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 14 | 15 | struct sockaddr_in servaddr; 16 | bzero(&servaddr, sizeof (servaddr)); 17 | servaddr.sin_family = AF_INET; 18 | servaddr.sin_port = htons(12356); 19 | inet_aton("127.0.0.1", &servaddr.sin_addr); 20 | 21 | int sendbuff = 104857600; 22 | if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)) == -1) 23 | printf("Error setsockopt"); 24 | 25 | if (connect(sockfd, (struct sockaddr* )&servaddr, sizeof(servaddr)) == -1) 26 | { 27 | perror("connect()"); 28 | exit(1); 29 | } 30 | unsigned cnt = 0; 31 | char buff[1000]; 32 | long lst_time = time(NULL); 33 | while (true) 34 | { 35 | int wn = send(sockfd, "X", 1, 0); 36 | if (wn == -1) 37 | break; 38 | int rd = recv(sockfd, buff, 1, 0); 39 | if (rd == -1 || rd == 0) 40 | break; 41 | cnt += 1; 42 | long cur_time = time(NULL); 43 | if (cur_time - lst_time >= 1) 44 | { 45 | printf("cnt %u\n", cnt); 46 | cnt = 0; 47 | lst_time = cur_time; 48 | } 49 | } 50 | close(sockfd); 51 | return 0; 52 | } -------------------------------------------------------------------------------- /test/echo server QPS/echo_serv.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "rlog.h" 15 | 16 | struct echo_ctl 17 | { 18 | int fd; 19 | int to_reply; 20 | }; 21 | 22 | int main(int argc, char const *argv[]) 23 | { 24 | LOG_INIT("log", "newlog-newlog", 3); 25 | int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); 26 | 27 | struct sockaddr_in servaddr, cliaddr; 28 | socklen_t cli_len; 29 | bzero(&servaddr, sizeof (servaddr)); 30 | servaddr.sin_family = AF_INET; 31 | servaddr.sin_port = htons(12356); 32 | inet_aton("127.0.0.1", &servaddr.sin_addr); 33 | bind(sockfd, (struct sockaddr* )&servaddr, sizeof(servaddr)); 34 | 35 | listen(sockfd, 10000); 36 | 37 | struct epoll_event events[100]; 38 | int efd = epoll_create1(0); 39 | 40 | struct epoll_event ev; 41 | ev.data.fd = sockfd; 42 | ev.events = EPOLLIN; 43 | epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &ev); 44 | 45 | char buff[100]; 46 | 47 | while (true) 48 | { 49 | int nfds = epoll_wait(efd, events, 10, 10); 50 | for (int i = 0;i < nfds; ++i) 51 | { 52 | if (events[i].data.fd == sockfd) 53 | { 54 | //connection is coming 55 | int connfd; 56 | while (true) 57 | { 58 | connfd = accept(sockfd, (struct sockaddr* )&cliaddr, &cli_len); 59 | if (connfd == -1) 60 | { 61 | if (errno == EINTR) 62 | continue; 63 | break; 64 | } 65 | 66 | int flag = fcntl(connfd, F_GETFL, 0); 67 | fcntl(connfd, F_SETFL, O_NONBLOCK | flag); 68 | struct epoll_event cev; 69 | 70 | echo_ctl *ctl = new echo_ctl(); 71 | ctl->fd = connfd; 72 | ctl->to_reply = 0; 73 | cev.data.ptr = ctl; 74 | cev.events = EPOLLIN; 75 | epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &cev); 76 | } 77 | } 78 | else if (events[i].events & EPOLLIN) 79 | { 80 | echo_ctl *ctl = (echo_ctl*)events[i].data.ptr; 81 | int connfd = ctl->fd; 82 | int rd = recv(connfd, buff, 1000, 0); 83 | while (rd == -1) 84 | { 85 | if (errno == EINTR) 86 | { 87 | rd = recv(connfd, buff, 1, 0); 88 | } 89 | else 90 | { 91 | delete ctl; 92 | close(connfd); 93 | break; 94 | } 95 | } 96 | if (rd == 0) 97 | { 98 | delete ctl; 99 | close(connfd); 100 | } 101 | if (rd > 0) 102 | { 103 | struct epoll_event cev; 104 | ctl->to_reply += rd; 105 | cev.data.ptr = ctl; 106 | cev.events = EPOLLOUT | EPOLLIN; 107 | epoll_ctl(efd, EPOLL_CTL_MOD, connfd, &cev); 108 | for (int i = 0;i < rd; ++i) 109 | { 110 | LOG_ERROR("i receive data: %c", buff[i]); 111 | } 112 | } 113 | } 114 | else if (events[i].events & EPOLLOUT) 115 | { 116 | echo_ctl* ctl = (echo_ctl*)events[i].data.ptr; 117 | int connfd = ctl->fd; 118 | int wr, wtn = 0; 119 | while (wtn < ctl->to_reply) 120 | { 121 | char buff[ctl->to_reply - wtn]; 122 | memset(buff, 'X', ctl->to_reply - wtn); 123 | wr = send(connfd, buff, ctl->to_reply - wtn, 0); 124 | if (wr == -1) 125 | { 126 | if (errno == EINTR) 127 | continue; 128 | else if (errno == EAGAIN) 129 | { 130 | ctl->to_reply = ctl->to_reply - wtn; 131 | struct epoll_event cev; 132 | cev.data.ptr = ctl; 133 | cev.events = EPOLLIN; 134 | epoll_ctl(efd, EPOLL_CTL_MOD, connfd, &cev); 135 | break; 136 | } 137 | else 138 | { 139 | delete ctl; 140 | close(connfd); 141 | break; 142 | } 143 | } 144 | wtn += wr; 145 | } 146 | if (wtn == ctl->to_reply) 147 | { 148 | ctl->to_reply = 0; 149 | struct epoll_event cev; 150 | cev.data.ptr = ctl; 151 | cev.events = EPOLLIN; 152 | epoll_ctl(efd, EPOLL_CTL_MOD, connfd, &cev); 153 | } 154 | } 155 | } 156 | } 157 | close(efd); 158 | close(sockfd); 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /test/echo server QPS/makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -o cli echo_cli.cc -lpthread -I../../src -L../../lib/ -lringlog 3 | g++ -o serv echo_serv.cc -lpthread -I../../src -L../../lib/ -lringlog 4 | -------------------------------------------------------------------------------- /test/multi thread/makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -o mtest mtest.cc -lpthread -I../../src -L../../lib/ -lringlog 3 | -------------------------------------------------------------------------------- /test/multi thread/mtest.cc: -------------------------------------------------------------------------------- 1 | #include "rlog.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int64_t get_current_millis(void) { 9 | struct timeval tv; 10 | gettimeofday(&tv, NULL); 11 | return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; 12 | } 13 | 14 | void* thdo(void* args) 15 | { 16 | for (int i = 0;i < 1e7; ++i) 17 | { 18 | LOG_ERROR("my number is number my number is my number is my number is my number is my number is my number is %d", i); 19 | } 20 | } 21 | 22 | int main(int argc, char** argv) 23 | { 24 | LOG_INIT("log", "myname", 3); 25 | uint64_t start_ts = get_current_millis(); 26 | pthread_t tids[5]; 27 | for (int i = 0;i < 5; ++i) 28 | pthread_create(&tids[i], NULL, thdo, NULL); 29 | 30 | for (int i = 0;i < 5; ++i) 31 | pthread_join(tids[i], NULL); 32 | uint64_t end_ts = get_current_millis(); 33 | printf("time use %lums\n", end_ts - start_ts); 34 | } 35 | -------------------------------------------------------------------------------- /test/single thread/makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -o test test.cc -lpthread -I../../src -L../../lib/ -lringlog 3 | -------------------------------------------------------------------------------- /test/single thread/test.cc: -------------------------------------------------------------------------------- 1 | #include "rlog.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int64_t get_current_millis(void) { 8 | struct timeval tv; 9 | gettimeofday(&tv, NULL); 10 | return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; 11 | } 12 | 13 | int main(int argc, char** argv) 14 | { 15 | LOG_INIT("log", "myname", 3); 16 | uint64_t start_ts = get_current_millis(); 17 | for (int i = 0;i < 1e8; ++i) 18 | { 19 | LOG_ERROR("my number is number my number is my number is my number is my number is my number is my number is %d", i); 20 | } 21 | uint64_t end_ts = get_current_millis(); 22 | printf("time use %lums\n", end_ts - start_ts); 23 | } 24 | --------------------------------------------------------------------------------