File indexing completed on 2025-01-05 04:37:24
0001 /* 0002 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "advancedchokealgorithm.h" 0007 0008 #include <algorithm> 0009 0010 #include <diskio/chunkmanager.h> 0011 #include <interfaces/torrentinterface.h> 0012 #include <peer/peer.h> 0013 #include <peer/peermanager.h> 0014 #include <util/functions.h> 0015 0016 #include <QRandomGenerator> 0017 0018 namespace bt 0019 { 0020 const Uint32 OPT_SEL_INTERVAL = 30 * 1000; // we switch optimistic peer each 30 seconds 0021 const double NEWBIE_BONUS = 1.0; 0022 const double SNUB_PENALTY = 10.0; 0023 0024 AdvancedChokeAlgorithm::AdvancedChokeAlgorithm() 0025 : ChokeAlgorithm() 0026 { 0027 last_opt_sel_time = 0; 0028 } 0029 0030 AdvancedChokeAlgorithm::~AdvancedChokeAlgorithm() 0031 { 0032 } 0033 0034 bool AdvancedChokeAlgorithm::calcACAScore(Peer::Ptr p, ChunkManager &cman, const TorrentStats &stats) 0035 { 0036 const PeerInterface::Stats &s = p->getStats(); 0037 if (p->isSeeder() || s.partial_seed) { 0038 p->setACAScore(0.0); 0039 return false; 0040 } 0041 0042 bool should_be_interested = false; 0043 // before we start calculating first check if we have piece that the peer doesn't have 0044 const BitSet &ours = cman.getBitSet(); 0045 const BitSet &theirs = p->getBitSet(); 0046 0047 should_be_interested = !theirs.includesBitSet(ours); 0048 0049 if (!should_be_interested || !p->isInterested()) { 0050 // not interseted so it doesn't make sense to unchoke it 0051 p->setACAScore(-50.0); 0052 return false; 0053 } 0054 0055 double nb = 0.0; // newbie bonus 0056 double cp = 0.0; // choke penalty 0057 double sp = s.snubbed ? SNUB_PENALTY : 0.0; // snubbing penalty 0058 double lb = s.local ? 10.0 : 0.0; // local peers get a bonus of 10 0059 double bd = s.bytes_downloaded; // bytes downloaded 0060 double tbd = stats.session_bytes_downloaded; // total bytes downloaded 0061 double ds = s.download_rate; // current download rate 0062 double tds = stats.download_rate; // total download speed 0063 0064 // if the peer has less than 1 MB or 0.5 % of the torrent it is a newbie 0065 if (p->percentAvailable() < 0.5 && stats.total_bytes * p->percentAvailable() < 1024 * 1024) { 0066 nb = NEWBIE_BONUS; 0067 } 0068 0069 if (p->isChoked()) { 0070 cp = NEWBIE_BONUS; // cp cancels out newbie bonus 0071 } 0072 0073 // NB + K * (BD/TBD) - CP - SP + L * (DS / TDS) 0074 double K = 5.0; 0075 double L = 5.0; 0076 double aca = lb + nb + (tbd > 0 ? K * (bd / tbd) : 0.0) + (tds > 0 ? L * (ds / tds) : 0.0) - cp - sp; 0077 0078 p->setACAScore(aca); 0079 return true; 0080 } 0081 0082 static bool ACAGreaterThan(Peer::Ptr a, Peer::Ptr b) 0083 { 0084 return a->getStats().aca_score > b->getStats().aca_score; 0085 } 0086 0087 void AdvancedChokeAlgorithm::doChokingLeechingState(PeerManager &pman, ChunkManager &cman, const TorrentStats &stats) 0088 { 0089 QList<Peer::Ptr> ppl = pman.getPeers(); 0090 for (QList<Peer::Ptr>::iterator i = ppl.begin(); i != ppl.end();) { 0091 Peer::Ptr p = *i; 0092 if (!calcACAScore(p, cman, stats)) { 0093 // choke seeders they do not want to download from us anyway 0094 p->choke(); 0095 i = ppl.erase(i); 0096 } else 0097 ++i; 0098 } 0099 0100 // sort list by ACA score 0101 std::sort(ppl.begin(), ppl.end(), ACAGreaterThan); 0102 0103 doUnchoking(ppl, updateOptimisticPeer(pman, ppl)); 0104 } 0105 0106 void AdvancedChokeAlgorithm::doUnchoking(const QList<Peer::Ptr> &ppl, Peer::Ptr poup) 0107 { 0108 // Get the number of upload slots 0109 Uint32 num_slots = Choker::getNumUploadSlots(); 0110 // Do the choking and unchoking 0111 Uint32 num_unchoked = 0; 0112 for (Peer::Ptr p : ppl) { 0113 if (!poup && num_unchoked < num_slots) { 0114 p->sendUnchoke(); 0115 num_unchoked++; 0116 } else if (num_unchoked < num_slots - 1 || p == poup) { 0117 p->sendUnchoke(); 0118 if (p != poup) 0119 num_unchoked++; 0120 } else { 0121 p->choke(); 0122 } 0123 } 0124 } 0125 0126 static bool UploadRateGreaterThan(Peer::Ptr a, Peer::Ptr b) 0127 { 0128 return a->getStats().upload_rate > b->getStats().upload_rate; 0129 } 0130 0131 void AdvancedChokeAlgorithm::doChokingSeedingState(PeerManager &pman, ChunkManager &cman, const TorrentStats &stats) 0132 { 0133 QList<Peer::Ptr> ppl = pman.getPeers(); 0134 for (QList<Peer::Ptr>::iterator i = ppl.begin(); i != ppl.end();) { 0135 Peer::Ptr p = *i; 0136 if (!calcACAScore(p, cman, stats)) { 0137 // choke seeders they do not want to download from us anyway 0138 p->choke(); 0139 i = ppl.erase(i); 0140 } else 0141 ++i; 0142 } 0143 0144 std::sort(ppl.begin(), ppl.end(), UploadRateGreaterThan); 0145 0146 doUnchoking(ppl, updateOptimisticPeer(pman, ppl)); 0147 } 0148 0149 static Uint32 FindPlannedOptimisticUnchokedPeer(const QList<Peer::Ptr> &ppl) 0150 { 0151 Uint32 num_peers = ppl.size(); 0152 if (num_peers == 0) 0153 return UNDEFINED_ID; 0154 0155 // find a random peer that is choked and interested 0156 Uint32 start = QRandomGenerator::global()->bounded(num_peers); 0157 Uint32 i = (start + 1) % num_peers; 0158 while (i != start) { 0159 Peer::Ptr p = ppl.at(i); 0160 if (p && p->isChoked() && p->isInterested() && !p->isSeeder() && ppl.contains(p)) 0161 return p->getID(); 0162 i = (i + 1) % num_peers; 0163 } 0164 0165 // we do not expect to have 4 billion peers 0166 return UNDEFINED_ID; 0167 } 0168 0169 Peer::Ptr AdvancedChokeAlgorithm::updateOptimisticPeer(PeerManager &pman, const QList<Peer::Ptr> &ppl) 0170 { 0171 // get the planned optimistic unchoked peer and change it if necessary 0172 Peer::Ptr poup = pman.findPeer(opt_unchoked_peer_id); 0173 TimeStamp now = CurrentTime(); 0174 if (now - last_opt_sel_time > OPT_SEL_INTERVAL || !poup) { 0175 opt_unchoked_peer_id = FindPlannedOptimisticUnchokedPeer(ppl); 0176 last_opt_sel_time = now; 0177 poup = pman.findPeer(opt_unchoked_peer_id); 0178 } 0179 return poup; 0180 } 0181 }