From ed55fd9ebb6d2fb4e0c0089a0feda0d7d6b18fda Mon Sep 17 00:00:00 2001 From: xinjian <04070628@163.com> Date: Fri, 10 Oct 2025 18:33:14 +0800 Subject: [PATCH 1/2] file_helper support mmap --- include/spdlog/details/file_helper-inl.h | 260 ++++++++++++++- include/spdlog/details/file_helper.h | 38 ++- tests/CMakeLists.txt | 1 + tests/test_mmap_file_helper.cpp | 387 +++++++++++++++++++++++ 4 files changed, 683 insertions(+), 3 deletions(-) create mode 100644 tests/test_mmap_file_helper.cpp diff --git a/include/spdlog/details/file_helper-inl.h b/include/spdlog/details/file_helper-inl.h index 0c514ef2a8..2f7cce9812 100644 --- a/include/spdlog/details/file_helper-inl.h +++ b/include/spdlog/details/file_helper-inl.h @@ -12,14 +12,27 @@ #include #include +#include #include #include +#include + +// Platform-specific includes for mmap support +#ifdef _WIN32 + #include + #include + #include + #include +#else + #include + #include +#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(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 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(_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(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(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 diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index f0e5d180e3..0918ead825 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -6,17 +6,26 @@ #include #include +// Platform-specific includes for mmap support +#ifdef _WIN32 + #include +#else + #include + #include +#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 @@ class SPDLOG_API file_helper { 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 @@ class SPDLOG_API file_helper { static std::tuple 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5ba2a106a7..bf9c238eac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 diff --git a/tests/test_mmap_file_helper.cpp b/tests/test_mmap_file_helper.cpp new file mode 100644 index 0000000000..b73b1436e2 --- /dev/null +++ b/tests/test_mmap_file_helper.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 +#include +#include + +#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(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(mmap_file)), + std::istreambuf_iterator()); + std::string stdio_content((std::istreambuf_iterator(stdio_file)), + std::istreambuf_iterator()); + + 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(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(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(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(duration_mmap.count()) / 1000.0) << " milliseconds)" << std::endl; + std::cout << "stdio mode duration: " << duration_stdio.count() << " microseconds (" + << (static_cast(duration_stdio.count()) / 1000.0) << " milliseconds)" << std::endl; + + double performance_ratio = static_cast(duration_stdio.count()) / static_cast(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(duration_mmap.count()) / static_cast(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(duration_mmap.count()) / 1000000.0); // MB/s + double stdio_throughput = total_mb / (static_cast(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; +} \ No newline at end of file From 2479ded0b2c581b72d3548a40d077eadc77692b2 Mon Sep 17 00:00:00 2001 From: xinjian <04070628@163.com> Date: Sat, 11 Oct 2025 10:22:12 +0800 Subject: [PATCH 2/2] fix Coverity Scan error : Check Coverity Token --- .github/workflows/coverity_scan.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/coverity_scan.yml b/.github/workflows/coverity_scan.yml index 373504d002..7f8575f02c 100644 --- a/.github/workflows/coverity_scan.yml +++ b/.github/workflows/coverity_scan.yml @@ -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 \