pull/3472/merge
seker 2 weeks ago committed by GitHub
commit aa74e31b8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,14 +17,38 @@ jobs:
sudo apt-get update
sudo apt-get install -y curl build-essential cmake pkg-config libsystemd-dev
- name: Check Coverity Token
id: check-token
env:
COVERITY_TOKEN: ${{ secrets.COVERITY_TOKEN }}
run: |
if [ -z "${COVERITY_TOKEN}" ]; then
echo "COVERITY_TOKEN secret is not set. Skipping Coverity scan."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Download Coverity Tool
if: steps.check-token.outputs.skip == 'false'
run: |
echo "Downloading Coverity tool..."
curl -s -L --output coverity_tool.tgz "https://scan.coverity.com/download/linux64?token=${{ secrets.COVERITY_TOKEN }}&project=gabime%2Fspdlog"
# Verify the downloaded file is a valid tar archive
if ! file coverity_tool.tgz | grep -q "gzip compressed"; then
echo "Error: Downloaded file is not a valid gzip archive"
echo "File content (first 200 bytes):"
head -c 200 coverity_tool.tgz
exit 1
fi
mkdir coverity_tool
tar -C coverity_tool --strip-components=1 -xf coverity_tool.tgz
echo "$PWD/coverity_tool/bin" >> $GITHUB_PATH
- name: Build with Coverity
if: steps.check-token.outputs.skip == 'false'
run: |
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17
@ -32,6 +56,7 @@ jobs:
cov-build --dir cov-int make -C build -j4
- name: Submit results to Coverity
if: steps.check-token.outputs.skip == 'false'
run: |
tar czf cov-int.tgz cov-int
response=$(curl --silent --show-error --fail \

@ -12,14 +12,27 @@
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <tuple>
#include <algorithm>
// Platform-specific includes for mmap support
#ifdef _WIN32
#include <spdlog/details/windows_include.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
namespace spdlog {
namespace details {
SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers)
: event_handlers_(event_handlers) {}
SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers, bool mmap_enabled)
: event_handlers_(event_handlers), mmap_enabled_(mmap_enabled) {}
SPDLOG_INLINE file_helper::~file_helper() { close(); }
@ -51,6 +64,13 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) {
if (event_handlers_.after_open) {
event_handlers_.after_open(filename_, fd_);
}
// Try to initialize mmap if enabled
if (mmap_enabled_ && !try_init_mmap()) {
// mmap initialization failed, continue with regular file I/O
// No need to throw exception, just log a debug message if needed
}
return;
}
@ -69,12 +89,40 @@ SPDLOG_INLINE void file_helper::reopen(bool truncate) {
}
SPDLOG_INLINE void file_helper::flush() {
if (mmap_active_) {
// For mmap, flush means sync the memory mapping
#ifdef _WIN32
if (!FlushViewOfFile(mmap_ptr_, mmap_offset_)) {
// If mmap flush fails, fallback to stdio
fallback_to_stdio();
}
#else
if (msync(mmap_ptr_, mmap_offset_, MS_ASYNC) != 0) {
// If mmap flush fails, fallback to stdio
fallback_to_stdio();
}
#endif
}
if (std::fflush(fd_) != 0) {
throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno);
}
}
SPDLOG_INLINE void file_helper::sync() {
if (mmap_active_) {
// For mmap, sync means synchronous sync of the memory mapping
#ifdef _WIN32
if (!FlushViewOfFile(mmap_ptr_, mmap_offset_)) {
fallback_to_stdio();
}
#else
if (msync(mmap_ptr_, mmap_offset_, MS_SYNC) != 0) {
fallback_to_stdio();
}
#endif
}
if (!os::fsync(fd_)) {
throw_spdlog_ex("Failed to fsync file " + os::filename_to_str(filename_), errno);
}
@ -86,6 +134,9 @@ SPDLOG_INLINE void file_helper::close() {
event_handlers_.before_close(filename_, fd_);
}
// Clean up mmap resources before closing file
cleanup_mmap();
std::fclose(fd_);
fd_ = nullptr;
@ -100,6 +151,31 @@ SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) {
size_t msg_size = buf.size();
auto data = buf.data();
// Try mmap write first if active
if (mmap_active_) {
size_t required_size = mmap_offset_ + msg_size;
// Check if we need to expand the mapping
if (required_size > mmap_size_) {
if (!expand_mmap(required_size)) {
// Expansion failed, fallback to stdio
goto stdio_write;
}
}
// Write to mmap
try {
std::memcpy(static_cast<char*>(mmap_ptr_) + mmap_offset_, data, msg_size);
mmap_offset_ += msg_size;
return;
} catch (...) {
// mmap write failed, fallback to stdio
fallback_to_stdio();
}
}
stdio_write:
// Standard file I/O fallback
if (!details::os::fwrite_bytes(data, msg_size, fd_)) {
throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno);
}
@ -109,6 +185,13 @@ SPDLOG_INLINE size_t file_helper::size() const {
if (fd_ == nullptr) {
throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_));
}
// If mmap is active, return the current offset (actual written size)
if (mmap_active_) {
// DEBUG: Print mmap_offset_ value
return mmap_offset_;
}
return os::filesize(fd_);
}
@ -147,5 +230,178 @@ SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension
return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index));
}
// mmap helper methods implementation
SPDLOG_INLINE void file_helper::set_mmap_enabled(bool enabled) {
mmap_enabled_ = enabled;
if (!enabled && mmap_active_) {
fallback_to_stdio();
}
}
SPDLOG_INLINE bool file_helper::is_mmap_enabled() const {
return mmap_enabled_;
}
SPDLOG_INLINE bool file_helper::try_init_mmap() {
if (!mmap_enabled_ || fd_ == nullptr) {
return false;
}
// Get current file size BEFORE any modifications
size_t current_file_size = os::filesize(fd_);
size_t required_size = (std::max)(current_file_size + initial_mmap_size_, initial_mmap_size_);
#ifdef _WIN32
// Windows implementation
file_handle_ = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fd_)));
if (file_handle_ == INVALID_HANDLE_VALUE) {
return false;
}
// Create file mapping
LARGE_INTEGER map_size;
map_size.QuadPart = required_size;
mapping_handle_ = CreateFileMapping(file_handle_, nullptr, PAGE_READWRITE,
map_size.HighPart, map_size.LowPart, nullptr);
if (mapping_handle_ == nullptr) {
return false;
}
// Map view of file
mmap_ptr_ = MapViewOfFile(mapping_handle_, FILE_MAP_WRITE, 0, 0, required_size);
if (mmap_ptr_ == nullptr) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
return false;
}
#else
// Unix/Linux implementation
file_descriptor_ = fileno(fd_);
if (file_descriptor_ == -1) {
return false;
}
// Create memory mapping first (without extending file)
mmap_ptr_ = mmap(nullptr, required_size, PROT_READ | PROT_WRITE, MAP_SHARED,
file_descriptor_, 0);
if (mmap_ptr_ == MAP_FAILED) {
mmap_ptr_ = nullptr;
return false;
}
// Extend file if necessary (only after mmap succeeds)
if (ftruncate(file_descriptor_, static_cast<off_t>(required_size)) != 0) {
munmap(mmap_ptr_, required_size);
mmap_ptr_ = nullptr;
return false;
}
#endif
mmap_size_ = required_size;
mmap_offset_ = 0; // Always start from beginning for new mmap
mmap_active_ = true;
return true;
}
SPDLOG_INLINE bool file_helper::expand_mmap(size_t required_size) {
if (!mmap_active_ || required_size <= mmap_size_) {
return true;
}
// Calculate new size (double current size or required size, whichever is larger)
size_t new_size = (std::max)(mmap_size_ * 2, required_size);
new_size = (std::min)(new_size, max_mmap_size_);
if (new_size <= mmap_size_) {
// Cannot expand further, fallback to stdio
fallback_to_stdio();
return false;
}
// Save current offset before cleanup
size_t saved_offset = mmap_offset_;
cleanup_mmap();
#ifdef _WIN32
// Windows re-mapping
LARGE_INTEGER map_size;
map_size.QuadPart = new_size;
mapping_handle_ = CreateFileMapping(file_handle_, nullptr, PAGE_READWRITE,
map_size.HighPart, map_size.LowPart, nullptr);
if (mapping_handle_ == nullptr) {
fallback_to_stdio();
return false;
}
mmap_ptr_ = MapViewOfFile(mapping_handle_, FILE_MAP_WRITE, 0, 0, new_size);
if (mmap_ptr_ == nullptr) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
fallback_to_stdio();
return false;
}
#else
// Unix/Linux re-mapping
if (ftruncate(file_descriptor_, static_cast<off_t>(new_size)) != 0) {
fallback_to_stdio();
return false;
}
mmap_ptr_ = mmap(nullptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED,
file_descriptor_, 0);
if (mmap_ptr_ == MAP_FAILED) {
mmap_ptr_ = nullptr;
fallback_to_stdio();
return false;
}
#endif
mmap_size_ = new_size;
mmap_offset_ = saved_offset; // Restore the saved offset
mmap_active_ = true;
return true;
}
SPDLOG_INLINE void file_helper::cleanup_mmap() {
if (!mmap_active_) {
return;
}
#ifdef _WIN32
if (mmap_ptr_ != nullptr) {
UnmapViewOfFile(mmap_ptr_);
mmap_ptr_ = nullptr;
}
if (mapping_handle_ != nullptr) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
}
#else
if (mmap_ptr_ != nullptr) {
munmap(mmap_ptr_, mmap_size_);
mmap_ptr_ = nullptr;
}
#endif
mmap_active_ = false;
mmap_size_ = 0;
mmap_offset_ = 0;
}
SPDLOG_INLINE void file_helper::fallback_to_stdio() {
if (mmap_active_) {
// Sync any pending data
sync();
cleanup_mmap();
}
mmap_enabled_ = false; // Disable mmap for this file
}
} // namespace details
} // namespace spdlog

