Files
2025-12-10 14:38:26 -08:00

227 lines
7.2 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "speed_test.h"
#include <zen/basic_math.h>
#include <zen/i18n.h>
#include <zen/format_unit.h>
using namespace zen;
using namespace fff;
void SpeedTest::addSample(std::chrono::nanoseconds timeElapsed, int itemsCurrent, int64_t bytesCurrent)
{
//time expected to be monotonously ascending
assert(samples_.empty() || samples_.back().timeElapsed <= timeElapsed);
samples_.push_back(Sample{timeElapsed, itemsCurrent, bytesCurrent});
//remove old records outside of "window"
std::optional<Sample> lastPop;
while (!samples_.empty() && samples_.front().timeElapsed <= timeElapsed - windowSize_)
{
lastPop = samples_.front();
samples_.pop_front();
}
if (lastPop) //keep one point before new start to handle gaps
samples_.push_front(*lastPop);
}
std::optional<double> SpeedTest::getRemainingSec(int /*itemsRemaining*/, int64_t bytesRemaining) const
{
if (!samples_.empty())
{
const double timeDelta = std::chrono::duration<double>(samples_.back().timeElapsed - samples_.front().timeElapsed).count();
const int64_t bytesDelta = samples_.back().bytes - samples_.front().bytes;
//"items" counts logical operations *NOT* disk accesses, so we better play safe and use "bytes" only!
if (bytesDelta != 0) //sign(dataRemaining) != sign(bytesDelta) usually an error, so show it!
return bytesRemaining * timeDelta / bytesDelta;
}
return std::nullopt;
}
std::optional<double> SpeedTest::getBytesPerSec() const
{
if (!samples_.empty())
{
const double timeDelta = std::chrono::duration<double>(samples_.back().timeElapsed - samples_.front().timeElapsed).count();
const int64_t bytesDelta = samples_.back().bytes - samples_.front().bytes;
if (!numeric::isNull(timeDelta))
return bytesDelta / timeDelta;
}
return std::nullopt;
}
std::optional<double> SpeedTest::getItemsPerSec() const
{
if (!samples_.empty())
{
const double timeDelta = std::chrono::duration<double>(samples_.back().timeElapsed - samples_.front().timeElapsed).count();
const int itemsDelta = samples_.back().items - samples_.front().items;
if (!numeric::isNull(timeDelta))
return itemsDelta / timeDelta;
}
return std::nullopt;
}
std::wstring SpeedTest::getBytesPerSecFmt() const
{
if (const std::optional<double> bps = getBytesPerSec())
return replaceCpy(_("%x/sec"), L"%x", formatFilesizeShort(std::llround(*bps)));
return {};
}
std::wstring SpeedTest::getItemsPerSecFmt() const
{
if (const std::optional<double> ips = getItemsPerSec())
return replaceCpy(_("%x/sec"), L"%x", replaceCpy(_("%x items"), L"%x", formatTwoDigitPrecision(*ips)));
return {};
}
/*
class for calculation of remaining time:
----------------------------------------
"filesize |-> time" is an affine linear function f(x) = z_1 + z_2 x
For given n measurements, sizes x_0, ..., x_n and times f_0, ..., f_n, the function f (as a polynom of degree 1) can be lineary approximated by
z_1 = (r - s * q / p) / ((n + 1) - s * s / p)
z_2 = (q - s * z_1) / p = (r - (n + 1) z_1) / s
with
p := x_0^2 + ... + x_n^2
q := f_0 x_0 + ... + f_n x_n
r := f_0 + ... + f_n
s := x_0 + ... + x_n
=> the time to process N files with amount of data D is: N * z_1 + D * z_2
Problem:
--------
Times f_0, ..., f_n can be very small so that precision of the PC clock is poor.
=> Times have to be accumulated to enhance precision:
Copying of m files with sizes x_i and times f_i (i = 1, ..., m) takes sum_i f(x_i) := m * z_1 + z_2 * sum x_i = sum f_i
With X defined as the accumulated sizes and F the accumulated times this gives: (in theory...)
m * z_1 + z_2 * X = F <=>
z_1 + z_2 * X / m = F / m
=> we obtain a new (artificial) measurement with size X / m and time F / m to be used in the linear approximation above
Statistics::Statistics(int totalObjectCount, double totalDataAmount, unsigned recordCount) :
itemsTotal(totalObjectCount),
bytesTotal(totalDataAmount),
recordsMax(recordCount),
objectsLast(0),
dataLast(0),
timeLast(wxGetLocalTimeMillis()),
z1_current(0),
z2_current(0),
dummyRecordPresent(false) {}
wxString Statistics::getRemainingTime(int objectsCurrent, double dataCurrent)
{
//add new measurement point
const int m = objectsCurrent - objectsLast;
if (m != 0)
{
objectsLast = objectsCurrent;
const double X = dataCurrent - dataLast;
dataLast = dataCurrent;
const int64_t timeCurrent = wxGetLocalTimeMillis();
const double F = (timeCurrent - timeLast).ToDouble();
timeLast = timeCurrent;
record newEntry;
newEntry.x_i = X / m;
newEntry.f_i = F / m;
//remove dummy record
if (dummyRecordPresent)
{
measurements.pop_back();
dummyRecordPresent = false;
}
//insert new record
measurements.push_back(newEntry);
if (measurements.size() > recordsMax)
measurements.pop_front();
}
else //dataCurrent increased without processing new objects:
{ //modify last measurement until m != 0
const double X = dataCurrent - dataLast; //do not set dataLast, timeLast variables here, but write dummy record instead
if (!isNull(X))
{
const int64_t timeCurrent = wxGetLocalTimeMillis();
const double F = (timeCurrent - timeLast).ToDouble();
record modifyEntry;
modifyEntry.x_i = X;
modifyEntry.f_i = F;
//insert dummy record
if (!dummyRecordPresent)
{
measurements.push_back(modifyEntry);
if (measurements.size() > recordsMax)
measurements.pop_front();
dummyRecordPresent = true;
}
else //modify dummy record
measurements.back() = modifyEntry;
}
}
//calculate remaining time based on stored measurement points
double p = 0;
double q = 0;
double r = 0;
double s = 0;
for (const record& rec : measurements)
{
const double x_i = rec.x_i;
const double f_i = rec.f_i;
p += x_i * x_i;
q += f_i * x_i;
r += f_i;
s += x_i;
}
if (!isNull(p))
{
const double n = measurements.size();
const double tmp = (n - s * s / p);
if (!isNull(tmp) && !isNull(s))
{
const double z1 = (r - s * q / p) / tmp;
const double z2 = (r - n * z1) / s; //not (n + 1) here, since n already is the number of measurements
//refresh current values for z1, z2
z1_current = z1;
z2_current = z2;
}
}
return formatRemainingTime((itemsTotal - objectsCurrent) * z1_current + (bytesTotal - dataCurrent) * z2_current);
}
*/