diff --git a/ROMFS/px4fmu_common/init.d-posix/rc.replay b/ROMFS/px4fmu_common/init.d-posix/rc.replay index ea0510280f..46c108836e 100644 --- a/ROMFS/px4fmu_common/init.d-posix/rc.replay +++ b/ROMFS/px4fmu_common/init.d-posix/rc.replay @@ -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 diff --git a/ROMFS/px4fmu_common/init.d-posix/rcS b/ROMFS/px4fmu_common/init.d-posix/rcS index cbaf724d9f..13813fa989 100644 --- a/ROMFS/px4fmu_common/init.d-posix/rcS +++ b/ROMFS/px4fmu_common/init.d-posix/rcS @@ -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 diff --git a/ROMFS/px4fmu_common/init.d/airframes/1002_standard_vtol.hil b/ROMFS/px4fmu_common/init.d/airframes/1002_standard_vtol.hil index 4129d2569c..1a983e6cf0 100644 --- a/ROMFS/px4fmu_common/init.d/airframes/1002_standard_vtol.hil +++ b/ROMFS/px4fmu_common/init.d/airframes/1002_standard_vtol.hil @@ -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 diff --git a/boards/gearup/airbrainh743/init/rc.board_defaults b/boards/gearup/airbrainh743/init/rc.board_defaults index 4ef2ce41ee..b6687723b7 100644 --- a/boards/gearup/airbrainh743/init/rc.board_defaults +++ b/boards/gearup/airbrainh743/init/rc.board_defaults @@ -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 diff --git a/boards/holybro/kakuteh7v2/init/rc.board_defaults b/boards/holybro/kakuteh7v2/init/rc.board_defaults index 519ca2aee4..79298940d9 100644 --- a/boards/holybro/kakuteh7v2/init/rc.board_defaults +++ b/boards/holybro/kakuteh7v2/init/rc.board_defaults @@ -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 diff --git a/docs/en/dev_log/logging.md b/docs/en/dev_log/logging.md index af68761902..91fd5eb4d7 100644 --- a/docs/en/dev_log/logging.md +++ b/docs/en/dev_log/logging.md @@ -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: diff --git a/src/modules/logger/logger.cpp b/src/modules/logger/logger.cpp index 7cb4e89e09..a42ecbc190 100644 --- a/src/modules/logger/logger.cpp +++ b/src/modules/logger/logger.cpp @@ -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 } diff --git a/src/modules/logger/logger.h b/src/modules/logger/logger.h index 54ba072b27..d46772fe90 100644 --- a/src/modules/logger/logger.h +++ b/src/modules/logger/logger.h @@ -401,8 +401,8 @@ private: DEFINE_PARAMETERS( (ParamInt) _param_sdlog_utc_offset, - (ParamInt) _param_sdlog_dirs_max, (ParamInt) _param_sdlog_max_size, + (ParamInt) _param_sdlog_rotate, (ParamInt) _param_sdlog_profile, (ParamInt) _param_sdlog_mission, (ParamBool) _param_sdlog_boot_bat, diff --git a/src/modules/logger/module.yaml b/src/modules/logger/module.yaml index 48b3b522fe..63fda27c83 100644 --- a/src/modules/logger/module.yaml +++ b/src/modules/logger/module.yaml @@ -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 diff --git a/src/modules/logger/util.cpp b/src/modules/logger/util.cpp index 691c154fec..5e6ab4b3f2 100644 --- a/src/modules/logger/util.cpp +++ b/src/modules/logger/util.cpp @@ -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 diff --git a/src/modules/logger/util.h b/src/modules/logger/util.h index ae5bf15182..d3aebbf70e 100644 --- a/src/modules/logger/util.h +++ b/src/modules/logger/util.h @@ -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