@ -6,17 +6,26 @@
#include <spdlog/common.h>
#include <tuple>
// Platform-specific includes for mmap support
#ifdef _WIN32
#include <spdlog/details/windows_include.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
namespace spdlog {
namespace details {
// Helper class for file sinks.
// When failing to open a file, retry several times(5) with a delay interval(10 ms).
// Throw spdlog_ex exception on errors.
// Supports mmap mode for improved performance with automatic fallback to regular file I/O.
class SPDLOG_API file_helper {
public:
file_helper() = default;
explicit file_helper(const file_event_handlers &event_handlers);
explicit file_helper(const file_event_handlers &event_handlers, bool mmap_enabled = false);
file_helper(const file_helper &) = delete;
file_helper &operator=(const file_helper &) = delete;
@ -31,6 +40,10 @@ public:
size_t size() const;
const filename_t &filename() const;
// Enable/disable mmap mode (enabled by default)
void set_mmap_enabled(bool enabled);
bool is_mmap_enabled() const;
//
// return file path and its extension:
//
@ -47,11 +60,34 @@ public:
static std::tuple<filename_t, filename_t> split_by_extension(const filename_t &fname);
private:
// Regular file I/O members
const int open_tries_ = 5;
const unsigned int open_interval_ = 10;
std::FILE *fd_{nullptr};
filename_t filename_;
file_event_handlers event_handlers_;
// mmap related members
bool mmap_enabled_{false}; // Disable mmap by default
bool mmap_active_{false}; // Whether mmap is currently active
void* mmap_ptr_{nullptr}; // Pointer to mapped memory
size_t mmap_size_{0}; // Current size of mapped region
size_t mmap_offset_{0}; // Current write offset in mapped region
size_t initial_mmap_size_{1024 * 1024}; // Initial mmap size (1MB)
size_t max_mmap_size_{100 * 1024 * 1024}; // Max mmap size (100MB)
#ifdef _WIN32
HANDLE file_handle_{INVALID_HANDLE_VALUE};
HANDLE mapping_handle_{nullptr};
#else
int file_descriptor_{-1};
#endif
// mmap helper methods
bool try_init_mmap();
bool expand_mmap(size_t required_size);
void cleanup_mmap();
void fallback_to_stdio();
};
} // namespace details
} // namespace spdlog

