Skip to content

Conversation

@luke-alloy
Copy link

@luke-alloy luke-alloy commented Oct 30, 2025

Description

Adds circular logging by split count, record size and duration to rosbag2 recording.

  • CLI: adds --max-splits
  • Writer: deletes oldest files when exceeding split count

Fixes #2217

Is this user-facing behavior change?

Yes.

  • Older recordings are deleted once max split is reached:
    • --max-splits N: limit number of retained bagfile splits.
  • Examples:
    • ros2 bag record -a --max-bag-size 100MB --max-splits 5

Did you use Generative AI?

Yes.

  • Tool/model: GPT-5
  • Used for: naming suggestions, CLI/help text wording, and drafting PR description.
  • Code impact: assisted in scaffolding changes (option wiring, param names); final code implementation was completed, built, run, and manually reviewed and tested locally.

Additional Information

  • The max split limit is checked together after each bag split
  • The writer logs a warning when deleting files due to limits. Adjust --log-level if needed.
  • metadata.yaml currently retains aggregate message_count and topics_with_message_count across the entire capture, even when older bagfiles are pruned by circular logging. File lists and durations reflect pruning, but those two fields still represent the full session totals.
  • Circular logging changes are only in sequential_writer.cpp (deleting the oldest file if needed); the rest involve adding StorageOptions parameters, updating the shell UI, and adjusting related unit tests.

- Add StorageOptions.max_splits (+ YAML/params, Python bindings)
- CLI: --max-splits (before size/duration)
- Writer: delete oldest files when exceeding size, duration, or split count

Signed-off-by: Luke Sy <[email protected]>
Signed-off-by: Luke Sy <[email protected]>
@luke-alloy luke-alloy force-pushed the feature/ros2bag-circular-logging-size-duration-2217 branch from 309537d to 7b4c6bb Compare October 30, 2025 13:20
@luke-alloy luke-alloy marked this pull request as ready for review October 30, 2025 21:29
@luke-alloy luke-alloy changed the title Feature/ros2bag circular logging size duration 2217 Feature/rosbag2 circular logging size duration 2217 Oct 30, 2025
@luke-alloy luke-alloy force-pushed the feature/ros2bag-circular-logging-size-duration-2217 branch from 30f9f5c to cf16699 Compare November 10, 2025 16:06
@luke-alloy
Copy link
Author

I've committed changes that should address the unit test error. I'm also happy to include @fujitatomoya ’s suggestion to remove --max-record-size and --max-record-duration, pending confirmation to proceed.

Simplify storage options by removing max_record_size and
max_record_duration fields and CLI arguments. Circular logging now
only supports limiting by split count (max_splits).

Signed-off-by: Luke Sy <[email protected]>
Copy link
Contributor

@MichaelOrlov MichaelOrlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luke-alloy Thank you for your contribution. This is a long-time-wanted feature that will be very useful.

I've made a thorough review, and overall implementation looks good to me. However, I have a few suggestions:

  1. The naming is hard. I would propose to rename --max-splits parameter to --max_bag_files to avoid confusion and multiple user questions, like:
  • Will splits stop after reaching max_splits?
  • Why is there one file when max_splits = 1? I would expect to have at least two files after the split.
  1. I would like to see at least one integration test with real files writing and deletion.
    To facilitate this, we do have a special TemporaryDirectoryFixture. Please consider modifying your tests or adding another one. I recently added a similar test in one of my PR. Please, refer to the 81ef9fd#diff-c14a7d44179cf64868ce86b5e60c7f741f957ef368c04a0477ccfe7ccd89f1f2R138-R164 as an example from the #2224.

'--max-splits', type=int, default=0,
help='Maximum number of splits to retain before deleting the oldest bagfile '
'(circular logging). Default: %(default)d, unlimited. '
'Requires --max-bag-size or --max-bag-duration to be set.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Requires --max-bag-size or --max-bag-duration to be set.')
'Requires --max-bag-size or --max-bag-duration to be set or usage of the bag split '
'via direct recorder API or service calls.')

Comment on lines 339 to +341
storage_options_.uri = format_storage_uri(
base_folder_,
metadata_.relative_file_paths.size());
next_file_index_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment would be better readable in one line

Suggested change
storage_options_.uri = format_storage_uri(
base_folder_,
metadata_.relative_file_paths.size());
next_file_index_);
storage_options_.uri = format_storage_uri(base_folder_, next_file_index_);

