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