Skip to content

Commit f988a6d

Browse files
committed
feat: Allow logging path to be warmed
SDB-10043
1 parent fd7346f commit f988a6d

4 files changed

Lines changed: 81 additions & 17 deletions

File tree

bench/Log.bm.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ class FileLogger final : public Logger {
4343

4444
private:
4545
void do_write_log(WallTime /*ts*/, LogLevel /*level*/, int /*tid*/, LogMsgPtr&& msg,
46-
size_t size) noexcept override
46+
size_t size, bool warming_fake) noexcept override
4747
{
48-
os::write(*fh_, msg.get(), size);
48+
if(!warming_fake) {
49+
os::write(*fh_, msg.get(), size);
50+
}
4951
}
5052
io::FileHandle fh_;
5153
};

toolbox/sys/Log.ut.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ StreamT& operator<<(StreamT& os, const Foo<int, int>& val)
4242

4343
struct TestLogger final : Logger {
4444
void do_write_log(WallTime /*ts*/, LogLevel level, int /*tid*/, LogMsgPtr&& msg,
45-
size_t size) noexcept override
45+
size_t size, bool warming_fake) noexcept override
4646
{
4747
const auto finally = make_finally([&]() noexcept {
4848
log_buf_pool().bounded_push(std::move(msg));
4949
});
50-
last_level = level;
51-
last_msg.assign(static_cast<const char*>(msg.get()), size);
50+
if (!warming_fake) {
51+
last_level = level;
52+
last_msg.assign(static_cast<const char*>(msg.get()), size);
53+
}
5254
}
5355
LogLevel last_level{};
5456
string last_msg{};

toolbox/sys/Logger.cpp

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,24 @@ static LogBufPoolWrapper log_buf_pool_{};
7777

7878
class NullLogger final : public Logger {
7979
void do_write_log(WallTime /*ts*/, LogLevel /*level*/, int /*tid*/, LogMsgPtr&& msg,
80-
size_t /*size*/) noexcept override
80+
size_t /*size*/, bool /*warming_fake*/) noexcept override
8181
{
8282
log_buf_pool().bounded_push(std::move(msg));
8383
}
8484
} null_logger_;
8585

