bandwidthmanager.cpp 17 KB

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