diff --git a/.gitignore b/.gitignore index 0c68bbd..d84f33d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ test-stdthreadostream *.exe *.out *.app +picobench test-stdthreadostream test-stdostream test-sizes diff --git a/.idea/cpp_logger.iml b/.idea/cpp_logger.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/cpp_logger.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bbaa508 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 452b759..e058277 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/test-stdostream", + "program": "${workspaceFolder}/test-stdthreadostream", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d930591..91679f7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,6 +7,7 @@ "command": "/usr/bin/clang++", "args": [ "-ggdb", + "-Og", "test/test-stdthreadostream.cpp", "-Wall", "-Wpedantic", diff --git a/CMakeLists.txt b/CMakeLists.txt index f9fc715..bb77b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,18 @@ cmake_minimum_required(VERSION 3.17) project(cpp_logger) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 17) include_directories(cpp-memory-manager) include_directories(src) -add_compile_options(-Os -Wall -Wextra -Wpedantic) +add_compile_options(-Og -ggdb -Wall -Wextra -Wpedantic) add_link_options(-lpthread) add_executable(cpp_logger cpp-memory-manager/PoolAllocator.h src/Log.h + src/LogAtomicBuffers.h src/LogAppInterfaceStd.h src/LogConverterCustomText.h src/LogMessageBase.h @@ -22,4 +23,4 @@ add_executable(cpp_logger src/LogQueueVoid.h src/LogSenderStdOstream.h src/LogSenderVoid.h - test/test-sizes-stdthreadostream.cpp) + test/test-stdthreadostream.cpp) diff --git a/README.md b/README.md index 42f88d3..9bcd897 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# C++ template-based crossplatform log library +# C++ template-based cross-platform log library ```C++ // Stripped template instantiations. @@ -12,16 +12,21 @@ Log::registerCurrentTask("main"); Log::i(nowtech::LogTopics::system) << "bulk data size:" << someCollection.size() << Log::end; // one group of 2 items -auto logger = Log::n() << "bulk data follows:"; // one group of many items starts +auto logger = Log::n() << "bulk data follows:"; // one group of many items starts for(auto item : someCollection) { logger << LC::X4 << item; // format: hexadecimal, fill to 4 digits } logger << Log::end; // the group of many items ends + +//... + +Log::unregisterCurrentTask(); +Log::done(); ``` -This is a complete rework of my [old logger](https://github.com/balazs-bamer/cpp-logger/tree/old/) after identifying the design flaws and performance problems. I used C++20 -capable compilers during development, but the code is probably C++17 compatible. +This is a complete rework of my [old logger](https://github.com/balazs-bamer/cpp-logger/tree/old/) after identifying the design flaws and performance problems. The library requires C++17. -The code was written to possibly conform MISRA C++ and High-Integrity C++. +The code was written to possibly conform to MISRA C++ and High-Integrity C++. The library is published under the [MIT license](https://opensource.org/licenses/MIT). @@ -30,17 +35,18 @@ Copyright 2020 Balázs Bámer. ## Aims - Provide a common logger API for cross-platform embedded / desktop code. -- Use as minimal code and data space as possible. I'm not sure if I'm was good enough obtaining it. On STM32 Cortex M3, flash overhead of direct sending mode is **4k**, queued multithreading mode id **6k**. Anyway, no more time to further develop this library. -- The library was designed to allow the programmer control log bandwidth usage, program and data memory consumption. +- Use as minimal code and data space as possible. I'm not sure if I was good enough to obtain it. On STM32 Cortex M3, flash overhead of direct sending mode is **4k**, queued multithreading mode id **6k**. Anyway, no more time to further develop this library. +- The library was designed to allow the programmer control of log bandwidth usage, program and data memory consumption. - Log arithmetic C++ types and strings with or without copying them (beneficial for literals). - String literals or string constants can be transferred using their pointers to save MCU cycles. - More transient strings will be copied instantly. - Operate in a _strictly typed_ way as opposed to the printf-based loggers some companies still use today. -- We needed a much finer granularity than the usual log levels, so I've introduced independently selectable topics. However, these allow easy simulation of traditional log levels, see in the API section. -- Important aim was to let the user log a _group of related items_ without other tasks interleaving the output of converting these items. -- Possibly minimal relying on external libraries. It does not depend on numeric to string conversion functions. +- We needed a much finer granularity than the usual log levels, so I've introduced independently selectable topics. Recently, I've also added traditional log levels. +- Provide blazing-fast atomic logging of one integral type into a buffer. Its contents can later be converted and sent. +- An important aim was to let the user log a _group of related items_ without other tasks interleaving the output of converting these items. +- Possibly minimal reliance on external libraries. It does not depend on numeric-to-string conversion functions. - In contrast with the original solution, the new one extensively uses templated classes to - - implement a flexible plugin architecture for adapting different user needs + - implement a flexible plugin architecture for adapting to different user needs - let the compiler optimizer do its finest. - _totally eliminate_ logging code generation with at least -O1 optimization level with a one-line change (template logic, without #ifdef). - Let the user decide to rely on C++ exceptions or implement a different kind of error management. @@ -61,25 +67,27 @@ The logger consists of six classes: - Obtaining the task name in C string. - Obtaining timestamps using some OS / STL time routine. - Error management. -- _Message_ - used as a transfer medium in the Queue. For numeric types and strings transferred by pointers, one message is needed per item. For stored strings several messages may be necessary. There are two general-purpose implementations: - - a variant based message. +- _Message_ - used as a transfer medium in the Queue. For numeric types and strings transferred by pointers, one message is needed per item. For stored strings, several messages may be necessary. There are two general-purpose implementations: + - a variant-based message. - a more space-efficient handcrafted message. +- _AtomicBuffer_ - provides a buffer and instrumentation for the atomic logging. This bypasses all the queues and logic used to log groups of various types. +- _Config_ - provides some configuration parameters. The logger can operate in two modes: -- Direct without queue, when the conversion and sending happens without message instantiation and queue usage. This can be useful for single-threaded applications. -- With queue, when each item sent will form one or more (in case of stored strings) message and use a central thread-safe queue. On the other end of the queue a background thread pops the messages and groups them by tasks in secondary lists. When a group of related items from a task has arrived, its conversion and sending begins. +- Direct without queue, when the conversion and sending happen without message instantiation and queue usage. This can be useful for single-threaded applications. +- With a queue, when each item is sent, it will form one or more (in case of stored strings) messages and use a central thread-safe queue. On the other end of the queue, a background thread pops the messages and groups them by tasks in secondary lists. When a group of related items from a task has arrived, its conversion and sending begin. ## Implementations -The current library provides some simple implementations for STL and Boost based desktop and server applications and FreeRTOS based embedded applications. The whole implementation assumes an architecture of at least 32 bits. +The current library offers simple implementations for STL and Boost-based desktop and server applications, as well as FreeRTOS-based embedded applications. The whole implementation assumes an architecture of at least 32 bits. ### Log This is a general implementation, but tailored for embedded requirements. -In queue-less mode, conversion and sending happens immediately for each item. Thus it is desirable that the Sender has some sort of buffering inside. +In queue-less mode, conversion and sending happen immediately for each item. Thus ,it is desirable that the Sender has some sort of buffering inside. -For the queue mode, it contains a secondary list or queue for each task, which gather the items in each logged group. After the terminal item arrives, conversion happens for each item in the sender buffer and then comes the sending. These secondary queues are backed by a pool allocator to avoid repeated dynamic memory access. +For the queue mode, it contains a secondary list or queue for each task, which gathers the items in each logged group. After the terminal item arrives, conversion occurs for each item in the sender buffer, and then the sending process begins. These secondary queues are backed by a pool allocator to avoid repeated dynamic memory access. ### ConverterCustomText @@ -94,43 +102,99 @@ A simple converter emitting character strings, with an emphasis on space-efficie ### AppInterfaceFreeRtosMinimal -This implementation assumes FreeRTOS 10.0.1, but should work as well as with 9.x or perhaps even older. The main objective was to keep it as simple and small as possible. It provides global overload of new and delete operators using FreeRTOS' dynamic memory management, but itself uses only a statically allocated array. It uses a linear array for task registry and omits unregistering, because a typical embedded application creates all the tasks beforehand and never kills them. Task names are native FreeRTOS task names. For similar reasons, logger shutdown is not implemented. -There is a design flaw in the library: checking for ISRs happen here, so the FreeRTOS implementation contains an MCU-specific function, here for STM32. However, it is easy to replace with the one for the actual MCU. +This implementation assumes FreeRTOS 10.0.1, but should also work with versions 9.x or possibly even older. The main objective was to keep it as simple and small as possible. It provides a global overload of new and delete operators using FreeRTOS' dynamic memory management, but itself uses only a statically allocated array. It uses a linear array for the task registry and omits unregistering, as a typical embedded application typically creates all tasks beforehand and never kills them. Task names are native to FreeRTOS. For similar reasons, logger shutdown is not implemented. +There is a design flaw in the library: checking for ISRs happens here, so the FreeRTOS implementation contains an MCU-specific function, here for STM32. However, it is easy to replace it with the one for the actual MCU. ### AppInterfaceStd -This is a general desktop-oriented C++17 STL implementation targeting speed over space. It uses a hash set and thread local storage for task registration, and task unregistration is also supported. The task registy API is protected by a mutex. Other, more frequently called functions work without locking. Logger initialization and shutdown are properly implemented. +This is a general, desktop-oriented C++17 STL implementation that prioritises speed over space. It uses a hash set and thread-local storage for task registration, and task unregistration is also supported. The task registry API is protected by a mutex. Other, more frequently called functions work without locking. Logger initialisation and shutdown are properly implemented. ### QueueVoid -Empty implementation, used for placeholder when no queue is needed. +An empty implementation is used as a placeholder when no queue is required. ### QueueFreeRtos -It uses the FreeRTOS' built-in queue, so no locking is needed on sending. Sending into the queue can happen from an ISR. +It utilises FreeRTOS's built-in queue, so no locking is required for sending. Sending an item into the queue can occur from an ISR. ### QueueStdBoost -This one uses a multi-producer multi-consumer lockfree queue of Boost, so no locking is needed either. +This one uses a multi-producer, multi-consumer lock-free queue of Boost, so no locking is needed either. + +### QueueStdCircular + +This one uses a simple circular buffer with `std::lock_guard`. ### SenderVoid -Emply implementation for the case when all the log calls have to be eliminated from the binary. This happens at gcc and clang optimization levels -Os, -O1, -O2 and -O3. The application can use a template metaprogramming technique to declare a Log using this as the appropriate parameter, so no #ifdef is needed. +Empty implementation for the case when all the log calls have to be eliminated from the binary. This occurs at GCC and Clang optimisation levels -Os, -O1, -O2, and -O3. The application can utilise a template metaprogramming technique to declare a Log, using this as the appropriate parameter, so no #ifdef is required. ### SenderStmHalMinimal -This is a minimal implementation for STM32 UART using blocking HAL transmission. A more sophisticated one would use double buffering with DMA to use the MCU more efficiently and allow buffering new data during the potentially slow transmission of the old buffer. This mechanism is implemented in the old version. +This is a minimal implementation for the STM32 UART using blocking HAL transmission. A more sophisticated approach would utilise double buffering with DMA to optimise the MCU's efficiency and allow for buffering new data during the potentially slow transmission of the old buffer. This mechanism is implemented in the old version. -### LogSenderStdOstream +### SenderStdOstream It is a simple std::ostream wrapper. +### SenderRos2 + +A simple ROS2 log wrapper. This wrapper has its own log levels and every property defined here. However, due to the architecture of this library, this wrapper uses only a compile-time hardwired ROS2 log level. + +### AtomicBufferOperational + +Normal cross-platform implementation. + +### AtomicBufferVoid + +Used the normal one instead to strip its static variables when not in use. + +### Config + +Converts the template arguments into public static variables. One can use it or write a template-less direct class instead, using this example: + +```C++ +template +struct Config final { +public: + static constexpr bool csAllowRegistrationLog = tAllowRegistrationLog; + static constexpr LogTopic csMaxTopicCount = tMaxTopicCount; + static constexpr TaskRepresentation csTaskRepresentation = tTaskRepresentation; + static constexpr size_t csDirectBufferSize = tDirectBufferSize; + static constexpr int32_t csRefreshPeriod = tRefreshPeriod; // Can represent 1s even if the unit is ns. + static constexpr ErrorLevel csErrorLevel = tErrorLevel; +}; +``` + +## Benchmarks + +I used [picobench](https://github.com/iboB/picobench) for benchmarks using the supplied benchmark apps. Here are some averaged results measured over 8192 iterations on my Intel(R) Xeon(R) CPU E31220L @ 2.20GHz. Compiled using `clang++ -O2`. There were no significant differences for `MessageVariant` or `MessageCompact`, or whether the task ID was provided or not. Log activity was +- print header + - print task name + - print timestamp +- print the logged string + +Each value is the average time required for _one_ log call in nanoseconds. Each result is an average of 8 runs, each having 262144 iterations. + +|Scenario |Constant string (no copy)|Transient string (copy)| +|-------------------------|------------------------:|----------------------:| +|direct |230 |230 | +|Lockfree queue |310 |500 | +|Circular buffer and lock |430 |590 | + +Here are benchmark results of atomic logging `int32_t` values, measured on 2097152 iterations. Note that this does not include offline buffer sending, so it is the same for any logger mode: + +|Scenario |Atomic (ns)| +|-------------------------|----------:| +|any |7 | + + ## Space requirements -I have investigated several scenarios using simple applications which contain practically nothing but a task creation apart of the logging. This way I could measure the net space required by the log library and its necessary supplementary functions like std::unordered_set or floating-point emulation for log10. +I have investigated several scenarios using simple applications which contain practically nothing but a task creation apart of the logging. This way, I could measure the net space required by the log library and its necessary supplementary functions, such as std::unordered_set or floating-point emulation for log10. -For desktop, I used clang version 10.0.0 on x64. For embedded, I used arm-none-eabi-g++ 10.1.1 on STM32 Cortex M3. This MCU needs emulation for floating point. All measurements were conducted using -Os. I present the net growths in segments text, data and BSS for each of the following scenarios: -- direct logging (for single threaded applications) +For desktop, I used clang version 10.0.0 on x64. For embedded development, I used arm-none-eabi-g++ 10.1.1 on an STM32 Cortex-M3. This MCU requires emulation for floating-point operations. All measurements were conducted using -Os. I present the net growth in segments, text, data and BSS for each of the following scenarios: +- direct logging (for single-threaded applications) - logging turned off with SenderVoid - logging with queue using MessageVariant (for multithreaded applications) - logging with queue using MessageCompact (for multithreaded applications) @@ -139,40 +203,38 @@ For desktop, I used clang version 10.0.0 on x64. For embedded, I used arm-none-e To obtain net results, I put some floating-point operations in the application *test-sizes-freertosminimal-float.cpp* because a real application would use them apart of logging. -|Scenario | Text| Data| BSS| -|--------------|------:|-----:|------:| -|direct |13152 | 108 |52 | -|off |0 |0 |0 | -|MessageVariant|15304 |112 | 76 | -|MessageCompact|15024 |112 | 76 | +|Atomic|Mode | Text| Data| BSS| +|------|------------------|------:|-----:|------:| +|- |off |0 |0 |0 | +|off |direct |13072 |108 |56 | +|on |direct |13400 |108 |80 | +|off |multitask, variant|15272 |108 |88 | +|on |multitask, variant|16000 |108 |112 | +|off |multitask, compact|14984 |108 |88 | +|on |multitask, compact|15712 |108 |112 | ### FreeRTOS without floating point -No floating point arithmetics in the application and the support is turned off in the logger. Source is *test-sizes-freertosminimal-nofloat.cpp* +No floating-point arithmetic is used in the application, and support is turned off in the logger. Source is *test-sizes-freertosminimal-nofloat.cpp*. Note, only this table contains values from the _only atomic logging_ scenario, since atomic logging assumes only integral types. -|Scenario | Text| Data| BSS| -|--------------|------:|-----:|------:| -|direct |4303 | 8 |56 | -|off |0 |0 |0 | -|MessageVariant|6440 |12 | 80 | -|MessageCompact|6192 |12 | 80 | +|Atomic|Mode | Text| Data| BSS| +|------|------------------|------:|-----:|------:| +|- |off |0 |0 |0 | +|only |direct * |3704 |4 |72 | +|off |direct |4212 |8 |60 | +|on |direct |4540 |8 |76 | +|off |multitask, variant|6412 |8 |84 | +|on |multitask, variant|7140 |8 |108 | +|off |multitask, compact|6140 |8 |84 | +|on |multitask, compact|6868 |8 |108 | -### x86 STL with floating point - -Not much point to calculate size growth here, but why not? Source is *test-sizes-stdthreadostream.cpp* - -|Scenario | Text| Data| BSS| -|--------------|------:|-----:|------:| -|direct |11899 | 273 |492 | -|off |0 |0 |0 | -|MessageVariant|22483 | 457 |892 | -|MessageCompact|20851 |457 | 892 | +* = When only using atomic logging, multitasking mode is meaningless. ## API ### Supported types -As all major coding standards suggest, use of integer types with indeterminate size is discouraged, so this library does not support `long` or `unsigned`. +As all major coding standards suggest, the use of integer types with indeterminate size is discouraged; therefore, this library does not support `long` or `unsigned`. `LogFormat` is a struct holding the numeric base and the fill value. It is easiest to reach in `LogConfig`. @@ -193,48 +255,54 @@ As all major coding standards suggest, use of integer types with indeterminate s |`char` |no |This and the strings support only plain old 8-bit ASCII.| |`char const *` |for no prefix |Can be of arbitrary length for string constants.| |`char const *` |for `LC::St` prefix |Only a limited length of _payload size_ * 255 characters can be transferred from a transient string.| +|`std::string` |no |Implicitely adds `LC::St` prefix, since `std::string` instances are usually mutable.| -64-bit integer types require emulation on 32-bit architectures. By default, `LogConverterCustomText`'s templated conversion routine use 32-bit numbers to gain speed. Using 64-bit operands instantiates the 64-bit emulated routines as well, which takes extra flash space on embedded. +64-bit integer types require emulation on 32-bit architectures. By default, `LogConverterCustomText`'s templated conversion routine uses 32-bit numbers to gain speed. Using 64-bit operands instantiates the 64-bit emulated routines as well, which takes extra flash space on embedded. ### Initialization -Log system initialiation consists of the following steps: +Log system initialisation consists of the following steps: 1. Declare possible topics (see next section). 2. Instantiate templates. -3. Define and fill `LogConfig` struct. All its fields have default value. -4. Initialize the _Sender_. This has implementation-specific arguments for the actual output. +3. Define and fill `LogConfig` struct. All its fields have a default value. +4. Initialise the _Sender_. This has implementation-specific arguments for the actual output. 5. Initialize the _Log_ using the _LogConfig_ instance. -6. Register the required topics (see nextr section). +6. Register the required topics (see next section). 7. Call `Log::registerCurrentTask("task name");` for each interested task. Other tasks won't be able to log. -Refer the beginning for an example for STL without the first step. Here is a FreeRTOS template declaration without floating point support but for multithreaded mode: +Refer to the beginning for an example of STL without the first step. Here is a FreeRTOS template declaration without floating point support, but for multithreaded mode: ```C++ -constexpr nowtech::log::TaskId cgMaxTaskCount = 1u; +constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; constexpr bool cgLogFromIsr = false; -constexpr size_t cgTaskShutdownPollPeriod = 100u; -constexpr bool cgArchitecture64 = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; constexpr uint8_t cgAppendStackBufferSize = 100u; constexpr bool cgAppendBasePrefix = true; constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; constexpr size_t cgTransmitBufferSize = 123u; -constexpr size_t cgPayloadSize = 6u; // This disables 64-bit integer arithmetic. -constexpr bool cgSupportFloatingPoint = false; -constexpr size_t cgQueueSize = 111u; +constexpr size_t cgPayloadSize = 14u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; -constexpr uint32_t cgLogTaskStackSize = 256u; -constexpr uint32_t cgLogTaskPriority = tskIDLE_PRIORITY + 1u; - constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueFreeRtos; -using Log = nowtech::log::Log; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; ``` Explanation of configuration parameters: @@ -252,25 +320,33 @@ Explanation of configuration parameters: |`bool tAlignSigned` |_Converter_ |If true, positive numbers will get an extra ' ' to be aligned with negatives. | |`typename tAppInterface` |_Sender_ |The _app interface_ type to use.| |`typename tConverter` |_Sender_ |The _Converter_ type to use.| -|`size_t tTransmitBufferSize` |_Sender_ |Length of buffer to use for conversion. This should be sufficient for the joint size of possible items in the largest group.| +|`size_t tTransmitBufferSize` |_Sender_ |Length of buffer to use for conversion. This should be sufficient for the joint size of the largest group of possible items.| |`typename tAppInterface::LogTime tTimeout` |_Sender_ |Timeout in implementation-defined unit (usually ms) for transmission.| |`typename tMessage` |_Queue_ |The _Message_ type to use.| |`typename tAppInterface` |_Queue_ |The _app interface_ type to use.| |`size_t tQueueSize` |_Queue_ |Number of items the queue should hold. This applies to the master queue and to the aggregated capacity of the per-task queues.| +|`typename tAppInterface` |`AtomicBufferOperational`|The _app interface_ to use.| +|`typename tAtomicBufferType` |`AtomicBufferOperational`|The type to log as atomic; only integral types are allowed.| +|`tAtomicBufferSizeExponent` |`AtomicBufferOperational`|Exponent of the buffer size (base of the power is 2). For numeric reasons, the buffer size is always a power of 2.| +|`tAtomicBufferType tInvalidValue` |`AtomicBufferOperational`|Invalid value, which won't be sent when all the buffer contents are being sent.| |`typename tQueue` |`Log` |The _Queue_ type to use.| |`typename tSender` |`Log` |The _Sender_ type to use.| -|`LogTopic tMaxTopicCount` |`Log` |LogTopic is `int8_t`. Maximum is 127.| -|`TaskRepresentation tTaskRepresentation` |`Log` |One of `cNone` (for omitting it), `cId` (for numeric task ID), `cName` (for task name).| -|`size_t tDirectBufferSize` |`Log` |When 0, the given _Queue_ will be used. Otherwise, it is the size of a buffer on stack to hold a converted item before sending it.| -|`typename tSender::tAppInterface_::LogTime tRefreshPeriod`|`Log` |Timeout in implementation-defined unit (usually ms) for waiting on the queue before sending what already present.| -|`bool allowRegistrationLog` |`LogConfig` |True if task (un)registering should be logged.| -|`LogFormat taskIdFormat` |`LogConfig` |Format of task ID to use when `tTaskRepresentation == TaskRepresentation::cId`.| -|`LogFormat tickFormat` |`LogConfig` |Format for displaying the timestamp in the header, if any. Should be `LogConfig::cInvalid` to disable tick output.| -|`LogFormat defaultFormat` |`LogConfig` |Default formatting, initially `LogConfig::Fm` to obtain maximum possible precision for floating point types.| +|`typename tAtomicBuffer` |`Log` |The _AtomicBuffer_ type to use.| +|`typename tLogConfig` |`Log` |The _LogConfig_ type to use.| +|`bool allowRegistrationLog` |`Config` |True if task (un)registering should be logged.| +|`LogTopic tMaxTopicCount` |`Config` |LogTopic is `int8_t`. Maximum is 127.| +|`TaskRepresentation tTaskRepresentation` |`Config` |One of `cNone` (for omitting it), `cId` (for numeric task ID), `cName` (for task name).| +|`size_t tDirectBufferSize` |`Config` |When 0, the given _Queue_ will be used. Otherwise, it is the size of a buffer on the stack to hold a converted item before sending it.| +|`int32_t tRefreshPeriod` |`Config` |Timeout in implementation-defined unit (usually ms) for waiting on the queue before sending what is already present.| +|`ErrorLevel tErrorLevel` |`Config` |The application log level with the default value `ErrorLevel::All`.| +|`LogFormat atomicFormat` |`LogFormatConfig` |Format used for converting the bulk data in the _AtomicBuffer_. +|`LogFormat taskIdFormat` |`LogFormatConfig` |Format of task ID to use when `tTaskRepresentation == TaskRepresentation::cId`.| +|`LogFormat tickFormat` |`LogFormatConfig` |Format for displaying the timestamp in the header, if any. Should be `LogConfig::cInvalid` to disable tick output.| +|`LogFormat defaultFormat` |`LogFormatConfig` |Default formatting, initially `LogConfig::Fm` to obtain maximum possible precision for floating point types.| ### Topics and log levels -The log system supports individually selectable topics to log. I've decided so because we needed a much finer granularity than the tradiotional log levels. These topics have to be declared first, after which they still would be disabled. +The log system supports individually selectable topics to log. I've decided so because we needed a much finer granularity than the traditional log levels. These topics must be declared first, after which they will still be disabled. ```C++ namespace nowtech::LogTopics { @@ -282,7 +358,32 @@ nowtech::log::TopicInstance someOtherTopic; } ``` -To enable some of them, the interesting ones must be registered in the log system. Although log levels are not supported natively, here is a workaround to define them. +To enable some of them, the interesting ones must be registered in the log system. When the log system receives the `LogTopic` parameter, it will be logged regardless of the actual application log level. + +There are three possibilities to use log levels. + +#### Native log levels + +The `Log` class takes an optional template argument, the application log level. The `Log::n(...)` and `Log::i(...)` functions' overloads without operands and taking a `TaskId` (see below) are templated methods with a default value of `ErrorLevel::All`. By providing template values for these methods, the given instantiation chooses between a functional and an empty return value. As the empty one will be fully optimised out for unused log levels, this is more performant than the next one. The log system has these predefined log levels in `Log`: + +```C++ +static constexpr ErrorLevel fatal = ErrorLevel::Fatal; +static constexpr ErrorLevel error = ErrorLevel::Error; +static constexpr ErrorLevel warn = ErrorLevel::Warning; +static constexpr ErrorLevel info = ErrorLevel::Info; +static constexpr ErrorLevel debug = ErrorLevel::Debug; +static constexpr ErrorLevel all = ErrorLevel::All; +``` + +So logging happens like + +```C++ +Log::i() << "x:" << LC::D4 << posX << "y:" << LC::D4 << posY << Log::end; +``` + +#### Emulated log levels + +For topic declarations, please refer to the section above. This method suits better architectures with limited flash space, since it requires no internal method instantiation for each log level used. However, checking topics is a runtime process, and total elimination of unused log statements won't happen for optimising compilation. ```C++ #ifdef LEVEL1 @@ -297,9 +398,15 @@ Log::registerTopic(nowtech::LogTopics::level3, "level3"); #endif ``` +#### Variadic macros + +C++20 features suitable variadic macros in the preprocessor, enabling the use of the folding expression API to define preprocessor-implemented log levels. I didn't implement it. This would mean a perfect solution from a performance and space point of view. + ### Logging -All the logging API is implemented as static functions in the `Log` template class. Logging happens using a `std::ostream` -like API, like in the example in the beginning. There are two overloaded functions to start the chain: +#### Group logging + +All logging APIs are implemented as static functions in the `Log` template class. Logging happens using a `std::ostream` -like API, like in the example at the beginning. There are two overloaded functions to start the chain: - `static LogShiftChainHelper i(...) noexcept` writes any header configured for the application. - `static LogShiftChainHelper n(...) noexcept` omits this header, just writes the actual stuff it receives using `<<`. Note, LogShiftChainHelper implementation depends on the given log mode (direct / queued / shut off). @@ -307,12 +414,12 @@ Note, LogShiftChainHelper implementation depends on the given log mode (direct / Each function has four overloads with the following parameter signatures: - `()` - logs unconditionally, and queries the task ID. - `(TaskId const aTaskId)` - logs unconditionally using the supplied task ID. -- `(LogTopic const aTopic)` - logs depending on the given task ID was registered, and queries the task ID. -- `(LogTopic const aTopic, TaskId const aTaskId)` - logs depending on the given task ID was registered using the supplied task ID. +- `(LogTopic const aTopic)` - logs depending on the given task ID were registered, and queries the task ID. +- `(LogTopic const aTopic, TaskId const aTaskId)` - logs depending on the given task ID were registered using the supplied task ID. -One can use the `static TaskId getCurrentTaskId() noexcept` function to query the current task ID and store it, This can be important if querying the task ID is expensive on the given platform. +One can use the `static TaskId getCurrentTaskId() noexcept` function to query the current task ID and store it. This can be important if querying the task ID is expensive on the given platform. -If you have many or unknown number of items to log, you can use the form +If you have many or an unknown number of items to log, you can use the form ```C++ auto logger = Log::n() << "someCollection contgents:"; @@ -328,4 +435,21 @@ I've implemented a function call-like entry point using C++17 folding expression Log::f(Log::i(nowtech::LogTopics::someTopic), some, variables, follow); ``` -and apart of being clumsy, it is even takes more binary space than the `std::ostream` -like API it uses under the hood. It appends `Log::end` automatically. +and apart from being clumsy, it even takes more binary space than the `std::ostream` -like API it uses under the hood. It appends `Log::end` automatically. + +#### Atomic logging + +Atomic logging can happen any time in this way (now the buffer is of type `int16_t`): + +```C++ +int16_t value = 13; +Log::pushAtomic(value); +``` + +Of course, this is only effective if the logging is on and the `AtomicBufferOperational` class is being used. These values are accepted from any task or ISR and land in a circular buffer, which is continuously overwritten. To extract the contents, the application should first stop calling `Log::pushAtomic`, as the slower readout would produce undefined behaviour when it is still being written to. (There is no locking for maximum performance.) Then, a task with a valid log registry should call + +```C++ +Log::sendAtomicBuffer(); +``` + +which is a blocking call. Note that in multithreaded mode, sending from all other tasks will not occur; only the queues will hold messages from concurrent logging as long as possible. diff --git a/benchmark/picobench-direct.cpp b/benchmark/picobench-direct.cpp new file mode 100644 index 0000000..aa567bd --- /dev/null +++ b/benchmark/picobench-direct.cpp @@ -0,0 +1,94 @@ +#define PICOBENCH_IMPLEMENT +#define PICOBENCH_DEFAULT_SAMPLES 10 +#define PICOBENCH_DEFAULT_ITERATIONS { 64, 512, 4096, 32768, 262144, 2097152 } +#include "picobench/picobench.hpp" + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueVoid.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include + +constexpr nowtech::log::TaskId cgMaxTaskCount = 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 123u; +constexpr size_t cgPayloadSize = 14u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr size_t cgDirectBufferSize = 1234u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Info; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + +nowtech::log::TaskId gTaskId; + +void nowtechLogRefTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefTaskId); + +void nowtechLogCopyTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyTaskId); + +void nowtechLogRefNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefNoTaskId); + +void nowtechLogCopyNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyNoTaskId); + + +int main(int argc, char* argv[]) +{ + nowtech::log::LogFormatConfig logConfig; + std::ofstream out("/tmp/x"); + LogSender::init(&out); + Log::init(logConfig); + Log::registerCurrentTask("main"); + gTaskId = Log::getCurrentTaskId(); + + picobench::runner r; + r.parse_cmd_line(argc, argv); + auto ret = r.run(); + Log::sendAtomicBuffer(); + Log::unregisterCurrentTask(); + Log::done(); + return ret; +} diff --git a/benchmark/picobench-msg-compact-boost.cpp b/benchmark/picobench-msg-compact-boost.cpp new file mode 100644 index 0000000..c1fd51c --- /dev/null +++ b/benchmark/picobench-msg-compact-boost.cpp @@ -0,0 +1,93 @@ +#define PICOBENCH_IMPLEMENT +#define PICOBENCH_DEFAULT_SAMPLES 10 +#include "picobench/picobench.hpp" + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueStdBoost.h" +#include "LogQueueVoid.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include +#include + +constexpr nowtech::log::TaskId cgMaxTaskCount = 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 444444u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 44444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr size_t cgDirectBufferSize = 0u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Info; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + +nowtech::log::TaskId gTaskId; + +void nowtechLogRefTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefTaskId); + +void nowtechLogCopyTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyTaskId); + +void nowtechLogRefNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefNoTaskId); + +void nowtechLogCopyNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyNoTaskId); + +int main(int argc, char* argv[]) +{ + nowtech::log::LogFormatConfig logConfig; + std::ofstream out("tmp"); + LogSender::init(&out); + Log::init(logConfig); + Log::registerCurrentTask("main"); + gTaskId = Log::getCurrentTaskId(); + + picobench::runner r; + r.parse_cmd_line(argc, argv); + auto ret = r.run(); + Log::unregisterCurrentTask(); + Log::done(); + return ret; +} diff --git a/benchmark/picobench-msg-compact-circular.cpp b/benchmark/picobench-msg-compact-circular.cpp new file mode 100644 index 0000000..5b048ea --- /dev/null +++ b/benchmark/picobench-msg-compact-circular.cpp @@ -0,0 +1,93 @@ +#define PICOBENCH_IMPLEMENT +#define PICOBENCH_DEFAULT_SAMPLES 10 +#include "picobench/picobench.hpp" + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogQueueStdCircular.h" +#include "LogQueueVoid.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include +#include + +constexpr nowtech::log::TaskId cgMaxTaskCount = 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 444444u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr size_t cgDirectBufferSize = 0u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Info; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdCircular; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + +nowtech::log::TaskId gTaskId; + +void nowtechLogRefTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefTaskId); + +void nowtechLogCopyTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyTaskId); + +void nowtechLogRefNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefNoTaskId); + +void nowtechLogCopyNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyNoTaskId); + + +int main(int argc, char* argv[]) +{ + nowtech::log::LogFormatConfig logConfig; + std::ofstream out("tmp"); + LogSender::init(&out); + Log::init(logConfig); + Log::registerCurrentTask("main"); + gTaskId = Log::getCurrentTaskId(); + + picobench::runner r; + r.parse_cmd_line(argc, argv); + auto ret = r.run(); + Log::unregisterCurrentTask(); + Log::done(); + return ret; +} diff --git a/benchmark/picobench-msg-variant-boost.cpp b/benchmark/picobench-msg-variant-boost.cpp new file mode 100644 index 0000000..5da55f9 --- /dev/null +++ b/benchmark/picobench-msg-variant-boost.cpp @@ -0,0 +1,94 @@ +#define PICOBENCH_IMPLEMENT +#define PICOBENCH_DEFAULT_SAMPLES 10 +#include "picobench/picobench.hpp" + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueStdBoost.h" +#include "LogQueueVoid.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include +#include + +constexpr nowtech::log::TaskId cgMaxTaskCount = 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 444444u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 44444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr size_t cgDirectBufferSize = 0u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Info; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + +nowtech::log::TaskId gTaskId; + +void nowtechLogRefTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefTaskId); + +void nowtechLogCopyTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyTaskId); + +void nowtechLogRefNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefNoTaskId); + +void nowtechLogCopyNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyNoTaskId); + + +int main(int argc, char* argv[]) +{ + nowtech::log::LogFormatConfig logConfig; + std::ofstream out("tmp"); + LogSender::init(&out); + Log::init(logConfig); + Log::registerCurrentTask("main"); + gTaskId = Log::getCurrentTaskId(); + + picobench::runner r; + r.parse_cmd_line(argc, argv); + auto ret = r.run(); + Log::unregisterCurrentTask(); + Log::done(); + return ret; +} diff --git a/benchmark/picobench-msg-variant-circular.cpp b/benchmark/picobench-msg-variant-circular.cpp new file mode 100644 index 0000000..0703d34 --- /dev/null +++ b/benchmark/picobench-msg-variant-circular.cpp @@ -0,0 +1,92 @@ +#define PICOBENCH_IMPLEMENT +#define PICOBENCH_DEFAULT_SAMPLES 10 +#include "picobench/picobench.hpp" + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueStdCircular.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include +#include + +constexpr nowtech::log::TaskId cgMaxTaskCount = 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 444444u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr size_t cgDirectBufferSize = 0u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Info; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdCircular; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + +nowtech::log::TaskId gTaskId; + +void nowtechLogRefTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefTaskId); + +void nowtechLogCopyTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i(gTaskId) << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyTaskId); + +void nowtechLogRefNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogRefNoTaskId); + +void nowtechLogCopyNoTaskId(picobench::state& s) { + for (auto _ : s) { + Log::i() << LC::St << "Info message" << Log::end; + } +} +PICOBENCH(nowtechLogCopyNoTaskId); + +int main(int argc, char* argv[]) +{ + nowtech::log::LogFormatConfig logConfig; + std::ofstream out("tmp"); + LogSender::init(&out); + Log::init(logConfig); + Log::registerCurrentTask("main"); + gTaskId = Log::getCurrentTaskId(); + + picobench::runner r; + r.parse_cmd_line(argc, argv); + auto ret = r.run(); + Log::unregisterCurrentTask(); + Log::done(); + return ret; +} diff --git a/sizecalc.ods b/sizecalc.ods index d39f419..27678cb 100644 Binary files a/sizecalc.ods and b/sizecalc.ods differ diff --git a/src/Log.h b/src/Log.h index cdc79f7..e4fc7e5 100644 --- a/src/Log.h +++ b/src/Log.h @@ -2,9 +2,11 @@ #define NOWTECH_LOG #include "LogMessageBase.h" +#include "LogAtomicBuffers.h" #include "PoolAllocator.h" #include #include +#include #include #include #include @@ -26,11 +28,21 @@ enum class TaskRepresentation : uint8_t { using LogTopic = int8_t; // this needs to be signed to let the overload resolution work -template +enum class ErrorLevel : uint8_t { + Off = 0u, + Fatal = 1u, + Error = 2u, + Warning = 3u, + Info = 4u, + Debug = 5u, + All = 6u +}; + +template class Log; class TopicInstance final { - template + template friend class Log; public: @@ -45,16 +57,27 @@ class TopicInstance final { } public: - LogTopic operator*() { + LogTopic operator*() const { return mValue; } - operator LogTopic() { + operator LogTopic() const { return mValue; } }; -struct LogConfig final { +template +struct Config final { +public: + static constexpr bool csAllowRegistrationLog = tAllowRegistrationLog; + static constexpr LogTopic csMaxTopicCount = tMaxTopicCount; + static constexpr TaskRepresentation csTaskRepresentation = tTaskRepresentation; + static constexpr size_t csDirectBufferSize = tDirectBufferSize; + static constexpr int32_t csRefreshPeriod = tRefreshPeriod; // Can represent 1s even if the unit is ns. + static constexpr ErrorLevel csErrorLevel = tErrorLevel; +}; + +struct LogFormatConfig final { public: /// This is the default logging format and the only one I will document /// here. For the others, the letter represents the base of the number @@ -89,10 +112,6 @@ struct LogConfig final { // Indicates the next char* value should be stored in messages instead of taking only its address inline static constexpr LogFormat St {13u, LogFormat::csFillValueStoreString }; - /// If true, task registration will be sent to the output in the form - /// in the form -=- Registered task: taskname (1) -=- - bool allowRegistrationLog = true; - /// Format for displaying the task ID in the message header. LogFormat taskIdFormat = X2; @@ -100,8 +119,9 @@ struct LogConfig final { /// cInvalid to disable tick output. LogFormat tickFormat = D5; LogFormat defaultFormat = Fm; + LogFormat atomicFormat = Fm; - LogConfig() noexcept = default; + LogFormatConfig() noexcept = default; }; /// Dummy type to use in << chain as end marker. @@ -109,28 +129,41 @@ enum class LogShiftChainEndMarker : uint8_t { cEnd = 0u }; -template +template class Log final { private: - static constexpr bool csShutdownLog = tSender::csVoid; - static constexpr bool csSendInBackground = (tDirectBufferSize == 0u); // will omit tQueue using tMessage = typename tQueue::tMessage_; using tAppInterface = typename tSender::tAppInterface_; using tConverter = typename tSender::tConverter_; using ConversionResult = typename tConverter::ConversionResult; using LogTime = typename tAppInterface::LogTime; using TopicName = char const *; - - static constexpr size_t csPayloadSizeBr = tMessage::csPayloadSize; - static constexpr size_t csPayloadSizeNet = tMessage::csPayloadSize - 1u; // we leave space for terminal 0 to avoid counting bytes - static constexpr size_t csQueueSize = tQueue::csQueueSize; - static constexpr TaskId csInvalidTaskId = tAppInterface::csInvalidTaskId; - static constexpr TaskId csIsrTaskId = tAppInterface::csIsrTaskId; - static constexpr TaskId csMaxTaskCount = tAppInterface::csMaxTaskCount; - static constexpr TaskId csMaxTotalTaskCount = tAppInterface::csMaxTaskCount + 1u; - static constexpr size_t csListItemOverhead = sizeof(void*) * 8u; - static constexpr bool csConstantTaskNames = tAppInterface::csConstantTaskNames; - + using tAtomicBufferType = typename tAtomicBuffer::tAtomicBufferType_; + + static constexpr size_t csDirectBufferSize = tLogConfig::csDirectBufferSize; + static constexpr bool csShutdownLog = tSender::csVoid; + static constexpr bool csSendInBackground = (csDirectBufferSize == 0u); // will omit tQueue + static constexpr size_t csAtomicBufferSizeExponent = tAtomicBuffer::csAtomicBufferSizeExponent; + static constexpr size_t csAtomicBufferSize = tAtomicBuffer::csAtomicBufferSize; + static constexpr bool csAtomicBufferOperational = tAtomicBuffer::csAtomicBufferSizeExponent > 0u; + static constexpr tAtomicBufferType csAtomicBufferInvalidValue = tAtomicBuffer::csInvalidValue; + static constexpr size_t csMaxAtomicBufferSizeExp = std::max(32u, sizeof(void*) * 8u - 1u); + static constexpr size_t csPayloadSizeBr = tMessage::csPayloadSize; + static constexpr size_t csPayloadSizeNet = tMessage::csPayloadSize - 1u; // we leave space for terminal 0 to avoid counting bytes + static constexpr int32_t csRefreshPeriod = tLogConfig::csRefreshPeriod; + static constexpr size_t csQueueSize = tQueue::csQueueSize; + static constexpr TaskId csInvalidTaskId = tAppInterface::csInvalidTaskId; + static constexpr TaskId csIsrTaskId = tAppInterface::csIsrTaskId; + static constexpr TaskId csMaxTaskCount = tAppInterface::csMaxTaskCount; + static constexpr TaskId csMaxTotalTaskCount = tAppInterface::csMaxTaskCount + 1u; + static constexpr size_t csListItemOverhead = sizeof(void*) * 8u; + static constexpr bool csConstantTaskNames = tAppInterface::csConstantTaskNames; + static constexpr bool csAllowRegistrationLog = tLogConfig::csAllowRegistrationLog; + + static constexpr ErrorLevel csErrorLevel = tLogConfig::csErrorLevel; + static constexpr TaskRepresentation csTaskRepresentation = tLogConfig::csTaskRepresentation; + + static constexpr LogTopic csMaxTopicCount = tLogConfig::csMaxTopicCount; static constexpr LogTopic csFirstFreeTopic = 0; static constexpr MessageSequence csSequence0 = 0u; static constexpr MessageSequence csSequence1 = 1u; @@ -149,14 +182,16 @@ class Log final { static_assert(csMaxTaskCount < std::numeric_limits::max()); static_assert(std::is_same_v); static_assert(std::is_same_v); + static_assert(std::is_integral_v); + static_assert(csAtomicBufferSizeExponent <= csMaxAtomicBufferSizeExp); inline static constexpr char csRegisteredTask[] = "-=- Registered task:"; inline static constexpr char csUnregisteredTask[] = "-=- Unregistered task:"; - inline static LogConfig const *sConfig; + inline static LogFormatConfig const *sConfig; inline static std::atomic sNextFreeTopic; inline static std::atomic sKeepAliveTask; - inline static std::array sRegisteredTopics; + inline static std::array sRegisteredTopics; inline static TaskShutdownArray *sTaskShutdowns; inline static Occupier sOccupier; @@ -167,10 +202,15 @@ class Log final { /// This will be used to send via queue. It stores the first message, and sends it only with the terminal marker. class LogShiftChainHelperBackgroundSend final { + private: + inline static constexpr char csEmptyString[] = ""; + inline static constexpr LogFormat csEmptyFormat {2u, 0u}; + TaskId mTaskId; LogFormat mNextFormat; MessageSequence mNextSequence; tMessage mFirstMessage; + bool mWasMessage; public: static constexpr LogShiftChainEndMarker end = LogShiftChainEndMarker::cEnd; @@ -179,8 +219,9 @@ class Log final { LogShiftChainHelperBackgroundSend(TaskId const aTaskId) noexcept : mTaskId(aTaskId) - , mNextSequence(0u) { - mNextFormat.invalidate(); + , mNextSequence(0u) + , mWasMessage(false) { + mNextFormat.invalidate(); } /// Can be used in application code to eliminate further operator<< calls when the topic is disabled. @@ -209,6 +250,11 @@ class Log final { return sendCharPointer(aValue); } + LogShiftChainHelperBackgroundSend& operator<<(std::string const &aValue) noexcept { + mNextFormat = LogFormatConfig::St; + return sendCharPointer(aValue.c_str()); + } + LogShiftChainHelperBackgroundSend& operator<<(LogFormat const aFormat) noexcept { mNextFormat = aFormat; return *this; @@ -216,6 +262,11 @@ class Log final { void operator<<(LogShiftChainEndMarker const) noexcept { if(mTaskId != csInvalidTaskId) { + if(!mWasMessage) { + mFirstMessage.set(csEmptyString, csEmptyFormat, mTaskId, csSequence0); + } + else { // nothing to do + } tQueue::push(mFirstMessage); } else { // nothing to do @@ -271,6 +322,7 @@ class Log final { void sendOrStore(tMessage const & aMessage) noexcept { if(mNextSequence == csSequence0) { + mWasMessage = true; mFirstMessage = aMessage; } else { @@ -309,8 +361,8 @@ class Log final { else { format = sConfig->defaultFormat; } - ConversionResult buffer[tDirectBufferSize]; - tConverter converter(buffer, buffer + tDirectBufferSize); + ConversionResult buffer[csDirectBufferSize]; + tConverter converter(buffer, buffer + csDirectBufferSize); converter.convert(aValue, format.mBase, format.mFill); tAppInterface::lock(); tSender::send(buffer, converter.end()); @@ -328,8 +380,8 @@ class Log final { void operator<<(LogShiftChainEndMarker const) noexcept { if(mTaskId != csInvalidTaskId) { - ConversionResult buffer[tDirectBufferSize]; - tConverter converter(buffer, buffer + tDirectBufferSize); + ConversionResult buffer[csDirectBufferSize]; + tConverter converter(buffer, buffer + csDirectBufferSize); converter.terminateSequence(); tAppInterface::lock(); tSender::send(buffer, converter.end()); @@ -366,15 +418,23 @@ class Log final { } }; // class LogShiftChainHelperEmpty - using LogShiftChainHelper = std::conditional_t>; + using LogShiftChainHelperRaw = std::conditional_t; + using LogShiftChainHelper = std::conditional_t; + template + using LogShiftChainHelperErrorLevel = std::conditional_t<(csShutdownLog || csErrorLevel < tRequestedErrorLevel), LogShiftChainHelperEmpty, LogShiftChainHelperRaw>; public: /// Will be used as Log << something << to << log << Log::end; - static constexpr LogShiftChainEndMarker end = LogShiftChainEndMarker::cEnd; + static constexpr LogShiftChainEndMarker end = LogShiftChainEndMarker::cEnd; + static constexpr ErrorLevel fatal = ErrorLevel::Fatal; + static constexpr ErrorLevel error = ErrorLevel::Error; + static constexpr ErrorLevel warn = ErrorLevel::Warning; + static constexpr ErrorLevel info = ErrorLevel::Info; + static constexpr ErrorLevel debug = ErrorLevel::Debug; + static constexpr ErrorLevel all = ErrorLevel::All; - // TODO remark in docs: must come before registering topics template - static void init(LogConfig const &aConfig, tTypes... aArgs) { + static void init(LogFormatConfig const &aConfig, tTypes... aArgs) { if constexpr(!csShutdownLog) { sConfig = &aConfig; if constexpr(csSendInBackground) { @@ -394,8 +454,9 @@ class Log final { tAppInterface::init(); } tQueue::init(); + tAtomicBuffer::init(); sNextFreeTopic = csFirstFreeTopic; - std::fill_n(sRegisteredTopics.begin(), tMaxTopicCount, nullptr); + std::fill_n(sRegisteredTopics.begin(), csMaxTopicCount, nullptr); } else { // nothing to do } @@ -418,6 +479,7 @@ class Log final { else { // nothing to do } tQueue::done(); + tAtomicBuffer::done(); tSender::done(); tAppInterface::done(); } @@ -448,7 +510,7 @@ class Log final { } else { // nothing to do } - if(sConfig->allowRegistrationLog) { + if constexpr(csAllowRegistrationLog) { n(taskId) << csRegisteredTask << aTaskName << taskId << end; } else { // nothing to do @@ -466,8 +528,12 @@ class Log final { static void unregisterCurrentTask() noexcept { if constexpr(!csShutdownLog) { TaskId taskId = tAppInterface::getCurrentTaskId(); - if(taskId != csInvalidTaskId && sConfig->allowRegistrationLog) { - n(taskId) << csUnregisteredTask << taskId << end; + if constexpr(csAllowRegistrationLog) { + if(taskId != csInvalidTaskId) { + n(taskId) << csUnregisteredTask << taskId << end; + } + else { // nothing to do + } } else { // nothing to do } @@ -490,7 +556,7 @@ class Log final { static void registerTopic(TopicInstance &aTopic, char const * const aPrefix) { if constexpr(!csShutdownLog) { aTopic = sNextFreeTopic++; - if(aTopic >= tMaxTopicCount) { + if(aTopic >= csMaxTopicCount) { tAppInterface::fatalError(Exception::cOutOfTopics); } else { @@ -510,33 +576,56 @@ class Log final { } } - static LogShiftChainHelper i() noexcept { - if constexpr(!csShutdownLog) { + static void pushAtomic(tAtomicBufferType const &aValue) noexcept { + if constexpr(!csShutdownLog && csAtomicBufferOperational) { + tAtomicBuffer::push(aValue); + } + else { // nothing to do + } + } + + static void sendAtomicBuffer() noexcept { + if constexpr(!csShutdownLog && csAtomicBufferOperational) { + if constexpr(csSendInBackground) { + tAtomicBuffer::scheduleForSend(); + tAppInterface::atomicBufferSendWait(); + } else { + doSendAtomicBuffer(); + } + } + else { // nothing to do + } + } + + template + static LogShiftChainHelperErrorLevel i() noexcept { + if constexpr(!csShutdownLog && csErrorLevel >= tRequestedErrorLevel) { TaskId const taskId = tAppInterface::getCurrentTaskId(); - return sendHeader(taskId); + return sendHeader>(taskId); } else { - return LogShiftChainHelper{csInvalidTaskId}; + return LogShiftChainHelperErrorLevel{csInvalidTaskId}; } } - static LogShiftChainHelper i(TaskId const aTaskId) noexcept { - if constexpr(!csShutdownLog) { - return sendHeader(aTaskId); + template + static LogShiftChainHelperErrorLevel i(TaskId const aTaskId) noexcept { + if constexpr(!csShutdownLog && csErrorLevel >= tRequestedErrorLevel) { + return sendHeader>(aTaskId); } else { - return LogShiftChainHelper{csInvalidTaskId}; + return LogShiftChainHelperErrorLevel{csInvalidTaskId}; } } static LogShiftChainHelper i(LogTopic const aTopic) noexcept { if constexpr(!csShutdownLog) { - if(sRegisteredTopics[aTopic] != nullptr) { + if(aTopic >= 0 && sRegisteredTopics[aTopic] != nullptr) { TaskId const taskId = tAppInterface::getCurrentTaskId(); - return sendHeader(taskId, sRegisteredTopics[aTopic]); + return sendHeader(taskId, sRegisteredTopics[aTopic]); } else { - return sendHeader(csInvalidTaskId); + return sendHeader(csInvalidTaskId); } } else { @@ -546,11 +635,11 @@ class Log final { static LogShiftChainHelper i(LogTopic const aTopic, TaskId const aTaskId) noexcept { if constexpr(!csShutdownLog) { - if(sRegisteredTopics[aTopic] != nullptr) { - return sendHeader(aTaskId, sRegisteredTopics[aTopic]); + if(aTopic >= 0 && sRegisteredTopics[aTopic] != nullptr) { + return sendHeader(aTaskId, sRegisteredTopics[aTopic]); } else { - return sendHeader(csInvalidTaskId); + return sendHeader(csInvalidTaskId); } } else { @@ -558,27 +647,29 @@ class Log final { } } - static LogShiftChainHelper n() noexcept { - if constexpr(!csShutdownLog) { - return LogShiftChainHelper{tAppInterface::getCurrentTaskId()}; + template + static LogShiftChainHelperErrorLevel n() noexcept { + if constexpr(!csShutdownLog && csErrorLevel >= tRequestedErrorLevel) { + return LogShiftChainHelperErrorLevel{tAppInterface::getCurrentTaskId()}; } else { - return LogShiftChainHelper{csInvalidTaskId}; + return LogShiftChainHelperErrorLevel{csInvalidTaskId}; } } - static LogShiftChainHelper n(TaskId const aTaskId) noexcept { - if constexpr(!csShutdownLog) { - return LogShiftChainHelper{aTaskId}; + template + static LogShiftChainHelperErrorLevel n(TaskId const aTaskId) noexcept { + if constexpr(!csShutdownLog && csErrorLevel >= tRequestedErrorLevel) { + return LogShiftChainHelperErrorLevel{aTaskId}; } else { - return LogShiftChainHelper{csInvalidTaskId}; + return LogShiftChainHelperErrorLevel{csInvalidTaskId}; } } static LogShiftChainHelper n(LogTopic const aTopic) noexcept { if constexpr(!csShutdownLog) { - if(sRegisteredTopics[aTopic] != nullptr) { + if(aTopic >= 0 && sRegisteredTopics[aTopic] != nullptr) { return LogShiftChainHelper{tAppInterface::getCurrentTaskId()}; } else { @@ -592,7 +683,7 @@ class Log final { static LogShiftChainHelper n(LogTopic const aTopic, TaskId const aTaskId) noexcept { if constexpr(!csShutdownLog) { - if(sRegisteredTopics[aTopic] != nullptr) { + if(aTopic >= 0 && sRegisteredTopics[aTopic] != nullptr) { return LogShiftChainHelper{aTaskId}; } else { @@ -610,18 +701,19 @@ class Log final { } private: - static LogShiftChainHelper sendHeader(TaskId const aTaskId) noexcept { - LogShiftChainHelper result{aTaskId}; + template + static tLogShiftChainHelper sendHeader(TaskId const aTaskId) noexcept { + tLogShiftChainHelper result{aTaskId}; if(result.isValid()) { - if constexpr(tTaskRepresentation == TaskRepresentation::cId) { + if constexpr(csTaskRepresentation == TaskRepresentation::cId) { result << sConfig->taskIdFormat << aTaskId; } - else if constexpr (tTaskRepresentation == TaskRepresentation::cName) { - if constexpr (csConstantTaskNames) { + else if constexpr (csTaskRepresentation == TaskRepresentation::cName) { + if constexpr (csConstantTaskNames || !csSendInBackground) { result << tAppInterface::getTaskName(aTaskId); } else { - result << LogConfig::St << tAppInterface::getTaskName(aTaskId); + result << LogFormatConfig::St << tAppInterface::getTaskName(aTaskId); } } else { // nothing to do @@ -637,8 +729,9 @@ class Log final { return result; } - static LogShiftChainHelper sendHeader(TaskId const aTaskId, char const * aTopicName) noexcept { - LogShiftChainHelper result = sendHeader(aTaskId); + template + static tLogShiftChainHelper sendHeader(TaskId const aTaskId, char const * aTopicName) noexcept { + tLogShiftChainHelper result = sendHeader(aTaskId); if(result.isValid() && aTopicName != nullptr) { result << aTopicName; } @@ -650,21 +743,27 @@ class Log final { static void transmitterTaskFunction() noexcept { while(sKeepAliveTask || !tQueue::empty()) { tMessage message; - if(tQueue::pop(message, tRefreshPeriod)) { + if(tQueue::pop(message, csRefreshPeriod)) { TaskId taskId = message.getTaskId(); - if constexpr(csSendInBackground) { - if (message.isShutdown()) { - (*sTaskShutdowns)[taskId] = true; - } - else { - checkAndInsertAndTransmit(taskId, message); - } + if (message.isShutdown()) { + (*sTaskShutdowns)[taskId] = true; } else { checkAndInsertAndTransmit(taskId, message); } } - else { // nothing to do + else { + if constexpr(csAtomicBufferOperational) { + if(tAtomicBuffer::isScheduledForSent()) { + doSendAtomicBuffer(); + tAppInterface::atomicBufferSendFinished(); + tAtomicBuffer::sendFinished(); + } + else { // nothing to do + } + } + else { // nothing to do + } } } tAppInterface::finish(); @@ -720,10 +819,37 @@ class Log final { converter.terminateSequence(); tSender::send(begin, converter.end()); } + + static void doSendAtomicBuffer() noexcept { + auto [inBuffer, inIndex] = tAtomicBuffer::getBuffer(); + auto [outBegin, outEnd] = tSender::getBuffer(); + size_t processed = 0u; + while(processed < csAtomicBufferSize) { + tConverter converter(outBegin, outEnd); + auto validOutEnd = converter.end(); + while((converter.end() != outEnd) && (processed < csAtomicBufferSize)) { + if(inBuffer[inIndex] != csAtomicBufferInvalidValue) { + converter.convert(inBuffer[inIndex], sConfig->atomicFormat.mBase, sConfig->atomicFormat.mFill); + if (converter.end() != outEnd) { + validOutEnd = converter.end(); + } else { // nothing to do + } + } + else { // nothing to do + } + inIndex = (inIndex + 1u) % csAtomicBufferSize; + ++processed; + } + tSender::send(outBegin, validOutEnd); + } + tConverter converter(outBegin, outEnd); + converter.terminateSequence(); + tSender::send(outBegin, converter.end()); + } }; } -using LC = nowtech::log::LogConfig; +using LC = nowtech::log::LogFormatConfig; #endif diff --git a/src/LogAppInterfaceFreeRtosMinimal.h b/src/LogAppInterfaceFreeRtosMinimal.h index 2b3f8df..0254f93 100644 --- a/src/LogAppInterfaceFreeRtosMinimal.h +++ b/src/LogAppInterfaceFreeRtosMinimal.h @@ -45,16 +45,18 @@ class AppInterfaceFreeRtosMinimal final { }; private: - inline static constexpr char csUnknownTaskName[] = "UNKNOWN"; - inline static constexpr char csIsrTaskName[] = "ISR"; - inline static constexpr char csTransmitterTaskName[] = "logTx"; - inline static constexpr TickType_t csRegistrationMutexTimeout = portMAX_DELAY; + inline static constexpr char csUnknownTaskName[] = "UNKNOWN"; + inline static constexpr char csIsrTaskName[] = "ISR"; + inline static constexpr char csTransmitterTaskName[] = "logTx"; + static constexpr TickType_t csRegistrationMutexTimeout = portMAX_DELAY; + static constexpr TickType_t csAtomicSendTimeout = portMAX_DELAY; inline static std::array sTaskHandles; inline static TaskId sPreviousTaskId = csFirstNormalTaskId - 1u; inline static SemaphoreHandle_t sRegistrationMutex; + inline static std::atomic sTaskToNotify; inline static TaskHandle_t sTransmitterTask; - + AppInterfaceFreeRtosMinimal() = delete; public: @@ -160,6 +162,15 @@ class AppInterfaceFreeRtosMinimal final { static void unlock() noexcept { // Now don't care. } + static void atomicBufferSendWait() noexcept { + sTaskToNotify = xTaskGetCurrentTaskHandle(); + ulTaskNotifyTake(pdTRUE, csAtomicSendTimeout); + } + + static void atomicBufferSendFinished() noexcept { + xTaskNotifyGive(sTaskToNotify); + } + static void error(Exception const aError) { while(true) { } diff --git a/src/LogAppInterfaceStd.h b/src/LogAppInterfaceStd.h index 231f86f..622cafe 100644 --- a/src/LogAppInterfaceStd.h +++ b/src/LogAppInterfaceStd.h @@ -41,21 +41,25 @@ class AppInterfaceStd final { private: class Semaphore final { private: - std::atomic mNotified = false; - std::mutex mMutex; - std::unique_lock mLock; - std::condition_variable mConditionVariable; + std::atomic mNotified = false; + std::mutex mMutex; + std::unique_lock mLock; + std::condition_variable mConditionVariable; public: Semaphore() noexcept : mLock(mMutex) { } void wait() noexcept { + mNotified = false; mConditionVariable.wait(mLock, [this] { return mNotified == true; }); } void notify() noexcept { - mNotified = true; + { + std::lock_guard lock(mMutex); + mNotified = true; + } mConditionVariable.notify_one(); } }; @@ -156,6 +160,14 @@ class AppInterfaceStd final { static void unlock() noexcept { // Now don't care. } + static void atomicBufferSendWait() noexcept { + sSemaphore.wait(); + } + + static void atomicBufferSendFinished() noexcept { + sSemaphore.notify(); + } + static void error(Exception const aError) { throw std::ios_base::failure(csErrorMessages[static_cast(aError)]); } diff --git a/src/LogAtomicBuffers.h b/src/LogAtomicBuffers.h new file mode 100644 index 0000000..db0f124 --- /dev/null +++ b/src/LogAtomicBuffers.h @@ -0,0 +1,96 @@ +#ifndef NOWTECH_LOG_ATOMIC_BUFFERS +#define NOWTECH_LOG_ATOMIC_BUFFERS + +#include +#include +#include + +namespace nowtech::log { + +template +class AtomicBufferOperational final { +public: + using tAtomicBufferType_ = tAtomicBufferType; + static constexpr std::size_t csAtomicBufferSizeExponent = tAtomicBufferSizeExponent; + static constexpr std::size_t csAtomicBufferSize = 1u << tAtomicBufferSizeExponent; + static constexpr tAtomicBufferType csInvalidValue = tInvalidValue; + +private: + inline static tAtomicBufferType *sBuffer; // We spare one instruction per access by having C-style array instead of std::array + inline static std::atomic sNextWrite; + inline static std::atomic sShouldSend; + +public: + static void init() { + sNextWrite = 0u; + sShouldSend = false; + sBuffer = tAppInterface::template _newArray(csAtomicBufferSize); + invalidate(); + } + + static void done() { + tAppInterface::template _deleteArray(sBuffer); + } + + static void push(tAtomicBufferType const aValue) noexcept { + std::size_t nextIndex = sNextWrite++ % csAtomicBufferSize; + sBuffer[nextIndex] = aValue; + } + + static void scheduleForSend() noexcept { + sShouldSend = true; + } + + static bool isScheduledForSent() noexcept { + return sShouldSend.load(); + } + + static void sendFinished() noexcept { + sShouldSend = false; + } + + static auto getBuffer() noexcept { + return std::pair{sBuffer, sNextWrite % csAtomicBufferSize}; + } + + static void invalidate() noexcept { + std::fill_n(sBuffer, csAtomicBufferSize, tInvalidValue); + } +}; + +class AtomicBufferVoid final { +public: + using tAtomicBufferType_ = char; + static constexpr std::size_t csAtomicBufferSizeExponent = 0u; + static constexpr std::size_t csAtomicBufferSize = 1u; + + static void init() { // nothing to do + } + + static void done() { // nothing to do + } + + static void push(tAtomicBufferType_ const) noexcept { // nothing to do + } + + static void scheduleForSend() noexcept { // nothing to do + } + + static bool isScheduledForSent() noexcept { // nothing to do + return false; + } + + static void sendFinished() noexcept { // nothing to do + } + + static std::pair getBuffer() noexcept { // nothing to do + return std::pair(nullptr, 0u); + } + + static void invalidate() noexcept { // nothing to do + } +}; + +} + +#endif diff --git a/src/LogConverterCustomText.h b/src/LogConverterCustomText.h index 7912876..7c9d003 100644 --- a/src/LogConverterCustomText.h +++ b/src/LogConverterCustomText.h @@ -10,9 +10,9 @@ namespace nowtech::log { template class ConverterCustomText final { public: - using tMessage_ = tMessage; - using ConversionResult = char; - using Iterator = char*; + using tMessage_ = tMessage; + using ConversionResult = char; + using Iterator = char*; static constexpr Iterator csNullIterator = nullptr; // Used in SenderVoid to return void begin-end pair. static constexpr bool csSupportFloatingPoint = tMessage::csSupportFloatingPoint; @@ -125,13 +125,9 @@ class ConverterCustomText final { appendSpace(); } - void convert(char const * const aValue, uint8_t const, uint8_t const aFill) noexcept { + void convert(char const * const aValue, uint8_t const, uint8_t const) noexcept { append(aValue); - if(aFill < LogFormat::csFillValueStoreString) { // Antipattern to use the fill for other purposes, but we go for space saving. - appendSpace(); - } - else { // nothing to do - } + appendSpace(); } void convert(bool const aValue, uint8_t const, uint8_t const) noexcept { @@ -139,8 +135,8 @@ class ConverterCustomText final { appendSpace(); } - void convert(std::array const &aValue, uint8_t const, uint8_t const aFill) noexcept { - append(aValue.data()); + void convert(char const * const aValue, uint8_t const aFill) noexcept { // extra function for MessageCompact + append(aValue); if(aFill < LogFormat::csFillValueStoreString) { // Antipattern to use the fill for other purposes, but we go for space saving. appendSpace(); } @@ -148,6 +144,10 @@ class ConverterCustomText final { } } + void convert(std::array const &aValue, uint8_t const, uint8_t const aFill) noexcept { + convert(aValue.data(), aFill); + } + void terminateSequence() noexcept { append(csEndOfLine); } diff --git a/src/LogMessageCompact.h b/src/LogMessageCompact.h index 4e62f27..f0d5f19 100644 --- a/src/LogMessageCompact.h +++ b/src/LogMessageCompact.h @@ -108,7 +108,7 @@ class MessageCompact final : public MessageBase(mData + csOffsetPayload), base, fill); + aConverter.convert(reinterpret_cast(mData + csOffsetPayload), fill); } else { if constexpr(csPayloadSize >= sizeof(int64_t) || sizeof(char*) > sizeof(int32_t)) { diff --git a/src/LogQueueStdBoost.h b/src/LogQueueStdBoost.h index 0480bc8..d4ec328 100644 --- a/src/LogQueueStdBoost.h +++ b/src/LogQueueStdBoost.h @@ -19,17 +19,16 @@ class QueueStdBoost final { private: class FreeRtosQueue final { - boost::lockfree::queue mQueue; - std::atomic mNotified; - std::mutex mMutex; - std::unique_lock mLock; - std::condition_variable mConditionVariable; + boost::lockfree::queue> mQueue; + std::atomic mNotified; + std::mutex mMutex; + std::unique_lock mLock; + std::condition_variable mConditionVariable; public: /// First implementation, we assume we have plenty of memory. FreeRtosQueue() noexcept - : mQueue(tQueueSize) - , mLock(mMutex) { + : mLock(mMutex) { mNotified = false; } @@ -42,7 +41,7 @@ class QueueStdBoost final { void push(tMessage const &aMessage) noexcept { bool success = mQueue.bounded_push(aMessage); if(success) { - mNotified = true; + mNotified = true; // Having no lock_guard here makes tremendous speedup. mConditionVariable.notify_one(); } else { // nothing to do @@ -52,7 +51,10 @@ class QueueStdBoost final { bool pop(tMessage &aMessage, LogTime const mPauseLength) noexcept { bool result; // Safe to call empty because there will be only one consumer. - if(mQueue.empty() && !mConditionVariable.wait_for(mLock, std::chrono::milliseconds(mPauseLength), [this]{return mNotified == true;})) { + if(mQueue.empty() && + (!mConditionVariable.wait_for(mLock, std::chrono::milliseconds(mPauseLength), [this]{return mNotified == true;}) + && !mNotified )) { // This check makes the lock_guard on notifying unnecessary, + // because I don't care if I get in right during waiting or just at the beginning of next check. result = false; } else { @@ -89,4 +91,4 @@ class QueueStdBoost final { } -#endif \ No newline at end of file +#endif diff --git a/src/LogQueueStdBoost.h.etalon b/src/LogQueueStdBoost.h.etalon new file mode 100644 index 0000000..7dbec29 --- /dev/null +++ b/src/LogQueueStdBoost.h.etalon @@ -0,0 +1,95 @@ +#ifndef LOG_QUEUE_STD_BOOST +#define LOG_QUEUE_STD_BOOST + +#include +#include +#include +#include + +namespace nowtech::log { + +template +class QueueStdBoost final { +public: + using tMessage_ = tMessage; + using tAppInterface_ = tAppInterface; + using LogTime = typename tAppInterface::LogTime; + + static constexpr size_t csQueueSize = tQueueSize; + +private: + class FreeRtosQueue final { + boost::lockfree::queue mQueue; + std::atomic mNotified; + std::mutex mMutex; + std::unique_lock mLock; + std::condition_variable mConditionVariable; + + public: + /// First implementation, we assume we have plenty of memory. + FreeRtosQueue() noexcept + : mQueue(tQueueSize) + , mLock(mMutex) { + mNotified = false; + } + + ~FreeRtosQueue() noexcept = default; + + bool empty() const noexcept { + return mQueue.empty(); + } + + void push(tMessage const &aMessage) noexcept { + bool success = mQueue.bounded_push(aMessage); + if(success) { + { + std::lock_guard lock(mMutex); + mNotified = true; + } + mConditionVariable.notify_one(); + } + else { // nothing to do + } + } + + bool pop(tMessage &aMessage, LogTime const mPauseLength) noexcept { + bool result; + // Safe to call empty because there will be only one consumer. + if(mQueue.empty() && !mConditionVariable.wait_for(mLock, std::chrono::milliseconds(mPauseLength), [this]{return mNotified == true;})) { + result = false; + } + else { + mNotified = false; + result = mQueue.pop(aMessage); + } + return result; + } + }; + + inline static FreeRtosQueue sQueue; + + QueueStdBoost() = delete; + +public: + static void init() { // nothing to do + } + + static void done() { // nothing to do + } + + static bool empty() noexcept { + return sQueue.empty(); + } + + static void push(tMessage const &aMessage) noexcept { + sQueue.push(aMessage); + } + + static bool pop(tMessage &aMessage, LogTime const aPauseLength) noexcept { + return sQueue.pop(aMessage, aPauseLength); + } +}; + +} + +#endif \ No newline at end of file diff --git a/src/LogQueueStdCircular.h b/src/LogQueueStdCircular.h new file mode 100644 index 0000000..7827485 --- /dev/null +++ b/src/LogQueueStdCircular.h @@ -0,0 +1,107 @@ +#ifndef LOG_QUEUE_STD_BOOST +#define LOG_QUEUE_STD_BOOST + +#include +#include +#include +#include + +namespace nowtech::log { + +template +class QueueStdCircular final { +public: + using tMessage_ = tMessage; + using tAppInterface_ = tAppInterface; + using LogTime = typename tAppInterface::LogTime; + + static constexpr size_t csQueueSize = tQueueSize; + +private: + class FreeRtosQueue final { + std::array mQueue; + size_t mNextWrite; + size_t mNextRead; + std::atomic mOccupied; + std::atomic mNotified; + std::mutex mMutexPush; + std::mutex mMutexCv; + std::unique_lock mLockCv; + std::condition_variable mConditionVariable; + + public: + /// First implementation, we assume we have plenty of memory. + FreeRtosQueue() noexcept + : mNextWrite(0u) + , mNextRead(0u) + , mOccupied(0u) + , mLockCv(mMutexCv) { + mNotified = false; + } + + ~FreeRtosQueue() noexcept = default; + + bool empty() const noexcept { + return mOccupied == 0u; + } + + void push(tMessage const &aMessage) noexcept { + std::lock_guard lock(mMutexPush); + if(mOccupied < tQueueSize) { + mQueue[mNextWrite] = aMessage; + mNextWrite = (mNextWrite + 1u) % tQueueSize; + ++mOccupied; + mNotified = true; // Having no lock_guard here makes tremendous speedup. + mConditionVariable.notify_one(); + } + else { // nothing to do + } + } + + bool pop(tMessage &aMessage, LogTime const mPauseLength) noexcept { + bool result; + // Safe to call empty because there will be only one consumer. + if(mOccupied == 0u && + (!mConditionVariable.wait_for(mLockCv, std::chrono::milliseconds(mPauseLength), [this]{return mNotified == true;}) + && !mNotified )) { // This check makes the lock_guard on notifying unnecessary, + // because I don't care if I get in right during waiting or just at the beginning of next check. + result = false; + } + else { + mNotified = false; + aMessage = mQueue[mNextRead]; + mNextRead = (mNextRead + 1u) % tQueueSize; + --mOccupied; + result = true; + } + return result; + } + }; + + inline static FreeRtosQueue sQueue; + + QueueStdCircular() = delete; + +public: + static void init() { // nothing to do + } + + static void done() { // nothing to do + } + + static bool empty() noexcept { + return sQueue.empty(); + } + + static void push(tMessage const &aMessage) noexcept { + sQueue.push(aMessage); + } + + static bool pop(tMessage &aMessage, LogTime const aPauseLength) noexcept { + return sQueue.pop(aMessage, aPauseLength); + } +}; + +} + +#endif diff --git a/src/LogQueueVoid.h b/src/LogQueueVoid.h index 0883b93..776581f 100644 --- a/src/LogQueueVoid.h +++ b/src/LogQueueVoid.h @@ -5,13 +5,15 @@ namespace nowtech::log { -template +template class QueueVoid final { public: using tMessage_ = tMessage; using tAppInterface_ = tAppInterface; using LogTime = typename tAppInterface::LogTime; + static constexpr size_t csQueueSize = 0u; + private: QueueVoid() = delete; diff --git a/src/LogSenderRos2.h b/src/LogSenderRos2.h new file mode 100644 index 0000000..de31c81 --- /dev/null +++ b/src/LogSenderRos2.h @@ -0,0 +1,69 @@ +#ifndef NOWTECH_LOG_SENDER_ROS2 +#define NOWTECH_LOG_SENDER_ROS2 + +#include "rcutils/logging.h" +#include "Log.h" +#include +#include + +namespace nowtech::log { + +template +class SenderRos2 final { +public: + using tAppInterface_ = tAppInterface; + using tConverter_ = tConverter; + using ConversionResult = typename tConverter::ConversionResult; + using Iterator = typename tConverter::Iterator; + + static constexpr bool csVoid = false; + +private: + inline static ConversionResult *sTransmitBuffer; + inline static Iterator sBegin; + inline static Iterator sEnd; + inline static constexpr char csDummyName[] = ""; + inline static constexpr char csDummyFunctionName[] = ""; + inline static constexpr char csDummyFilename[] = ""; + inline static constexpr char csFormat[] = "%s"; + inline static constexpr rcutils_log_location_t csDummyLocation = { csDummyFunctionName, csDummyFilename, tSimulatedRos2Loglevel }; + static constexpr char csNewline = '\n'; + + SenderRos2() = delete; + +public: + static void init() { + sTransmitBuffer = tAppInterface::template _newArray(tTransmitBufferSize); + sBegin = sTransmitBuffer; + sEnd = sTransmitBuffer + tTransmitBufferSize; + } + + static void done() noexcept { + tAppInterface::template _deleteArray(sTransmitBuffer); + } + + static void send(char const * const aBegin, char const * const aEnd) { + try { + std::string buffer{aBegin, static_cast(aEnd - aBegin)}; + if(aBegin < aEnd && aEnd[-1] == csNewline) { + buffer.pop_back(); + } + else { // nothing to do + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" + rcutils_log(&csDummyLocation, tSimulatedRos2Loglevel, csDummyName, csFormat, buffer.c_str()); +#pragma GCC diagnostic pop + } catch(...) { + tAppInterface::error(Exception::cSenderError); + } + } + + static auto getBuffer() { + return std::pair(sBegin, sEnd); + } +}; + +} + +#endif diff --git a/test/test-sizes-atomic-stdostream-boost.cpp b/test/test-sizes-atomic-stdostream-boost.cpp new file mode 100644 index 0000000..e1886ba --- /dev/null +++ b/test/test-sizes-atomic-stdostream-boost.cpp @@ -0,0 +1,118 @@ +// +// Created by balazs on 2020. 12. 03.. +// + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueVoid.h" +#include "LogQueueStdBoost.h" +#include "LogMessageCompact.h" +#include "Log.h" +#include +#include + +#include + +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -Wl,-Map,test-sizes.map -demangle test/test-sizes-stdthreadostream.cpp -lpthread -o test-sizes +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel test/test-sizes.cpp -o test-sizes.s +// llvm-size-10 test-sizes + +constexpr size_t cgThreadCount = 0; + +constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 123u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + +constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +/* + text data bss dec hex filename + 12803 1053 520 14376 3828 test-sizes +*/ + +/* No log call present at all, not even cout.write, only thread creation: + text data bss dec hex filename + 3267 820 4 4091 ffb test-sizes +*/ + +/*constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderVoid; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +text data bss dec hex filename + 3267 820 4 4091 ffb test-sizes + */ + +/*constexpr size_t cgDirectBufferSize = 0u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 23734 1277 920 25931 654b test-sizes + */ + +int32_t gInt32 = 4; + +void todo() noexcept { + gInt32 = getpid(); +} + +int main() { + std::thread thread(todo); + thread.join(); + + nowtech::log::LogFormatConfig logConfig; + LogSender::init(&std::cout); +// LogSender::init(); + Log::init(logConfig); + Log::registerCurrentTask("main"); + + Log::pushAtomic(gInt32); + Log::sendAtomicBuffer(); + + Log::unregisterCurrentTask(); + Log::done(); + + return gInt32; +} + diff --git a/test/test-sizes-atomic-stdostream-circular.cpp b/test/test-sizes-atomic-stdostream-circular.cpp new file mode 100644 index 0000000..359a121 --- /dev/null +++ b/test/test-sizes-atomic-stdostream-circular.cpp @@ -0,0 +1,118 @@ +// +// Created by balazs on 2020. 12. 03.. +// + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueVoid.h" +#include "LogQueueStdCircular.h" +#include "LogMessageCompact.h" +#include "Log.h" +#include +#include + +#include + +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -Wl,-Map,test-sizes.map -demangle test/test-sizes-stdthreadostream.cpp -lpthread -o test-sizes +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel test/test-sizes.cpp -o test-sizes.s +// llvm-size-10 test-sizes + +constexpr size_t cgThreadCount = 0; + +constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 123u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + +constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +/* + text data bss dec hex filename + 12803 1053 520 14376 3828 test-sizes +*/ + +/* No log call present at all, not even cout.write, only thread creation: + text data bss dec hex filename + 3267 820 4 4091 ffb test-sizes +*/ + +/*constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderVoid; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +text data bss dec hex filename + 3267 820 4 4091 ffb test-sizes + */ + +/*constexpr size_t cgDirectBufferSize = 0u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdCircular; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 23734 1277 920 25931 654b test-sizes + */ + +int32_t gInt32 = 4; + +void todo() noexcept { + gInt32 = getpid(); +} + +int main() { + std::thread thread(todo); + thread.join(); + + nowtech::log::LogFormatConfig logConfig; + LogSender::init(&std::cout); +// LogSender::init(); + Log::init(logConfig); + Log::registerCurrentTask("main"); + + Log::pushAtomic(gInt32); + Log::sendAtomicBuffer(); + + Log::unregisterCurrentTask(); + Log::done(); + + return gInt32; +} + diff --git a/test/test-sizes-freertosminimal-float.cpp b/test/test-sizes-freertosminimal-float.cpp index 646e288..26d2572 100644 --- a/test/test-sizes-freertosminimal-float.cpp +++ b/test/test-sizes-freertosminimal-float.cpp @@ -40,32 +40,44 @@ namespace nowtech::LogTopics { } constexpr nowtech::log::TaskId cgMaxTaskCount = 1u; +constexpr bool cgAllowRegistrationLog = true; constexpr bool cgLogFromIsr = false; constexpr size_t cgTaskShutdownSleepPeriod = 100u; constexpr bool cgArchitecture64 = false; constexpr uint8_t cgAppendStackBufferSize = 100u; constexpr bool cgAppendBasePrefix = true; constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; constexpr size_t cgTransmitBufferSize = 123u; -constexpr size_t cgPayloadSize = 6u; // This disables 64-bit arithmetics. +constexpr size_t cgPayloadSize = 6u; constexpr bool cgSupportFloatingPoint = true; constexpr size_t cgQueueSize = 111u; constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; -constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cId; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + constexpr uint32_t cgLogTaskStackSize = 256u; constexpr uint32_t cgLogTaskPriority = tskIDLE_PRIORITY + 1u; +using LogAppInterface = nowtech::log::AppInterfaceFreeRtosMinimal; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; -/*constexpr size_t cgDirectBufferSize = 43u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; +//using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; + +constexpr size_t cgDirectBufferSize = 43u; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; - text data bss dec hex filename - 22260 132 19068 41460 a1f4 cpp-logger-embedded.elf +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStmHalMinimal; +using LogQueue = nowtech::log::QueueVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +/* text data bss dec hex filename +noat 22180 132 19072 41384 a1a8 cpp-logger-embedded.elf +atom 22508 132 19096 41736 a308 cpp-logger-embedded.elf +new 22508 132 19096 41736 a308 cpp-logger-embedded.elf */ /* No log call present at all, not even cout.write, only thread creation: @@ -73,43 +85,41 @@ using Log = nowtech::log::Log; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderVoid; -using LogQueue = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderVoid; +using LogQueue = nowtech::log::QueueVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; text data bss dec hex filename - 9108 24 19016 28148 6df4 cpp-logger-embedded.elf +noat 9108 24 19016 28148 6df4 cpp-logger-embedded.elf +atom 9108 24 19016 28148 6df4 cpp-logger-embedded.elf */ /*constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageVariant; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueFreeRtos; -using Log = nowtech::log::Log; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStmHalMinimal; +using LogQueue = nowtech::log::QueueFreeRtos; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; text data bss dec hex filename - 24412 136 19092 43640 aa78 cpp-logger-embedded.elf +noat 24380 132 19104 43616 aa60 cpp-logger-embedded.elf +atom 25108 132 19128 44368 ad50 cpp-logger-embedded.elf */ -constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; +/*constexpr size_t cgDirectBufferSize = 0u; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueFreeRtos; -using Log = nowtech::log::Log; -/* text data bss dec hex filename - 24132 136 19092 43360 a960 cpp-logger-embedded.elf +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStmHalMinimal; +using LogQueue = nowtech::log::QueueFreeRtos; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename +noat 24092 132 19104 43328 a940 cpp-logger-embedded.elf +atom 24820 132 19128 44080 ac30 cpp-logger-embedded.elf */ void step() { @@ -130,8 +140,7 @@ extern "C" void myDefaultTask() { else { // nothing to do } HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, real > 0.0 ? GPIO_PIN_SET : GPIO_PIN_RESET); - nowtech::log::LogConfig logConfig; - logConfig.allowRegistrationLog = true; + nowtech::log::LogFormatConfig logConfig; LogSender::init(&huart1); Log::init(logConfig, cgLogTaskStackSize, cgLogTaskPriority); @@ -144,6 +153,9 @@ extern "C" void myDefaultTask() { Log::n() << real << cgString << Log::end; Log::n() << real << LC::St << cgString << Log::end; + Log::pushAtomic(cgInt32); + Log::sendAtomicBuffer(); + while(true) { step(); } diff --git a/test/test-sizes-freertosminimal-nofloat.cpp b/test/test-sizes-freertosminimal-nofloat.cpp index dff2692..f2ce775 100644 --- a/test/test-sizes-freertosminimal-nofloat.cpp +++ b/test/test-sizes-freertosminimal-nofloat.cpp @@ -40,32 +40,44 @@ namespace nowtech::LogTopics { } constexpr nowtech::log::TaskId cgMaxTaskCount = 1u; +constexpr bool cgAllowRegistrationLog = true; constexpr bool cgLogFromIsr = false; constexpr size_t cgTaskShutdownSleepPeriod = 100u; constexpr bool cgArchitecture64 = false; constexpr uint8_t cgAppendStackBufferSize = 100u; constexpr bool cgAppendBasePrefix = true; constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; constexpr size_t cgTransmitBufferSize = 123u; -constexpr size_t cgPayloadSize = 6u; // This disables 64-bit integer arithmetic. +constexpr size_t cgPayloadSize = 6u; constexpr bool cgSupportFloatingPoint = false; constexpr size_t cgQueueSize = 111u; constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; -constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cId; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + constexpr uint32_t cgLogTaskStackSize = 256u; constexpr uint32_t cgLogTaskPriority = tskIDLE_PRIORITY + 1u; +using LogAppInterface = nowtech::log::AppInterfaceFreeRtosMinimal; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; -/*constexpr size_t cgDirectBufferSize = 43u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; +//using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; + +constexpr size_t cgDirectBufferSize = 43u; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; - text data bss dec hex filename - 11772 32 19072 30876 789c cpp-logger-embedded.elf +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStmHalMinimal; +using LogQueue = nowtech::log::QueueVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +/* text data bss dec hex filename +noat 11680 32 19076 30788 7844 cpp-logger-embedded.elf +atom 12008 32 19092 31132 799c cpp-logger-embedded.elf +onlyat 11172 28 19088 30288 7650 cpp-logger-embedded.elf */ /* No log call present at all, not even cout.write, only thread creation: @@ -74,42 +86,40 @@ using Log = nowtech::log::Log; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderVoid; -using LogQueue = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderVoid; +using LogQueue = nowtech::log::QueueVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; text data bss dec hex filename - 7468 24 19016 26508 678c cpp-logger-embedded.elf +noat 7468 24 19016 26508 678c cpp-logger-embedded.elf +atom 7468 24 19016 26508 678c cpp-logger-embedded.elf */ /*constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageVariant; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueFreeRtos; -using Log = nowtech::log::Log; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStmHalMinimal; +using LogQueue = nowtech::log::QueueFreeRtos; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; text data bss dec hex filename - 13908 36 19096 33040 8110 cpp-logger-embedded.elf +noat 13880 32 19100 33012 80f4 cpp-logger-embedded.elf +atom 14608 32 19124 33764 83e4 cpp-logger-embedded.elf */ -constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceFreeRtosMinimal = nowtech::log::AppInterfaceFreeRtosMinimal; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceFreeRtosMinimal::LogTime cgRefreshPeriod = 444; +/*constexpr size_t cgDirectBufferSize = 0u; using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStmHalMinimal; -using LogQueue = nowtech::log::QueueFreeRtos; -using Log = nowtech::log::Log; -/* text data bss dec hex filename - 13660 36 19096 32792 8018 cpp-logger-embedded.elf +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStmHalMinimal; +using LogQueue = nowtech::log::QueueFreeRtos; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename +noat 13608 32 19100 32740 7fe4 cpp-logger-embedded.elf +atom 14336 32 19124 33492 82d4 cpp-logger-embedded.elf +onlyat 13676 28 19120 32824 8038 cpp-logger-embedded.elf */ void step() { @@ -122,19 +132,21 @@ int32_t cgInt32; char constexpr cgString[] = "string\n"; extern "C" void myDefaultTask() { - nowtech::log::LogConfig logConfig; - logConfig.allowRegistrationLog = true; + nowtech::log::LogFormatConfig logConfig; LogSender::init(&huart1); Log::init(logConfig, cgLogTaskStackSize, cgLogTaskPriority); - Log::registerTopic(nowtech::LogTopics::system, "system"); - Log::registerTopic(nowtech::LogTopics::surplus, "surplus"); +// Log::registerTopic(nowtech::LogTopics::system, "system"); +// Log::registerTopic(nowtech::LogTopics::surplus, "surplus"); Log::registerCurrentTask("main"); - Log::i(nowtech::LogTopics::surplus) << cgUint32 << Log::end; + /*Log::i(nowtech::LogTopics::surplus) << cgUint32 << Log::end; Log::i(nowtech::LogTopics::system) << cgInt32 << Log::end; Log::n() << cgString << Log::end; - Log::n() << LC::St << cgString << Log::end; + Log::n() << LC::St << cgString << Log::end;*/ + + Log::pushAtomic(cgInt32); + Log::sendAtomicBuffer(); while(true) { step(); diff --git a/test/test-sizes-stdthreadostream-boost.cpp b/test/test-sizes-stdthreadostream-boost.cpp new file mode 100644 index 0000000..454e555 --- /dev/null +++ b/test/test-sizes-stdthreadostream-boost.cpp @@ -0,0 +1,149 @@ +// +// Created by balazs on 2020. 12. 03.. +// + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueStdBoost.h" +#include "LogQueueVoid.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include +#include + +#include + +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -Wl,-Map,test-sizes.map -demangle test/test-sizes-stdthreadostream.cpp -lpthread -o test-sizes +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel test/test-sizes.cpp -o test-sizes.s +// llvm-size-10 test-sizes + +constexpr size_t cgThreadCount = 1; + +namespace nowtech::LogTopics { +nowtech::log::TopicInstance system; +nowtech::log::TopicInstance surplus; +} + +constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 123u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + +/*constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 15118 1101 496 16715 414b test-sizes +*/ + +/* No log call present at all, not even cout.write, only thread creation: + text data bss dec hex filename + 3283 828 4 4115 1013 test-sizes +*/ + +/*constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderVoid; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 3283 828 4 4115 1013 test-sizes + */ + +/* +constexpr size_t cgDirectBufferSize = 0u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 26086 1285 896 28267 6e6b test-sizes + */ + + +constexpr size_t cgDirectBufferSize = 0u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +/* text data bss dec hex filename + 24458 1285 896 26639 680f test-sizes + */ + +uint32_t cgUint32 = 3; +int32_t cgInt32 = 4; +double constexpr cgReal = 3.3; +char constexpr cgString[] = "string\n"; + +void todo() noexcept { + cgUint32 = getpid(); +} + +int main() { + cgInt32 = getpid(); + std::thread thread(todo); + thread.join(); + + nowtech::log::LogFormatConfig logConfig; + LogSender::init(&std::cout); +// LogSender::init(); + Log::init(logConfig); + + Log::registerTopic(nowtech::LogTopics::system, "system"); + Log::registerTopic(nowtech::LogTopics::surplus, "surplus"); + Log::registerCurrentTask("main"); + + Log::i(nowtech::LogTopics::surplus) << cgUint32 << Log::end; + Log::i(nowtech::LogTopics::system) << cgInt32 << Log::end; + Log::n() << cgReal << cgString << Log::end; + Log::n() << cgReal << LC::St << cgString << Log::end; + + Log::unregisterCurrentTask(); + Log::done(); + + return cgInt32 + cgUint32; +} + diff --git a/test/test-sizes-stdthreadostream-circular.cpp b/test/test-sizes-stdthreadostream-circular.cpp new file mode 100644 index 0000000..cbac6fd --- /dev/null +++ b/test/test-sizes-stdthreadostream-circular.cpp @@ -0,0 +1,149 @@ +// +// Created by balazs on 2020. 12. 03.. +// + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogSenderVoid.h" +#include "LogQueueStdCircular.h" +#include "LogQueueVoid.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" +#include +#include + +#include + +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -Wl,-Map,test-sizes.map -demangle test/test-sizes-stdthreadostream.cpp -lpthread -o test-sizes +// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel test/test-sizes.cpp -o test-sizes.s +// llvm-size-10 test-sizes + +constexpr size_t cgThreadCount = 1; + +namespace nowtech::LogTopics { +nowtech::log::TopicInstance system; +nowtech::log::TopicInstance surplus; +} + +constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 123u; +constexpr size_t cgPayloadSize = 8u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + +/*constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 15118 1101 496 16715 414b test-sizes +*/ + +/* No log call present at all, not even cout.write, only thread creation: + text data bss dec hex filename + 3283 828 4 4115 1013 test-sizes +*/ + +/*constexpr size_t cgDirectBufferSize = 43u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderVoid; +using LogQueue = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 3283 828 4 4115 1013 test-sizes + */ + +/* +constexpr size_t cgDirectBufferSize = 0u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageVariant; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdCircular; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + text data bss dec hex filename + 26086 1285 896 28267 6e6b test-sizes + */ + + +constexpr size_t cgDirectBufferSize = 0u; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverter = nowtech::log::ConverterCustomText; +using LogSender = nowtech::log::SenderStdOstream; +using LogQueue = nowtech::log::QueueStdCircular; +using LogAtomicBuffer = nowtech::log::AtomicBufferVoid; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; +/* text data bss dec hex filename + 24458 1285 896 26639 680f test-sizes + */ + +uint32_t cgUint32 = 3; +int32_t cgInt32 = 4; +double constexpr cgReal = 3.3; +char constexpr cgString[] = "string\n"; + +void todo() noexcept { + cgUint32 = getpid(); +} + +int main() { + cgInt32 = getpid(); + std::thread thread(todo); + thread.join(); + + nowtech::log::LogFormatConfig logConfig; + LogSender::init(&std::cout); +// LogSender::init(); + Log::init(logConfig); + + Log::registerTopic(nowtech::LogTopics::system, "system"); + Log::registerTopic(nowtech::LogTopics::surplus, "surplus"); + Log::registerCurrentTask("main"); + + Log::i(nowtech::LogTopics::surplus) << cgUint32 << Log::end; + Log::i(nowtech::LogTopics::system) << cgInt32 << Log::end; + Log::n() << cgReal << cgString << Log::end; + Log::n() << cgReal << LC::St << cgString << Log::end; + + Log::unregisterCurrentTask(); + Log::done(); + + return cgInt32 + cgUint32; +} + diff --git a/test/test-sizes-stdthreadostream.cpp b/test/test-sizes-stdthreadostream.cpp deleted file mode 100644 index f3cbab8..0000000 --- a/test/test-sizes-stdthreadostream.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// -// Created by balazs on 2020. 12. 03.. -// - -#include "LogAppInterfaceStd.h" -#include "LogConverterCustomText.h" -#include "LogSenderStdOstream.h" -#include "LogSenderVoid.h" -#include "LogQueueStdBoost.h" -#include "LogQueueVoid.h" -#include "LogMessageCompact.h" -#include "LogMessageVariant.h" -#include "Log.h" -#include -#include - -#include - -// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -Wl,-Map,test-sizes.map -demangle test/test-sizes.cpp -lpthread -o test-sizes -// clang++ -std=c++20 -Isrc -Icpp-memory-manager -Os -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel test/test-sizes.cpp -o test-sizes.s -// llvm-size-10 test-sizes - -constexpr size_t cgThreadCount = 1; - -namespace nowtech::LogTopics { -nowtech::log::TopicInstance system; -nowtech::log::TopicInstance surplus; -} - -constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; -constexpr bool cgLogFromIsr = false; -constexpr size_t cgTaskShutdownSleepPeriod = 100u; -constexpr bool cgArchitecture64 = true; -constexpr uint8_t cgAppendStackBufferSize = 100u; -constexpr bool cgAppendBasePrefix = true; -constexpr bool cgAlignSigned = false; -constexpr size_t cgTransmitBufferSize = 123u; -constexpr size_t cgPayloadSize = 8u; -constexpr bool cgSupportFloatingPoint = true; -constexpr size_t cgQueueSize = 444u; -constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; -constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; - - -/*constexpr size_t cgDirectBufferSize = 43u; -using LogAppInterfaceStd = nowtech::log::AppInterfaceStd; -constexpr typename LogAppInterfaceStd::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceStd::LogTime cgRefreshPeriod = 444; -using LogMessage = nowtech::log::MessageVariant; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStdOstream; -using LogQueue = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; - text data bss dec hex filename - 15182 1101 496 16779 418b test-sizes -*/ - -/* No log call present at all, not even cout.write, only thread creation: - text data bss dec hex filename - 3283 828 4 4115 1013 test-sizes -*/ - - -/*constexpr size_t cgDirectBufferSize = 43u; -using LogAppInterfaceStd = nowtech::log::AppInterfaceStd; -constexpr typename LogAppInterfaceStd::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceStd::LogTime cgRefreshPeriod = 444; -using LogMessage = nowtech::log::MessageVariant; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderVoid; -using LogQueue = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; - text data bss dec hex filename - 3283 828 4 4115 1013 test-sizes - */ - -/*constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceStd = nowtech::log::AppInterfaceStd; -constexpr typename LogAppInterfaceStd::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceStd::LogTime cgRefreshPeriod = 444; -using LogMessage = nowtech::log::MessageVariant; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStdOstream; -using LogQueue = nowtech::log::QueueStdBoost; -using Log = nowtech::log::Log; - text data bss dec hex filename - 25766 1285 896 27947 6d2b test-sizes - */ - - -constexpr size_t cgDirectBufferSize = 0u; -using LogAppInterfaceStd = nowtech::log::AppInterfaceStd; -constexpr typename LogAppInterfaceStd::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceStd::LogTime cgRefreshPeriod = 444; -using LogMessage = nowtech::log::MessageCompact; -using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSender = nowtech::log::SenderStdOstream; -using LogQueue = nowtech::log::QueueStdBoost; -using Log = nowtech::log::Log; -/* text data bss dec hex filename - 24134 1285 896 26315 66cb test-sizes - */ - -uint32_t cgUint32 = 3; -int32_t cgInt32 = 4; -double constexpr cgReal = 3.3; -char constexpr cgString[] = "string\n"; - -void todo() noexcept { - cgUint32 = getpid(); -} - -int main() { - cgInt32 = getpid(); - std::thread thread(todo); - thread.join(); - - nowtech::log::LogConfig logConfig; - logConfig.allowRegistrationLog = true; - LogSender::init(&std::cout); -// LogSender::init(); - Log::init(logConfig); - - Log::registerTopic(nowtech::LogTopics::system, "system"); - Log::registerTopic(nowtech::LogTopics::surplus, "surplus"); - Log::registerCurrentTask("main"); - - Log::i(nowtech::LogTopics::surplus) << cgUint32 << Log::end; - Log::i(nowtech::LogTopics::system) << cgInt32 << Log::end; - Log::n() << cgReal << cgString << Log::end; - Log::n() << cgReal << LC::St << cgString << Log::end; - - Log::unregisterCurrentTask(); - Log::done(); - - return cgInt32 + cgUint32; -} - diff --git a/test/test-stdostream.cpp b/test/test-stdostream.cpp index 55cd613..a875bc0 100644 --- a/test/test-stdostream.cpp +++ b/test/test-stdostream.cpp @@ -54,44 +54,61 @@ namespace nowtech::LogTopics { } constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; constexpr bool cgLogFromIsr = false; constexpr size_t cgTaskShutdownSleepPeriod = 100u; constexpr bool cgArchitecture64 = true; constexpr uint8_t cgAppendStackBufferSize = 100u; constexpr bool cgAppendBasePrefix = true; constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; constexpr size_t cgTransmitBufferSize = 123u; -constexpr size_t cgPayloadSize = 8u; +constexpr size_t cgPayloadSize = 14u; constexpr bool cgSupportFloatingPoint = true; constexpr size_t cgQueueSize = 444u; constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; constexpr size_t cgDirectBufferSize = 43u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; -using LogAppInterfaceStd = nowtech::log::AppInterfaceStd; -constexpr typename LogAppInterfaceStd::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceStd::LogTime cgRefreshPeriod = 444; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageVariant; using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSenderStdOstream = nowtech::log::SenderStdOstream; -using LogQueueVoid = nowtech::log::QueueVoid; -using Log = nowtech::log::Log; - +using LogSenderStdOstream = nowtech::log::SenderStdOstream; +using LogQueueVoid = nowtech::log::QueueVoid; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + void delayedLog(size_t n) { Log::registerCurrentTask(cgThreadNames[n]); Log::i(nowtech::LogTopics::system) << n << ": " << 0 << Log::end; - for(int64_t i = 1; i < 13; ++i) { + for(int64_t i = 1; i < 3; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(1 << i)); Log::i(nowtech::LogTopics::system) << n << ". thread delay logarithm: " << LC::X1 << i << Log::end; } Log::unregisterCurrentTask(); } +std::atomic gCounter; +constexpr int32_t cgAtomicCount = 100; + +void atomicLog(size_t n) { + Log::registerCurrentTask(cgThreadNames[n]); + for(int32_t i = 0; i < cgAtomicCount; ++i) { + Log::pushAtomic(gCounter++); + } + Log::unregisterCurrentTask(); +} + int main() { std::thread threads[cgThreadCount]; - nowtech::log::LogConfig logConfig; - logConfig.allowRegistrationLog = true; + nowtech::log::LogFormatConfig logConfig; LogSenderStdOstream::init(&std::cout); Log::init(logConfig); @@ -146,10 +163,18 @@ int main() { for(size_t i = 0; i < cgThreadCount; ++i) { threads[i].join(); } - Log::unregisterCurrentTask(); - Log::done(); + gCounter = 0; + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i] = std::thread(atomicLog, i); + } + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i].join(); + } + Log::sendAtomicBuffer(); + Log::unregisterCurrentTask(); + Log::done(); return 0; } diff --git a/test/test-stdthreadostream.cpp b/test/test-stdthreadostream-boost.cpp similarity index 74% rename from test/test-stdthreadostream.cpp rename to test/test-stdthreadostream-boost.cpp index 99527f0..c60b3e9 100644 --- a/test/test-stdthreadostream.cpp +++ b/test/test-stdthreadostream-boost.cpp @@ -35,7 +35,7 @@ // clang++ -std=c++20 -Isrc -Icpp-memory-manager test/test-stdthreadostream.cpp -lpthread -o test-stdthreadostream -constexpr size_t cgThreadCount = 2; +constexpr size_t cgThreadCount = 4; char cgThreadNames[10][10] = { "thread_0", @@ -56,12 +56,16 @@ namespace nowtech::LogTopics { } constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; constexpr bool cgLogFromIsr = false; constexpr size_t cgTaskShutdownSleepPeriod = 100u; constexpr bool cgArchitecture64 = true; constexpr uint8_t cgAppendStackBufferSize = 100u; constexpr bool cgAppendBasePrefix = true; constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; constexpr size_t cgTransmitBufferSize = 123u; constexpr size_t cgPayloadSize = 14u; constexpr bool cgSupportFloatingPoint = true; @@ -69,33 +73,46 @@ constexpr size_t cgQueueSize = 444u; constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; constexpr size_t cgDirectBufferSize = 0u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; -using LogAppInterfaceStd = nowtech::log::AppInterfaceStd; -constexpr typename LogAppInterfaceStd::LogTime cgTimeout = 123u; -constexpr typename LogAppInterfaceStd::LogTime cgRefreshPeriod = 444; +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; using LogMessage = nowtech::log::MessageCompact; using LogConverterCustomText = nowtech::log::ConverterCustomText; -using LogSenderStdOstream = nowtech::log::SenderStdOstream; -using LogQueueStdBoost = nowtech::log::QueueStdBoost; -using Log = nowtech::log::Log; - +using LogSenderStdOstream = nowtech::log::SenderStdOstream; +using LogQueueStdBoost = nowtech::log::QueueStdBoost; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + void delayedLog(size_t n) { Log::registerCurrentTask(cgThreadNames[n]); Log::i(nowtech::LogTopics::system) << static_cast(n) << ": " << static_cast(0) << Log::end; - for(int64_t i = 1; i < 13; ++i) { + for(int64_t i = 1; i < 7; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(1 << i)); Log::i(nowtech::LogTopics::system) << static_cast(n) << "thread delay logarithm: " << LC::X1 << i << Log::end; } Log::unregisterCurrentTask(); } +std::atomic gCounter; +constexpr int32_t cgAtomicCount = 100; + +void atomicLog(size_t n) { + Log::registerCurrentTask(cgThreadNames[n]); + for(int32_t i = 0; i < cgAtomicCount; ++i) { + Log::pushAtomic(gCounter++); + } + Log::unregisterCurrentTask(); +} + char gTextToCopy[] = "This_text_will_be_copied_in_messages."; int main() { std::thread threads[cgThreadCount + 1u]; // let there be zero threads - nowtech::log::LogConfig logConfig; - logConfig.allowRegistrationLog = true; + nowtech::log::LogFormatConfig logConfig; LogSenderStdOstream::init(&std::cout); Log::init(logConfig); @@ -148,6 +165,17 @@ int main() { Log::i() << "bool:" << true << Log::end; Log::i() << "bool:" << false << Log::end; + Log::i() << "fatal" << Log::end; + Log::i() << "error" << Log::end; + Log::i() << "warning" << Log::end; + Log::i() << "info" << Log::end; + Log::i() << "debug" << Log::end; + Log::n() << "fatal" << Log::end; + Log::n() << "error" << Log::end; + Log::n() << "warning" << Log::end; + Log::n() << "info" << Log::end; + Log::n() << "debug" << Log::end; + for(size_t i = 0; i < cgThreadCount; ++i) { threads[i] = std::thread(delayedLog, i); } @@ -155,6 +183,16 @@ int main() { threads[i].join(); } + gCounter = 0; + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i] = std::thread(atomicLog, i); + } + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i].join(); + } + Log::sendAtomicBuffer(); + Log::n() << Log::end; + Log::unregisterCurrentTask(); Log::done(); return 0; diff --git a/test/test-stdthreadostream-circular.cpp b/test/test-stdthreadostream-circular.cpp new file mode 100644 index 0000000..fe297dc --- /dev/null +++ b/test/test-stdthreadostream-circular.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2018 Now Technologies Zrt. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH + * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "LogAppInterfaceStd.h" +#include "LogConverterCustomText.h" +#include "LogSenderStdOstream.h" +#include "LogQueueStdCircular.h" +#include "LogMessageCompact.h" +#include "LogMessageVariant.h" +#include "Log.h" + +#include +#include +#include + +// clang++ -std=c++20 -Isrc -Icpp-memory-manager test/test-stdthreadostream.cpp -lpthread -o test-stdthreadostream + +constexpr size_t cgThreadCount = 4; + +char cgThreadNames[10][10] = { + "thread_0", + "thread_1", + "thread_2", + "thread_3", + "thread_4", + "thread_5", + "thread_6", + "thread_7", + "thread_8", + "thread_9" +}; + +namespace nowtech::LogTopics { + nowtech::log::TopicInstance system; + nowtech::log::TopicInstance surplus; +} + +constexpr nowtech::log::TaskId cgMaxTaskCount = cgThreadCount + 1; +constexpr bool cgAllowRegistrationLog = true; +constexpr bool cgLogFromIsr = false; +constexpr size_t cgTaskShutdownSleepPeriod = 100u; +constexpr bool cgArchitecture64 = true; +constexpr uint8_t cgAppendStackBufferSize = 100u; +constexpr bool cgAppendBasePrefix = true; +constexpr bool cgAlignSigned = false; +using AtomicBufferType = int32_t; +constexpr size_t cgAtomicBufferExponent = 14u; +constexpr AtomicBufferType cgAtomicBufferInvalidValue = 1234546789; +constexpr size_t cgTransmitBufferSize = 123u; +constexpr size_t cgPayloadSize = 14u; +constexpr bool cgSupportFloatingPoint = true; +constexpr size_t cgQueueSize = 444u; +constexpr nowtech::log::LogTopic cgMaxTopicCount = 2; +constexpr nowtech::log::TaskRepresentation cgTaskRepresentation = nowtech::log::TaskRepresentation::cName; +constexpr size_t cgDirectBufferSize = 0u; +constexpr nowtech::log::ErrorLevel cgErrorLevel = nowtech::log::ErrorLevel::Error; + +using LogAppInterface = nowtech::log::AppInterfaceStd; +constexpr typename LogAppInterface::LogTime cgTimeout = 123u; +constexpr typename LogAppInterface::LogTime cgRefreshPeriod = 444; +using LogMessage = nowtech::log::MessageCompact; +using LogConverterCustomText = nowtech::log::ConverterCustomText; +using LogSenderStdOstream = nowtech::log::SenderStdOstream; +using LogQueueStdCircular = nowtech::log::QueueStdCircular; +using LogAtomicBuffer = nowtech::log::AtomicBufferOperational; +using LogConfig = nowtech::log::Config; +using Log = nowtech::log::Log; + +void delayedLog(size_t n) { + Log::registerCurrentTask(cgThreadNames[n]); + Log::i(nowtech::LogTopics::system) << static_cast(n) << ": " << static_cast(0) << Log::end; + for(int64_t i = 1; i < 7; ++i) { + std::this_thread::sleep_for(std::chrono::milliseconds(1 << i)); + Log::i(nowtech::LogTopics::system) << static_cast(n) << "thread delay logarithm: " << LC::X1 << i << Log::end; + } + Log::unregisterCurrentTask(); +} + +std::atomic gCounter; +constexpr int32_t cgAtomicCount = 100; + +void atomicLog(size_t n) { + Log::registerCurrentTask(cgThreadNames[n]); + for(int32_t i = 0; i < cgAtomicCount; ++i) { + Log::pushAtomic(gCounter++); + } + Log::unregisterCurrentTask(); +} + +char gTextToCopy[] = "This_text_will_be_copied_in_messages."; + +int main() { + std::thread threads[cgThreadCount + 1u]; // let there be zero threads + + nowtech::log::LogFormatConfig logConfig; + LogSenderStdOstream::init(&std::cout); + Log::init(logConfig); + + Log::registerTopic(nowtech::LogTopics::system, "system"); + Log::registerTopic(nowtech::LogTopics::surplus, "surplus"); + Log::registerCurrentTask("main"); + + uint64_t const uint64 = 123456789012345; + int64_t const int64 = -123456789012345; + + Log::i(nowtech::LogTopics::surplus) << "message" << Log::end; + for(size_t remaining = std::strlen(gTextToCopy); remaining > 0u; --remaining) { + gTextToCopy[remaining] = 0; + Log::n() << LC::St << gTextToCopy << '#' << Log::end; + } + + Log::i(nowtech::LogTopics::system) << "uint64: " << uint64 << " int64: " << int64 << Log::end; + Log::n(nowtech::LogTopics::system) << "uint64: " << uint64 << " int64: " << int64 << Log::end; + Log::i() << "uint64: " << uint64 << " int64: " << int64 << Log::end; + Log::n() << "uint64: " << uint64 << " int64: " << int64 << Log::end; + + uint8_t const uint8 = 42; + int8_t const int8 = -42; + + try { + Log::i(nowtech::LogTopics::system) << uint8 << ' ' << int8 << Log::end; + Log::i(nowtech::LogTopics::system) << LC::X2 << uint8 << ' ' << LC::D3 << int8 << Log::end; + Log::i() << uint8 << ' ' << int8 << Log::end; + Log::i() << LC::X2 << uint8 << int8 << Log::end; + Log::i() << Log::end; + } + catch(std::exception &e) { + Log::i() << "Exception: " << e.what() << Log::end; + } + + Log::i() << "int8: " << static_cast(123) << Log::end; + Log::i() << "int16: " << static_cast(123) << Log::end; + Log::i() << "int32: " << static_cast(123) << Log::end; + Log::i() << "int64: " << static_cast(123) << Log::end; + Log::i() << "uint8: " << static_cast(123) << Log::end; + Log::i() << "uint16: " << static_cast(123) << Log::end; + Log::i() << "uint32: " << static_cast(123) << Log::end; + Log::i() << "uint64: " << static_cast(123) << Log::end; + Log::i() << "float: " << 1.234567890f << Log::end; + Log::i() << "double: " << -1.234567890 << Log::end; + Log::i() << "float: " << LC::Fm << -123.4567890f << Log::end; + Log::i() << "double: " << LC::Fm << 123.4567890 << Log::end; +// Log::i() << "long double: " << -0.01234567890L << Log::end; +// Log::i() << "long double: " << LC::D16 << 0.01234567890L << Log::end; + Log::i() << "bool:" << true << Log::end; + Log::i() << "bool:" << false << Log::end; + + Log::i() << "fatal" << Log::end; + Log::i() << "error" << Log::end; + Log::i() << "warning" << Log::end; + Log::i() << "info" << Log::end; + Log::i() << "debug" << Log::end; + Log::n() << "fatal" << Log::end; + Log::n() << "error" << Log::end; + Log::n() << "warning" << Log::end; + Log::n() << "info" << Log::end; + Log::n() << "debug" << Log::end; + + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i] = std::thread(delayedLog, i); + } + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i].join(); + } + + gCounter = 0; + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i] = std::thread(atomicLog, i); + } + for(size_t i = 0; i < cgThreadCount; ++i) { + threads[i].join(); + } + Log::sendAtomicBuffer(); + Log::n() << Log::end; + + Log::unregisterCurrentTask(); + Log::done(); + return 0; +} +