diff --git a/wpilibc/src/main/native/cpp/DataLogManager.cpp b/wpilibc/src/main/native/cpp/DataLogManager.cpp index 7c55fe7460..3907db8432 100644 --- a/wpilibc/src/main/native/cpp/DataLogManager.cpp +++ b/wpilibc/src/main/native/cpp/DataLogManager.cpp @@ -111,7 +111,7 @@ Thread::~Thread() { void Thread::Main() { // based on free disk space, scan for "old" FRC_*.wpilog files and remove { - uintmax_t freeSpace = fs::space(m_logDir).free; + uintmax_t freeSpace = fs::space(m_logDir).available; if (freeSpace < kFreeSpaceThreshold) { // Delete oldest FRC_*.wpilog files (ignore FRC_TBD_*.wpilog as we just // created one) diff --git a/wpiutil/src/main/native/cpp/DataLog.cpp b/wpiutil/src/main/native/cpp/DataLog.cpp index 967fd183da..441cd34de7 100644 --- a/wpiutil/src/main/native/cpp/DataLog.cpp +++ b/wpiutil/src/main/native/cpp/DataLog.cpp @@ -40,6 +40,22 @@ static constexpr size_t kBlockSize = 16 * 1024; static constexpr size_t kMaxBufferCount = 1024 * 1024 / kBlockSize; static constexpr size_t kMaxFreeCount = 256 * 1024 / kBlockSize; static constexpr size_t kRecordMaxHeaderSize = 17; +static constexpr uintmax_t kMinFreeSpace = 5 * 1024 * 1024; + +static std::string FormatBytesSize(uintmax_t value) { + static constexpr uintmax_t kKiB = 1024; + static constexpr uintmax_t kMiB = kKiB * 1024; + static constexpr uintmax_t kGiB = kMiB * 1024; + if (value >= kGiB) { + return fmt::format("{:.1f} GiB", static_cast(value) / kGiB); + } else if (value >= kMiB) { + return fmt::format("{:.1f} MiB", static_cast(value) / kMiB); + } else if (value >= kKiB) { + return fmt::format("{:.1f} KiB", static_cast(value) / kKiB); + } else { + return fmt::format("{} B", value); + } +} template static unsigned int WriteVarInt(uint8_t* buf, T val) { @@ -256,32 +272,42 @@ void DataLog::WriterThreadMain(std::string_view dir) { filename = MakeRandomFilename(); } - // try preferred filename, or randomize it a few times, before giving up - fs::file_t f; - for (int i = 0; i < 5; ++i) { - // open file for append -#ifdef _WIN32 - // WIN32 doesn't allow combination of CreateNew and Append - f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew, - fs::OF_None); -#else - f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew, - fs::OF_Append); -#endif - if (ec) { - WPI_ERROR(m_msglog, "Could not open log file '{}': {}", - (dirPath / filename).string(), ec.message()); - // try again with random filename - filename = MakeRandomFilename(); - } else { - break; - } - } + fs::file_t f = fs::kInvalidFile; - if (f == fs::kInvalidFile) { - WPI_ERROR(m_msglog, "Could not open log file, no log being saved"); + // get free space + uintmax_t freeSpace = fs::space(dirPath).available; + if (freeSpace < kMinFreeSpace) { + WPI_ERROR(m_msglog, + "Insufficient free space ({} available), no log being saved", + FormatBytesSize(freeSpace)); } else { - WPI_INFO(m_msglog, "Logging to '{}'", (dirPath / filename).string()); + // try preferred filename, or randomize it a few times, before giving up + for (int i = 0; i < 5; ++i) { + // open file for append +#ifdef _WIN32 + // WIN32 doesn't allow combination of CreateNew and Append + f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew, + fs::OF_None); +#else + f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew, + fs::OF_Append); +#endif + if (ec) { + WPI_ERROR(m_msglog, "Could not open log file '{}': {}", + (dirPath / filename).string(), ec.message()); + // try again with random filename + filename = MakeRandomFilename(); + } else { + break; + } + } + + if (f == fs::kInvalidFile) { + WPI_ERROR(m_msglog, "Could not open log file, no log being saved"); + } else { + WPI_INFO(m_msglog, "Logging to '{}' ({} free space)", + (dirPath / filename).string(), FormatBytesSize(freeSpace)); + } } // write header (version 1.0) @@ -300,6 +326,8 @@ void DataLog::WriterThreadMain(std::string_view dir) { } std::vector toWrite; + int freeSpaceCount = 0; + bool blocked = false; std::unique_lock lock{m_mutex}; while (m_active) { @@ -309,7 +337,7 @@ void DataLog::WriterThreadMain(std::string_view dir) { doFlush = true; } - if (!m_newFilename.empty()) { + if (!m_newFilename.empty() && f != fs::kInvalidFile) { auto newFilename = std::move(m_newFilename); m_newFilename.clear(); lock.unlock(); @@ -337,10 +365,27 @@ void DataLog::WriterThreadMain(std::string_view dir) { // swap outgoing with empty vector toWrite.swap(m_outgoing); - if (f != fs::kInvalidFile) { + if (f != fs::kInvalidFile && !blocked) { lock.unlock(); + + // update free space every 10 flushes (in case other things are writing) + if (++freeSpaceCount >= 10) { + freeSpaceCount = 0; + freeSpace = fs::space(dirPath).available; + } + // write buffers to file for (auto&& buf : toWrite) { + // stop writing when we go below the minimum free space + freeSpace -= buf.GetData().size(); + if (freeSpace < kMinFreeSpace) { + [[unlikely]] WPI_ERROR( + m_msglog, + "Stopped logging due to low free space ({} available)", + FormatBytesSize(freeSpace)); + blocked = true; + break; + } WriteToFile(f, buf.GetData(), filename, m_msglog); } @@ -351,6 +396,9 @@ void DataLog::WriterThreadMain(std::string_view dir) { ::fsync(f); #endif lock.lock(); + if (blocked) { + [[unlikely]] m_paused = true; + } } // release buffers back to free list