diff --git a/ChangeLog b/ChangeLog index 45fcb7971..7aa3771d4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,7 @@ * fix uTP edge case where udp socket buffer fills up * fix nagle implementation in uTP + * fix round-robin seed-unchoke algorithm * add bootstrap.sh to generage configure script and run configure * fix bug in SOCK5 UDP support * fix issue where torrents added by URL would not be started immediately diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index a8c56c616..84708515f 100644 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -623,11 +623,14 @@ namespace libtorrent // enum from peer_info::bw_state char m_channel_state[2]; - size_type uploaded_since_unchoke() const - { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + size_type uploaded_in_last_round() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_round; } - size_type downloaded_since_unchoke() const - { return m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; } + size_type downloaded_in_last_round() const + { return m_statistics.total_payload_download() - m_downloaded_at_last_round; } + + size_type uploaded_since_unchoked() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } // called when the disk write buffer is drained again, and we can // start downloading payload again @@ -847,11 +850,19 @@ namespace libtorrent size_type m_free_upload; // the total payload download bytes - // at the last unchoke cycle. This is used to + // at the last unchoke round. This is used to // measure the number of bytes transferred during // an unchoke cycle, to unchoke peers the more bytes // they sent us - size_type m_downloaded_at_last_unchoke; + size_type m_downloaded_at_last_round; + size_type m_uploaded_at_last_round; + + // this is the number of bytes we had uploaded the + // last time this peer was unchoked. This does not + // reset each unchoke interval/round. This is used to + // track upload across rounds, for the full duration of + // the peer being unchoked. Specifically, it's used + // for the round-robin unchoke algorithm. size_type m_uploaded_at_last_unchoke; #ifndef TORRENT_DISABLE_GEO_IP diff --git a/parse_peer_log.py b/parse_peer_log.py new file mode 100644 index 000000000..c087fdc4a --- /dev/null +++ b/parse_peer_log.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +import glob +import os +import sys + +# usage: parse_peer_log + +log_files = [] + +for p in glob.iglob(os.path.join(sys.argv[1], '*.log')): + name = os.path.split(p)[1] + if name == 'main_session.log': continue + print name + f = open(p, 'r') + out_file = p + '.dat' + log_files.append(out_file) + out = open(out_file, 'w+') + + uploaded_blocks = 0; + downloaded_blocks = 0; + + for l in f: + t = l.split(': ')[0].split('.')[0] + log_line = False + if ' ==> PIECE' in l: + uploaded_blocks+= 1 + log_line = True + + if ' <== PIECE' in l: + downloaded_blocks+= 1 + log_line = True + + if log_line: + print >>out, '%s\t%d\t%d' % (t, uploaded_blocks, downloaded_blocks) + + out.close() + f.close() + +out = open('peers.gnuplot', 'wb') +print >>out, "set term png size 1200,700" +print >>out, 'set xrange [0:*]' +print >>out, 'set xlabel "time"' +print >>out, 'set ylabel "blocks"' +print >>out, 'set key box' +print >>out, 'set xdata time' +print >>out, 'set timefmt "%H:%M:%S"' +print >>out, 'set title "uploaded blocks"' +print >>out, 'set output "peers_upload.png"' +print >>out, 'plot', +first = True +for n in log_files: + if not first: + print >>out, ',', + first = False + print >>out, ' "%s" using 1:2 title "%s" with steps' % (n, os.path.split(n)[1].split('.log')[0]), +print >>out, '' + +print >>out, 'set title "downloaded blocks"' +print >>out, 'set output "peers_download.png"' +print >>out, 'plot', +first = True +for n in log_files: + if not first: + print >>out, ',', + first = False + print >>out, ' "%s" using 1:3 title "%s" with steps' % (n, os.path.split(n)[1].split('.log')[0]), +print >>out, '' +out.close() + +os.system('gnuplot peers.gnuplot'); + diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 556770259..784e8c97b 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -129,7 +129,8 @@ namespace libtorrent , m_became_uninterested(time_now()) , m_became_uninteresting(time_now()) , m_free_upload(0) - , m_downloaded_at_last_unchoke(0) + , m_downloaded_at_last_round(0) + , m_uploaded_at_last_round(0) , m_uploaded_at_last_unchoke(0) , m_disk_recv_buffer(ses, 0) , m_socket(s) @@ -294,11 +295,11 @@ namespace libtorrent size_type d1, d2, u1, u2; // first compare how many bytes they've sent us - d1 = m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; - d2 = rhs.m_statistics.total_payload_download() - rhs.m_downloaded_at_last_unchoke; + d1 = downloaded_in_last_round(); + d2 = rhs.downloaded_in_last_round(); // divided by the number of bytes we've sent them - u1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; - u2 = rhs.m_statistics.total_payload_upload() - rhs.m_uploaded_at_last_unchoke; + u1 = uploaded_in_last_round(); + u2 = rhs.uploaded_in_last_round(); boost::shared_ptr t1 = m_torrent.lock(); TORRENT_ASSERT(t1); @@ -338,31 +339,49 @@ namespace libtorrent // compare how many bytes they've sent us size_type c1; size_type c2; - c1 = m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; - c2 = rhs.m_statistics.total_payload_download() - rhs.m_downloaded_at_last_unchoke; + c1 = downloaded_in_last_round(); + c2 = rhs.downloaded_in_last_round(); if (c1 != c2) return c1 > c2; if (m_ses.settings().seed_choking_algorithm == session_settings::round_robin) { - // if they are equal, compare how much we have uploaded - c1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; - c2 = rhs.m_statistics.total_payload_upload() - rhs.m_uploaded_at_last_unchoke; + // the amount uploaded since unchoked (not just in the last round) + c1 = uploaded_since_unchoked(); + c2 = rhs.uploaded_since_unchoked(); + + // the way the round-robin unchoker works is that it, + // by default, prioritizes any peer that is already unchoked. + // this maintain the status quo across unchoke rounds. However, + // peers that are unchoked, but have sent more than one quota + // since they were unchoked, they get de-prioritized. - // in order to not switch back and forth too often, - // unchoked peers must be at least one piece ahead - // of a choked peer to be sorted at a lower unchoke-priority int pieces = m_ses.settings().seeding_piece_quota; - bool c1_done = is_choked() || c1 > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); - bool c2_done = rhs.is_choked() || c2 > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); + // if a peer is already unchoked, and the number of bytes sent since it was unchoked + // is greater than the send quanta, then it's done with it' upload slot, and we + // can de-prioritize it + bool c1_quota_complete = !is_choked() && c1 > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); + bool c2_quota_complete = !rhs.is_choked() && c2 > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); - if (!c1_done && c2_done) return true; - if (c1_done && !c2_done) return false; + // if c2 has completed a quanta, it shuold be de-prioritized + // and vice versa + if (c1_quota_complete < c2_quota_complete) return true; + if (c1_quota_complete > c2_quota_complete) return false; + + // if both peers have either completed a quanta, or not. + // keep unchoked peers prioritized over choked ones, to let + // peers keep working on uploading a full quanta + if (is_choked() < rhs.is_choked()) return true; + if (is_choked() > rhs.is_choked()) return false; + + // if the peers are still identical (say, they're both waiting to be unchoked) + // fall through and rely on the logic to prioritize peers who have waited + // the longest to be unchoked } else if (m_ses.settings().seed_choking_algorithm == session_settings::fastest_upload) { - c1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; - c2 = rhs.m_statistics.total_payload_upload() - rhs.m_uploaded_at_last_unchoke; + c1 = uploaded_in_last_round(); + c2 = rhs.uploaded_in_last_round(); // take torrent priority into account c1 *= 1 + t1->priority(); @@ -403,6 +422,8 @@ namespace libtorrent } // prioritize the one that has waited the longest to be unchoked + // the round-robin unchoker relies on this logic. Don't change it + // without moving this into that unchoker logic return m_last_unchoke < rhs.m_last_unchoke; } @@ -416,8 +437,8 @@ namespace libtorrent boost::shared_ptr t2 = p->associated_torrent().lock(); TORRENT_ASSERT(t2); - c1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; - c2 = p->m_statistics.total_payload_upload() - p->m_uploaded_at_last_unchoke; + c1 = uploaded_in_last_round(); + c2 = p->uploaded_in_last_round(); // take torrent priority into account c1 *= 1 + t1->priority(); @@ -428,8 +449,8 @@ namespace libtorrent void peer_connection::reset_choke_counters() { - m_downloaded_at_last_unchoke = m_statistics.total_payload_download(); - m_uploaded_at_last_unchoke = m_statistics.total_payload_upload(); + m_downloaded_at_last_round= m_statistics.total_payload_download(); + m_uploaded_at_last_round = m_statistics.total_payload_upload(); } void peer_connection::start() @@ -3096,6 +3117,8 @@ namespace libtorrent write_unchoke(); m_choked = false; + m_uploaded_at_last_unchoke = m_statistics.total_payload_upload(); + #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> UNCHOKE"); #endif diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 7898a5a44..4e042049a 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -4599,9 +4599,9 @@ retry: TORRENT_ASSERT(t1); boost::shared_ptr t2 = (*i)->associated_torrent().lock(); TORRENT_ASSERT(t2); - TORRENT_ASSERT((*prev)->uploaded_since_unchoke() * 1000 + TORRENT_ASSERT((*prev)->uploaded_in_last_round() * 1000 * (1 + t1->priority()) / total_milliseconds(unchoke_interval) - >= (*i)->uploaded_since_unchoke() * 1000 + >= (*i)->uploaded_in_last_round() * 1000 * (1 + t2->priority()) / total_milliseconds(unchoke_interval)); } prev = i; @@ -4615,7 +4615,7 @@ retry: , end(peers.end()); i != end; ++i) { peer_connection const& p = **i; - int rate = int(p.uploaded_since_unchoke() + int rate = int(p.uploaded_in_last_round() * 1000 / total_milliseconds(unchoke_interval)); if (rate < rate_threshold) break;