// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/file_manager/speedometer.h"
#include <algorithm>
#include <limits>
#include "base/logging.h"
#include "base/time/time.h"
namespace file_manager {
void Speedometer::SetTotalBytes(const int64_t total_bytes) {
if (total_bytes != total_bytes_) {
DCHECK_GE(total_bytes, 0);
// The goalposts are moving. Throw away the current samples.
samples_.Clear();
VLOG_IF(1, total_bytes_ > 0)
<< "Total bytes changed from " << total_bytes_ << " to " << total_bytes;
total_bytes_ = total_bytes;
}
}
size_t Speedometer::GetSampleCount() const {
// While the buffer isn't full, we want the CurrentIndex().
return std::min(samples_.CurrentIndex(), samples_.BufferSize());
}
bool Speedometer::Update(const int64_t bytes) {
DCHECK_GE(bytes, 0);
if (total_bytes_ < bytes) {
VLOG_IF(1, total_bytes_ > 0)
<< "Total bytes changed from " << total_bytes_ << " to " << bytes
<< " to match the already processed bytes";
total_bytes_ = bytes;
}
const base::TimeTicks now = base::TimeTicks::Now();
if (const auto it = samples_.End()) {
const Sample& last = **it;
DCHECK_GE(now, last.time);
// Drop this sample if we received the previous one less than 3 second ago.
if (const base::TimeDelta d = now - last.time; d < base::Seconds(3)) {
VLOG(1) << "Dropped sample {bytes: " << bytes
<< "} as the previous one was received " << d << " ago";
return false;
}
if (bytes < last.bytes) {
// Progress is going backwards. Throw away the previous samples.
samples_.Clear();
VLOG(1) << "Progress went backwards from " << last.bytes << " bytes to "
<< bytes << " bytes";
}
}
samples_.SaveToBuffer({now, bytes});
return true;
}
base::TimeDelta Speedometer::GetRemainingTime() const {
auto it = samples_.Begin();
if (!it) {
return base::TimeDelta::Max();
}
const Sample& first = **it;
int n = 1;
double average_bytes = 0;
double average_time = 0;
while (++it) {
const Sample& sample = **it;
DCHECK_GE(sample.bytes, first.bytes);
average_bytes += double(sample.bytes - first.bytes);
DCHECK_GT(sample.time, first.time);
average_time += (sample.time - first.time).InSecondsF();
n++;
}
DCHECK_EQ(size_t(n), GetSampleCount());
if (n < 2) {
return base::TimeDelta::Max();
}
average_bytes /= double(n);
average_time /= double(n);
DCHECK_LE(average_bytes, total_bytes_ - first.bytes);
double variance_time = 0;
double covariance_time_bytes = 0;
for (auto it2 = samples_.Begin(); it2; ++it2) {
const Sample& sample = **it2;
const double time_diff =
(sample.time - first.time).InSecondsF() - average_time;
variance_time += time_diff * time_diff;
covariance_time_bytes +=
time_diff * (double(sample.bytes - first.bytes) - average_bytes);
}
// Speed is the slope of the linear interpolation in bytes per second.
const double speed = covariance_time_bytes / variance_time;
DLOG_IF(FATAL, speed < 0)
<< " speed = " << speed << ", variance_time = " << variance_time
<< ", covariance_time_bytes = " << covariance_time_bytes;
if (!(speed > 0)) {
return base::TimeDelta::Max();
}
// The linear interpolation goes through (average_time, average_bytes).
const double end_time =
(double(total_bytes_ - first.bytes) - average_bytes) / speed +
average_time;
return base::Seconds(end_time) - (base::TimeTicks::Now() - first.time);
}
} // namespace file_manager