feat(logger): add SDLOG_ROTATE, disentangle cleanup thresholds

The previous cleanup logic conflated two independent concerns into
SDLOG_MAX_SIZE: the maximum size of a single log file (rotation
trigger) AND the minimum free space to maintain. That was broken:
the 4095 MB default meant "keep 4 GB free", which over-cleaned on
large SD cards and was impossible to satisfy on small flash.

Disentangle the two:

- SDLOG_ROTATE (new, int %, default 90): maximum disk usage percentage.
  Cleanup guarantees at least (100 - SDLOG_ROTATE)% free even during
  writing of a new log file. Setting 0 disables space-based cleanup;
  100 allows filling the disk completely.
- SDLOG_MAX_SIZE (default lowered from 4095 to 1024): pure max file
  size. The value is added on top of the rotate-derived threshold as
  headroom for the next file write, so the rotate guarantee holds
  even mid-write.
- SDLOG_DIRS_MAX: removed. Directory count limits were confusing and
  orthogonal to the space-management goal; free-space cleanup alone
  covers the use case. Drop the param and all remaining overrides
  (rc.board_defaults, rcS, rc.replay, 1002_standard_vtol.hil).

Cleanup threshold is now:
    ((100 - SDLOG_ROTATE)% of disk) + SDLOG_MAX_SIZE

Small-flash boards can override SDLOG_MAX_SIZE to get more retained
logs within the available space. kakuteh7v2 and airbrainh743 drop
their SDLOG_DIRS_MAX overrides accordingly.