8686
class StdLogger final : public Logger {
8787
void do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg,
88-
size_t size) noexcept override
88+
size_t size, bool warming_fake) noexcept override
8989
{
9090
const auto finally = make_finally([&]() noexcept {
9191
log_buf_pool().bounded_push(std::move(msg));
9292
});
9393

94+
if (warming_fake) [[unlikely]] {
95+
return;
96+
}
97+
9498
const auto t{WallClock::to_time_t(ts)};
9599
tm tm;
96100
localtime_r(&t, &tm);
@@ -124,12 +128,16 @@ class StdLogger final : public Logger {
124128

125129
class SysLogger final : public Logger {
126130
void do_write_log(WallTime /*ts*/, LogLevel level, int /*tid*/, LogMsgPtr&& msg,
127-
size_t size) noexcept override
131+
size_t size, bool warming_fake) noexcept override
128132
{
129133
const auto finally = make_finally([&]() noexcept {
130134
log_buf_pool().bounded_push(std::move(msg));
131135
});
132136

137+
if (warming_fake) [[unlikely]] {
138+
return;
139+
}
140+
133141
int prio;
134142
switch (level) {
135143
case LogLevel::None:
@@ -162,6 +170,7 @@ class SysLogger final : public Logger {
162170
// Global log level and logger function.
163171
atomic<LogLevel> level_{LogLevel::Info};
164172
atomic<Logger*> logger_{&std_logger_};
173+
thread_local bool warming_mode_{false};
165174

166175
inline LogLevel acquire_level() noexcept
167176
{
@@ -180,6 +189,16 @@ LogBufPool& log_buf_pool() noexcept
180189
return log_buf_pool_.pool;
181190
}
182191

192+
void set_log_warming_mode(bool enabled) noexcept
193+
{
194+
warming_mode_ = enabled;
195+
}
196+
197+
bool log_warming_mode_enabled() noexcept
198+
{
199+
return warming_mode_;
200+
}
201+
183202
Logger& null_logger() noexcept
184203
{
185204
return null_logger_;
@@ -222,7 +241,8 @@ Logger& set_logger(Logger& logger) noexcept
222241

223242
void write_log(WallTime ts, LogLevel level, LogMsgPtr&& msg, std::size_t size) noexcept
224243
{
225-
acquire_logger().write_log(ts, level, static_cast<int>(gettid()), std::move(msg), size);
244+
acquire_logger().write_log(ts, level, static_cast<int>(gettid()), std::move(msg), size,
245+
warming_mode_);
226246
}
227247

228248
Logger::~Logger() = default;
@@ -240,9 +260,16 @@ AsyncLogger::~AsyncLogger()
240260
void AsyncLogger::write_all_messages()
241261
{
242262
Task t;
263+
int fake_count = 0;
264+
243265
while (tq_.pop(t)) {
244-
logger_.write_log(t.ts, t.level, t.tid, LogMsgPtr{t.msg}, t.size);
266+
if (t.msg != nullptr) {
267+
logger_.write_log(t.ts, t.level, t.tid, LogMsgPtr{t.msg}, t.size, false);
268+
} else {
269+
fake_count++;
270+
}
245271
}
272+
fake_pushed_count_.fetch_sub(fake_count, std::memory_order_relaxed);
246273
}
247274

248275
bool AsyncLogger::run()
@@ -259,19 +286,40 @@ void AsyncLogger::stop()
259286
}
260287

261288
void AsyncLogger::do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg,
262-
size_t size) noexcept
289+
size_t size, bool warming_fake) noexcept
263290
{
264291
char* const msg_ptr = msg.release();
292+
auto push_to_queue = [&](char* ptr) {
293+
return tq_.push(Task{.ts = ts, .level = level, .tid = tid, .msg = ptr, .size = size});
294+
};
265295
try {
266-
if (tq_.push(Task{.ts = ts, .level = level, .tid = tid, .msg = msg_ptr, .size = size})) {
296+
if (warming_fake) [[unlikely]] {
297+
const auto cnt = fake_pushed_count_.load(std::memory_order_relaxed);
298+
const auto d = ts - last_time_fake_pushed_.load(std::memory_order_relaxed);
299+
300+
constexpr Millis FakePushInterval = 10ms;
301+
constexpr int MaxPushedFakeCount = 25;
302+
303+
// It's possible that `last_time_fake_pushed_` OR `fake_pushed_count_` have since
304+
// been modified by another thread (since the load). In this extremely rare case, a back
305+
// to back push to the queue will occur. But that's fine -- not a big deal, these limits
306+
// are not supposed to absolute hard limits, they are just to ensure that the queue is
307+
// not flooded with "fake" entries. This compromise allows keeping the implementation
308+
// simple, otherwise more complicated code is required to deal with this case.
309+
if (duration_cast<Millis>(d) >= FakePushInterval && cnt < MaxPushedFakeCount) {
310+
push_to_queue(nullptr);
311+
last_time_fake_pushed_.store(ts, std::memory_order_relaxed);
312+
fake_pushed_count_.fetch_add(1, std::memory_order_relaxed);
313+
}
314+
} else if (push_to_queue(msg_ptr)) [[likely]] {
267315
// Successfully pushed the task to the queue, release ownership of msg_ptr.
268316
return;
269317
}
270318
} catch (const std::bad_alloc&) {
271319
// Catching `std::bad_alloc` here is *defensive plumbing* that keeps the logger non-throwing
272320
// and prevents crashes caused by an out-of-memory situation during rare log-burst spikes.
273321
}
274-
// Failed to push the task, restore ownership of msg_ptr.
322+
// Failed to push the task OR warming fake, restore ownership of msg_ptr.
275323
log_buf_pool().bounded_push(LogMsgPtr{msg_ptr});
276324
}
277325

toolbox/sys/Logger.hpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ using LogBufPool = boost::lockfree::stack<LogMsgPtr, boost::lockfree::capacity<L
5858
/// A pool of log buffers, eliminating the need for dynamic memory allocations when logging
5959
TOOLBOX_API LogBufPool& log_buf_pool() noexcept;
6060

61+
/// Disables logging while this mode is enabled. This is a per-thread setting.
62+
TOOLBOX_API void set_log_warming_mode(bool enabled) noexcept;
63+
64+
/// Returns true if log warming mode is enabled.
65+
TOOLBOX_API bool log_warming_mode_enabled() noexcept;
66+
6167
/// Null logger. This logger does nothing and is effectively /dev/null.
6268
TOOLBOX_API Logger& null_logger() noexcept;
6369

@@ -114,14 +120,15 @@ class TOOLBOX_API Logger {
114120
Logger(Logger&&) noexcept = default;
115121
Logger& operator=(Logger&&) noexcept = default;
116122

117-
void write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg, std::size_t size) noexcept
123+
void write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg, std::size_t size,
124+
bool warming_fake) noexcept
118125
{
119-
do_write_log(ts, level, tid, std::move(msg), size);
126+
do_write_log(ts, level, tid, std::move(msg), size, warming_fake);
120127
}
121128

122129
protected:
123130
virtual void do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg,
124-
std::size_t size) noexcept
131+
std::size_t size, bool warming_fake) noexcept
125132
= 0;
126133
};
127134

@@ -156,11 +163,16 @@ class TOOLBOX_API AsyncLogger : public Logger {
156163
private:
157164
void write_all_messages();
158165
void do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg,
159-
std::size_t size) noexcept override;
166+
std::size_t size, bool warming_fake) noexcept override;
160167

161168
Logger& logger_;
162169
boost::lockfree::queue<Task, boost::lockfree::fixed_sized<false>> tq_{512};
163170
std::atomic<bool> stop_{false};
171+
172+
// warming variables
173+
std::atomic<int> fake_pushed_count_{0};
174+
std::atomic<WallTime> last_time_fake_pushed_{};
175+
static_assert(std::atomic<WallTime>::is_always_lock_free, "atomic not lock free");
164176
};
165177

166178
/// ScopedLogLevel provides a convenient RAII-style utility for setting the log-level for the

0 commit comments

Comments
 (0)