-
Notifications
You must be signed in to change notification settings - Fork 321
rosbag2_cpp: Add support for reindexing compressed bags #2326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rolling
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <vector> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "rcpputils/asserts.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "rosbag2_compression/compression_factory.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "rosbag2_cpp/logging.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "rosbag2_cpp/reader.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,13 +46,17 @@ namespace rosbag2_cpp | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Reindexer::Reindexer( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::unique_ptr<rosbag2_storage::StorageFactoryInterface> storage_factory, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::unique_ptr<rosbag2_storage::MetadataIo> metadata_io) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::unique_ptr<rosbag2_storage::MetadataIo> metadata_io, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::unique_ptr<rosbag2_compression::CompressionFactory> compression_factory) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : storage_factory_(std::move(storage_factory)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_io_(std::move(metadata_io)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_io_(std::move(metadata_io)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| compression_factory_(std::move(compression_factory)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regex_bag_pattern_ = R"(.+_(\d+)\.([a-zA-Z0-9])+)"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regex_bag_pattern_ = R"(.+_(\d+)\.([a-zA-Z0-9]+)(?:\.([a-zA-Z0-9]+))?)"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Reindexer::~Reindexer() = default; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Determine which path should be placed first in a vector ordered by file number. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Used to re-order discovered bag files, since the filesystem discovery functions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -176,9 +181,29 @@ void Reindexer::aggregate_metadata( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_.bag_size += fs::file_size(f_); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for compressed file source | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto file_string = f_.generic_string(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::string uri_to_read = file_string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::string original_filename = f_.filename().string(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (file_string.size() > 5 && file_string.substr(file_string.size() - 5) == ".zstd") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!compression_factory_) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| compression_factory_ = std::make_unique<rosbag2_compression::CompressionFactory>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Decompress to /tmp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto decompressor = compression_factory_->create_decompressor("zstd"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uri_to_read = decompressor->decompress_uri(file_string); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+194
to
+196
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uri_to_read = decompressor->decompress_uri(file_string); | |
| try { | |
| uri_to_read = decompressor->decompress_uri(file_string); | |
| } catch (const std::exception & e) { | |
| ROSBAG2_CPP_LOG_ERROR_STREAM( | |
| "Failed to decompress bag file '" << file_string << | |
| "' for reindexing: " << e.what()); | |
| throw; | |
| } catch (...) { | |
| ROSBAG2_CPP_LOG_ERROR_STREAM( | |
| "Failed to decompress bag file '" << file_string << | |
| "' for reindexing due to an unknown error."); | |
| throw; | |
| } |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's trailing whitespace at the end of line 188. This should be removed to maintain code cleanliness.
| if (file_string.size() > 5 && file_string.substr(file_string.size() - 5) == ".zstd") { | |
| if (!compression_factory_) { | |
| compression_factory_ = std::make_unique<rosbag2_compression::CompressionFactory>(); | |
| } | |
| // Decompress to /tmp | |
| auto decompressor = compression_factory_->create_decompressor("zstd"); | |
| uri_to_read = decompressor->decompress_uri(file_string); | |
| if (file_string.size() > 5 && file_string.substr(file_string.size() - 5) == ".zstd") { | |
| if (!compression_factory_) { | |
| compression_factory_ = std::make_unique<rosbag2_compression::CompressionFactory>(); | |
| } | |
| // Decompress to /tmp | |
| auto decompressor = compression_factory_->create_decompressor("zstd"); | |
| uri_to_read = decompressor->decompress_uri(file_string); |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decompression logic hardcodes support for only the ".zstd" extension. This approach has several issues:
-
It only supports zstd compression, but the compression system is pluggable and could support other formats. The compression format should be detected dynamically from the file extension or from discovered metadata.
-
The hardcoded string ".zstd" check is fragile. Consider using the decompressor's
get_decompression_identifier()method or checking against available compression plugins. -
The code doesn't handle the case where the compression factory fails to create a decompressor (if
create_decompressorreturns nullptr), which could cause a null pointer dereference.
Consider refactoring to:
- Extract the file extension and use it to dynamically determine the compression format
- Add error handling for when decompressor creation fails
- Support all available compression formats, not just zstd
| if (file_string.size() > 5 && file_string.substr(file_string.size() - 5) == ".zstd") { | |
| if (!compression_factory_) { | |
| compression_factory_ = std::make_unique<rosbag2_compression::CompressionFactory>(); | |
| } | |
| // Decompress to /tmp | |
| auto decompressor = compression_factory_->create_decompressor("zstd"); | |
| uri_to_read = decompressor->decompress_uri(file_string); | |
| if (metadata_.compression_format.empty()) { | |
| metadata_.compression_format = "zstd"; | |
| metadata_.compression_mode = "FILE"; | |
| // Detect compression format from file extension (e.g. ".zstd", ".lz4") | |
| const std::string extension = fs::path(file_string).extension().string(); | |
| if (!extension.empty()) { | |
| // Strip leading '.' to get the compression format identifier | |
| const std::string compression_format = extension.substr(1); | |
| if (!compression_factory_) { | |
| compression_factory_ = std::make_unique<rosbag2_compression::CompressionFactory>(); | |
| } | |
| // Attempt to create a decompressor for the detected format | |
| auto decompressor = compression_factory_->create_decompressor(compression_format); | |
| if (!decompressor) { | |
| ROSBAG2_CPP_LOG_ERROR_STREAM( | |
| "Failed to create decompressor for format '" << compression_format << | |
| "' when processing file '" << file_string << "'. Proceeding without decompression."); | |
| } else { | |
| // Decompress to a temporary location | |
| uri_to_read = decompressor->decompress_uri(file_string); | |
| if (metadata_.compression_format.empty()) { | |
| metadata_.compression_format = compression_format; | |
| metadata_.compression_mode = "FILE"; | |
| } |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compression mode is hardcoded to "FILE" as a string literal. This should use the proper enum value or constant. Looking at the compression system, there should be a function like compression_mode_to_string that converts the enum to string.
Additionally, the check if (metadata_.compression_format.empty()) means that only the first compressed file will set the compression format and mode. If different files in the bag have different compression formats, this will silently ignore them, which could lead to incorrect metadata.
| metadata_.compression_mode = "FILE"; | |
| metadata_.compression_mode = | |
| rosbag2_compression::compression_mode_to_string( | |
| rosbag2_compression::CompressionMode::FILE); | |
| } else if (metadata_.compression_format != "zstd") { | |
| ROSBAG2_CPP_LOG_WARN_STREAM( | |
| "Mixed compression formats detected while reindexing bag. " | |
| "Clearing bag-level compression metadata to avoid incorrect description."); | |
| metadata_.compression_format.clear(); | |
| metadata_.compression_mode.clear(); |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For large compressed bag files, decompressing each file synchronously could take a significant amount of time and disk I/O. The decompression creates a full uncompressed copy of each bag file, potentially doubling the disk space usage temporarily.
Consider adding:
- A progress indicator or logging to inform users about decompression progress
- A warning message if the bag directory doesn't have sufficient free space for decompression
- Documentation about the temporary disk space requirements for reindexing compressed bags
Additionally, consider whether there's a way to extract only the metadata from compressed files without fully decompressing them, which would be much more efficient.
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR adds support for reindexing compressed bags, which is a significant new feature, but there are no new test cases added to verify this functionality works correctly. The existing test in test_reindexer.cpp only tests uncompressed bags.
Consider adding test coverage for:
- Reindexing bags with zstd compression
- Verifying that temporary decompressed files are cleaned up
- Verifying that the compression metadata (format and mode) are correctly set
- Handling errors when decompression fails
The test could create a compressed bag, remove the metadata.yaml, and then verify that reindexing reconstructs the metadata correctly with proper compression information.
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decompressed temporary files created by decompress_uri are never cleaned up. The zstd decompressor creates files in the same directory as the source file (by calling replace_extension() on the input path), which means temporary decompressed files will be left in the bag directory after reindexing completes.
This is a resource leak that could accumulate large amounts of disk space over time. The temporary files should be explicitly deleted after the bag_reader is closed, or the decompression should be directed to a true temporary directory with automatic cleanup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The forward declaration comment mentions "circular dependency" but doesn't explain what the circular dependency is. Since rosbag2_cpp depends on rosbag2_compression (added in CMakeLists.txt), and this is a public header, the forward declaration is actually being used to avoid including the full header in a public API. The comment should be clarified to explain this is to minimize header dependencies in the public API, not to avoid a circular dependency.