Update docs/en/dev_log/logging.md with the new semantics and a
worked example for the typical 8 GB SD case.
This commit is contained in:
Julian Oes
2026-04-11 06:35:58 +12:00
parent 7f76b71526
commit 1a96bd2c94
11 changed files with 79 additions and 72 deletions
@@ -20,7 +20,6 @@ module: replay
ignore_others: false
EOF
param set SDLOG_DIRS_MAX 7
param set SDLOG_PROFILE 3
# apply all params before ekf starts, as some params cannot be changed after startup
-1
View File
@@ -172,7 +172,6 @@ param set-default -s MC_AT_EN 1
param set-default SDLOG_MODE 1
# enable default, estimator replay and vision/avoidance logging profiles
param set-default SDLOG_PROFILE 131
param set-default SDLOG_DIRS_MAX 7
param set-default TRIG_INTERFACE 3
@@ -56,8 +56,6 @@ param set-default NAV_DLL_ACT 2
param set-default RTL_DESCEND_ALT 10
param set-default RTL_RETURN_ALT 30
param set-default SDLOG_DIRS_MAX 7
param set-default VT_F_TRANS_THR 0.75
param set-default VT_TYPE 2
@@ -12,6 +12,10 @@ param set-default CBRK_SUPPLY_CHK 894281
param set-default IMU_GYRO_RATEMAX 2000
# W25N NAND flash with littlefs (128 MB): larger buffer, auto-rotate
set LOGGER_BUF 32
param set-default SDLOG_DIRS_MAX 3
# W25N NAND flash with littlefs (128 MB): small log file size so we can keep
# a few recent logs. Default SDLOG_ROTATE=90 keeps at least 10% free during
# writing (no bad block management yet, so avoid hammering the flash near full).
param set-default SDLOG_MAX_SIZE 40
# Store missions in RAM
param set-default SYS_DM_BACKEND 1
@@ -38,7 +38,7 @@ param set-default SYS_DM_BACKEND 1
# Ignore that there is no SD card
param set-default COM_ARM_SDCARD 0
# W25N NAND flash with littlefs (128 MB): larger buffer, auto-rotate
set LOGGER_BUF 32
param set-default SDLOG_DIRS_MAX 3
# W25N NAND flash with littlefs (128 MB): small log file size so we can keep
# a few recent logs. Default SDLOG_ROTATE=90 keeps at least 10% free during
# writing (no bad block management yet, so avoid hammering the flash near full).
param set-default SDLOG_MAX_SIZE 30
+12 -3
View File
@@ -86,10 +86,19 @@ There are several scripts to analyze and convert logging files in the [pyulog](h
## Log Cleanup
PX4 automatically manages log storage by cleaning up old logs when starting to log.
Cleanup is triggered based on two criteria:
Two parameters control how much space logs may use:
- **Storage-based cleanup**: Ensures minimum free space (300 MB or 10% of disk, whichever is smaller) is available.
- **Count-based cleanup**: If [SDLOG_DIRS_MAX](../advanced_config/parameter_reference.md#SDLOG_DIRS_MAX) is set, limits the total number of log directories.
- [SDLOG_ROTATE](../advanced_config/parameter_reference.md#SDLOG_ROTATE) is the maximum disk usage percentage (default 90).
Cleanup ensures at least `(100 - SDLOG_ROTATE)%` of the disk stays free at all times, **even while writing a new log file**.
Setting it to `0` disables space-based cleanup entirely; setting it to `100` lets logs fill the disk completely.
- [SDLOG_MAX_SIZE](../advanced_config/parameter_reference.md#SDLOG_MAX_SIZE) is the maximum size of a single log file in MB
(default 1024). It also reserves headroom so that a full new file always fits after cleanup.
At log start, the cleanup threshold is `((100 - SDLOG_ROTATE)% of disk) + SDLOG_MAX_SIZE`.
Oldest logs are deleted until the free space meets this threshold.
For example, on an 8 GB card with defaults, cleanup keeps at least `820 + 1024 = ~1.8 GB` free at log start,
so ~6 GB is usable for logs and disk usage never exceeds 90% during writing.
Small flash targets override `SDLOG_MAX_SIZE` to a smaller value to keep more logs within the available space.
The cleanup algorithm prioritizes deleting logs from the directory naming scheme not currently in use.
PX4 uses two directory naming schemes:
+5 -2
View File
@@ -1449,11 +1449,14 @@ void Logger::start_log_file(LogType type)
_max_log_file_size = 0; // unlimited
}
// Cleanup old logs if needed (storage-based and/or count-based)
// Cleanup old logs if needed.
// SDLOG_ROTATE is the max disk-usage percentage; cleanup ensures at least
// (100 - rotate)% is free even during writing. SDLOG_MAX_SIZE is passed so
// there's always room for the next log file on top of the free-space target.
hrt_abstime cleanup_start = hrt_absolute_time();
if (util::cleanup_old_logs(LOG_ROOT[(int)LogType::Full], _mavlink_log_pub,
(uint32_t)max_size_mb, _param_sdlog_dirs_max.get()) == 1) {
(uint32_t)_param_sdlog_rotate.get(), (uint32_t)max_size_mb) == 1) {
return; // Not enough space even after cleanup
}
+1 -1
View File
@@ -401,8 +401,8 @@ private:
DEFINE_PARAMETERS(
(ParamInt<px4::params::SDLOG_UTC_OFFSET>) _param_sdlog_utc_offset,
(ParamInt<px4::params::SDLOG_DIRS_MAX>) _param_sdlog_dirs_max,
(ParamInt<px4::params::SDLOG_MAX_SIZE>) _param_sdlog_max_size,
(ParamInt<px4::params::SDLOG_ROTATE>) _param_sdlog_rotate,
(ParamInt<px4::params::SDLOG_PROFILE>) _param_sdlog_profile,
(ParamInt<px4::params::SDLOG_MISSION>) _param_sdlog_mission,
(ParamBool<px4::params::SDLOG_BOOT_BAT>) _param_sdlog_boot_bat,
+21 -19
View File
@@ -102,34 +102,36 @@ parameters:
min: 0
max: 4095
reboot_required: true
SDLOG_DIRS_MAX:
description:
short: Maximum number of log directories to keep
long: 'If there are more log directories than this value, the system will
delete the oldest directories when starting to log. Cleanup prioritizes
directories from the naming scheme not currently in use (e.g., sess dirs
are deleted first when date dirs are being used). If set to 0, directories
are only removed based on storage space. The minimum free space threshold
is 300 MB or 10% of disk size, whichever is smaller. Note: this does not
apply to mission log files.'
type: int32
default: 0
min: 0
max: 1000
reboot_required: false
SDLOG_MAX_SIZE:
description:
short: Maximum log file size
long: 'Maximum size of a single log file in megabytes. When reached,
the log file is closed and a new one is started.
Cleanup of old logs always happens at log start (not boot) to allow
downloading logs via FTP before deletion.
the log file is closed and a new one is started. This value is also
added to the cleanup threshold (see SDLOG_ROTATE) to reserve headroom
for the next log file. A value of 0 disables both file rotation and
the cleanup reservation.
Must stay below the FAT32 file size limit of 4 GiB.'
type: int32
default: 4095
default: 1024
min: 0
max: 4095
reboot_required: false
SDLOG_ROTATE:
description:
short: Maximum disk usage percentage
long: 'Maximum percentage of disk space that logs may occupy during operation,
including while writing a new log file. For example, a value of 90 means
at least 10% of disk is always kept free, even while writing. A value of
100 lets logs fill the disk completely. A value of 0 disables space-based
cleanup entirely. At log start, oldest logs are deleted as needed to
maintain this guarantee, accounting for the next file write of up to
SDLOG_MAX_SIZE. Cleanup always happens at log start (not boot) so logs
can be downloaded via FTP before deletion.'
type: int32
default: 90
min: 0
max: 100
reboot_required: false
SDLOG_UUID:
description:
short: Log UUID
+18 -34
View File
@@ -138,7 +138,7 @@ bool scan_log_directories(const char *log_root_dir, LogDirInfo &info)
}
int cleanup_old_logs(const char *log_root_dir, orb_advert_t &mavlink_log_pub,
uint32_t target_free_mb, int32_t max_log_dirs_to_keep)
uint32_t rotate_pct, uint32_t max_file_size_mb)
{
uint64_t avail_bytes = 0;
uint64_t total_bytes = 0;
@@ -147,25 +147,21 @@ int cleanup_old_logs(const char *log_root_dir, orb_advert_t &mavlink_log_pub,
return PX4_ERROR;
}
// Calculate cleanup threshold
uint64_t cleanup_threshold;
// Calculate cleanup threshold. rotate_pct is the maximum allowed disk usage;
// we guarantee the disk never exceeds rotate_pct% full even during writing of
// a new log file. So at cleanup time, free space must be at least
// ((100 - rotate_pct)% of disk) + max log file size, where the latter term
// reserves headroom for the next file write.
// rotate_pct == 0 disables space-based cleanup entirely.
uint64_t cleanup_threshold = 0;
if (target_free_mb > 0) {
cleanup_threshold = (uint64_t)target_free_mb * 1024ULL * 1024ULL;
} else {
// Default: 300 MiB or 10% of disk, whichever is smaller
cleanup_threshold = 300ULL * 1024ULL * 1024ULL;
if (total_bytes / 10 < cleanup_threshold) {
cleanup_threshold = total_bytes / 10;
}
if (rotate_pct > 0 && rotate_pct <= 100) {
cleanup_threshold = (total_bytes * (100 - rotate_pct)) / 100;
cleanup_threshold += (uint64_t)max_file_size_mb * 1024ULL * 1024ULL;
}
// Early out if we have enough space and no directory limit
bool need_space_cleanup = avail_bytes < cleanup_threshold;
if (!need_space_cleanup && max_log_dirs_to_keep <= 0) {
// Early out if we have enough space
if (avail_bytes >= cleanup_threshold) {
return PX4_OK;
}
@@ -176,26 +172,18 @@ int cleanup_old_logs(const char *log_root_dir, orb_advert_t &mavlink_log_pub,
return PX4_OK; // ignore if we cannot access the log directory
}
int total_dirs = info.num_sess + info.num_dates;
bool need_count_cleanup = (max_log_dirs_to_keep > 0) && (total_dirs > max_log_dirs_to_keep);
if (!need_space_cleanup && !need_count_cleanup) {
return PX4_OK;
}
PX4_INFO("Log cleanup: %u MiB free, threshold %u MiB, %d dirs (max %" PRId32 ")",
(unsigned)(avail_bytes / 1024U / 1024U), (unsigned)(cleanup_threshold / 1024U / 1024U),
total_dirs, max_log_dirs_to_keep > 0 ? max_log_dirs_to_keep : -1);
PX4_INFO("Log cleanup: %u MiB free, threshold %u MiB",
(unsigned)(avail_bytes / 1024U / 1024U), (unsigned)(cleanup_threshold / 1024U / 1024U));
// Determine if we currently have valid time (using date dirs) or not (using sess dirs)
// Delete from the "other" scheme first to avoid deleting current log
uint64_t utc_time_usec;
bool have_time = get_log_time(utc_time_usec, 0, false);
// Cleanup oldest .ulg files one by one until conditions are met
// Cleanup oldest .ulg files one by one until we have enough free space
int empty_dir_failures = 0;
while (need_space_cleanup || need_count_cleanup) {
while (avail_bytes < cleanup_threshold) {
char oldest_file[LOG_DIR_LEN] = "";
char oldest_dir[LOG_DIR_LEN];
@@ -203,7 +191,6 @@ int cleanup_old_logs(const char *log_root_dir, orb_advert_t &mavlink_log_pub,
break;
}
total_dirs = info.num_sess + info.num_dates;
bool found_sess = info.num_sess > 0;
bool found_date = info.num_dates > 0;
@@ -299,13 +286,10 @@ int cleanup_old_logs(const char *log_root_dir, orb_advert_t &mavlink_log_pub,
break;
}
// Re-check conditions
// Re-check free space
if (!get_free_space(log_root_dir, &avail_bytes, nullptr)) {
break;
}
need_space_cleanup = avail_bytes < cleanup_threshold;
need_count_cleanup = (max_log_dirs_to_keep > 0) && (total_dirs > max_log_dirs_to_keep);
}
// Final check: if still not enough space, refuse to log
+12 -3
View File
@@ -87,14 +87,23 @@ bool scan_log_directories(const char *log_root_dir, LogDirInfo &info);
* Cleanup old logs to ensure sufficient free space. Deletes oldest files,
* preferring the opposite directory type first (sess dirs when time is known,
* date dirs when it is not), then falls back to its own type.
*
* The cleanup threshold is computed as:
* ((100 - rotate_pct) / 100) * disk_size + max_file_size_mb
*
* i.e. after cleanup there is enough free space to write one more full log
* file while still maintaining the rotate_pct usage guarantee.
*
* @param log_root_dir log root directory
* @param mavlink_log_pub mavlink log publisher
* @param target_free_mb target free space in MB (0 = use default minimum)
* @param max_log_dirs_to_keep maximum log directories to keep (0 = unlimited)
* @param rotate_pct maximum disk usage percentage (0 disables space-based
* cleanup; 90 keeps at least 10% free during writing)
* @param max_file_size_mb maximum log file size in MB; reserved as headroom
* for the next log file write
* @return 0 on success, 1 if not enough space even after cleanup
*/
int cleanup_old_logs(const char *log_root_dir, orb_advert_t &mavlink_log_pub,
uint32_t target_free_mb, int32_t max_log_dirs_to_keep);
uint32_t rotate_pct, uint32_t max_file_size_mb);
/**
* Get UTC time in microseconds from CLOCK_REALTIME