@ -28,6 +28,7 @@ endif()
set(SPDLOG_UTESTS_SOURCES
test_file_helper.cpp
test_mmap_file_helper.cpp
test_file_logging.cpp
test_daily_logger.cpp
test_misc.cpp

@ -0,0 +1,387 @@
/*
* This content is released under the MIT License as specified in
* https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE
*/
#include "includes.h"
#include <chrono>
#include <iomanip>
#include <iostream>
#define TEST_FILENAME "test_logs/mmap_file_helper_test.txt"
using spdlog::details::file_helper;
static void write_with_helper(file_helper &helper, size_t howmany) {
std::cout << " Writing " << howmany << " bytes of data..." << std::endl;
spdlog::memory_buf_t formatted;
spdlog::fmt_lib::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1'));
helper.write(formatted);
helper.flush();
std::cout << " Write completed, current file size: " << helper.size() << " bytes" << std::endl;
}
TEST_CASE("file_helper_mmap_disabled_by_default", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap disabled by default ===" << std::endl;
prepare_logdir();
file_helper helper;
std::cout << "Checking mmap default state..." << std::endl;
bool mmap_enabled = helper.is_mmap_enabled();
std::cout << "mmap status: " << (mmap_enabled ? "enabled" : "disabled") << std::endl;
REQUIRE(helper.is_mmap_enabled() == false);
std::cout << "✓ Test passed: mmap disabled by default" << std::endl;
}
TEST_CASE("file_helper_mmap_enable_disable", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap enable/disable functionality ===" << std::endl;
prepare_logdir();
file_helper helper;
std::cout << "Initial mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
// Test enable/disable
std::cout << "Enabling mmap..." << std::endl;
helper.set_mmap_enabled(true);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
REQUIRE(helper.is_mmap_enabled() == true);
std::cout << "Disabling mmap..." << std::endl;
helper.set_mmap_enabled(false);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
REQUIRE(helper.is_mmap_enabled() == false);
std::cout << "✓ Test passed: mmap enable/disable functionality works correctly" << std::endl;
}
TEST_CASE("file_helper_mmap_basic_write", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap basic write functionality ===" << std::endl;
prepare_logdir();
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
size_t expected_size = 123;
std::cout << "Opening file: " << TEST_FILENAME << std::endl;
{
file_helper helper;
helper.set_mmap_enabled(true); // Enable mmap for this test
helper.open(target_filename);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
write_with_helper(helper, expected_size);
REQUIRE(static_cast<size_t>(helper.size()) == expected_size);
}
// Verify file was written correctly
size_t actual_size = get_filesize(TEST_FILENAME);
std::cout << "Verifying file size - expected: " << expected_size << " bytes, actual: " << actual_size << " bytes" << std::endl;
REQUIRE(get_filesize(TEST_FILENAME) == expected_size);
std::cout << "✓ Test passed: mmap basic write functionality works correctly" << std::endl;
}
TEST_CASE("file_helper_mmap_vs_stdio_content", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap vs stdio content consistency ===" << std::endl;
prepare_logdir();
std::string test_content = "Hello mmap world!\nLine 2\nLine 3\n";
std::cout << "Test content length: " << test_content.size() << " bytes" << std::endl;
// Write with mmap enabled
std::cout << "Writing file using mmap mode..." << std::endl;
{
file_helper helper_mmap;
helper_mmap.set_mmap_enabled(true); // Enable mmap for this test
spdlog::filename_t mmap_filename = SPDLOG_FILENAME_T("test_logs/mmap_test.txt");
helper_mmap.open(mmap_filename);
std::cout << " mmap status: " << (helper_mmap.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
spdlog::memory_buf_t buf;
buf.append(test_content.data(), test_content.data() + test_content.size());
helper_mmap.write(buf);
helper_mmap.flush();
std::cout << " mmap file write completed, size: " << helper_mmap.size() << " bytes" << std::endl;
}
// Write with stdio
std::cout << "Writing file using stdio mode..." << std::endl;
{
file_helper helper_stdio;
helper_stdio.set_mmap_enabled(false);
spdlog::filename_t stdio_filename = SPDLOG_FILENAME_T("test_logs/stdio_test.txt");
helper_stdio.open(stdio_filename);
std::cout << " mmap status: " << (helper_stdio.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
spdlog::memory_buf_t buf;
buf.append(test_content.data(), test_content.data() + test_content.size());
helper_stdio.write(buf);
helper_stdio.flush();
std::cout << " stdio file write completed, size: " << helper_stdio.size() << " bytes" << std::endl;
}
// Compare file contents
size_t mmap_size = get_filesize("test_logs/mmap_test.txt");
size_t stdio_size = get_filesize("test_logs/stdio_test.txt");
std::cout << "Comparing file sizes - mmap: " << mmap_size << " bytes, stdio: " << stdio_size << " bytes" << std::endl;
REQUIRE(get_filesize("test_logs/mmap_test.txt") == get_filesize("test_logs/stdio_test.txt"));
// Read and compare actual content
std::cout << "Reading and comparing file contents..." << std::endl;
std::ifstream mmap_file("test_logs/mmap_test.txt");
std::ifstream stdio_file("test_logs/stdio_test.txt");
std::string mmap_content((std::istreambuf_iterator<char>(mmap_file)),
std::istreambuf_iterator<char>());
std::string stdio_content((std::istreambuf_iterator<char>(stdio_file)),
std::istreambuf_iterator<char>());
std::cout << "Content comparison result: " << (mmap_content == stdio_content ? "identical" : "different") << std::endl;
REQUIRE(mmap_content == stdio_content);
REQUIRE(mmap_content == test_content);
std::cout << "✓ Test passed: mmap and stdio mode contents are completely identical" << std::endl;
}
TEST_CASE("file_helper_mmap_large_write", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap large write ===" << std::endl;
prepare_logdir();
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
file_helper helper;
helper.set_mmap_enabled(true); // Enable mmap for this test
helper.open(target_filename);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
// Write multiple chunks to test mmap expansion
size_t total_size = 0;
std::cout << "Starting to write 100 lines of test data..." << std::endl;
for (int i = 0; i < 100; ++i) {
spdlog::memory_buf_t buf;
std::string line = "This is test line " + std::to_string(i) + " with some data.\n";
buf.append(line.data(), line.data() + line.size());
helper.write(buf);
total_size += line.size();
if ((i + 1) % 20 == 0) {
std::cout << " Written " << (i + 1) << " lines, cumulative size: " << total_size << " bytes" << std::endl;
}
}
helper.flush();
std::cout << "Write completed, total size: " << total_size << " bytes" << std::endl;
std::cout << "helper reported size: " << helper.size() << " bytes" << std::endl;
std::cout << "actual file size: " << get_filesize(TEST_FILENAME) << " bytes" << std::endl;
REQUIRE(static_cast<size_t>(helper.size()) == total_size);
REQUIRE(get_filesize(TEST_FILENAME) == total_size);
std::cout << "✓ Test passed: mmap large write functionality works correctly" << std::endl;
}
TEST_CASE("file_helper_mmap_reopen", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap file reopen ===" << std::endl;
prepare_logdir();
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
file_helper helper;
// Enable mmap for this test since default is now false
std::cout << "Enabling mmap for reopen test..." << std::endl;
helper.set_mmap_enabled(true);
std::cout << "Opening file and writing data..." << std::endl;
helper.open(target_filename);
write_with_helper(helper, 12);
std::cout << "Current file size: " << helper.size() << " bytes" << std::endl;
REQUIRE(helper.size() == 12);
// Test reopen with truncate
std::cout << "Reopening file (truncate mode)..." << std::endl;
helper.reopen(true);
std::cout << "File size after reopen: " << helper.size() << " bytes" << std::endl;
REQUIRE(helper.size() == 0);
// Verify mmap is still enabled after reopen
std::cout << "mmap status after reopen: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
REQUIRE(helper.is_mmap_enabled() == true);
std::cout << "✓ Test passed: mmap file reopen functionality works correctly" << std::endl;
}
TEST_CASE("file_helper_mmap_disable_during_operation", "[file_helper::mmap]") {
std::cout << "\n=== Test: runtime mmap disable ===" << std::endl;
prepare_logdir();
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
file_helper helper;
// Enable mmap first for this test since default is now false
helper.set_mmap_enabled(true);
helper.open(target_filename);
std::cout << "Initial mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
// Write some data with mmap
std::cout << "Writing data using mmap mode..." << std::endl;
write_with_helper(helper, 50);
REQUIRE(helper.size() == 50);
// Disable mmap during operation
std::cout << "Disabling mmap at runtime..." << std::endl;
helper.set_mmap_enabled(false);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
REQUIRE(helper.is_mmap_enabled() == false);
// Write more data (should use stdio now)
std::cout << "Continuing to write data using stdio mode..." << std::endl;
write_with_helper(helper, 50);
REQUIRE(helper.size() == 100);
size_t final_size = get_filesize(TEST_FILENAME);
std::cout << "Final file size: " << final_size << " bytes" << std::endl;
REQUIRE(get_filesize(TEST_FILENAME) == 100);
std::cout << "✓ Test passed: runtime mmap disable functionality works correctly" << std::endl;
}
TEST_CASE("file_helper_mmap_sync", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap sync operation ===" << std::endl;
prepare_logdir();
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
file_helper helper;
helper.set_mmap_enabled(true); // Enable mmap for this test
helper.open(target_filename);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
write_with_helper(helper, 123);
// Test sync operation
std::cout << "Performing sync operation..." << std::endl;
REQUIRE_NOTHROW(helper.sync());
std::cout << "Sync operation completed, file size: " << helper.size() << " bytes" << std::endl;
REQUIRE(helper.size() == 123);
std::cout << "✓ Test passed: mmap sync operation works correctly" << std::endl;
}
TEST_CASE("file_helper_mmap_filename_consistency", "[file_helper::mmap]") {
std::cout << "\n=== Test: mmap filename consistency ===" << std::endl;
prepare_logdir();
file_helper helper;
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
helper.open(target_filename);
std::cout << "Target filename: " << TEST_FILENAME << std::endl;
std::cout << "helper reported filename: " << spdlog::details::os::filename_to_str(helper.filename()) << std::endl;
// Filename should be consistent regardless of mmap mode
std::cout << "Checking filename consistency with mmap disabled (default)..." << std::endl;
REQUIRE(helper.filename() == target_filename);
std::cout << "Checking filename consistency after enabling mmap..." << std::endl;
helper.set_mmap_enabled(true);
std::cout << "mmap status: " << (helper.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
std::cout << "helper reported filename: " << spdlog::details::os::filename_to_str(helper.filename()) << std::endl;
REQUIRE(helper.filename() == target_filename);
std::cout << "✓ Test passed: filename remains consistent across different mmap modes" << std::endl;
}
TEST_CASE("file_helper_mmap_vs_stdio_performance", "[file_helper::mmap][performance]") {
std::cout << "\n=== Test: mmap vs stdio performance comparison ===" << std::endl;
prepare_logdir();
const size_t test_iterations = 1000;
const size_t data_size_per_write = 256; // 256 bytes per write
const std::string test_data(data_size_per_write - 1, 'A');
const std::string test_data_with_newline = test_data + "\n";
std::cout << "Performance test configuration:" << std::endl;
std::cout << " Test iterations: " << test_iterations << " times" << std::endl;
std::cout << " Data size per write: " << data_size_per_write << " bytes" << std::endl;
std::cout << " Total data volume: " << (test_iterations * data_size_per_write) << " bytes ("
<< (test_iterations * data_size_per_write / 1024.0) << " KB)" << std::endl;
// Test mmap performance
std::cout << "\n--- Testing mmap mode performance ---" << std::endl;
auto start_mmap = std::chrono::high_resolution_clock::now();
{
file_helper helper_mmap;
helper_mmap.set_mmap_enabled(true); // Enable mmap for performance test
spdlog::filename_t mmap_filename = SPDLOG_FILENAME_T("test_logs/performance_mmap.txt");
helper_mmap.open(mmap_filename);
std::cout << "mmap status: " << (helper_mmap.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
for (size_t i = 0; i < test_iterations; ++i) {
spdlog::memory_buf_t buf;
buf.append(test_data_with_newline.data(), test_data_with_newline.data() + test_data_with_newline.size());
helper_mmap.write(buf);
if ((i + 1) % 200 == 0) {
std::cout << " Completed " << (i + 1) << "/" << test_iterations << " writes" << std::endl;
}
}
helper_mmap.flush();
}
auto end_mmap = std::chrono::high_resolution_clock::now();
auto duration_mmap = std::chrono::duration_cast<std::chrono::microseconds>(end_mmap - start_mmap);
// Test stdio performance
std::cout << "\n--- Testing stdio mode performance ---" << std::endl;
auto start_stdio = std::chrono::high_resolution_clock::now();
{
file_helper helper_stdio;
helper_stdio.set_mmap_enabled(false);
spdlog::filename_t stdio_filename = SPDLOG_FILENAME_T("test_logs/performance_stdio.txt");
helper_stdio.open(stdio_filename);
std::cout << "mmap status: " << (helper_stdio.is_mmap_enabled() ? "enabled" : "disabled") << std::endl;
for (size_t i = 0; i < test_iterations; ++i) {
spdlog::memory_buf_t buf;
buf.append(test_data_with_newline.data(), test_data_with_newline.data() + test_data_with_newline.size());
helper_stdio.write(buf);
if ((i + 1) % 200 == 0) {
std::cout << " Completed " << (i + 1) << "/" << test_iterations << " writes" << std::endl;
}
}
helper_stdio.flush();
}
auto end_stdio = std::chrono::high_resolution_clock::now();
auto duration_stdio = std::chrono::duration_cast<std::chrono::microseconds>(end_stdio - start_stdio);
// Performance results analysis
std::cout << "\n=== Performance Test Results ===" << std::endl;
std::cout << "mmap mode duration: " << duration_mmap.count() << " microseconds ("
<< (static_cast<double>(duration_mmap.count()) / 1000.0) << " milliseconds)" << std::endl;
std::cout << "stdio mode duration: " << duration_stdio.count() << " microseconds ("
<< (static_cast<double>(duration_stdio.count()) / 1000.0) << " milliseconds)" << std::endl;
double performance_ratio = static_cast<double>(duration_stdio.count()) / static_cast<double>(duration_mmap.count());
if (duration_mmap.count() < duration_stdio.count()) {
std::cout << "mmap mode is " << std::fixed << std::setprecision(2)
<< performance_ratio << " times faster than stdio mode" << std::endl;
} else if (duration_mmap.count() > duration_stdio.count()) {
std::cout << "stdio mode is " << std::fixed << std::setprecision(2)
<< (static_cast<double>(duration_mmap.count()) / static_cast<double>(duration_stdio.count())) << " times faster than mmap mode" << std::endl;
} else {
std::cout << "Both modes have similar performance" << std::endl;
}
// Calculate throughput
double total_mb = (test_iterations * data_size_per_write) / (1024.0 * 1024.0);
double mmap_throughput = total_mb / (static_cast<double>(duration_mmap.count()) / 1000000.0); // MB/s
double stdio_throughput = total_mb / (static_cast<double>(duration_stdio.count()) / 1000000.0); // MB/s
std::cout << "\nThroughput comparison:" << std::endl;
std::cout << "mmap mode throughput: " << std::fixed << std::setprecision(2)
<< mmap_throughput << " MB/s" << std::endl;
std::cout << "stdio mode throughput: " << std::fixed << std::setprecision(2)
<< stdio_throughput << " MB/s" << std::endl;
// Verify file sizes
size_t expected_size = test_iterations * data_size_per_write;
size_t mmap_file_size = get_filesize("test_logs/performance_mmap.txt");
size_t stdio_file_size = get_filesize("test_logs/performance_stdio.txt");
std::cout << "\nFile size verification:" << std::endl;
std::cout << "Expected file size: " << expected_size << " bytes" << std::endl;
std::cout << "mmap file size: " << mmap_file_size << " bytes" << std::endl;
std::cout << "stdio file size: " << stdio_file_size << " bytes" << std::endl;
REQUIRE(mmap_file_size == expected_size);
REQUIRE(stdio_file_size == expected_size);
REQUIRE(mmap_file_size == stdio_file_size);
std::cout << "✓ Test passed: performance comparison test completed, both modes wrote data correctly" << std::endl;
}
Loading…
Cancel
Save