summaryrefslogtreecommitdiff
path: root/lib/statistics.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:15:16 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:15:16 +0200
commitbd6336c629841c6db3a6ca53a936d629d34db53b (patch)
tree3721ef997864108df175ce677a8a7d4342a6f1d2 /lib/statistics.cpp
parent4.0 (diff)
downloadFreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.gz
FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.bz2
FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.zip
4.1
Diffstat (limited to 'lib/statistics.cpp')
-rw-r--r--lib/statistics.cpp279
1 files changed, 279 insertions, 0 deletions
diff --git a/lib/statistics.cpp b/lib/statistics.cpp
new file mode 100644
index 00000000..9924091c
--- /dev/null
+++ b/lib/statistics.cpp
@@ -0,0 +1,279 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "statistics.h"
+
+#include <wx/ffile.h>
+#include <zen/basic_math.h>
+#include "status_handler.h"
+#include <wx+/format_unit.h>
+#include <limits>
+#include <wx/stopwatch.h>
+#include <zen/assert_static.h>
+#include <zen/i18n.h>
+
+
+using namespace zen;
+
+
+RetrieveStatistics::~RetrieveStatistics()
+{
+ //write statistics to a file
+ wxFFile outputFile(wxT("statistics.dat"), wxT("w"));
+
+ outputFile.Write(wxT("Time(ms);Objects;Data\n"));
+
+ std::for_each(data.begin(), data.end(),
+ [&](const StatEntry& entry)
+ {
+ outputFile.Write(toString<wxString>(entry.time));
+ outputFile.Write(wxT(";"));
+ outputFile.Write(toString<wxString>(entry.objects));
+ outputFile.Write(wxT(";"));
+ outputFile.Write(toString<wxString>(entry.value));
+ outputFile.Write(wxT("\n"));
+ });
+}
+
+
+void RetrieveStatistics::writeEntry(double value, int objects)
+{
+ StatEntry newEntry;
+ newEntry.value = value;
+ newEntry.objects = objects;
+ newEntry.time = timer.Time();
+ data.push_back(newEntry);
+}
+
+
+//########################################################################################
+Statistics::Statistics(int totalObjectCount,
+ double totalDataAmount,
+ unsigned windowSizeRemainingTime,
+ unsigned windowSizeBytesPerSecond) :
+ objectsTotal(totalObjectCount),
+ dataTotal(totalDataAmount),
+ windowSizeRemTime(windowSizeRemainingTime),
+ windowSizeBPS(windowSizeBytesPerSecond),
+ windowMax(std::max(windowSizeRemainingTime, windowSizeBytesPerSecond)) {}
+
+
+void Statistics::addMeasurement(int objectsCurrent, double dataCurrent)
+{
+ Record newRecord;
+ newRecord.objects = objectsCurrent;
+ newRecord.data = dataCurrent;
+
+ const long now = timer.Time();
+
+ measurements.insert(measurements.end(), std::make_pair(now, newRecord)); //use fact that time is monotonously ascending
+
+ //remove all records earlier than "now - windowMax"
+ const long newBegin = now - windowMax;
+ TimeRecordMap::iterator windowBegin = measurements.upper_bound(newBegin);
+ if (windowBegin != measurements.begin())
+ measurements.erase(measurements.begin(), --windowBegin); //retain one point before newBegin in order to handle "measurement holes"
+}
+
+
+wxString Statistics::getRemainingTime() const
+{
+ if (!measurements.empty())
+ {
+ const TimeRecordMap::value_type& backRecord = *measurements.rbegin();
+ //find start of records "window"
+ const long frontTime = backRecord.first - windowSizeRemTime;
+ TimeRecordMap::const_iterator windowBegin = measurements.upper_bound(frontTime);
+ if (windowBegin != measurements.begin())
+ --windowBegin; //one point before window begin in order to handle "measurement holes"
+
+ const TimeRecordMap::value_type& frontRecord = *windowBegin;
+ //-----------------------------------------------------------------------------------------------
+ const double timeDelta = backRecord.first - frontRecord.first;
+ const double dataDelta = backRecord.second.data - frontRecord.second.data;
+
+ const double dataRemaining = dataTotal - backRecord.second.data;
+
+ if (!numeric::isNull(dataDelta))
+ {
+ int remTimeSec = dataRemaining * timeDelta / (1000.0 * dataDelta);
+ return zen::remainingTimeToShortString(remTimeSec);
+ }
+ }
+
+ return wxT("-"); //fallback
+}
+
+
+wxString Statistics::getBytesPerSecond() const
+{
+ if (!measurements.empty())
+ {
+ const TimeRecordMap::value_type& backRecord = *measurements.rbegin();
+ //find start of records "window"
+ const long frontTime = backRecord.first - windowSizeBPS;
+ TimeRecordMap::const_iterator windowBegin = measurements.upper_bound(frontTime);
+ if (windowBegin != measurements.begin())
+ --windowBegin; //one point before window begin in order to handle "measurement holes"
+
+ const TimeRecordMap::value_type& frontRecord = *windowBegin;
+ //-----------------------------------------------------------------------------------------------
+ const double timeDelta = backRecord.first - frontRecord.first;
+ const double dataDelta = backRecord.second.data - frontRecord.second.data;
+
+ if (!numeric::isNull(timeDelta))
+ if (dataDelta > 0) //may be negative if user cancels copying
+ return zen::filesizeToShortString(zen::UInt64(dataDelta * 1000 / timeDelta)) + _("/sec");
+ }
+
+ return wxT("-"); //fallback
+}
+
+
+void Statistics::pauseTimer()
+{
+ timer.Pause();
+}
+
+
+void Statistics::resumeTimer()
+{
+ timer.Resume();
+}
+
+/*
+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(const int totalObjectCount, const double totalDataAmount, const unsigned recordCount) :
+ objectsTotal(totalObjectCount),
+ dataTotal(totalDataAmount),
+ recordsMax(recordCount),
+ objectsLast(0),
+ dataLast(0),
+ timeLast(wxGetLocalTimeMillis()),
+ z1_current(0),
+ z2_current(0),
+ dummyRecordPresent(false) {}
+
+
+wxString Statistics::getRemainingTime(const int objectsCurrent, const double dataCurrent)
+{
+ //add new measurement point
+ const int m = objectsCurrent - objectsLast;
+ if (m != 0)
+ {
+ objectsLast = objectsCurrent;
+
+ const double X = dataCurrent - dataLast;
+ dataLast = dataCurrent;
+
+ const zen::Int64 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 zen::Int64 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 (std::list<record>::const_iterator i = measurements.begin(); i != measurements.end(); ++i)
+ {
+ const double x_i = i->x_i;
+ const double f_i = i->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((objectsTotal - objectsCurrent) * z1_current + (dataTotal - dataCurrent) * z2_current);
+}
+
+*/
bgstack15