Skip to content

Commit f0511b0

Browse files
committed
Add Concurrency option and MPSC support using farbot.
context: #6 #14
1 parent 3f27c54 commit f0511b0

5 files changed

Lines changed: 94 additions & 10 deletions

File tree

CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ if(NOT TARGET readerwriterqueue)
3636
FetchContent_MakeAvailable(ReaderWriterQueue)
3737
endif()
3838

39+
if (NOT TARGET farbot)
40+
include(FetchContent)
41+
42+
FetchContent_Declare(farbot
43+
GIT_REPOSITORY https://github.com/hogliux/farbot
44+
GIT_TAG 0416705394720c12f0d02e55c144e4f69bb06912
45+
)
46+
# Note we do not "MakeAvailable" here, because farbot does not fully work via FetchContent
47+
if(NOT farbot_POPULATED)
48+
FetchContent_Populate(farbot)
49+
endif()
50+
add_library(farbot INTERFACE)
51+
add_library(farbot::farbot ALIAS farbot)
52+
53+
target_include_directories(farbot INTERFACE
54+
$<BUILD_INTERFACE:${farbot_SOURCE_DIR}/include>
55+
$<INSTALL_INTERFACE:include>
56+
)
57+
endif()
58+
3959
if(NOT TARGET stb::stb)
4060
# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24:
4161
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
@@ -73,6 +93,7 @@ target_link_libraries(rtlog
7393
INTERFACE
7494
readerwriterqueue
7595
stb::stb
96+
farbot::farbot
7697
$<$<BOOL:${RTLOG_USE_FMTLIB}>:fmt::fmt>
7798
)
7899

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ The design behind this logger was presented at ADCx 2023. Presentation [video](h
1111
- Ability to log messages of any type and size from the real-time thread
1212
- Statically allocated memory at compile time, no allocations in the real-time thread
1313
- Support for printf-style format specifiers (using [a version of the printf family](https://github.com/nothings/stb/blob/master/stb_sprintf.h) that doesn't hit the `localeconv` lock)
14-
- Efficient thread-safe logging using a [lock free queue](https://github.com/cameron314/readerwriterqueue).
14+
- Efficient thread-safe logging using lock free queues (either [single consumer](https://github.com/cameron314/readerwriterqueue) or [multi consumer](https://github.com/hogliux/farbot)).
1515

1616
## Requirements
1717

1818
- A C++17 compatible compiler
1919
- The C++17 standard library
2020
- moodycamel::ReaderWriterQueue (will be downloaded via cmake if not provided)
21+
- farbot's fifo (will be downloaded via cmake if not provided)
2122
- stb's vsnprintf (will be downloaded via cmake if not provided)
2223

2324
## Installation via CMake

examples/everlog/everlogmain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace evr {
66
constexpr auto MAX_LOG_MESSAGE_LENGTH = 256;
7-
constexpr auto MAX_NUM_LOG_MESSAGES = 100;
7+
constexpr auto MAX_NUM_LOG_MESSAGES = 128;
88

99
enum class LogLevel { Debug, Info, Warning, Critical };
1010

include/rtlog/rtlog.h

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <fmt/format.h>
1111
#endif // RTLOG_USE_FMTLIB
1212

13+
#include <farbot/fifo.hpp>
1314
#include <readerwriterqueue.h>
1415

1516
#ifndef STB_SPRINTF_IMPLEMENTATION
@@ -31,14 +32,20 @@ enum class Status {
3132
Error_MessageTruncated = 2,
3233
};
3334

35+
enum class QueueConcurrency {
36+
Single_Producer_Single_Consumer = 0,
37+
Multi_Producer_Single_Consumer = 1,
38+
};
39+
3440
/**
3541
* @brief A logger class for logging messages.
3642
* This class allows you to log messages of type LogData.
3743
* This type is user defined, and is often the additional data outside the
3844
* format string you want to log. For instance: The log level, the log region,
3945
* the file name, the line number, etc. See examples or tests for some ideas.
4046
*
41-
* TODO: Currently is built on a single input/single output queue. Do not call
47+
* NOTE: by default, it is built on a single input/single output queue. You have
48+
* to specify QueueConcurrency for other types of queues. Otherwise, do not call
4249
* Log or PrintAndClearLogQueue from multiple threads.
4350
*
4451
* @tparam LogData The type of the data to be logged.
@@ -49,9 +56,16 @@ enum class Status {
4956
* @tparam SequenceNumber This number is incremented when the message is
5057
* enqueued. It is assumed that your non-realtime logger increments and logs it
5158
* on Log.
59+
* @tparam QueueConcurrency The concurrency type of the internal queue.
60+
* The default Single_Producer_Single_Consumer is for the simplest queue that
61+
* works in single-producer thread model.
62+
* Multi_Producer_Single_Consumer is for such an application that needs to
63+
* handle multiple logging clients.
5264
*/
5365
template <typename LogData, size_t MaxNumMessages, size_t MaxMessageLength,
54-
std::atomic<std::size_t> &SequenceNumber>
66+
std::atomic<std::size_t> &SequenceNumber,
67+
QueueConcurrency Concurrency =
68+
QueueConcurrency::Single_Producer_Single_Consumer>
5569
class Logger {
5670
public:
5771
/*
@@ -97,7 +111,7 @@ class Logger {
97111

98112
// Even if the message was truncated, we still try to enqueue it to minimize
99113
// data loss
100-
const bool dataWasEnqueued = mQueue.try_enqueue(dataToQueue);
114+
const bool dataWasEnqueued = mQueue->tryEnqueue(std::move(dataToQueue));
101115

102116
if (!dataWasEnqueued)
103117
retVal = Status::Error_QueueFull;
@@ -196,7 +210,7 @@ class Logger {
196210

197211
// Even if the message was truncated, we still try to enqueue it to minimize
198212
// data loss
199-
const bool dataWasEnqueued = mQueue.try_enqueue(dataToQueue);
213+
const bool dataWasEnqueued = mQueue->tryEnqueue(std::move(dataToQueue));
200214

201215
if (!dataWasEnqueued)
202216
retVal = Status::Error_QueueFull;
@@ -227,7 +241,7 @@ class Logger {
227241
int numProcessed = 0;
228242

229243
InternalLogData value;
230-
while (mQueue.try_dequeue(value)) {
244+
while (mQueue->tryDequeue(value)) {
231245
printLogFn(value.mLogData, value.mSequenceNumber, "%s",
232246
value.mMessage.data());
233247
numProcessed++;
@@ -243,7 +257,39 @@ class Logger {
243257
std::array<char, MaxMessageLength> mMessage{};
244258
};
245259

246-
moodycamel::ReaderWriterQueue<InternalLogData> mQueue{MaxNumMessages};
260+
class InternalQueue {
261+
public:
262+
virtual bool tryEnqueue(InternalLogData&& value) = 0;
263+
virtual bool tryDequeue(InternalLogData& value) = 0;
264+
};
265+
class InternalQueueSPSC : public InternalQueue {
266+
moodycamel::ReaderWriterQueue<InternalLogData> mQueue{MaxNumMessages};
267+
public:
268+
bool tryEnqueue(InternalLogData&& value) override {
269+
return mQueue.try_enqueue(std::move(value));
270+
}
271+
bool tryDequeue(InternalLogData& value) override {
272+
return mQueue.try_dequeue(value);
273+
}
274+
};
275+
class InternalQueueMPSC : public InternalQueue {
276+
farbot::fifo<InternalLogData, farbot::fifo_options::concurrency::single,
277+
farbot::fifo_options::concurrency::multiple>
278+
mQueue{ MaxNumMessages };
279+
public:
280+
bool tryEnqueue(InternalLogData&& value) override {
281+
return mQueue.push(std::move(value));
282+
}
283+
bool tryDequeue(InternalLogData& value) override {
284+
return mQueue.pop(value);
285+
}
286+
};
287+
288+
std::unique_ptr<InternalQueue> mQueue{
289+
Concurrency == QueueConcurrency::Single_Producer_Single_Consumer
290+
? (std::unique_ptr<InternalQueue>)
291+
std::make_unique<InternalQueueSPSC>()
292+
: std::make_unique<InternalQueueMPSC>()};
247293
};
248294

249295
/**

test/test_rtlog.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace rtlog::test {
66
std::atomic<std::size_t> gSequenceNumber{0};
77

88
constexpr auto MAX_LOG_MESSAGE_LENGTH = 256;
9-
constexpr auto MAX_NUM_LOG_MESSAGES = 100;
9+
constexpr auto MAX_NUM_LOG_MESSAGES = 128;
1010

1111
enum class ExampleLogLevel { Debug, Info, Warning, Critical };
1212

@@ -81,6 +81,22 @@ TEST_CASE("Test rtlog basic construction") {
8181
CHECK(logger.PrintAndClearLogQueue(PrintMessage) == 4);
8282
}
8383

84+
TEST_CASE("Test rtlog MPSC basic construction") {
85+
rtlog::Logger<ExampleLogData, MAX_NUM_LOG_MESSAGES, MAX_LOG_MESSAGE_LENGTH,
86+
gSequenceNumber,
87+
rtlog::QueueConcurrency::Multi_Producer_Single_Consumer>
88+
logger;
89+
logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine},
90+
"Hello, world!");
91+
logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, world!");
92+
logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network},
93+
"Hello, world!");
94+
logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio},
95+
"Hello, world!");
96+
97+
CHECK(logger.PrintAndClearLogQueue(PrintMessage) == 4);
98+
}
99+
84100
TEST_CASE("va_args works as intended") {
85101
rtlog::Logger<ExampleLogData, MAX_NUM_LOG_MESSAGES, MAX_LOG_MESSAGE_LENGTH,
86102
gSequenceNumber>
@@ -199,7 +215,7 @@ TEST_CASE("Errors are returned from Log") {
199215
}
200216

201217
SUBCASE("Enqueue more than capacity and get an error") {
202-
const auto maxNumMessages = 10;
218+
const auto maxNumMessages = 16;
203219
rtlog::Logger<ExampleLogData, maxNumMessages, MAX_LOG_MESSAGE_LENGTH,
204220
gSequenceNumber>
205221
logger;

0 commit comments

Comments
 (0)