From d124c24e892308e617f1701f2ee71dcc7adc4dda Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sat, 18 Dec 2010 15:34:38 +0000 Subject: [PATCH] Improved ETA calculation --- Changelog | 1 + src/qtlibtorrent/qbtsession.cpp | 73 ++++---------------- src/qtlibtorrent/qbtsession.h | 8 +-- src/qtlibtorrent/qtlibtorrent.pri | 6 +- src/qtlibtorrent/torrentspeedmonitor.cpp | 84 ++++++++++++++++++++++++ src/qtlibtorrent/torrentspeedmonitor.h | 59 +++++++++++++++++ 6 files changed, 162 insertions(+), 69 deletions(-) create mode 100644 src/qtlibtorrent/torrentspeedmonitor.cpp create mode 100644 src/qtlibtorrent/torrentspeedmonitor.h diff --git a/Changelog b/Changelog index 3d713b577..c62ab4b0d 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,6 @@ * Unreleased - Christophe Dumez - v2.6.0 - FEATURE: Use system icons (Linux, Qt >= 4.6) + - FEATURE: Improved ETA calculation - FEATURE: Simplify program preferences - COSMETIC: Same deletion confirmation dialog in the GUI and Web UI - COSMETIC: Simplified the top toolbar diff --git a/src/qtlibtorrent/qbtsession.cpp b/src/qtlibtorrent/qbtsession.cpp index c1188a4fe..953b808fe 100644 --- a/src/qtlibtorrent/qbtsession.cpp +++ b/src/qtlibtorrent/qbtsession.cpp @@ -39,6 +39,7 @@ #include "smtp.h" #include "filesystemwatcher.h" +#include "torrentspeedmonitor.h" #include "qbtsession.h" #include "misc.h" #include "downloadthread.h" @@ -143,12 +144,17 @@ QBtSession::QBtSession() connect(m_scanFolders, SIGNAL(torrentsAdded(QStringList&)), this, SLOT(addTorrentsFromScanFolder(QStringList&))); // Apply user settings to Bittorrent session configureSession(); + // Torrent speed monitor + m_speedMonitor = new TorrentSpeedMonitor(this); + m_speedMonitor->start(); qDebug("* BTSession constructed"); } // Main destructor QBtSession::~QBtSession() { qDebug("BTSession destructor IN"); + delete m_speedMonitor; + qDebug("Deleted the torrent speed monitor"); // Do some BT related saving #if LIBTORRENT_VERSION_MINOR < 15 saveDHTEntry(); @@ -172,8 +178,6 @@ QBtSession::~QBtSession() { // HTTP Server if(httpServer) delete httpServer; - if(timerETA) - delete timerETA; qDebug("BTSession destructor OUT"); } @@ -598,66 +602,6 @@ void QBtSession::useAlternativeSpeedsLimit(bool alternative) { emit alternativeSpeedsModeChanged(alternative); } -void QBtSession::takeETASamples() { - bool change = false;; - foreach(const QString &hash, ETA_samples.keys()) { - const QTorrentHandle h = getTorrentHandle(hash); - if(h.is_valid() && !h.is_paused() && !h.is_seed()) { - QList samples = ETA_samples.value(h.hash(), QList()); - if(samples.size() >= MAX_SAMPLES) - samples.removeFirst(); - samples.append(h.download_payload_rate()); - ETA_samples[h.hash()] = samples; - change = true; - } else { - ETA_samples.remove(hash); - } - } - if(!change && timerETA) { - delete timerETA; - } -} - -// This algorithm was inspired from KTorrent - http://www.ktorrent.org -// Calculate the ETA using a combination of several algorithms: -// GASA: Global Average Speed Algorithm -// CSA: Current Speed Algorithm -// WINX: Window of X Algorithm -qlonglong QBtSession::getETA(QString hash) { - const QTorrentHandle h = getTorrentHandle(hash); - if(!h.is_valid() || h.state() != torrent_status::downloading || !h.active_time()) - return -1; - // See if the torrent is going to be completed soon - const qulonglong bytes_left = h.actual_size() - h.total_wanted_done(); - if(h.actual_size() > 10485760L) { // Size > 10MiB - if(h.progress() >= (float)0.99 && bytes_left < 10485760L) { // Progress>99% but less than 10MB left. - // Compute by taking samples - if(!ETA_samples.contains(h.hash())) { - ETA_samples[h.hash()] = QList(); - } - if(!timerETA) { - timerETA = new QTimer(this); - connect(timerETA, SIGNAL(timeout()), this, SLOT(takeETASamples())); - timerETA->start(); - } else { - const QList samples = ETA_samples.value(h.hash(), QList()); - const int nb_samples = samples.size(); - if(nb_samples > 3) { - long sum_samples = 0; - foreach(const int val, samples) { - sum_samples += val; - } - // Use WINX - return (qlonglong)(((double)bytes_left) / (((double)sum_samples) / ((double)nb_samples))); - } - } - } - } - // Normal case: Use GASA - double avg_speed = (double)h.all_time_download() / h.active_time(); - return (qlonglong) floor((double) (bytes_left) / avg_speed); -} - // Return the torrent handle, given its hash QTorrentHandle QBtSession::getTorrentHandle(QString hash) const{ return QTorrentHandle(s->find_torrent(misc::QStringToSha1(hash))); @@ -2593,3 +2537,8 @@ void QBtSession::drop() m_instance = 0; } } + +qlonglong QBtSession::getETA(const QString &hash) const +{ + return m_speedMonitor->getETA(hash); +} diff --git a/src/qtlibtorrent/qbtsession.h b/src/qtlibtorrent/qbtsession.h index 3d27e0eb9..2eba9f626 100644 --- a/src/qtlibtorrent/qbtsession.h +++ b/src/qtlibtorrent/qbtsession.h @@ -59,6 +59,7 @@ class FilterParserThread; class HttpServer; class BandwidthScheduler; class ScanFoldersModel; +class TorrentSpeedMonitor; class QBtSession : public QObject { Q_OBJECT @@ -86,7 +87,6 @@ public: //int getMaximumActiveDownloads() const; //int getMaximumActiveTorrents() const; int loadTorrentPriority(QString hash); - qlonglong getETA(QString hash); inline QStringList getConsoleMessages() const { return consoleMessages; } inline QStringList getPeerBanMessages() const { return peerBanMessages; } inline libtorrent::session* getSession() const { return s; } @@ -108,6 +108,7 @@ public slots: void startUpTorrents(); void recheckTorrent(QString hash); void useAlternativeSpeedsLimit(bool alternative); + qlonglong getETA(const QString& hash) const; /* Needed by Web UI */ void pauseAllTorrents(); void pauseTorrent(QString hash); @@ -171,7 +172,6 @@ protected slots: void addTorrentsFromScanFolder(QStringList&); void readAlerts(); void processBigRatios(); - void takeETASamples(); void exportTorrentFiles(QString path); void saveTempFastResumeData(); void sendNotificationEmail(QTorrentHandle h); @@ -243,9 +243,6 @@ private: #endif QString defaultSavePath; QString defaultTempPath; - // ETA Computation - QPointer timerETA; - QHash > ETA_samples; // IP filtering QPointer filterParser; QString filterPath; @@ -259,6 +256,7 @@ private: #endif // Tracker QPointer m_tracker; + TorrentSpeedMonitor *m_speedMonitor; }; diff --git a/src/qtlibtorrent/qtlibtorrent.pri b/src/qtlibtorrent/qtlibtorrent.pri index 7bc0035d4..19206ac31 100644 --- a/src/qtlibtorrent/qtlibtorrent.pri +++ b/src/qtlibtorrent/qtlibtorrent.pri @@ -3,10 +3,12 @@ INCLUDEPATH += $$PWD HEADERS += $$PWD/qbtsession.h \ $$PWD/qtorrenthandle.h \ $$PWD/bandwidthscheduler.h \ - $$PWD/trackerinfos.h + $$PWD/trackerinfos.h \ + qtlibtorrent/torrentspeedmonitor.h SOURCES += $$PWD/qbtsession.cpp \ - $$PWD/qtorrenthandle.cpp + $$PWD/qtorrenthandle.cpp \ + qtlibtorrent/torrentspeedmonitor.cpp !contains(DEFINES, DISABLE_GUI) { HEADERS += $$PWD/torrentmodel.h diff --git a/src/qtlibtorrent/torrentspeedmonitor.cpp b/src/qtlibtorrent/torrentspeedmonitor.cpp new file mode 100644 index 000000000..4634c42ec --- /dev/null +++ b/src/qtlibtorrent/torrentspeedmonitor.cpp @@ -0,0 +1,84 @@ +#include +#include "torrentspeedmonitor.h" +#include "qbtsession.h" +#include "misc.h" + +using namespace libtorrent; + +TorrentSpeedMonitor::TorrentSpeedMonitor(QBtSession* session) : + QThread(session), m_abort(false), m_session(session) +{ + connect(m_session, SIGNAL(deletedTorrent(QString)), SLOT(removeSamples(QString))); + connect(m_session, SIGNAL(pausedTorrent(QTorrentHandle)), SLOT(removeSamples(QTorrentHandle))); +} + +TorrentSpeedMonitor::~TorrentSpeedMonitor() { + m_abort = true; + m_abortCond.wakeOne(); + wait(); +} + +void TorrentSpeedMonitor::run() +{ + do { + mutex.lock(); + getSamples(); + m_abortCond.wait(&mutex, 1000); + mutex.unlock(); + } while(!m_abort); +} + +void SpeedSample::addSample(int s) +{ + m_speedSamples << s; + if(m_speedSamples.size() > max_samples) + m_speedSamples.removeFirst(); +} + +float SpeedSample::average() const +{ + if(m_speedSamples.empty()) return 0; + qlonglong sum = 0; + foreach (int s, m_speedSamples) { + sum += s; + } + return sum/static_cast(m_speedSamples.size()); +} + +void SpeedSample::clear() +{ + m_speedSamples.clear(); +} + +void TorrentSpeedMonitor::removeSamples(const QString &hash) +{ + m_samples.remove(hash); +} + +void TorrentSpeedMonitor::removeSamples(const QTorrentHandle& h) { + try { + m_samples.remove(h.hash()); + } catch(invalid_handle&){} +} + +qlonglong TorrentSpeedMonitor::getETA(const QString &hash) const +{ + QMutexLocker locker(&mutex); + QTorrentHandle h = m_session->getTorrentHandle(hash); + if(h.is_paused() || !m_samples.contains(hash)) return -1; + const float speed_average = m_samples.value(hash).average(); + if(speed_average == 0) return -1; + return (h.total_wanted() - h.total_done()) / speed_average; +} + +void TorrentSpeedMonitor::getSamples() +{ + const std::vector torrents = m_session->getSession()->get_torrents(); + std::vector::const_iterator it; + for(it = torrents.begin(); it != torrents.end(); it++) { + try { + if(!it->is_paused()) + m_samples[misc::toQString(it->info_hash())].addSample(it->status().download_payload_rate); + } catch(invalid_handle&){} + } +} diff --git a/src/qtlibtorrent/torrentspeedmonitor.h b/src/qtlibtorrent/torrentspeedmonitor.h new file mode 100644 index 000000000..131de01b0 --- /dev/null +++ b/src/qtlibtorrent/torrentspeedmonitor.h @@ -0,0 +1,59 @@ +#ifndef TORRENTSPEEDMONITOR_H +#define TORRENTSPEEDMONITOR_H + +#include +#include +#include +#include +#include +#include "qtorrenthandle.h" + +class QBtSession; + +class SpeedSample { +public: + SpeedSample(){} + void addSample(int s); + float average() const; + void clear(); + +private: + static const int max_samples = 30; + +private: + QList m_speedSamples; +}; + +class TorrentSpeedMonitor : public QThread +{ + Q_OBJECT + +public: + explicit TorrentSpeedMonitor(QBtSession* session); + ~TorrentSpeedMonitor(); + qlonglong getETA(const QString &hash) const; + +protected: + void run(); + +signals: + +private: + void getSamples(); + +private slots: + void removeSamples(const QString& hash); + void removeSamples(const QTorrentHandle& h); + +private: + static const int sampling_interval = 1000; // 1s + +private: + bool m_abort; + QWaitCondition m_abortCond; + QHash m_samples; + mutable QMutex mutex; + QBtSession *m_session; +}; + +#endif // TORRENTSPEEDMONITOR_H