Add ETA hysteresis to smooth fluctuations

- Added time_to_seconds() and seconds_to_time() helper functions
- Modified calculate_eta() to use hysteresis (70% last rsync ETA, 30% calculated)
- Static variable in parse_rsync_progress() stores last known rsync ETA
- At 0% progress: use last known ETA if available, otherwise N/A
- Prevents ETA from jumping wildly when rsync alternates between ETA and elapsed time
- Smooths out rounding errors at low percentages
This commit is contained in:
mgutt
2025-10-26 21:33:23 +01:00
parent d764b1e823
commit 6a4ec6ee3e
+46 -14
View File
@@ -61,7 +61,21 @@ function mb_strimhalf($text, $width, $trim_marker = "") {
return mb_substr($text, 0, $half) . $trim_marker . mb_substr($text, -1 - $half);
}
function calculate_eta($transferred, $percent, $speed) {
function time_to_seconds($time_str) {
if (!preg_match('/^(\d+):(\d+):(\d+)$/', $time_str, $matches)) {
return null;
}
return intval($matches[1]) * 3600 + intval($matches[2]) * 60 + intval($matches[3]);
}
function seconds_to_time($seconds) {
$hours = intval($seconds / 3600);
$minutes = intval(($seconds % 3600) / 60);
$secs = $seconds % 60;
return sprintf("%d:%02d:%02d", $hours, $minutes, $secs);
}
function calculate_eta($transferred, $percent, $speed, $last_rsync_eta_seconds = null) {
// Convert transferred size to bytes
$multipliers = ['K' => 1024, 'M' => 1024*1024, 'G' => 1024*1024*1024, 'T' => 1024*1024*1024*1024];
$transferred_bytes = floatval($transferred);
@@ -71,7 +85,7 @@ function calculate_eta($transferred, $percent, $speed) {
break;
}
}
// Convert speed to bytes/sec
$speed_bytes = floatval($speed);
if (stripos($speed, 'kB/s') !== false) {
@@ -81,25 +95,40 @@ function calculate_eta($transferred, $percent, $speed) {
} elseif (stripos($speed, 'GB/s') !== false) {
$speed_bytes *= 1024 * 1024 * 1024;
}
// Calculate total size from percent
$percent_val = intval(str_replace('%', '', $percent));
// At 0%, we cannot calculate ETA reliably from transferred/percent
// but we can use the last known rsync ETA if available
if ($percent_val == 0) {
if ($last_rsync_eta_seconds !== null && $last_rsync_eta_seconds > 0) {
return seconds_to_time($last_rsync_eta_seconds);
}
return "N/A";
}
if ($percent_val > 0 && $percent_val < 100 && $speed_bytes > 0) {
$total_bytes = $transferred_bytes * 100 / $percent_val;
$remaining_bytes = $total_bytes - $transferred_bytes;
$eta_seconds = intval($remaining_bytes / $speed_bytes);
$calculated_eta_seconds = intval($remaining_bytes / $speed_bytes);
// Format as HH:MM:SS
$hours = intval($eta_seconds / 3600);
$minutes = intval(($eta_seconds % 3600) / 60);
$seconds = $eta_seconds % 60;
return sprintf("%d:%02d:%02d", $hours, $minutes, $seconds);
// Apply hysteresis: blend with last known rsync ETA to smooth out fluctuations
if ($last_rsync_eta_seconds !== null && $last_rsync_eta_seconds > 0) {
// Weight: 70% last rsync ETA, 30% calculated ETA
$eta_seconds = intval($last_rsync_eta_seconds * 0.7 + $calculated_eta_seconds * 0.3);
} else {
$eta_seconds = $calculated_eta_seconds;
}
return seconds_to_time($eta_seconds);
}
return "N/A";
}
function parse_rsync_progress($status, $action_label) {
static $last_rsync_eta_seconds = null;
// initialize text array with action label
$text[0] = $action_label . "... ";
@@ -118,14 +147,17 @@ function parse_rsync_progress($status, $action_label) {
$percent = $parts[1];
$speed = $parts[2];
$time = $parts[3];
// Check if this is an ETA line or elapsed time line
// ETA lines have only 4 parts, elapsed time lines have additional (xfr#...) info
if (isset($parts[4])) {
// Calculate our own ETA from transferred size, percent, and speed
$time = calculate_eta($transferred, $percent, $speed);
// Elapsed time line - calculate our own ETA with hysteresis
$time = calculate_eta($transferred, $percent, $speed, $last_rsync_eta_seconds);
} else {
// ETA line from rsync - store it for hysteresis
$last_rsync_eta_seconds = time_to_seconds($time);
}
$text[1] = _('Completed') . ": " . $percent . ", " . _('Speed') . ": " . $speed . ", " . _('ETA') . ": " . $time;
}
}