TorrentButler.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. from BTL.obsoletepythonsupport import set
  2. from BitTorrent.TorrentPolicy import Policy
  3. from BitTorrent.Torrent import *
  4. class TorrentButler(Policy):
  5. def __init__(self, multitorrent):
  6. Policy.__init__(self, multitorrent)
  7. self.multitorrent = multitorrent
  8. class EverythingAllOfTheTimeTorrentButler(TorrentButler):
  9. def __init__(self, multitorrent):
  10. TorrentButler.__init__(self, multitorrent)
  11. def butle(self):
  12. for torrent in self.multitorrent.get_torrents():
  13. if not self.multitorrent.torrent_running(torrent.metainfo.infohash):
  14. self.multitorrent.start_torrent(torrent.metainfo.infohash)
  15. class EverythingOneTimeTorrentButler(TorrentButler):
  16. def __init__(self, multitorrent):
  17. TorrentButler.__init__(self, multitorrent)
  18. self.started_torrents = set()
  19. def butle(self):
  20. for torrent in self.multitorrent.get_torrents():
  21. if ((not self.multitorrent.torrent_running(torrent.metainfo.infohash)) and
  22. (not torrent.metainfo.infohash in self.started_torrents)):
  23. self.multitorrent.start_torrent(torrent.metainfo.infohash)
  24. self.started_torrents.add(torrent.metainfo.infohash)
  25. class DownloadTorrentButler(TorrentButler):
  26. # TODO: this one should probably be configurable, once the new choker works
  27. SIMULTANEOUS = 3
  28. GOOD_RATE_THRESHOLD = 0.25 # minimal fraction of the average rate
  29. PURGATORY_TIME = 60 # seconds before we declare an underperformer "bad"
  30. LIMIT_MARGIN = 0.9 # consider bandwidth maximized at limit * margin
  31. REFRACTORY_PERIOD = 30 # seconds we wait after falling below the limit
  32. def __init__(self, multitorrent):
  33. TorrentButler.__init__(self, multitorrent)
  34. self.suspects = {}
  35. self.time_last_pushed_limit = bttime() - self.REFRACTORY_PERIOD
  36. def butles(self, torrent):
  37. return torrent.policy == "auto" and (not torrent.completed) and torrent.state in ["initialized", "running"]
  38. def butle(self):
  39. going = []
  40. waiting = []
  41. finishing = []
  42. num_initializing = 0
  43. for torrent in self.multitorrent.get_torrents():
  44. if self.butles(torrent):
  45. if self.multitorrent.torrent_running(torrent.metainfo.infohash):
  46. going.append(torrent)
  47. elif torrent.is_initialized():
  48. if torrent.get_percent_complete() < 1.0:
  49. waiting.append(torrent)
  50. else:
  51. finishing.append(torrent)
  52. elif not torrent.completed:
  53. if torrent.state in ["created", "initializing"]:
  54. if (bttime() - torrent.time_created < 10):
  55. num_initializing += 1
  56. for t in finishing:
  57. self.multitorrent.start_torrent(t.metainfo.infohash)
  58. starting = [t for t in going
  59. if bttime() - t.time_started < 60]
  60. transferring = [t for t in going
  61. if t not in starting
  62. and t.get_downrate() > 0.0
  63. and t.get_num_connections() > 0]
  64. num_good = 0
  65. num_virtual_good = 0
  66. if (len(transferring) > 0):
  67. total_rate = sum([t.get_downrate() for t in transferring])
  68. good_rate = (total_rate / len(transferring)) * self.GOOD_RATE_THRESHOLD
  69. if good_rate > 0:
  70. bad = []
  71. for t in transferring:
  72. if t.get_downrate() >= good_rate:
  73. if self.suspects.has_key(t.metainfo.infohash):
  74. #print t.working_path + " is good now, popping"
  75. self.suspects.pop(t.metainfo.infohash)
  76. else:
  77. if self.suspects.has_key(t.metainfo.infohash):
  78. #print t.working_path, bttime() - self.suspects[t.metainfo.infohash]
  79. if (bttime() - self.suspects[t.metainfo.infohash] >=
  80. self.PURGATORY_TIME):
  81. bad.append(t)
  82. else:
  83. #print t.working_path + " is bad now, inserting"
  84. self.suspects[t.metainfo.infohash] = bttime()
  85. total_bad_rate = sum([t.get_downrate() for t in bad])
  86. num_virtual_good = total_bad_rate / good_rate
  87. num_good = len(transferring) - len(bad)
  88. uprate, downrate = self.multitorrent.get_total_rates()
  89. downrate_limit = self.multitorrent.config['max_download_rate']
  90. #print num_initializing, num_good, num_virtual_good, len(starting)
  91. #print downrate, '/', downrate_limit
  92. if (downrate >= downrate_limit * self.LIMIT_MARGIN):
  93. self.time_last_pushed_limit = bttime()
  94. #print "pushing limit"
  95. #print bttime() - self.time_last_pushed_limit
  96. if ((bttime() - self.time_last_pushed_limit >
  97. self.REFRACTORY_PERIOD) and
  98. (num_initializing == 0) and
  99. (num_good + num_virtual_good + len(starting) < self.SIMULTANEOUS)):
  100. high = []
  101. norm = []
  102. low = []
  103. for torrent in waiting:
  104. if torrent.priority == "high":
  105. high.append(torrent)
  106. elif torrent.priority == "normal":
  107. norm.append(torrent)
  108. elif torrent.priority == "low":
  109. low.append(torrent)
  110. for p in (high, norm, low):
  111. best = None
  112. for torrent in p:
  113. if ((not best) or
  114. (best.get_percent_complete() == 0 and
  115. torrent.total_bytes < best.total_bytes) or
  116. (torrent.get_percent_complete() >
  117. best.get_percent_complete())):
  118. best = torrent
  119. if best:
  120. break
  121. if best:
  122. self.multitorrent.start_torrent(best.metainfo.infohash)
  123. class SeedTorrentButler(TorrentButler):
  124. FREQUENCY = 15
  125. MIN_RATE = 2000.0
  126. THRESHOLD = 1.25
  127. MIN_TRANSFERRING = 1
  128. def __init__(self, multitorrent):
  129. TorrentButler.__init__(self, multitorrent)
  130. self.counter = 0
  131. def butles(self, torrent):
  132. return torrent.policy == "auto" and torrent.completed and torrent.state in ["initialized", "running"]
  133. def butle(self):
  134. self.counter += 1
  135. self.counter %= self.FREQUENCY
  136. if self.counter != 0:
  137. return
  138. num_connections = 0
  139. transferring = []
  140. stopped = []
  141. total_rate = 0.0
  142. for torrent in self.multitorrent.get_torrents():
  143. if self.butles(torrent):
  144. if self.multitorrent.torrent_running(torrent.metainfo.infohash):
  145. #print "found running torrent: ", torrent.get_uprate(), torrent.get_num_connections(), torrent._activity
  146. if (torrent.get_uprate() > 0.0
  147. and torrent.get_num_connections() > 0):
  148. transferring.append(torrent)
  149. for c in torrent.get_connections():
  150. total_rate += c.upload.measure.get_rate()
  151. num_connections += 1
  152. else:
  153. #print "found stopped torrent: ", torrent._activity
  154. stopped.append(torrent)
  155. #print num_connections, len(transferring), len(stopped)
  156. if (len(transferring) < self.MIN_TRANSFERRING
  157. or total_rate / num_connections > self.MIN_RATE * self.THRESHOLD):
  158. if len(stopped):
  159. r = random.randint(0, len(stopped) - 1)
  160. #print "starting torrent"
  161. self.multitorrent.start_torrent(stopped[r].metainfo.infohash)
  162. elif total_rate / num_connections < self.MIN_RATE:
  163. if len(transferring) > self.MIN_TRANSFERRING:
  164. def lambda_dammit(x, y):
  165. try:
  166. x_contribution = x.get_uprate() / x.get_avg_peer_downrate()
  167. except ZeroDivisionError:
  168. x_contribution = 1.0
  169. try:
  170. y_contribution = y.get_uprate() / y.get_avg_peer_downrate()
  171. except ZeroDivisionError:
  172. y_contribution = 1.0
  173. return cmp(x_contribution, y_contribution)
  174. transferring.sort(lambda_dammit)
  175. self.multitorrent.stop_torrent(transferring[0].metainfo.infohash)