diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index fd40b6f80..01b9de3a7 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -1356,7 +1356,7 @@ namespace stat_helpers } // namespace stat_helpers } // namespace -tr_stat const* tr_torrentStat(tr_torrent* tor) +tr_stat const* tr_torrentStat(tr_torrent* const tor) { using namespace stat_helpers; @@ -1388,10 +1388,10 @@ tr_stat const* tr_torrentStat(tr_torrent* tor) s->peersFrom[i] = swarm_stats.peer_from_count[i]; } - auto const piece_upload_speed_bytes_per_second = tor->bandwidth_.get_piece_speed_bytes_per_second(now_msec, TR_UP); - s->pieceUploadSpeed_KBps = tr_toSpeedKBps(piece_upload_speed_bytes_per_second); - auto const piece_download_speed_bytes_per_second = tor->bandwidth_.get_piece_speed_bytes_per_second(now_msec, TR_DOWN); - s->pieceDownloadSpeed_KBps = tr_toSpeedKBps(piece_download_speed_bytes_per_second); + auto const piece_upload_speed_byps = tor->bandwidth_.get_piece_speed_bytes_per_second(now_msec, TR_UP); + s->pieceUploadSpeed_KBps = tr_toSpeedKBps(piece_upload_speed_byps); + auto const piece_download_speed_byps = tor->bandwidth_.get_piece_speed_bytes_per_second(now_msec, TR_DOWN); + s->pieceDownloadSpeed_KBps = tr_toSpeedKBps(piece_download_speed_byps); s->percentComplete = tor->completion.percent_complete(); s->metadataPercentComplete = tr_torrentGetMetadataPercent(tor); @@ -1423,47 +1423,30 @@ tr_stat const* tr_torrentStat(tr_torrent* tor) auto seed_ratio_bytes_goal = uint64_t{}; bool const seed_ratio_applies = tr_torrentGetSeedRatioBytes(tor, &seed_ratio_bytes_left, &seed_ratio_bytes_goal); + // eta, etaIdle s->eta = TR_ETA_NOT_AVAIL; s->etaIdle = TR_ETA_NOT_AVAIL; if (activity == TR_STATUS_DOWNLOAD) { - /* etaSpeed exists because if we use the piece speed directly, - * brief fluctuations cause the ETA to jump all over the place. - * so, etaXLSpeed is a smoothed-out version of the piece speed - * to dampen the effect of fluctuations */ - if (tor->etaSpeedCalculatedAt + 800 < now_msec) - { - tor->etaSpeed_Bps = tor->etaSpeedCalculatedAt + 4000 < now_msec ? - piece_download_speed_bytes_per_second : /* if no recent previous speed, no need to smooth */ - (tor->etaSpeed_Bps * 4.0 + piece_download_speed_bytes_per_second) / 5.0; /* smooth across 5 readings */ - tor->etaSpeedCalculatedAt = now_msec; - } - - if (tor->etaSpeed_Bps == 0) + if (auto const eta_speed_byps = tor->eta_speed_.update(now_msec, piece_download_speed_byps); eta_speed_byps == 0U) { s->eta = TR_ETA_UNKNOWN; } else if (s->leftUntilDone <= s->desiredAvailable || tor->webseed_count() >= 1U) { - s->eta = s->leftUntilDone / tor->etaSpeed_Bps; + s->eta = s->leftUntilDone / eta_speed_byps; } } else if (activity == TR_STATUS_SEED) { + auto const eta_speed_byps = tor->eta_speed_.update(now_msec, piece_upload_speed_byps); + if (seed_ratio_applies) { - if (tor->etaSpeedCalculatedAt + 800 < now_msec) - { - tor->etaSpeed_Bps = tor->etaSpeedCalculatedAt + 4000 < now_msec ? - piece_upload_speed_bytes_per_second : /* if no recent previous speed, no need to smooth */ - (tor->etaSpeed_Bps * 4.0 + piece_upload_speed_bytes_per_second) / 5.0; /* smooth across 5 readings */ - tor->etaSpeedCalculatedAt = now_msec; - } - - s->eta = tor->etaSpeed_Bps == 0U ? TR_ETA_UNKNOWN : seed_ratio_bytes_left / tor->etaSpeed_Bps; + s->eta = eta_speed_byps == 0U ? TR_ETA_UNKNOWN : seed_ratio_bytes_left / eta_speed_byps; } - if (tor->etaSpeed_Bps < 1U) + if (eta_speed_byps < 1U) { if (auto const secs_left = tor->idle_seconds_left(now_sec); secs_left) { diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 47b019e16..602057d0e 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -959,8 +959,6 @@ public: uint64_t corruptCur = 0; uint64_t corruptPrev = 0; - uint64_t etaSpeedCalculatedAt = 0; - tr_interned_string error_announce_url; // Where the files are when the torrent is complete. @@ -976,8 +974,6 @@ public: tr_stat_errtype error = TR_STAT_OK; - tr_bytes_per_second_t etaSpeed_Bps = 0; - size_t queuePosition = 0; tr_torrent_id_t unique_id_ = 0; @@ -1001,6 +997,39 @@ public: private: friend tr_stat const* tr_torrentStat(tr_torrent* tor); + // Helper class to smooth out speed estimates. + // Used to prevent temporary speed changes from skewing the ETA too much. + class SimpleSmoothedSpeed + { + public: + constexpr auto update(uint64_t time_msec, tr_bytes_per_second_t speed_byps) + { + // If the old speed is too old, just replace it + if (timestamp_msec_ + MaxAgeMSec <= time_msec) + { + timestamp_msec_ = time_msec; + speed_byps_ = speed_byps; + } + + // To prevent the smoothing from being overwhelmed by frequent calls + // to update(), do nothing if not enough time elapsed since last update. + else if (timestamp_msec_ + MinUpdateMSec <= time_msec) + { + timestamp_msec_ = time_msec; + speed_byps_ = (speed_byps_ * 4U + speed_byps) / 5U; + } + + return speed_byps_; + } + + private: + static auto constexpr MaxAgeMSec = 4000U; + static auto constexpr MinUpdateMSec = 800U; + + uint64_t timestamp_msec_ = {}; + tr_bytes_per_second_t speed_byps_ = {}; + }; + [[nodiscard]] constexpr std::optional effective_idle_limit_minutes() const noexcept { auto const mode = idle_limit_mode(); @@ -1066,6 +1095,8 @@ private: } } + SimpleSmoothedSpeed eta_speed_; + /* If the initiator of the connection receives a handshake in which the * peer_id does not match the expected peerid, then the initiator is * expected to drop the connection. Note that the initiator presumably