Comment on lines +430 to +431
// Delete oldest files if circular buffer limit exceeded (after split creates new file)
delete_oldest_files_if_needed();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Delete oldest files if circular buffer limit exceeded (after split creates new file)
delete_oldest_files_if_needed();

I think it would be better to move this delete_oldest_files call into the void SequentialWriter::switch_to_next_storage() right before storage_->update_metadata(metadata_);. This way we can avoid extra storage_->update_metadata(metadata_); call inside delete_oldest_files_if_needed();
Please be aware that our update_metadata(metadata_) in mcap is a bit suboptimal, and each time update is called, we are writing a new section with metadata instead of really updating the one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Delete the oldest files if circular buffer limit exceeded (after split creates new file)
delete_oldest_files_if_needed();
storage_->update_metadata(metadata_);

"Moved from after the split"

Comment on lines +526 to +532
// Delete oldest files until we're under the split-count limit
// Note: We just split, so we have at least 2 files and the oldest is definitely not current
while (metadata_.files.size() > 1) {
// If we're under the limit, stop deleting
if (metadata_.files.size() <= storage_options_.max_splits) {
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Delete oldest files until we're under the split-count limit
// Note: We just split, so we have at least 2 files and the oldest is definitely not current
while (metadata_.files.size() > 1) {
// If we're under the limit, stop deleting
if (metadata_.files.size() <= storage_options_.max_splits) {
break;
}
// Delete the oldest files until we're under the split-count limit
while (metadata_.files.size() <= storage_options_.max_splits) {

Simplify logic.
Note: if storage_options_.max_splits were storage_options_.max_bag_files, the logic would be easy to understand.

Comment on lines +553 to +556
// Update metadata after deletions
if (!metadata_.files.empty()) {
storage_->update_metadata(metadata_);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Update metadata after deletions
if (!metadata_.files.empty()) {
storage_->update_metadata(metadata_);
}

Metadata update should happen once in the SequentialWriter::switch_to_next_storage()


// The maximum number of bagfile splits to retain before the oldest bagfile is deleted.
// A value of 0 indicates that no deletion based on split count will occur.
// This feature is only available if either max_bagfile_size or max_bagfile_duration is set.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This feature is only available if either max_bagfile_size or max_bagfile_duration is set.
// This feature is only available when the bag split is active.
// Requires --max-bag-size or --max-bag-duration to be set or usage of the bag split via
// direct recorder API or service calls.set.

// The maximum number of bagfile splits to retain before the oldest bagfile is deleted.
// A value of 0 indicates that no deletion based on split count will occur.
// This feature is only available if either max_bagfile_size or max_bagfile_duration is set.
uint64_t max_splits = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint64_t max_splits = 0;
uint64_t max_bag_files = 0;

The naming is hard. I would propose to rename this parameter to max_bag_files to avoid confusion and multiple user questions, like:

  • Will splits stop after reaching max_splits?
  • Why is there one file when max_splits = 1? I would expect to have at least two files after the split.

}
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

The stray empty line should be removed.

@luke-alloy
Copy link
Author

@luke-alloy Thank you for your contribution. This is a long-time-wanted feature that will be very useful.

I've made a thorough review, and overall implementation looks good to me. However, I have a few suggestions:

  1. The naming is hard. I would propose to rename --max-splits parameter to --max_bag_files to avoid confusion and multiple user questions, like:
  • Will splits stop after reaching max_splits?
  • Why is there one file when max_splits = 1? I would expect to have at least two files after the split.
  1. I would like to see at least one integration test with real files writing and deletion.
    To facilitate this, we do have a special TemporaryDirectoryFixture. Please consider modifying your tests or adding another one. I recently added a similar test in one of my PR. Please, refer to the 81ef9fd#diff-c14a7d44179cf64868ce86b5e60c7f741f957ef368c04a0477ccfe7ccd89f1f2R138-R164 as an example from the Fix for C++ Recorder failure on stop() -> record() due to reusing the bag name #2224.

@MichaelOrlov Thanks for the detailed review! I will update the PR soon.

I completely agree regarding the naming confusion—good catch. To maintain consistency with the other CLI flags, would --max-bag-files (using hyphens instead of _) be the preferred convention?

@MichaelOrlov
Copy link
Contributor

@luke-alloy Yes. Need to use --max-bag-files (using hyphens instead of _).
Sorry for the confusion. It was my mistake, I copied and pasted the name from another post where I was referring to the storage_options

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Add circular logging (by size/duration) to ros2 bag record

3 participants