3 Copyright (c) 2008, Arvid Norberg
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
35 #include "libtorrent/bandwidth_manager.hpp"
36 #include "libtorrent/bandwidth_queue_entry.hpp"
37 #include "libtorrent/bandwidth_limit.hpp"
38 #include "libtorrent/socket.hpp"
39 #include "libtorrent/stat.hpp"
40 #include "libtorrent/time.hpp"
41 #include "libtorrent/intrusive_ptr_base.hpp"
43 #include <boost/lexical_cast.hpp>
46 struct peer_connection
;
48 using namespace libtorrent
;
50 const float sample_time
= 6.f
; // seconds
52 //#define VERBOSE_LOGGING
55 struct peer_connection
: intrusive_ptr_base
<peer_connection
>
57 typedef torrent torrent_type
;
59 peer_connection(io_service
& ios
, boost::shared_ptr
<torrent
> const& t
60 , int prio
, bool ignore_limits
, std::string name
);
62 bool ignore_bandwidth_limits() { return m_ignore_limits
; }
63 int max_assignable_bandwidth(int channel
) const
64 { return m_bandwidth_limit
[channel
].max_assignable(); }
65 boost::weak_ptr
<torrent
> associated_torrent() const
67 bool is_disconnecting() const { return m_abort
; }
68 void assign_bandwidth(int channel
, int amount
);
69 void on_transfer(int channel
, int amount
);
71 void stop() { m_abort
= true; }
72 void expire_bandwidth(int channel
, int amount
);
75 int bandwidth_throttle(int channel
) const
76 { return m_bandwidth_limit
[channel
].throttle(); }
78 void throttle(int limit
) { m_bandwidth_limit
[0].throttle(limit
); }
80 bandwidth_limit m_bandwidth_limit
[1];
81 boost::weak_ptr
<torrent
> m_torrent
;
85 libtorrent::stat m_stats
;
93 torrent(bandwidth_manager
<peer_connection
, torrent
>& m
)
94 : m_bandwidth_manager(m
)
97 void assign_bandwidth(int channel
, int amount
, int max_block_size
)
99 #ifdef VERBOSE_LOGGING
100 std::cerr
<< time_now_string()
101 << ": assign bandwidth, " << amount
<< " blk: " << max_block_size
<< std::endl
;
103 TEST_CHECK(amount
> 0);
104 TEST_CHECK(amount
<= max_block_size
);
105 if (amount
< max_block_size
)
106 expire_bandwidth(channel
, max_block_size
- amount
);
109 int bandwidth_throttle(int channel
) const
110 { return m_bandwidth_limit
[channel
].throttle(); }
112 int max_assignable_bandwidth(int channel
) const
113 { return m_bandwidth_limit
[channel
].max_assignable(); }
115 void request_bandwidth(int channel
116 , boost::intrusive_ptr
<peer_connection
> const& p
120 TORRENT_ASSERT(max_block_size
> 0);
121 TORRENT_ASSERT(m_bandwidth_limit
[channel
].throttle() > 0);
122 int block_size
= (std::min
)(m_bandwidth_limit
[channel
].throttle() / 10
124 if (block_size
<= 0) block_size
= 1;
126 if (m_bandwidth_limit
[channel
].max_assignable() > 0)
128 #ifdef VERBOSE_LOGGING
129 std::cerr
<< time_now_string()
130 << ": request bandwidth " << block_size
<< std::endl
;
132 perform_bandwidth_request(channel
, p
, block_size
, priority
);
136 #ifdef VERBOSE_LOGGING
137 std::cerr
<< time_now_string()
138 << ": queue bandwidth request" << block_size
<< std::endl
;
140 // skip forward in the queue until we find a prioritized peer
141 // or hit the front of it.
142 queue_t::reverse_iterator i
= m_bandwidth_queue
[channel
].rbegin();
143 while (i
!= m_bandwidth_queue
[channel
].rend() && priority
> i
->priority
)
148 m_bandwidth_queue
[channel
].insert(i
.base(), bw_queue_entry
<peer_connection
, torrent
>(
149 p
, block_size
, priority
));
153 void expire_bandwidth(int channel
, int amount
);
155 void perform_bandwidth_request(int channel
156 , boost::intrusive_ptr
<peer_connection
> const& p
160 m_bandwidth_manager
.request_bandwidth(p
161 , block_size
, priority
);
162 m_bandwidth_limit
[channel
].assign(block_size
);
164 bandwidth_limit m_bandwidth_limit
[1];
165 typedef std::deque
<bw_queue_entry
<peer_connection
, torrent
> > queue_t
;
166 queue_t m_bandwidth_queue
[1];
167 bandwidth_manager
<peer_connection
, torrent
>& m_bandwidth_manager
;
170 peer_connection::peer_connection(io_service
& ios
, boost::shared_ptr
<torrent
> const& t
171 , int prio
, bool ignore_limits
, std::string name
)
174 , m_ignore_limits(ignore_limits
)
181 void peer_connection::assign_bandwidth(int channel
, int amount
)
183 TEST_CHECK(m_writing
);
184 #ifdef VERBOSE_LOGGING
185 std::cerr
<< time_now_string() << ": [" << m_name
186 << "] assign bandwidth, " << amount
<< std::endl
;
188 TEST_CHECK(amount
> 0);
189 m_bandwidth_limit
[channel
].assign(amount
);
190 m_ios
.post(boost::bind(&peer_connection::on_transfer
, self(), channel
, amount
));
193 void peer_connection::on_transfer(int channel
, int amount
)
195 TEST_CHECK(m_writing
);
197 m_stats
.sent_bytes(amount
, 0);
199 boost::shared_ptr
<torrent
> t
= m_torrent
.lock();
201 if (m_bandwidth_limit
[channel
].max_assignable() > 0)
204 t
->request_bandwidth(0, this, 32 * 1024, m_priority
);
208 void peer_connection::start()
210 boost::shared_ptr
<torrent
> t
= m_torrent
.lock();
213 t
->request_bandwidth(0, this, 32 * 1024, m_priority
);
216 void peer_connection::expire_bandwidth(int channel
, int amount
)
218 TEST_CHECK(amount
> 0);
219 #ifdef VERBOSE_LOGGING
220 std::cerr
<< time_now_string() << ": [" << m_name
221 << "] expire bandwidth, " << amount
<< std::endl
;
223 m_bandwidth_limit
[channel
].expire(amount
);
225 if (!m_writing
&& m_bandwidth_limit
[channel
].max_assignable() > 0)
227 boost::shared_ptr
<torrent
> t
= m_torrent
.lock();
230 t
->request_bandwidth(0, this, 32 * 1024, m_priority
);
234 void peer_connection::tick()
236 #ifdef VERBOSE_LOGGING
237 std::cerr
<< time_now_string() << ": [" << m_name
238 << "] tick, rate: " << m_stats
.upload_rate() << std::endl
;
240 m_stats
.second_tick(1.f
);
244 void torrent::expire_bandwidth(int channel
, int amount
)
246 #ifdef VERBOSE_LOGGING
247 std::cerr
<< time_now_string()
248 << ": expire bandwidth, " << amount
<< std::endl
;
250 TEST_CHECK(amount
> 0);
251 m_bandwidth_limit
[channel
].expire(amount
);
253 while (!m_bandwidth_queue
[channel
].empty())
255 bw_queue_entry
<peer_connection
, torrent
> qe
= m_bandwidth_queue
[channel
].front();
256 if (m_bandwidth_limit
[channel
].max_assignable() == 0)
258 m_bandwidth_queue
[channel
].pop_front();
259 if (qe
.peer
->max_assignable_bandwidth(channel
) <= 0)
261 if (!qe
.peer
->is_disconnecting()) tmp
.push_back(qe
);
264 perform_bandwidth_request(channel
, qe
.peer
265 , qe
.max_block_size
, qe
.priority
);
267 m_bandwidth_queue
[channel
].insert(m_bandwidth_queue
[channel
].begin(), tmp
.begin(), tmp
.end());
270 typedef std::vector
<boost::intrusive_ptr
<peer_connection
> > connections_t
;
272 bool abort_tick
= false;
274 void do_tick(error_code
const&e
, deadline_timer
& tick
, connections_t
& v
)
278 std::cerr
<< " tick aborted" << std::endl
;
281 std::for_each(v
.begin(), v
.end()
282 , boost::bind(&peer_connection::tick
, _1
));
283 tick
.expires_from_now(seconds(1));
284 tick
.async_wait(boost::bind(&do_tick
, _1
, boost::ref(tick
), boost::ref(v
)));
287 void do_stop(deadline_timer
& tick
, connections_t
& v
)
291 std::for_each(v
.begin(), v
.end()
292 , boost::bind(&peer_connection::stop
, _1
));
293 std::cerr
<< " stopping..." << std::endl
;
296 void do_change_rate(error_code
const&e
, deadline_timer
& tick
297 , boost::shared_ptr
<torrent
> t1
298 , boost::shared_ptr
<torrent
> t2
307 t1
->m_bandwidth_limit
[0].throttle(limit
);
308 t2
->m_bandwidth_limit
[0].throttle(limit
);
312 t1
->m_bandwidth_limit
[0].throttle(limit
+ limit
/ 2 * ((counter
& 1)?-1:1));
313 t2
->m_bandwidth_limit
[0].throttle(limit
+ limit
/ 2 * ((counter
& 1)?1:-1));
315 tick
.expires_from_now(milliseconds(1600));
316 tick
.async_wait(boost::bind(&do_change_rate
, _1
, boost::ref(tick
), t1
, t2
, limit
, counter
-1));
319 void do_change_peer_rate(error_code
const&e
, deadline_timer
& tick
329 std::for_each(v
.begin(), v
.end()
330 , boost::bind(&peer_connection::throttle
, _1
, limit
));
335 for (connections_t::iterator i
= v
.begin(); i
!= v
.end(); ++i
, ++c
)
336 i
->get()->throttle(limit
+ limit
/ 2 * ((c
& 1)?-1:1));
338 tick
.expires_from_now(milliseconds(1100));
339 tick
.async_wait(boost::bind(&do_change_peer_rate
, _1
, boost::ref(tick
), boost::ref(v
), limit
, counter
-1));
342 void run_test(io_service
& ios
, connections_t
& v
)
345 std::cerr
<< "-------------" << std::endl
;
346 deadline_timer
tick(ios
);
347 tick
.expires_from_now(seconds(1));
348 tick
.async_wait(boost::bind(&do_tick
, _1
, boost::ref(tick
), boost::ref(v
)));
350 deadline_timer
complete(ios
);
351 complete
.expires_from_now(milliseconds(int(sample_time
* 1000)));
352 complete
.async_wait(boost::bind(&do_stop
, boost::ref(tick
), boost::ref(v
)));
354 std::for_each(v
.begin(), v
.end()
355 , boost::bind(&peer_connection::start
, _1
));
360 bool close_to(float val
, float comp
, float err
)
362 return fabs(val
- comp
) <= err
;
365 void spawn_connections(connections_t
& v
, io_service
& ios
366 , boost::shared_ptr
<torrent
> t
, int num
, char const* prefix
)
368 for (int i
= 0; i
< num
; ++i
)
370 v
.push_back(new peer_connection(ios
, t
, 200, false
371 , prefix
+ boost::lexical_cast
<std::string
>(i
)));
375 void test_equal_connections(int num
, int limit
)
377 std::cerr
<< "\ntest equal connections " << num
<< " " << limit
<< std::endl
;
379 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
380 manager
.throttle(limit
);
382 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
385 spawn_connections(v
, ios
, t1
, num
, "p");
389 float err
= (std::max
)(limit
/ num
* 0.3f
, 1000.f
);
390 for (connections_t::iterator i
= v
.begin()
391 , end(v
.end()); i
!= end
; ++i
)
393 sum
+= (*i
)->m_stats
.total_payload_upload();
395 std::cerr
<< (*i
)->m_stats
.total_payload_upload() / sample_time
396 << " target: " << (limit
/ num
) << " eps: " << err
<< std::endl
;
397 TEST_CHECK(close_to((*i
)->m_stats
.total_payload_upload() / sample_time
, limit
/ num
, err
));
400 std::cerr
<< "sum: " << sum
<< " target: " << limit
<< std::endl
;
402 TEST_CHECK(close_to(sum
, limit
, 50));
405 void test_connections_variable_rate(int num
, int limit
, int torrent_limit
)
407 std::cerr
<< "\ntest connections variable rate" << num
409 << " t: " << torrent_limit
412 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
414 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
416 t1
->m_bandwidth_limit
[0].throttle(torrent_limit
);
419 spawn_connections(v
, ios
, t1
, num
, "p");
420 std::for_each(v
.begin(), v
.end()
421 , boost::bind(&peer_connection::throttle
, _1
, limit
));
423 deadline_timer
change_rate(ios
);
424 change_rate
.expires_from_now(milliseconds(1600));
425 change_rate
.async_wait(boost::bind(&do_change_peer_rate
, _1
, boost::ref(change_rate
)
426 , boost::ref(v
), limit
, 9));
429 if (torrent_limit
> 0 && limit
* num
> torrent_limit
)
430 limit
= torrent_limit
/ num
;
433 float err
= limit
* 0.3f
;
434 for (connections_t::iterator i
= v
.begin()
435 , end(v
.end()); i
!= end
; ++i
)
437 sum
+= (*i
)->m_stats
.total_payload_upload();
439 std::cerr
<< (*i
)->m_stats
.total_payload_upload() / sample_time
440 << " target: " << limit
<< " eps: " << err
<< std::endl
;
441 TEST_CHECK(close_to((*i
)->m_stats
.total_payload_upload() / sample_time
, limit
, err
));
444 std::cerr
<< "sum: " << sum
<< " target: " << (limit
* num
) << std::endl
;
446 TEST_CHECK(close_to(sum
, limit
* num
, limit
* 0.3f
* num
));
449 void test_single_peer(int limit
, bool torrent_limit
)
451 std::cerr
<< "\ntest single peer " << limit
<< " " << torrent_limit
<< std::endl
;
453 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
454 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
457 t1
->m_bandwidth_limit
[0].throttle(limit
);
459 manager
.throttle(limit
);
462 spawn_connections(v
, ios
, t1
, 1, "p");
466 for (connections_t::iterator i
= v
.begin()
467 , end(v
.end()); i
!= end
; ++i
)
469 sum
+= (*i
)->m_stats
.total_payload_upload();
472 std::cerr
<< sum
<< " target: " << limit
<< std::endl
;
474 TEST_CHECK(close_to(sum
, limit
, 1000));
477 void test_torrents(int num
, int limit1
, int limit2
, int global_limit
)
479 std::cerr
<< "\ntest equal torrents " << num
482 << " g: " << global_limit
<< std::endl
;
484 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
485 if (global_limit
> 0)
486 manager
.throttle(global_limit
);
488 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
489 boost::shared_ptr
<torrent
> t2(new torrent(manager
));
491 t1
->m_bandwidth_limit
[0].throttle(limit1
);
492 t2
->m_bandwidth_limit
[0].throttle(limit2
);
495 spawn_connections(v1
, ios
, t1
, num
, "t1p");
497 spawn_connections(v2
, ios
, t2
, num
, "t2p");
499 std::copy(v1
.begin(), v1
.end(), std::back_inserter(v
));
500 std::copy(v2
.begin(), v2
.end(), std::back_inserter(v
));
503 if (global_limit
> 0 && global_limit
< limit1
+ limit2
)
505 limit1
= (std::min
)(limit1
, global_limit
/ 2);
506 limit2
= global_limit
- limit1
;
509 for (connections_t::iterator i
= v1
.begin()
510 , end(v1
.end()); i
!= end
; ++i
)
512 sum
+= (*i
)->m_stats
.total_payload_upload();
515 std::cerr
<< sum
<< " target: " << limit1
<< std::endl
;
517 TEST_CHECK(close_to(sum
, limit1
, 1000));
520 for (connections_t::iterator i
= v2
.begin()
521 , end(v2
.end()); i
!= end
; ++i
)
523 sum
+= (*i
)->m_stats
.total_payload_upload();
526 std::cerr
<< sum
<< " target: " << limit2
<< std::endl
;
528 TEST_CHECK(close_to(sum
, limit2
, 1000));
531 void test_torrents_variable_rate(int num
, int limit
, int global_limit
)
533 std::cerr
<< "\ntest torrents variable rate" << num
535 << " g: " << global_limit
<< std::endl
;
537 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
538 if (global_limit
> 0)
539 manager
.throttle(global_limit
);
541 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
542 boost::shared_ptr
<torrent
> t2(new torrent(manager
));
544 t1
->m_bandwidth_limit
[0].throttle(limit
);
545 t2
->m_bandwidth_limit
[0].throttle(limit
);
548 spawn_connections(v1
, ios
, t1
, num
, "t1p");
550 spawn_connections(v2
, ios
, t2
, num
, "t2p");
552 std::copy(v1
.begin(), v1
.end(), std::back_inserter(v
));
553 std::copy(v2
.begin(), v2
.end(), std::back_inserter(v
));
555 deadline_timer
change_rate(ios
);
556 change_rate
.expires_from_now(milliseconds(1100));
557 change_rate
.async_wait(boost::bind(&do_change_rate
, _1
, boost::ref(change_rate
), t1
, t2
, limit
, 9));
561 if (global_limit
> 0 && global_limit
< 2 * limit
)
562 limit
= global_limit
/ 2;
565 for (connections_t::iterator i
= v1
.begin()
566 , end(v1
.end()); i
!= end
; ++i
)
568 sum
+= (*i
)->m_stats
.total_payload_upload();
571 std::cerr
<< sum
<< " target: " << limit
<< std::endl
;
573 TEST_CHECK(close_to(sum
, limit
, 1000));
576 for (connections_t::iterator i
= v2
.begin()
577 , end(v2
.end()); i
!= end
; ++i
)
579 sum
+= (*i
)->m_stats
.total_payload_upload();
582 std::cerr
<< sum
<< " target: " << limit
<< std::endl
;
584 TEST_CHECK(close_to(sum
, limit
, 1000));
587 void test_peer_priority(int limit
, bool torrent_limit
)
589 std::cerr
<< "\ntest peer priority " << limit
<< " " << torrent_limit
<< std::endl
;
591 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
592 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
595 t1
->m_bandwidth_limit
[0].throttle(limit
);
597 manager
.throttle(limit
);
600 spawn_connections(v1
, ios
, t1
, 10, "p");
602 std::copy(v1
.begin(), v1
.end(), std::back_inserter(v
));
603 boost::intrusive_ptr
<peer_connection
> p(
604 new peer_connection(ios
, t1
, 0, false, "no-priority"));
609 for (connections_t::iterator i
= v1
.begin()
610 , end(v1
.end()); i
!= end
; ++i
)
612 sum
+= (*i
)->m_stats
.total_payload_upload();
615 std::cerr
<< sum
<< " target: " << limit
<< std::endl
;
617 TEST_CHECK(close_to(sum
, limit
, 50));
619 std::cerr
<< "non-prioritized rate: " << p
->m_stats
.total_payload_upload() / sample_time
<< std::endl
;
620 TEST_CHECK(p
->m_stats
.total_payload_upload() / sample_time
< 10);
623 void test_no_starvation(int limit
)
625 std::cerr
<< "\ntest no starvation " << limit
<< std::endl
;
627 bandwidth_manager
<peer_connection
, torrent
> manager(ios
, 0);
628 boost::shared_ptr
<torrent
> t1(new torrent(manager
));
629 boost::shared_ptr
<torrent
> t2(new torrent(manager
));
631 manager
.throttle(limit
);
633 const int num_peers
= 20;
636 spawn_connections(v1
, ios
, t1
, num_peers
, "p");
638 std::copy(v1
.begin(), v1
.end(), std::back_inserter(v
));
639 boost::intrusive_ptr
<peer_connection
> p(
640 new peer_connection(ios
, t2
, 0, false, "no-priority"));
645 for (connections_t::iterator i
= v
.begin()
646 , end(v
.end()); i
!= end
; ++i
)
648 sum
+= (*i
)->m_stats
.total_payload_upload();
651 std::cerr
<< sum
<< " target: " << limit
<< std::endl
;
653 TEST_CHECK(close_to(sum
, limit
, 50));
655 std::cerr
<< "non-prioritized rate: " << p
->m_stats
.total_payload_upload() / sample_time
<< std::endl
;
656 TEST_CHECK(close_to(p
->m_stats
.total_payload_upload() / sample_time
, limit
/ (num_peers
+ 1), 1000));
661 using namespace libtorrent
;
663 test_equal_connections(2, 20);
664 test_equal_connections(2, 2000);
665 test_equal_connections(2, 20000);
666 test_equal_connections(3, 20000);
667 test_equal_connections(5, 20000);
668 test_equal_connections(7, 20000);
669 test_equal_connections(33, 60000);
670 test_equal_connections(33, 500000);
671 test_connections_variable_rate(2, 20, 0);
672 test_connections_variable_rate(5, 20000, 0);
673 test_connections_variable_rate(3, 2000, 6000);
674 test_connections_variable_rate(5, 2000, 30000);
675 test_connections_variable_rate(33, 500000, 0);
676 test_torrents(2, 400, 400, 0);
677 test_torrents(2, 100, 500, 0);
678 test_torrents(2, 3000, 3000, 6000);
679 test_torrents(1, 40000, 40000, 0);
680 test_torrents(24, 50000, 50000, 0);
681 test_torrents(5, 6000, 6000, 3000);
682 test_torrents(5, 6000, 5000, 4000);
683 test_torrents(5, 20000, 20000, 30000);
684 test_torrents_variable_rate(5, 6000, 3000);
685 test_torrents_variable_rate(5, 20000, 30000);
686 test_single_peer(40000, true);
687 test_single_peer(40000, false);
688 test_peer_priority(40000, false);
689 test_peer_priority(40000, true);
690 test_no_starvation(40000);