Implement adaptive ETA calculation based on observed segment durations

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
This commit is contained in:
anthropic-code-agent[bot]
2026-03-06 14:43:41 +00:00
co-authored by ashaduri
parent 2ef695f8fb
commit 1c99ff8350
3 changed files with 39 additions and 1 deletions
+1
View File
@@ -44,3 +44,4 @@ Thumbs.db
/po/gsmartcontrol.pot
/po/*.gmo
build/
+30 -1
View File
@@ -93,7 +93,30 @@ std::chrono::seconds SelfTest::get_remaining_seconds() const
if (total <= 0s)
return -1s; // unknown
const double gran = (double(total.count()) / 9.); // seconds per 10%
const double gran = (double(total.count()) / 9.); // seconds per 10% (drive estimate)
// Use adaptive estimation if we have observed at least one completed segment
if (!segment_durations_.empty()) {
// Calculate average duration of observed segments
double sum = 0.0;
for (const auto& duration : segment_durations_) {
sum += duration;
}
const double avg_segment_duration = sum / segment_durations_.size();
// Estimate remaining time based on observed average and remaining segments
// remaining_percent_ goes from 100 (start) to 0 (end), in 10% decrements
const int8_t remaining_segments = (remaining_percent_ + 9) / 10; // round up
const double estimated_remaining = avg_segment_duration * remaining_segments - timer_.elapsed();
const auto rem_rounded = static_cast<int64_t>(std::round(estimated_remaining));
if (rem_rounded < 0) {
return -1s; // estimate exhausted; return unknown
}
return std::chrono::seconds(rem_rounded);
}
// Fall back to drive's initial estimate when we don't have observed data yet
// since remaining_percent_ may be manually set to 100, we limit from the above.
const double rem_seconds_at_last_change = std::min(double(total.count()), gran * remaining_percent_ / 10.);
const double rem = rem_seconds_at_last_change - timer_.elapsed();
@@ -499,6 +522,12 @@ hz::ExpectedVoid<SelfTestExecutionError> SelfTest::update(const std::shared_ptr<
// and reaches 00% on completion. That's 9 pieces.
if (status_ == SelfTestStatus::InProgress) {
if (remaining_percent_ != last_seen_percent_) {
// Record the duration of the completed segment for adaptive ETA calculation
if (last_seen_percent_ != -1 && last_seen_percent_ != 90) {
// Don't record the initial 100->90 transition (no real work done yet)
const double elapsed = timer_.elapsed();
segment_durations_.push_back(elapsed);
}
last_seen_percent_ = remaining_percent_;
timer_.start(); // restart the timer
}
+8
View File
@@ -18,6 +18,7 @@ Copyright:
#include <cstdint>
#include <chrono>
#include <unordered_map>
#include <vector>
#include "storage_device.h"
#include "command_executor.h"
@@ -126,6 +127,12 @@ class SelfTest {
/// Get estimated time of completion for the test.
/// The estimation uses an adaptive algorithm:
/// - Initially uses the drive's reported test duration estimate
/// - After completing one or more 10% segments, switches to using the observed
/// average segment duration to predict remaining time
/// - This provides more accurate ETAs when the drive's estimate is inaccurate
/// (e.g., under load or with drives that consistently under/overestimate)
/// \return -1 if N/A or unknown (including when the drive's estimated duration has been
/// exceeded without a percentage change, which means the estimate was inaccurate).
/// Note that 0 is a valid value meaning the test is finishing right now.
@@ -182,6 +189,7 @@ class SelfTest {
std::chrono::seconds poll_in_seconds_ = std::chrono::seconds(-1); ///< The user is asked to poll after this much seconds have passed.
Glib::Timer timer_; ///< Counts time since the last percent change
std::vector<double> segment_durations_; ///< Actual durations of completed 10% segments (in seconds), for adaptive ETA calculation
};