├── .clang-format ├── .gitignore ├── .travis.yml ├── README.md ├── limlog.h └── tests ├── Benchmark.cpp ├── BlockingBufferTest.cpp ├── ItoaTest.cpp ├── Makefile └── Test.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | ConstructorInitializerAllOnOneLineOrOnePerLine: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .vscode/ 3 | tests/* 4 | !tests/*[.cpp|c|h] 5 | *.log 6 | *.txt -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | sudo: required 4 | 5 | matrix: 6 | include: 7 | - name: "GCC-8" 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-8 14 | env: 15 | - CC: gcc-8 16 | - CXX: g++-8 17 | 18 | before_script: 19 | - ulimit -c unlimited -S # enable core dumps 20 | 21 | script: 22 | - cd tests && make -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # limlog ![](https://www.travis-ci.org/zxhio/limlog.svg?branch=master) 2 | 3 | Lightweight, easy-to-use and fast logging library, providing only a front-end for log writing. 4 | 5 | Header-only, cross-platform which implemented in C++11. [细节分析](https://www.cnblogs.com/shuqin/p/12103952.html) 6 | 7 | limlog uses the `singleton` pattern and is recommended for simple logging scenarios 8 | 9 | ## Usage 10 | 11 | Header only, simply include 'limlog.h'. 12 | 13 | ```cpp 14 | #include "limlog.h" 15 | 16 | int main() { 17 | limlog::singleton()->setLogLevel(limlog::kDebug); 18 | 19 | LOG_INFO << 123 << ' ' << 1.23 << ' ' << true << ' ' << "123"; 20 | return 0; 21 | } 22 | ``` 23 | 24 | A logline format as follow: 25 | ```shell 26 | // +-------+------+-----------+------+------+------------+------+ 27 | // | level | time | thread id | logs | file | (function) | line | 28 | // +-------+------+-----------+------+------+------------+------+ 29 | 30 | // such as: 31 | INFO 2022-02-28T15:45:56.341+08:00 25332 fuck.cpp:4 123 1.230000 true 123 32 | ``` 33 | 34 | ### Logging Output 35 | limlog does not provide a rotation utility for logs, which is required for external programs. 36 | 37 | But limlog provides an output interface for users to customize. 38 | ```cpp 39 | #include "limlog.h" 40 | 41 | // send log info to remote server. 42 | ssize_t send_to_remote(const char *, size_t n) { 43 | // do send 44 | return 0; 45 | } 46 | 47 | int main() { 48 | limlog::singleton()->setOutput(send_to_remote); 49 | 50 | LOG_INFO << 123 << ' ' << 1.23 << ' ' << true << ' ' << "123"; 51 | return 0; 52 | } 53 | ``` 54 | 55 | ## Optimization 56 | 57 | ### Time 58 | see https://github.com/zxhio/time_rfc3339. 59 | 60 | ### Thread local cache thread id 61 | Introduce thread_local to avoid race conditions between threads. And reduce the number of gettid system calls 62 | 63 | ### Number to String 64 | Uses search table to optimise integer and peer search can confirm two characters. 65 | 66 | 67 | ### TODO 68 | 1. support more pattern for logging. 69 | 2. support async process. 70 | 71 | ### Reference 72 | 1. [Iyengar111/NanoLog](https://github.com/Iyengar111/NanoLog), Low Latency C++11 Logging Library. 73 | 2. [PlatformLab/NanoLog](https://github.com/PlatformLab/NanoLog), Nanolog is an extremely performant nanosecond scale logging system for C++ that exposes a simple printf-like API. 74 | 3. [kfifo](https://github.com/torvalds/linux/blob/master/lib/kfifo.c), kernel ring buffer. 75 | 5. [itoa-benchmark](https://github.com/miloyip/itoa-benchmark), some itoa algorithm, limlog uses search table. -------------------------------------------------------------------------------- /limlog.h: -------------------------------------------------------------------------------- 1 | //===- limlog.h - LimLog ----------------------------------------*- C++ -*-===// 2 | // 3 | /// \file 4 | /// Lightweight, easy-to-use and fast logging library, providing only a 5 | /// front-end for log writing. 6 | // 7 | // Author: zxh 8 | // Date: 2022/02/15 22:26:09 9 | //===----------------------------------------------------------------------===// 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #ifdef __linux 24 | #include // gettid(). 25 | #include 26 | typedef pid_t thread_id_t; 27 | #elif __APPLE__ 28 | #include 29 | typedef uint64_t thread_id_t; 30 | #else 31 | #include 32 | typedef unsigned int thread_id_t; // MSVC 33 | #endif 34 | 35 | namespace limlog { 36 | 37 | // The digits table is used to look up for number within 100. 38 | // Each two character corresponds to one digit and ten digits. 39 | static constexpr char DigitsTable[200] = { 40 | '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', 41 | '7', '0', '8', '0', '9', '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', 42 | '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '2', '0', '2', '1', '2', 43 | '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', 44 | '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', 45 | '7', '3', '8', '3', '9', '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', 46 | '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '5', '0', '5', '1', '5', 47 | '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', 48 | '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', 49 | '7', '6', '8', '6', '9', '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', 50 | '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '8', '0', '8', '1', '8', 51 | '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', 52 | '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', 53 | '7', '9', '8', '9', '9'}; 54 | 55 | template ::value, T>::type = 0> 57 | static inline size_t formatUIntInternal(T v, char to[]) { 58 | char *p = to; 59 | 60 | while (v >= 100) { 61 | const unsigned idx = (v % 100) << 1; 62 | v /= 100; 63 | *p++ = DigitsTable[idx + 1]; 64 | *p++ = DigitsTable[idx]; 65 | } 66 | 67 | if (v < 10) { 68 | *p++ = v + '0'; 69 | } else { 70 | const unsigned idx = v << 1; 71 | *p++ = DigitsTable[idx + 1]; 72 | *p++ = DigitsTable[idx]; 73 | } 74 | 75 | return p - to; 76 | } 77 | 78 | template ::value, T>::type = 0> 80 | inline size_t formatSIntInternal(T v, char to[]) { 81 | char *p = to; 82 | 83 | while (v <= static_cast(-100)) { 84 | const T idx = -(v % 100) * 2; 85 | v /= 100; 86 | *p++ = DigitsTable[idx + 1]; 87 | *p++ = DigitsTable[idx]; 88 | } 89 | 90 | if (v > static_cast(-10)) { 91 | *p++ = -v + '0'; 92 | } else { 93 | const T idx = -v * 2; 94 | *p++ = DigitsTable[idx + 1]; 95 | *p++ = DigitsTable[idx]; 96 | } 97 | 98 | return p - to; 99 | } 100 | 101 | template ::value, T>::type = 0> 103 | inline size_t formatInt(T v, char *to) { 104 | char buf[sizeof(v) * 4]; 105 | size_t signLen = 0; 106 | size_t intLen = 0; 107 | 108 | if (v < 0) { 109 | *to++ = '-'; 110 | signLen = 1; 111 | intLen = formatSIntInternal(v, buf); 112 | } else { 113 | intLen = formatUIntInternal(v, buf); 114 | } 115 | 116 | char *p = buf + intLen; 117 | for (size_t i = 0; i < intLen; ++i) 118 | *to++ = *--p; 119 | 120 | return signLen + intLen; 121 | } 122 | 123 | template ::value, T>::type = 0> 125 | inline size_t formatUIntWidth(T v, char *to, size_t fmtLen) { 126 | char buf[sizeof(v) * 4]; 127 | size_t len = formatUIntInternal(v, buf); 128 | char *p = buf + len; 129 | 130 | for (size_t i = len; i < fmtLen; i++) 131 | *to++ = '0'; 132 | 133 | size_t minLen = std::min(len, fmtLen); 134 | for (size_t i = 0; i < minLen; ++i) 135 | *to++ = *--p; 136 | 137 | return fmtLen; 138 | } 139 | 140 | inline size_t formatChar(char *to, char c) { 141 | *to = c; 142 | return sizeof(char); 143 | } 144 | 145 | enum TimeFieldLen : size_t { 146 | Year = 4, 147 | Month = 2, 148 | Day = 2, 149 | Hour = 2, 150 | Minute = 2, 151 | Second = 2, 152 | }; 153 | 154 | enum SecFracLen : size_t { Sec = 0, Milli = 3, Macro = 6, Nano = 9 }; 155 | 156 | class Time { 157 | public: 158 | using TimePoint = std::chrono::time_point; 160 | Time() = delete; 161 | Time(const Time &) = default; 162 | Time &operator=(const Time &) = default; 163 | 164 | explicit Time(time_t second) 165 | : Time(TimePoint(std::chrono::seconds(second))) {} 166 | explicit Time(const TimePoint &tp) : tp_(tp) {} 167 | 168 | /// Current time. 169 | static Time now() { return Time(std::chrono::system_clock::now()); } 170 | 171 | /// Year (4 digits, e.g. 1996). 172 | int year() const { return toTm().tm_year + 1900; } 173 | 174 | /// Month of then year, in the range [1, 12]. 175 | int month() const { return toTm().tm_mon + 1; } 176 | 177 | /// Day of the month, in the range [1, 28/29/30/31]. 178 | int day() const { return toTm().tm_mday; } 179 | 180 | /// Day of the week, in the range [1, 7]. 181 | int weekday() const { return toTm().tm_wday; } 182 | 183 | /// Hour within day, in the range [0, 23]. 184 | int hour() const { return toTm().tm_hour; } 185 | 186 | /// Minute offset within the hour, in the range [0, 59]. 187 | int minute() const { return toTm().tm_min; } 188 | 189 | /// Second offset within the minute, in the range [0, 59]. 190 | int second() const { return toTm().tm_sec; } 191 | 192 | /// Nanosecond offset within the second, in the range [0, 999999999]. 193 | int nanosecond() const { return static_cast(count() % std::nano::den); } 194 | 195 | /// Count of nanosecond elapsed since 1970-01-01T00:00:00Z . 196 | int64_t count() const { return tp_.time_since_epoch().count(); } 197 | 198 | /// Timezone name and offset in seconds east of UTC. 199 | std::pair timezone() const { 200 | static thread_local long int t_off = std::numeric_limits::min(); 201 | static thread_local char t_zone[8]; 202 | 203 | if (t_off == std::numeric_limits::min()) { 204 | struct tm t; 205 | time_t c = std::chrono::system_clock::to_time_t(tp_); 206 | localtime_r(&c, &t); 207 | t_off = t.tm_gmtoff; 208 | std::copy(t.tm_zone, 209 | t.tm_zone + std::char_traits::length(t.tm_zone), t_zone); 210 | } 211 | 212 | return std::make_pair(t_off, t_zone); 213 | } 214 | 215 | /// Standard date-time full format using RFC3339 specification. 216 | /// e.g. 217 | /// 2021-10-10T13:46:58Z 218 | /// 2021-10-10T05:46:58+08:00 219 | std::string format() const { return formatInternal(SecFracLen::Sec); } 220 | 221 | /// Standard date-time format with millisecond using RFC3339 specification. 222 | /// e.g. 223 | /// 2021-10-10T13:46:58.123Z 224 | /// 2021-10-10T05:46:58.123+08:00 225 | std::string formatMilli() const { return formatInternal(SecFracLen::Milli); } 226 | 227 | /// Standard date-time format with macrosecond using RFC3339 specification. 228 | /// e.g. 229 | /// 2021-10-10T13:46:58.123456Z 230 | /// 2021-10-10T05:46:58.123456+08:00 231 | std::string formatMacro() const { return formatInternal(SecFracLen::Macro); } 232 | 233 | /// Standard date-time format with nanosecond using RFC3339 specification. 234 | /// e.g. 235 | /// 2021-10-10T13:46:58.123456789Z 236 | /// 2021-10-10T05:46:58.123456789+08:00 237 | std::string formatNano() const { return formatInternal(SecFracLen::Nano); } 238 | 239 | private: 240 | struct tm toTm() const { 241 | struct tm t; 242 | time_t c = std::chrono::system_clock::to_time_t(tp_); 243 | 244 | // gmtime_r() is 150% faster than localtime_r() because it doesn't need to 245 | // calculate the time zone. For a more faster implementation refer to 246 | // musl-libc, which is 10% faster than the glibc gmtime_r(). 247 | // ref: 248 | // https://git.musl-libc.org/cgit/musl/tree/src/time/gmtime_r.c?h=v1.2.2#n4 249 | // gmtime_r(&c, &t); 250 | localtime_r(&c, &t); 251 | 252 | return t; 253 | } 254 | 255 | std::string formatInternal(size_t fracLen) const { 256 | char datetime[40]; 257 | char *p = datetime; 258 | struct tm t = toTm(); 259 | 260 | p += formatDate(p, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday); 261 | p += formatChar(p, 'T'); 262 | p += formatTime(p, t.tm_hour, t.tm_min, t.tm_sec, fracLen); 263 | 264 | return std::string(datetime, p - datetime); 265 | } 266 | 267 | size_t formatDate(char *to, int year, int mon, int mday) const { 268 | char *p = to; 269 | p += formatUIntWidth(year, p, TimeFieldLen::Year); 270 | p += formatChar(p, '-'); 271 | p += formatUIntWidth(mon, p, TimeFieldLen::Month); 272 | p += formatChar(p, '-'); 273 | p += formatUIntWidth(mday, p, TimeFieldLen::Day); 274 | return p - to; 275 | } 276 | 277 | size_t formatTime(char *to, int hour, int min, int sec, 278 | size_t fracLen) const { 279 | char *p = to; 280 | p += formatPartialTime(p, hour, min, sec, fracLen); 281 | p += formatTimeOff(p); 282 | return p - to; 283 | } 284 | 285 | size_t formatPartialTime(char *to, int hour, int min, int sec, 286 | size_t fracLen) const { 287 | char *p = to; 288 | p += formatUIntWidth(hour, p, TimeFieldLen::Hour); 289 | p += formatChar(p, ':'); 290 | p += formatUIntWidth(min, p, TimeFieldLen::Minute); 291 | p += formatChar(p, ':'); 292 | p += formatUIntWidth(sec, p, TimeFieldLen::Second); 293 | p += formatSecFrac(p, nanosecond(), fracLen); 294 | return p - to; 295 | } 296 | 297 | size_t formatSecFrac(char *to, int frac, size_t fracLen) const { 298 | if (fracLen == 0 || frac == 0) 299 | return 0; 300 | 301 | char *p = to; 302 | p += formatChar(p, '.'); 303 | p += formatUIntWidth(frac, p, fracLen); 304 | return p - to; 305 | } 306 | 307 | size_t formatTimeOff(char *to) const { 308 | long int off = timezone().first; 309 | char *p = to; 310 | 311 | if (off == 0) { 312 | p += formatChar(p, 'Z'); 313 | } else { 314 | p += formatChar(p, off < 0 ? '-' : '+'); 315 | p += formatUIntWidth(off / 3600, p, TimeFieldLen::Hour); 316 | p += formatChar(p, ':'); 317 | p += formatUIntWidth(off % 3600, p, TimeFieldLen::Minute); 318 | } 319 | 320 | return p - to; 321 | } 322 | 323 | TimePoint tp_; 324 | }; 325 | 326 | inline thread_id_t gettid() { 327 | static thread_local thread_id_t t_tid = 0; 328 | 329 | if (t_tid == 0) { 330 | #ifdef __linux 331 | t_tid = syscall(__NR_gettid); 332 | #elif __APPLE__ 333 | pthread_threadid_np(NULL, &t_tid); 334 | #else 335 | std::stringstream ss; 336 | ss << std::this_thread::get_id(); 337 | ss >> t_tid; 338 | #endif 339 | } 340 | return t_tid; 341 | } 342 | 343 | enum LogLevel : uint8_t { kTrace, kDebug, kInfo, kWarn, kError, kFatal }; 344 | 345 | // Stringify log level with width of 5. 346 | inline const char *stringifyLogLevel(LogLevel level) { 347 | const char *levelName[] = {"TRAC", "DEBU", "INFO", "WARN", "ERRO", "FATA"}; 348 | return levelName[level]; 349 | } 350 | 351 | /// Circle FIFO blocking produce/consume byte queue. Hold log info to wait for 352 | /// background thread consume. It exists in each thread. 353 | class BlockingBuffer { 354 | public: 355 | BlockingBuffer() : producePos_(0), consumePos_(0), consumablePos_(0) {} 356 | 357 | /// Buffer size. 358 | uint32_t size() const { return kBlockingBufferSize; } 359 | 360 | /// Already used bytes. 361 | /// It may be called by different threads, so add memory barrier to 362 | /// ensure the lasted *Pos_ is read. 363 | uint32_t used() const { 364 | std::atomic_thread_fence(std::memory_order_acquire); 365 | return producePos_ - consumePos_; 366 | } 367 | 368 | /// Unused bytes. 369 | uint32_t unused() const { return kBlockingBufferSize - used(); } 370 | 371 | /// Reset buffer's position. 372 | void reset() { 373 | producePos_ = 0; 374 | consumePos_ = 0; 375 | consumablePos_ = 0; 376 | } 377 | 378 | /// The position at the end of the last complete log. 379 | uint32_t consumable() const { 380 | std::atomic_thread_fence(std::memory_order_acquire); 381 | return consumablePos_ - consumePos_; 382 | } 383 | 384 | /// Increase consumable position with a complete log length \a n . 385 | void incConsumablePos(uint32_t n) { 386 | consumablePos_ += n; 387 | std::atomic_thread_fence(std::memory_order_release); 388 | } 389 | 390 | /// Pointer to comsume position. 391 | char *data() { return &storage_[offsetOfPos(consumePos_)]; } 392 | 393 | /// Consume n bytes data and only move the consume position. 394 | void consume(uint32_t n) { consumePos_ += n; } 395 | 396 | /// Consume \a n bytes data to \a to . 397 | uint32_t consume(char *to, uint32_t n) { 398 | // available bytes to consume. 399 | uint32_t avail = std::min(consumable(), n); 400 | 401 | // offset of consumePos to buffer end. 402 | uint32_t off2End = std::min(avail, size() - offsetOfPos(consumePos_)); 403 | 404 | // first put the data starting from consumePos until the end of buffer. 405 | memcpy(to, storage_ + offsetOfPos(consumePos_), off2End); 406 | 407 | // then put the rest at beginning of the buffer. 408 | memcpy(to + off2End, storage_, avail - off2End); 409 | 410 | consumePos_ += avail; 411 | std::atomic_thread_fence(std::memory_order_release); 412 | 413 | return avail; 414 | } 415 | 416 | /// Copy \a n bytes log info from \a from to buffer. It will be blocking 417 | /// when buffer space is insufficient. 418 | void produce(const char *from, uint32_t n) { 419 | n = std::min(size(), n); 420 | while (unused() < n) 421 | /* blocking */; 422 | 423 | // offset of producePos to buffer end. 424 | uint32_t off2End = std::min(n, size() - offsetOfPos(producePos_)); 425 | 426 | // first put the data starting from producePos until the end of buffer. 427 | memcpy(storage_ + offsetOfPos(producePos_), from, off2End); 428 | 429 | // then put the rest at beginning of the buffer. 430 | memcpy(storage_, from + off2End, n - off2End); 431 | 432 | producePos_ += n; 433 | std::atomic_thread_fence(std::memory_order_release); 434 | } 435 | 436 | private: 437 | /// Get position offset calculated from buffer start. 438 | uint32_t offsetOfPos(uint32_t pos) const { return pos & (size() - 1); } 439 | 440 | static const uint32_t kBlockingBufferSize = 1024 * 1024 * 1; // 1 MB 441 | uint32_t producePos_; 442 | uint32_t consumePos_; 443 | uint32_t consumablePos_; // increase every time with a complete log length. 444 | char storage_[kBlockingBufferSize]; // buffer size power of 2. 445 | }; 446 | 447 | using OutputFunc = ssize_t (*)(const char *, size_t); 448 | 449 | struct StdoutWriter { 450 | static ssize_t write(const char *data, size_t n) { 451 | return fwrite(data, sizeof(char), n, stdout); 452 | } 453 | }; 454 | 455 | struct NullWriter { 456 | static ssize_t write(const char *data, size_t n) { return 0; } 457 | }; 458 | 459 | class SyncLogger { 460 | public: 461 | SyncLogger() : output_(StdoutWriter::write) {} 462 | 463 | void setOutput(OutputFunc w) { output_ = w; } 464 | 465 | void produce(const char *data, size_t n) { buffer_.produce(data, n); } 466 | 467 | void flush(size_t n) { 468 | buffer_.incConsumablePos(n); 469 | output_(buffer_.data(), buffer_.consumable()); 470 | buffer_.reset(); 471 | } 472 | 473 | private: 474 | OutputFunc output_; 475 | BlockingBuffer buffer_; 476 | }; 477 | 478 | class AsyncLogger { 479 | public: 480 | AsyncLogger() : output_(StdoutWriter::write) {} 481 | 482 | void setOutput(OutputFunc w) { output_ = w; } 483 | 484 | /// TODO: 485 | void produce(const char *data, size_t n) {} 486 | 487 | /// TODO: 488 | void flush(size_t n) {} 489 | 490 | private: 491 | OutputFunc output_; 492 | }; 493 | 494 | template class LimLog { 495 | public: 496 | LimLog() : level_(LogLevel::kInfo), output_(StdoutWriter::write) {} 497 | ~LimLog() { 498 | for (auto l : loggers_) 499 | delete (l); 500 | } 501 | LimLog(const LimLog &) = delete; 502 | LimLog &operator=(const LimLog &) = delete; 503 | 504 | /// Produce \a data which length \a n to BlockingBuffer in each thread. 505 | void produce(const char *data, size_t n) { logger()->produce(data, n); } 506 | 507 | /// Flush a logline with length \a n . 508 | void flush(size_t n) { logger()->flush(n); } 509 | 510 | /// Set log level \a level. 511 | void setLogLevel(LogLevel level) { level_ = level; } 512 | 513 | /// Get log level. 514 | LogLevel getLogLevel() const { return level_; } 515 | 516 | /// Set logger output \a w . 517 | void setOutput(OutputFunc w) { 518 | output_ = w; 519 | logger()->setOutput(w); 520 | } 521 | 522 | private: 523 | Logger *logger() { 524 | static thread_local Logger *l = nullptr; 525 | if (!l) { 526 | std::lock_guard lock(loggerMutex_); 527 | l = static_cast(new Logger); 528 | l->setOutput(output_); 529 | loggers_.push_back(l); 530 | } 531 | return l; 532 | } 533 | 534 | LogLevel level_; 535 | OutputFunc output_; 536 | std::mutex loggerMutex_; 537 | std::vector loggers_; 538 | }; 539 | 540 | /// Singleton pointer. 541 | inline LimLog *singleton() { 542 | static LimLog s_limlog; 543 | return &s_limlog; 544 | } 545 | 546 | /// Log Location, include file, function, line. 547 | struct LogLoc { 548 | public: 549 | LogLoc() : LogLoc("", "", 0) {} 550 | 551 | LogLoc(const char *file, const char *function, uint32_t line) 552 | : file_(file), function_(function), line_(line) {} 553 | 554 | bool empty() const { return line_ == 0; } 555 | 556 | const char *file_; 557 | const char *function_; 558 | uint32_t line_; 559 | }; 560 | 561 | /// A line log info, usage is same as 'std::cout'. 562 | // Log format in memory. 563 | // +-------+------+-----------+------+------+------------+------+ 564 | // | level | time | thread id | logs | file | (function) | line | 565 | // +-------+------+-----------+------+------+------------+------+ 566 | class LogLine { 567 | public: 568 | LogLine() = delete; 569 | LogLine(const LogLine &) = delete; 570 | LogLine &operator=(const LogLine &) = delete; 571 | 572 | LogLine(LogLevel level, const LogLoc &loc) : count_(0), loc_(loc) { 573 | *this << stringifyLogLevel(level) << ' ' << Time::now().formatMilli() << ' ' 574 | << gettid() << loc_ << ' '; 575 | } 576 | 577 | ~LogLine() { 578 | *this << '\n'; 579 | singleton()->flush(count_); 580 | } 581 | 582 | /// Overloaded `operator<<` for type various of integral num. 583 | template ::value, T>::type = 0> 585 | LogLine &operator<<(T v) { 586 | char buf[sizeof(T) * 4]; 587 | size_t len = formatInt(v, buf); 588 | append(buf, len); 589 | return *this; 590 | } 591 | 592 | /// Overloaded `operator<<` for type various of bool. 593 | LogLine &operator<<(bool v) { 594 | if (v) 595 | append("true", 4); 596 | else 597 | append("false", 5); 598 | return *this; 599 | } 600 | 601 | /// Overloaded `operator<<` for type various of char. 602 | LogLine &operator<<(char v) { 603 | append(&v, 1); 604 | return *this; 605 | } 606 | 607 | /// Overloaded `operator<<` for type various of float num. 608 | LogLine &operator<<(float v) { 609 | std::numeric_limits::min(); 610 | std::string s = std::to_string(v); 611 | append(s.data(), s.length()); 612 | return *this; 613 | } 614 | 615 | /// Overloaded `operator<<` for type various of float num. 616 | LogLine &operator<<(double v) { 617 | std::string s = std::to_string(v); 618 | append(s.data(), s.length()); 619 | return *this; 620 | } 621 | 622 | /// Overloaded `operator<<` for type various of char*. 623 | LogLine &operator<<(const char *v) { 624 | append(v); 625 | return *this; 626 | } 627 | 628 | /// Overloaded `operator<<` for type various of std::string. 629 | LogLine &operator<<(const std::string &v) { 630 | append(v.data(), v.length()); 631 | return *this; 632 | } 633 | 634 | LogLine &operator<<(const LogLoc &loc) { 635 | if (!loc.empty()) 636 | *this << ' ' << loc.file_ << ":" << loc.line_; 637 | return *this; 638 | } 639 | 640 | private: 641 | void append(const char *data, size_t n) { 642 | singleton()->produce(data, n); 643 | count_ += n; 644 | } 645 | 646 | void append(const char *data) { append(data, strlen(data)); } 647 | 648 | size_t count_; // count of a log line bytes. 649 | LogLoc loc_; 650 | }; 651 | } // namespace limlog 652 | 653 | /// Create a logline with log level \a level and the log location \a loc . 654 | #define LOG(level, loc) \ 655 | if (limlog::singleton()->getLogLevel() <= level) \ 656 | limlog::LogLine(level, loc) 657 | 658 | /// Create a logline with log level \a level and the log localtion. 659 | #define LOG_LOC(level) \ 660 | LOG(level, limlog::LogLoc(__FILE__, __FUNCTION__, __LINE__)) 661 | 662 | #define LOG_TRACE LOG_LOC(limlog::LogLevel::kTrace) 663 | #define LOG_DEBUG LOG_LOC(limlog::LogLevel::kDebug) 664 | #define LOG_INFO LOG_LOC(limlog::LogLevel::kInfo) 665 | #define LOG_WARN LOG_LOC(limlog::LogLevel::kWarn) 666 | #define LOG_ERROR LOG_LOC(limlog::LogLevel::kError) 667 | #define LOG_FATAL LOG_LOC(limlog::LogLevel::kFatal) -------------------------------------------------------------------------------- /tests/Benchmark.cpp: -------------------------------------------------------------------------------- 1 | //===- LogTest.cpp - Benchmark ----------------------------------*- C++ -*-===// 2 | // 3 | /// \file 4 | /// Benchmark. 5 | // 6 | // Author: zxh(definezxh@163.com) 7 | // Date: 2019/12/12 11:41:58 8 | //===----------------------------------------------------------------------===// 9 | 10 | #include 11 | 12 | #include 13 | 14 | const int kLogTestCount = 1000000; 15 | const int kTestThreadCount = 1; 16 | 17 | uint64_t sys_clock_now() { 18 | return std::chrono::duration_cast( 19 | std::chrono::system_clock().now().time_since_epoch()) 20 | .count(); 21 | } 22 | 23 | #define LOG_TIME(func, type, n, idx) \ 24 | do { \ 25 | uint64_t start = sys_clock_now(); \ 26 | func(); \ 27 | uint64_t end = sys_clock_now(); \ 28 | fprintf(stdout, \ 29 | "thread: %d, %d (%s) logs takes %" PRIu64 \ 30 | " us, average: %.2lf us\n", \ 31 | idx, kLogTestCount *n, type, end - start, \ 32 | static_cast(end - start) / kLogTestCount / n); \ 33 | } while (0) 34 | 35 | void log_1_same_element_x6() { 36 | for (int i = 0; i < kLogTestCount; ++i) 37 | LOG_DEBUG << i; 38 | 39 | for (int i = 0; i < kLogTestCount; ++i) 40 | LOG_DEBUG << 3.14159; 41 | 42 | for (int i = 0; i < kLogTestCount; ++i) 43 | LOG_DEBUG << true; 44 | 45 | for (int i = 0; i < kLogTestCount; ++i) 46 | LOG_DEBUG << 'c'; 47 | 48 | for (int i = 0; i < kLogTestCount; ++i) 49 | LOG_DEBUG << "c@string"; 50 | 51 | std::string str = "std::string"; 52 | for (int i = 0; i < kLogTestCount; ++i) 53 | LOG_DEBUG << str; 54 | } 55 | 56 | void log_4_same_element_x6() { 57 | for (int i = 0; i < kLogTestCount; ++i) 58 | LOG_DEBUG << i << i + 1 << i + 2 << i + 3; 59 | 60 | for (int i = 0; i < kLogTestCount; ++i) 61 | LOG_DEBUG << 3.14159 << 1.12312 << 1.01 << 1.1; 62 | 63 | for (int i = 0; i < kLogTestCount; ++i) 64 | LOG_DEBUG << true << false << true << false; 65 | 66 | for (int i = 0; i < kLogTestCount; ++i) 67 | LOG_DEBUG << 'c' << 'd' << 'e' << 'f'; 68 | 69 | for (int i = 0; i < kLogTestCount; ++i) 70 | LOG_DEBUG << "c@string" 71 | << "hello" 72 | << "world" 73 | << "the c program"; 74 | 75 | std::string str = "std::string"; 76 | for (int i = 0; i < kLogTestCount; ++i) 77 | LOG_DEBUG << str << str << str << str; 78 | } 79 | 80 | void log_16_same_element_x6() { 81 | for (int i = 0; i < kLogTestCount; ++i) 82 | LOG_DEBUG << i << i + 1 << i + 2 << i + 3 << i + 4 << i + 5 << i + 6 83 | << i + 7 << i + 8 << i + 9 << i + 10 << i + 11 << i + 12 << i + 13 84 | << i + 14 << i + 15; 85 | 86 | for (int i = 0; i < kLogTestCount; ++i) 87 | LOG_DEBUG << 3.14159 << 1.12312 << 1.01 << 1.1 << 3.14159 << 1.12312 << 1.01 88 | << 1.1 << 3.14159 << 1.12312 << 1.01 << 1.1 << 3.14159 << 1.12312 89 | << 1.01 << 1.1; 90 | 91 | for (int i = 0; i < kLogTestCount; ++i) 92 | LOG_DEBUG << true << false << true << false << true << false << true 93 | << false << true << false << true << false << true << false 94 | << true << false; 95 | 96 | for (int i = 0; i < kLogTestCount; ++i) 97 | LOG_DEBUG << 'c' << 'd' << 'e' << 'f' << 'c' << 'd' << 'e' << 'f' << 'c' 98 | << 'd' << 'e' << 'f' << 'c' << 'd' << 'e' << 'f'; 99 | 100 | for (int i = 0; i < kLogTestCount; ++i) 101 | LOG_DEBUG << "c@string" 102 | << "hello" 103 | << "world" 104 | << "the c program" 105 | << "c@string" 106 | << "hello" 107 | << "world" 108 | << "the c program" 109 | << "c@string" 110 | << "hello" 111 | << "world" 112 | << "the c program" 113 | << "c@string" 114 | << "hello" 115 | << "world" 116 | << "the c program"; 117 | 118 | std::string str("std::string"); 119 | for (int i = 0; i < kLogTestCount; ++i) 120 | LOG_DEBUG << str << str << str << str << str << str << str << str << str 121 | << str << str << str << str << str << str << str; 122 | } 123 | 124 | void log_10_diff_element_x1() { 125 | char ch = 'a'; 126 | int16_t int16 = INT16_MIN; 127 | uint16_t uint16 = UINT16_MAX; 128 | int32_t int32 = INT32_MIN; 129 | uint32_t uint32 = UINT32_MAX; 130 | int64_t int64 = INT64_MIN; 131 | uint64_t uint64 = UINT64_MAX; 132 | double d = 1.844674; 133 | std::string str("std::string"); 134 | 135 | for (int i = 0; i < kLogTestCount; ++i) 136 | LOG_DEBUG << ch << int16 << uint16 << int32 << uint32 << int64 << uint64 137 | << d << "c@string" << str; 138 | } 139 | 140 | void log_10_diff_element_len(const char *data, size_t n, const char *type, 141 | int thread_idx) { 142 | char ch = 'a'; 143 | int16_t int16 = INT16_MIN; 144 | uint16_t uint16 = UINT16_MAX; 145 | int32_t int32 = INT32_MIN; 146 | uint32_t uint32 = UINT32_MAX; 147 | int64_t int64 = INT64_MIN; 148 | uint64_t uint64 = UINT64_MAX; 149 | double d = 1.844674; 150 | std::string str("std::string"); 151 | uint64_t start = sys_clock_now(); 152 | for (int i = 0; i < kLogTestCount; ++i) 153 | LOG_DEBUG << ch << int16 << uint16 << int32 << uint32 << int64 << uint64 154 | << d << "c@string" << str << data; 155 | 156 | uint64_t end = sys_clock_now(); 157 | fprintf(stdout, 158 | "thread: %d, %d (%s) logs takes %" PRIu64 " us, average: %.2lf us\n", 159 | thread_idx, kLogTestCount, type, end - start, 160 | static_cast(end - start) / kLogTestCount); 161 | } 162 | 163 | void log_10_diff_element_len(const std::string &s, const char *type, 164 | int thread_idx) { 165 | char ch = 'a'; 166 | int16_t int16 = INT16_MIN; 167 | uint16_t uint16 = UINT16_MAX; 168 | int32_t int32 = INT32_MIN; 169 | uint32_t uint32 = UINT32_MAX; 170 | int64_t int64 = INT64_MIN; 171 | uint64_t uint64 = UINT64_MAX; 172 | double d = 1.844674; 173 | std::string str("std::string"); 174 | uint64_t start = sys_clock_now(); 175 | for (int i = 0; i < kLogTestCount; ++i) 176 | LOG_DEBUG << ch << int16 << uint16 << int32 << uint32 << int64 << uint64 177 | << d << "c@string" << str << s; 178 | 179 | uint64_t end = sys_clock_now(); 180 | fprintf(stdout, 181 | "thread: %d, %d (%s) logs takes %" PRIu64 " us, average: %.2lf us\n", 182 | thread_idx, kLogTestCount, type, end - start, 183 | static_cast(end - start) / kLogTestCount); 184 | } 185 | 186 | void log_10_diff_element_str(int thread_idx) { 187 | std::string str(64, '1'); 188 | log_10_diff_element_len( 189 | str.c_str(), 64, "10 diff element logs + 64 bytes c@string", thread_idx); 190 | log_10_diff_element_len(str, "10 diff element logs + 64 bytes std::string", 191 | thread_idx); 192 | 193 | str = std::string(256, '2'); 194 | log_10_diff_element_len(str.c_str(), 256, 195 | "10 diff element logs + 256 bytes c@string", 196 | thread_idx); 197 | log_10_diff_element_len(str, "10 diff element logs + 256 bytes std::string", 198 | thread_idx); 199 | 200 | str = std::string(1024, '3'); 201 | log_10_diff_element_len(str.c_str(), 1024, 202 | "10 diff element logs + 1024 bytes c@string", 203 | thread_idx); 204 | log_10_diff_element_len(str, "10 diff element logs + 1024 bytes std::string", 205 | thread_idx); 206 | 207 | str = std::string(4096, '4'); 208 | log_10_diff_element_len(str.c_str(), 4096, 209 | "10 diff element logs + 4096 bytes c@string", 210 | thread_idx); 211 | log_10_diff_element_len(str, "10 diff element logs + 4096 bytes std::string", 212 | thread_idx); 213 | } 214 | 215 | void benchmark(int thread_idx) { 216 | LOG_TIME(log_1_same_element_x6, "1 same element logs x 6", 6, thread_idx); 217 | LOG_TIME(log_4_same_element_x6, "4 same element logs x 6", 6, thread_idx); 218 | LOG_TIME(log_16_same_element_x6, "16 same element logs x 6", 6, thread_idx); 219 | LOG_TIME(log_10_diff_element_x1, "10 diff element logs x 1", 1, thread_idx); 220 | log_10_diff_element_str(thread_idx); 221 | } 222 | 223 | int main() { 224 | limlog::singleton()->setLogLevel(limlog::LogLevel::kDebug); 225 | limlog::singleton()->setOutput(limlog::NullWriter::write); 226 | 227 | std::vector threads; 228 | for (int i = 0; i < kTestThreadCount; ++i) 229 | threads.emplace_back(std::thread(benchmark, i)); 230 | 231 | for (int i = 0; i < kTestThreadCount; ++i) 232 | threads[i].join(); 233 | 234 | return 0; 235 | } 236 | -------------------------------------------------------------------------------- /tests/BlockingBufferTest.cpp: -------------------------------------------------------------------------------- 1 | //===- BlockingBufferTest.cpp - BlockingBuffer Test -------------*- C++ -*-===// 2 | // 3 | /// \file 4 | /// BlockingBuffer Test routine. 5 | // 6 | // Author: zxh 7 | // Date: 2020/07/09 23:21:31 8 | //===----------------------------------------------------------------------===// 9 | 10 | #include "Test.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | using namespace limlog; 18 | 19 | #define TEST_BUFFER(buf, s, u, unu, c) \ 20 | do { \ 21 | TEST_INT_EQ(buf->size(), s); \ 22 | TEST_INT_EQ(buf->used(), u); \ 23 | TEST_INT_EQ(buf->unused(), unu); \ 24 | TEST_INT_EQ(buf->consumable(), c); \ 25 | } while (0) 26 | 27 | #define TEST_BUFFER_PRODUCE(buf, from, n, s, u, unu, c) \ 28 | do { \ 29 | buf->produce(from, n); \ 30 | TEST_BUFFER(buf, s, u, unu, c); \ 31 | } while (0) 32 | 33 | #define TEST_BUFFER_CONSUMABLE(buf, n, s, u, unu, c) \ 34 | do { \ 35 | buf->incConsumablePos(n); \ 36 | TEST_BUFFER(buf, s, u, unu, c); \ 37 | } while (0) 38 | 39 | #define TEST_BUFFER_CONSUME(buf, to, n, s, u, unu, c) \ 40 | do { \ 41 | buf->consume(to, n); \ 42 | TEST_BUFFER(buf, s, u, unu, c); \ 43 | } while (0) 44 | 45 | void test_blocking_buffer() { 46 | 47 | #if 1 48 | 49 | // alloc memory before test. 50 | const uint32_t kBytesPerMb = 1 << 20; 51 | const uint32_t kBytesPerKb = 1 << 10; 52 | 53 | char ch = 'c'; // 1 character 54 | char *mem_128b = static_cast(malloc(sizeof(char) * 128)); 55 | char *mem_1kb = static_cast(malloc(sizeof(char) * kBytesPerKb)); 56 | char *mem_64kb = static_cast(malloc(sizeof(char) * kBytesPerKb * 64)); 57 | char *mem_256kb = 58 | static_cast(malloc(sizeof(char) * kBytesPerKb * 256)); 59 | char *mem_1mb = static_cast(malloc(sizeof(char) * kBytesPerMb)); 60 | char *mem_buf = static_cast(malloc(sizeof(BlockingBuffer))); 61 | memset(mem_128b, '1', 128); 62 | memset(mem_1kb, '2', kBytesPerKb); 63 | memset(mem_64kb, '3', kBytesPerKb * 64); 64 | memset(mem_256kb, '4', kBytesPerKb * 256); 65 | memset(mem_1mb, '5', kBytesPerMb); 66 | 67 | BlockingBuffer *buf = ::new (mem_buf) BlockingBuffer; 68 | uint32_t size = buf->size(); 69 | uint32_t used = 0; 70 | assert(size == kBytesPerMb); 71 | TEST_BUFFER(buf, kBytesPerMb, 0, kBytesPerMb, 0); 72 | 73 | // test BlockingBuffer::produce() 74 | TEST_BUFFER_PRODUCE(buf, &ch, 0, size, 0, size, 0); // 0 bytes 75 | used += 1; 76 | TEST_BUFFER_PRODUCE(buf, &ch, 1, size, 1, size - 1, 0); 77 | used += 128; 78 | TEST_BUFFER_PRODUCE(buf, mem_128b, 128, size, used, size - used, 0); 79 | used += kBytesPerKb; 80 | TEST_BUFFER_PRODUCE(buf, mem_1kb, kBytesPerKb, size, used, size - used, 0); 81 | used += kBytesPerKb * 64; 82 | TEST_BUFFER_PRODUCE(buf, mem_64kb, kBytesPerKb * 64, size, used, size - used, 83 | 0); 84 | used += kBytesPerKb * 256; 85 | TEST_BUFFER_PRODUCE(buf, mem_256kb, kBytesPerKb * 256, size, used, 86 | size - used, 0); 87 | // the rest of BlockingBuffer is 719743 bytes. 88 | // 719743 = size(1Mb) - 1b - 128b - 1kb - 64kb - 256kb 89 | // now. the max bytes of producable is 719743. 90 | used += 719743; 91 | TEST_BUFFER_PRODUCE(buf, mem_1mb, 719743, size, used, size - used, 0); 92 | 93 | #if 0 94 | // blocking buffer is full, it will be blocking if we produce a character. 95 | used += 1; 96 | TEST_BUFFER_PRODUCE(buf, mem_1mb, 1, size, used, size - used, 0); 97 | #endif 98 | 99 | // test BlockingBuffer::consume() and BlockingBuffer::consumbale(). 100 | TEST_BUFFER_CONSUMABLE(buf, 0, size, used, size - used, 0); 101 | TEST_BUFFER_CONSUMABLE(buf, 1, size, used, size - used, 1); 102 | TEST_BUFFER_CONSUME(buf, &ch, 0, size, used, size - used, 1); 103 | used -= 1; 104 | TEST_BUFFER_CONSUME(buf, &ch, 1, size, used, size - used, 0); 105 | 106 | TEST_BUFFER_CONSUMABLE(buf, 128, size, used, size - used, 128); 107 | TEST_BUFFER_CONSUMABLE(buf, kBytesPerKb, size, used, size - used, 108 | 128 + kBytesPerKb); 109 | used -= 128; 110 | TEST_BUFFER_CONSUME(buf, mem_128b, 128, size, used, size - used, kBytesPerKb); 111 | used -= kBytesPerKb; 112 | TEST_BUFFER_CONSUME(buf, mem_1kb, kBytesPerKb, size, used, size - used, 0); 113 | TEST_BUFFER_CONSUMABLE(buf, 320 * kBytesPerKb + 719743, size, used, 114 | size - used, 320 * kBytesPerKb + 719743); 115 | used -= kBytesPerKb * 64; 116 | TEST_BUFFER_CONSUME(buf, mem_64kb, kBytesPerKb * 64, size, used, size - used, 117 | 256 * kBytesPerKb + 719743); 118 | used -= kBytesPerKb * 256; 119 | TEST_BUFFER_CONSUME(buf, mem_256kb, kBytesPerKb * 256, size, used, 120 | size - used, 719743); 121 | used -= 719743; 122 | TEST_BUFFER_CONSUME(buf, mem_1mb, 719743, size, 0, size, 0); 123 | 124 | // test circle buffer. 125 | TEST_BUFFER(buf, kBytesPerMb, 0, size, 0); 126 | TEST_BUFFER_PRODUCE(buf, mem_1mb, kBytesPerMb, size, kBytesPerMb, 0, 0); 127 | TEST_BUFFER_CONSUMABLE(buf, kBytesPerMb, size, kBytesPerMb, 0, kBytesPerMb); 128 | TEST_BUFFER_CONSUME(buf, mem_1mb, kBytesPerMb, size, 0, kBytesPerMb, 0); 129 | 130 | free(mem_buf); 131 | free(mem_128b); 132 | free(mem_1kb); 133 | free(mem_64kb); 134 | free(mem_256kb); 135 | free(mem_1mb); 136 | 137 | #endif 138 | } 139 | 140 | int main() { 141 | test_blocking_buffer(); 142 | 143 | PRINT_PASS_RATE(); 144 | 145 | return !ALL_TEST_PASS(); 146 | } 147 | -------------------------------------------------------------------------------- /tests/ItoaTest.cpp: -------------------------------------------------------------------------------- 1 | //===- ItoaTest.cpp - Itoa Test ----------------------------------------*- C++ 2 | //-*-===// 3 | // 4 | /// \file 5 | /// Test of Digits to string. 6 | // 7 | // Author: zxh 8 | // Date: 2020/07/09 23:47:38 9 | //===----------------------------------------------------------------------===// 10 | 11 | #include "Test.h" 12 | 13 | #include 14 | 15 | using namespace limlog; 16 | 17 | #define TEST_STRING_INTEGER_EQ(actual, expect) \ 18 | do { \ 19 | char buf[32]; \ 20 | size_t len = formatInt(actual, buf); \ 21 | TEST_STRING_EQ(std::string(buf, len), expect); \ 22 | } while (0) 23 | 24 | void test_itoa() { 25 | #if 1 26 | // test signed integer conversion. 27 | TEST_STRING_INTEGER_EQ(0, "0"); 28 | TEST_STRING_INTEGER_EQ(-0, "0"); 29 | TEST_STRING_INTEGER_EQ(-1, "-1"); 30 | TEST_STRING_INTEGER_EQ(1, "1"); 31 | TEST_STRING_INTEGER_EQ(INT8_MAX, "127"); 32 | TEST_STRING_INTEGER_EQ(INT8_MIN, "-128"); 33 | TEST_STRING_INTEGER_EQ(UINT8_MAX, "255"); 34 | TEST_STRING_INTEGER_EQ(INT16_MAX, "32767"); 35 | TEST_STRING_INTEGER_EQ(INT16_MIN, "-32768"); 36 | TEST_STRING_INTEGER_EQ(UINT16_MAX, "65535"); 37 | TEST_STRING_INTEGER_EQ(INT32_MAX, "2147483647"); 38 | TEST_STRING_INTEGER_EQ(INT32_MIN, "-2147483648"); 39 | TEST_STRING_INTEGER_EQ(UINT32_MAX, "4294967295"); 40 | TEST_STRING_INTEGER_EQ(INT64_MAX, "9223372036854775807"); 41 | TEST_STRING_INTEGER_EQ(INT64_MIN, "-9223372036854775808"); 42 | TEST_STRING_INTEGER_EQ(UINT64_MAX, "18446744073709551615"); 43 | #endif 44 | } 45 | 46 | int main() { 47 | test_itoa(); 48 | 49 | PRINT_PASS_RATE(); 50 | 51 | return !ALL_TEST_PASS(); 52 | } -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXXFLAGS = -std=c++11 -march=native -O2 -Wall -Werror -I../ 3 | LDFLAGS = -lpthread 4 | 5 | SRCS = \ 6 | ItoaTest.cpp \ 7 | BlockingBufferTest.cpp \ 8 | Benchmark.cpp 9 | 10 | OBJS = $(patsubst %.cpp, %.o, $(SRCS)) 11 | DEPS = $(patsubst %.cpp, %.d, $(SRCS)) 12 | TARGETS = $(patsubst %.cpp, %, $(SRCS)) 13 | 14 | all: $(TARGETS) 15 | 16 | $(TARGETS): $(OBJS) 17 | $(CXX) $(CXXFLAGS) $@.o -o $@ $(LDFLAGS) 18 | 19 | %.o: %.cpp 20 | $(CXX) $(CXXFLAGS) -c $< -o $@ 21 | 22 | %.d: %.cpp 23 | @ $(CXX) $(CXXFLAGS) -MM $< > $@ 24 | 25 | .PHONY: clean 26 | clean: 27 | rm -rf *.log $(TARGETS) $(OBJS) $(DEPS) 28 | 29 | -include $(DEPS) -------------------------------------------------------------------------------- /tests/Test.h: -------------------------------------------------------------------------------- 1 | //===- Test.h - Test Common Header ------------------------------*- C++ -*-===// 2 | // 3 | /// \file 4 | /// Common test helper macro. 5 | // 6 | // Author: zxh 7 | // Date: 2020/07/09 23:18:23 8 | //===----------------------------------------------------------------------===// 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | 16 | static int test_count = 0; 17 | static int test_pass = 0; 18 | 19 | #define EQ(equality, actual, expect, format) \ 20 | do { \ 21 | test_count++; \ 22 | if (equality) \ 23 | test_pass++; \ 24 | else { \ 25 | fprintf(stderr, "%s:%d: actual: '" format "' expect: '" format "'\n", \ 26 | __FILE__, __LINE__, actual, expect); \ 27 | } \ 28 | } while (0) 29 | 30 | #define TEST_INT_EQ(actual, expect) EQ(actual == expect, actual, expect, "%d") 31 | #define TEST_CHAR_EQ(actual, expect) EQ(actual == expect, actual, expect, "%c") 32 | 33 | #define TEST_STRING_EQ(actual, expect) \ 34 | do { \ 35 | std::string a(actual); \ 36 | std::string e(expect); \ 37 | EQ(a.compare(e) == 0, a.c_str(), e.c_str(), "%s"); \ 38 | } while (0) 39 | 40 | #define PRINT_PASS_RATE() \ 41 | do { \ 42 | fprintf(stderr, "[%.2f%%] all test: %d, pass: %d.\n", \ 43 | test_count == 0 \ 44 | ? 0 \ 45 | : static_cast(test_pass * 100) / test_count, \ 46 | test_count, test_pass); \ 47 | } while (0) 48 | 49 | #define ALL_TEST_PASS() (test_count == test_pass) --------------------------------------------------------------------------------