1 /*
2  * Copyright (C) by Markus Goetz <markus@woboq.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12  * for more details.
13  */
14 
15 #include "owncloudpropagator.h"
16 #include "propagatedownload.h"
17 #include "propagateupload.h"
18 #include "propagatorjobs.h"
19 #include "common/utility.h"
20 
21 #ifdef Q_OS_WIN
22 #include <windef.h>
23 #include <winbase.h>
24 #endif
25 
26 #include <QLoggingCategory>
27 #include <QTimer>
28 #include <QObject>
29 
30 namespace OCC {
31 
32 Q_LOGGING_CATEGORY(lcBandwidthManager, "nextcloud.sync.bandwidthmanager", QtInfoMsg)
33 
34 // Because of the many layers of buffering inside Qt (and probably the OS and the network)
35 // we cannot lower this value much more. If we do, the estimated bw will be very high
36 // because the buffers fill fast while the actual network algorithms are not relevant yet.
37 static qint64 relativeLimitMeasuringTimerIntervalMsec = 1000 * 2;
38 // See also WritingState in http://code.woboq.org/qt5/qtbase/src/network/access/qhttpprotocolhandler.cpp.html#_ZN20QHttpProtocolHandler11sendRequestEv
39 
40 // FIXME At some point:
41 //  * Register device only after the QNR received its metaDataChanged() signal
42 //  * Incorporate Qt buffer fill state (it's a negative absolute delta).
43 //  * Incorporate SSL overhead (percentage)
44 //  * For relative limiting, do less measuring and more delaying+giving quota
45 //  * For relative limiting, smoothen measurements
46 
BandwidthManager(OwncloudPropagator * p)47 BandwidthManager::BandwidthManager(OwncloudPropagator *p)
48     : QObject()
49     , _propagator(p)
50     , _relativeLimitCurrentMeasuredDevice(nullptr)
51     , _relativeUploadLimitProgressAtMeasuringRestart(0)
52     , _currentUploadLimit(0)
53     , _relativeLimitCurrentMeasuredJob(nullptr)
54     , _currentDownloadLimit(0)
55 {
56     _currentUploadLimit = _propagator->_uploadLimit;
57     _currentDownloadLimit = _propagator->_downloadLimit;
58 
59     QObject::connect(&_switchingTimer, &QTimer::timeout, this, &BandwidthManager::switchingTimerExpired);
60     _switchingTimer.setInterval(10 * 1000);
61     _switchingTimer.start();
62     QMetaObject::invokeMethod(this, "switchingTimerExpired", Qt::QueuedConnection);
63 
64     // absolute uploads/downloads
65     QObject::connect(&_absoluteLimitTimer, &QTimer::timeout, this, &BandwidthManager::absoluteLimitTimerExpired);
66     _absoluteLimitTimer.setInterval(1000);
67     _absoluteLimitTimer.start();
68 
69     // Relative uploads
70     QObject::connect(&_relativeUploadMeasuringTimer, &QTimer::timeout,
71         this, &BandwidthManager::relativeUploadMeasuringTimerExpired);
72     _relativeUploadMeasuringTimer.setInterval(relativeLimitMeasuringTimerIntervalMsec);
73     _relativeUploadMeasuringTimer.start();
74     _relativeUploadMeasuringTimer.setSingleShot(true); // will be restarted from the delay timer
75     QObject::connect(&_relativeUploadDelayTimer, &QTimer::timeout,
76         this, &BandwidthManager::relativeUploadDelayTimerExpired);
77     _relativeUploadDelayTimer.setSingleShot(true); // will be restarted from the measuring timer
78 
79     // Relative downloads
80     QObject::connect(&_relativeDownloadMeasuringTimer, &QTimer::timeout,
81         this, &BandwidthManager::relativeDownloadMeasuringTimerExpired);
82     _relativeDownloadMeasuringTimer.setInterval(relativeLimitMeasuringTimerIntervalMsec);
83     _relativeDownloadMeasuringTimer.start();
84     _relativeDownloadMeasuringTimer.setSingleShot(true); // will be restarted from the delay timer
85     QObject::connect(&_relativeDownloadDelayTimer, &QTimer::timeout,
86         this, &BandwidthManager::relativeDownloadDelayTimerExpired);
87     _relativeDownloadDelayTimer.setSingleShot(true); // will be restarted from the measuring timer
88 }
89 
90 BandwidthManager::~BandwidthManager() = default;
91 
registerUploadDevice(UploadDevice * p)92 void BandwidthManager::registerUploadDevice(UploadDevice *p)
93 {
94     _absoluteUploadDeviceList.push_back(p);
95     _relativeUploadDeviceList.push_back(p);
96     QObject::connect(p, &QObject::destroyed, this, &BandwidthManager::unregisterUploadDevice);
97 
98     if (usingAbsoluteUploadLimit()) {
99         p->setBandwidthLimited(true);
100         p->setChoked(false);
101     } else if (usingRelativeUploadLimit()) {
102         p->setBandwidthLimited(true);
103         p->setChoked(true);
104     } else {
105         p->setBandwidthLimited(false);
106         p->setChoked(false);
107     }
108 }
109 
unregisterUploadDevice(QObject * o)110 void BandwidthManager::unregisterUploadDevice(QObject *o)
111 {
112     auto p = reinterpret_cast<UploadDevice *>(o); // note, we might already be in the ~QObject
113     _absoluteUploadDeviceList.remove(p);
114     _relativeUploadDeviceList.remove(p);
115     if (p == _relativeLimitCurrentMeasuredDevice) {
116         _relativeLimitCurrentMeasuredDevice = nullptr;
117         _relativeUploadLimitProgressAtMeasuringRestart = 0;
118     }
119 }
120 
registerDownloadJob(GETFileJob * j)121 void BandwidthManager::registerDownloadJob(GETFileJob *j)
122 {
123     _downloadJobList.push_back(j);
124     QObject::connect(j, &QObject::destroyed, this, &BandwidthManager::unregisterDownloadJob);
125 
126     if (usingAbsoluteDownloadLimit()) {
127         j->setBandwidthLimited(true);
128         j->setChoked(false);
129     } else if (usingRelativeDownloadLimit()) {
130         j->setBandwidthLimited(true);
131         j->setChoked(true);
132     } else {
133         j->setBandwidthLimited(false);
134         j->setChoked(false);
135     }
136 }
137 
unregisterDownloadJob(QObject * o)138 void BandwidthManager::unregisterDownloadJob(QObject *o)
139 {
140     auto *j = reinterpret_cast<GETFileJob *>(o); // note, we might already be in the ~QObject
141     _downloadJobList.remove(j);
142     if (_relativeLimitCurrentMeasuredJob == j) {
143         _relativeLimitCurrentMeasuredJob = nullptr;
144         _relativeDownloadLimitProgressAtMeasuringRestart = 0;
145     }
146 }
147 
relativeUploadMeasuringTimerExpired()148 void BandwidthManager::relativeUploadMeasuringTimerExpired()
149 {
150     if (!usingRelativeUploadLimit() || _relativeUploadDeviceList.empty()) {
151         // Not in this limiting mode, just wait 1 sec to continue the cycle
152         _relativeUploadDelayTimer.setInterval(1000);
153         _relativeUploadDelayTimer.start();
154         return;
155     }
156     if (!_relativeLimitCurrentMeasuredDevice) {
157         qCDebug(lcBandwidthManager) << "No device set, just waiting 1 sec";
158         _relativeUploadDelayTimer.setInterval(1000);
159         _relativeUploadDelayTimer.start();
160         return;
161     }
162 
163     qCDebug(lcBandwidthManager) << _relativeUploadDeviceList.size() << "Starting Delay";
164 
165     qint64 relativeLimitProgressMeasured = (_relativeLimitCurrentMeasuredDevice->_readWithProgress
166                                                + _relativeLimitCurrentMeasuredDevice->_read)
167         / 2;
168     qint64 relativeLimitProgressDifference = relativeLimitProgressMeasured - _relativeUploadLimitProgressAtMeasuringRestart;
169     qCDebug(lcBandwidthManager) << _relativeUploadLimitProgressAtMeasuringRestart
170                                 << relativeLimitProgressMeasured << relativeLimitProgressDifference;
171 
172     qint64 speedkBPerSec = (relativeLimitProgressDifference / relativeLimitMeasuringTimerIntervalMsec * 1000) / 1024;
173     qCDebug(lcBandwidthManager) << relativeLimitProgressDifference / 1024 << "kB =>" << speedkBPerSec << "kB/sec on full speed ("
174                                 << _relativeLimitCurrentMeasuredDevice->_readWithProgress << _relativeLimitCurrentMeasuredDevice->_read
175                                 << qAbs(_relativeLimitCurrentMeasuredDevice->_readWithProgress
176                                        - _relativeLimitCurrentMeasuredDevice->_read)
177                                 << ")";
178 
179     qint64 uploadLimitPercent = -_currentUploadLimit;
180     // don't use too extreme values
181     uploadLimitPercent = qMin(uploadLimitPercent, qint64(90));
182     uploadLimitPercent = qMax(qint64(10), uploadLimitPercent);
183     qint64 wholeTimeMsec = (100.0 / uploadLimitPercent) * relativeLimitMeasuringTimerIntervalMsec;
184     qint64 waitTimeMsec = wholeTimeMsec - relativeLimitMeasuringTimerIntervalMsec;
185     qint64 realWaitTimeMsec = waitTimeMsec + wholeTimeMsec;
186     qCDebug(lcBandwidthManager) << waitTimeMsec << " - " << realWaitTimeMsec << " msec for " << uploadLimitPercent << "%";
187 
188     // We want to wait twice as long since we want to give all
189     // devices the same quota we used now since we don't want
190     // any upload to timeout
191     _relativeUploadDelayTimer.setInterval(realWaitTimeMsec);
192     _relativeUploadDelayTimer.start();
193 
194     auto deviceCount = _relativeUploadDeviceList.size();
195     qint64 quotaPerDevice = relativeLimitProgressDifference * (uploadLimitPercent / 100.0) / deviceCount + 1.0;
196     Q_FOREACH (UploadDevice *ud, _relativeUploadDeviceList) {
197         ud->setBandwidthLimited(true);
198         ud->setChoked(false);
199         ud->giveBandwidthQuota(quotaPerDevice);
200         qCDebug(lcBandwidthManager) << "Gave" << quotaPerDevice / 1024.0 << "kB to" << ud;
201     }
202     _relativeLimitCurrentMeasuredDevice = nullptr;
203 }
204 
relativeUploadDelayTimerExpired()205 void BandwidthManager::relativeUploadDelayTimerExpired()
206 {
207     // Switch to measuring state
208     _relativeUploadMeasuringTimer.start(); // always start to continue the cycle
209 
210     if (!usingRelativeUploadLimit()) {
211         return; // oh, not actually needed
212     }
213 
214     if (_relativeUploadDeviceList.empty()) {
215         return;
216     }
217 
218     qCDebug(lcBandwidthManager) << _relativeUploadDeviceList.size() << "Starting measuring";
219 
220     // Take first device and then append it again (= we round robin all devices)
221     _relativeLimitCurrentMeasuredDevice = _relativeUploadDeviceList.front();
222     _relativeUploadDeviceList.pop_front();
223     _relativeUploadDeviceList.push_back(_relativeLimitCurrentMeasuredDevice);
224 
225     _relativeUploadLimitProgressAtMeasuringRestart = (_relativeLimitCurrentMeasuredDevice->_readWithProgress
226                                                          + _relativeLimitCurrentMeasuredDevice->_read)
227         / 2;
228     _relativeLimitCurrentMeasuredDevice->setBandwidthLimited(false);
229     _relativeLimitCurrentMeasuredDevice->setChoked(false);
230 
231     // choke all other UploadDevices
232     Q_FOREACH (UploadDevice *ud, _relativeUploadDeviceList) {
233         if (ud != _relativeLimitCurrentMeasuredDevice) {
234             ud->setBandwidthLimited(true);
235             ud->setChoked(true);
236         }
237     }
238 
239     // now we're in measuring state
240 }
241 
242 // for downloads:
relativeDownloadMeasuringTimerExpired()243 void BandwidthManager::relativeDownloadMeasuringTimerExpired()
244 {
245     if (!usingRelativeDownloadLimit() || _downloadJobList.empty()) {
246         // Not in this limiting mode, just wait 1 sec to continue the cycle
247         _relativeDownloadDelayTimer.setInterval(1000);
248         _relativeDownloadDelayTimer.start();
249         return;
250     }
251     if (!_relativeLimitCurrentMeasuredJob) {
252         qCDebug(lcBandwidthManager) << "No job set, just waiting 1 sec";
253         _relativeDownloadDelayTimer.setInterval(1000);
254         _relativeDownloadDelayTimer.start();
255         return;
256     }
257 
258     qCDebug(lcBandwidthManager) << _downloadJobList.size() << "Starting Delay";
259 
260     qint64 relativeLimitProgressMeasured = _relativeLimitCurrentMeasuredJob->currentDownloadPosition();
261     qint64 relativeLimitProgressDifference = relativeLimitProgressMeasured - _relativeDownloadLimitProgressAtMeasuringRestart;
262     qCDebug(lcBandwidthManager) << _relativeDownloadLimitProgressAtMeasuringRestart
263                                 << relativeLimitProgressMeasured << relativeLimitProgressDifference;
264 
265     qint64 speedkBPerSec = (relativeLimitProgressDifference / relativeLimitMeasuringTimerIntervalMsec * 1000) / 1024;
266     qCDebug(lcBandwidthManager) << relativeLimitProgressDifference / 1024 << "kB =>" << speedkBPerSec << "kB/sec on full speed ("
267                                 << _relativeLimitCurrentMeasuredJob->currentDownloadPosition();
268 
269     qint64 downloadLimitPercent = -_currentDownloadLimit;
270     // don't use too extreme values
271     downloadLimitPercent = qMin(downloadLimitPercent, qint64(90));
272     downloadLimitPercent = qMax(qint64(10), downloadLimitPercent);
273     qint64 wholeTimeMsec = (100.0 / downloadLimitPercent) * relativeLimitMeasuringTimerIntervalMsec;
274     qint64 waitTimeMsec = wholeTimeMsec - relativeLimitMeasuringTimerIntervalMsec;
275     qint64 realWaitTimeMsec = waitTimeMsec + wholeTimeMsec;
276     qCDebug(lcBandwidthManager) << waitTimeMsec << " - " << realWaitTimeMsec << " msec for " << downloadLimitPercent << "%";
277 
278     // We want to wait twice as long since we want to give all
279     // devices the same quota we used now since we don't want
280     // any download to timeout
281     _relativeDownloadDelayTimer.setInterval(realWaitTimeMsec);
282     _relativeDownloadDelayTimer.start();
283 
284     auto jobCount = _downloadJobList.size();
285     qint64 quota = relativeLimitProgressDifference * (downloadLimitPercent / 100.0);
286     if (quota > 20 * 1024) {
287         qCInfo(lcBandwidthManager) << "ADJUSTING QUOTA FROM " << quota << " TO " << quota - 20 * 1024;
288         quota -= 20 * 1024;
289     }
290     qint64 quotaPerJob = quota / jobCount + 1;
291     Q_FOREACH (GETFileJob *gfj, _downloadJobList) {
292         gfj->setBandwidthLimited(true);
293         gfj->setChoked(false);
294         gfj->giveBandwidthQuota(quotaPerJob);
295         qCDebug(lcBandwidthManager) << "Gave" << quotaPerJob / 1024.0 << "kB to" << gfj;
296     }
297     _relativeLimitCurrentMeasuredDevice = nullptr;
298 }
299 
relativeDownloadDelayTimerExpired()300 void BandwidthManager::relativeDownloadDelayTimerExpired()
301 {
302     // Switch to measuring state
303     _relativeDownloadMeasuringTimer.start(); // always start to continue the cycle
304 
305     if (!usingRelativeDownloadLimit()) {
306         return; // oh, not actually needed
307     }
308 
309     if (_downloadJobList.empty()) {
310         qCDebug(lcBandwidthManager) << _downloadJobList.size() << "No jobs?";
311         return;
312     }
313 
314     qCDebug(lcBandwidthManager) << _downloadJobList.size() << "Starting measuring";
315 
316     // Take first device and then append it again (= we round robin all devices)
317     _relativeLimitCurrentMeasuredJob = _downloadJobList.front();
318     _downloadJobList.pop_front();
319     _downloadJobList.push_back(_relativeLimitCurrentMeasuredJob);
320 
321     _relativeDownloadLimitProgressAtMeasuringRestart = _relativeLimitCurrentMeasuredJob->currentDownloadPosition();
322     _relativeLimitCurrentMeasuredJob->setBandwidthLimited(false);
323     _relativeLimitCurrentMeasuredJob->setChoked(false);
324 
325     // choke all other download jobs
326     Q_FOREACH (GETFileJob *gfj, _downloadJobList) {
327         if (gfj != _relativeLimitCurrentMeasuredJob) {
328             gfj->setBandwidthLimited(true);
329             gfj->setChoked(true);
330         }
331     }
332 
333     // now we're in measuring state
334 }
335 
336 // end downloads
337 
switchingTimerExpired()338 void BandwidthManager::switchingTimerExpired()
339 {
340     qint64 newUploadLimit = _propagator->_uploadLimit;
341     if (newUploadLimit != _currentUploadLimit) {
342         qCInfo(lcBandwidthManager) << "Upload Bandwidth limit changed" << _currentUploadLimit << newUploadLimit;
343         _currentUploadLimit = newUploadLimit;
344         Q_FOREACH (UploadDevice *ud, _relativeUploadDeviceList) {
345             if (newUploadLimit == 0) {
346                 ud->setBandwidthLimited(false);
347                 ud->setChoked(false);
348             } else if (newUploadLimit > 0) {
349                 ud->setBandwidthLimited(true);
350                 ud->setChoked(false);
351             } else if (newUploadLimit < 0) {
352                 ud->setBandwidthLimited(true);
353                 ud->setChoked(true);
354             }
355         }
356     }
357     qint64 newDownloadLimit = _propagator->_downloadLimit;
358     if (newDownloadLimit != _currentDownloadLimit) {
359         qCInfo(lcBandwidthManager) << "Download Bandwidth limit changed" << _currentDownloadLimit << newDownloadLimit;
360         _currentDownloadLimit = newDownloadLimit;
361         Q_FOREACH (GETFileJob *j, _downloadJobList) {
362             if (usingAbsoluteDownloadLimit()) {
363                 j->setBandwidthLimited(true);
364                 j->setChoked(false);
365             } else if (usingRelativeDownloadLimit()) {
366                 j->setBandwidthLimited(true);
367                 j->setChoked(true);
368             } else {
369                 j->setBandwidthLimited(false);
370                 j->setChoked(false);
371             }
372         }
373     }
374 }
375 
absoluteLimitTimerExpired()376 void BandwidthManager::absoluteLimitTimerExpired()
377 {
378     if (usingAbsoluteUploadLimit() && !_absoluteUploadDeviceList.empty()) {
379         qint64 quotaPerDevice = _currentUploadLimit / qMax((std::list<UploadDevice *>::size_type)1, _absoluteUploadDeviceList.size());
380         qCDebug(lcBandwidthManager) << quotaPerDevice << _absoluteUploadDeviceList.size() << _currentUploadLimit;
381         Q_FOREACH (UploadDevice *device, _absoluteUploadDeviceList) {
382             device->giveBandwidthQuota(quotaPerDevice);
383             qCDebug(lcBandwidthManager) << "Gave " << quotaPerDevice / 1024.0 << " kB to" << device;
384         }
385     }
386     if (usingAbsoluteDownloadLimit() && !_downloadJobList.empty()) {
387         qint64 quotaPerJob = _currentDownloadLimit / qMax((std::list<GETFileJob *>::size_type)1, _downloadJobList.size());
388         qCDebug(lcBandwidthManager) << quotaPerJob << _downloadJobList.size() << _currentDownloadLimit;
389         Q_FOREACH (GETFileJob *j, _downloadJobList) {
390             j->giveBandwidthQuota(quotaPerJob);
391             qCDebug(lcBandwidthManager) << "Gave " << quotaPerJob / 1024.0 << " kB to" << j;
392         }
393     }
394 }
395 
396 } // namespace OCC
397