source: trunk/libtransmission/bandwidth.c @ 7154

Last change on this file since 7154 was 7154, checked in by charles, 12 years ago

(libT) yet another stab at getting bandwidth management under control. this version may suck less than previous attempts. It also breaks the mac build until someone adds iobuf.[ch] to xcode...

File size: 9.3 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id:$
11 */
12
13#include <assert.h>
14#include <limits.h>
15
16#include "event.h"
17
18#include "transmission.h"
19#include "bandwidth.h"
20#include "iobuf.h"
21#include "ptrarray.h"
22#include "utils.h"
23
24/***
25****
26***/
27
28enum
29{
30    HISTORY_MSEC = 2000,
31    INTERVAL_MSEC = HISTORY_MSEC,
32    GRANULARITY_MSEC = 250,
33    HISTORY_SIZE = ( INTERVAL_MSEC / GRANULARITY_MSEC ),
34    MAGIC_NUMBER = 43143
35};
36
37struct bratecontrol
38{
39    int newest;
40    struct { uint64_t date, size; } transfers[HISTORY_SIZE];
41};
42
43static float
44getSpeed( const struct bratecontrol * r )
45{
46    const uint64_t interval_msec = HISTORY_MSEC;
47    uint64_t       bytes = 0;
48    const uint64_t cutoff = tr_date ( ) - interval_msec;
49    int            i = r->newest;
50
51    for( ;; )
52    {
53        if( r->transfers[i].date <= cutoff )
54            break;
55
56        bytes += r->transfers[i].size;
57
58        if( --i == -1 ) i = HISTORY_SIZE - 1; /* circular history */
59        if( i == r->newest ) break; /* we've come all the way around */
60    }
61
62    return ( bytes / 1024.0 ) * ( 1000.0 / interval_msec );
63}
64
65static void
66bytesUsed( struct bratecontrol * r, size_t size )
67{
68    const uint64_t now = tr_date ( );
69
70    if( r->transfers[r->newest].date + GRANULARITY_MSEC >= now )
71        r->transfers[r->newest].size += size;
72    else
73    {
74        if( ++r->newest == HISTORY_SIZE ) r->newest = 0;
75        r->transfers[r->newest].date = now;
76        r->transfers[r->newest].size = size;
77    }
78}
79
80/******
81*******
82*******
83******/
84
85struct tr_band
86{
87    unsigned int isLimited : 1;
88    unsigned int honorParentLimits : 1;
89    size_t bytesLeft;
90    double desiredSpeed;
91    struct bratecontrol raw;
92    struct bratecontrol piece;
93};
94
95struct tr_bandwidth
96{
97    struct tr_band band[2];
98    struct tr_bandwidth * parent;
99    int magicNumber;
100    tr_session * session;
101    tr_ptrArray * children; /* struct tr_bandwidth */
102    tr_ptrArray * iobufs; /* struct tr_iobuf */
103};
104
105/***
106****
107***/
108
109static int
110comparePointers( const void * a, const void * b )
111{
112    return a - b;
113}
114
115static int
116isBandwidth( const tr_bandwidth * b )
117{
118    return ( b != NULL ) && ( b->magicNumber == MAGIC_NUMBER );
119}
120
121static int
122isDirection( const tr_direction dir )
123{
124    return ( dir == TR_UP ) || ( dir == TR_DOWN );
125}
126
127/***
128****
129***/
130
131tr_bandwidth*
132tr_bandwidthNew( tr_session * session, tr_bandwidth * parent )
133{
134    tr_bandwidth * b = tr_new0( tr_bandwidth, 1 );
135    b->session = session;
136    b->children = tr_ptrArrayNew( );
137    b->iobufs = tr_ptrArrayNew( );
138    b->magicNumber = MAGIC_NUMBER;
139    b->band[TR_UP].honorParentLimits = 1;
140    b->band[TR_DOWN].honorParentLimits = 1;
141    tr_bandwidthSetParent( b, parent );
142    return b;
143}
144
145void
146tr_bandwidthFree( tr_bandwidth * b )
147{
148    assert( isBandwidth( b ) );
149
150    tr_bandwidthSetParent( b, NULL );
151    tr_ptrArrayFree( b->iobufs, NULL );
152    tr_ptrArrayFree( b->children, NULL );
153    b->magicNumber = 0xDEAD;
154    tr_free( b );
155}
156
157/***
158****
159***/
160
161void
162tr_bandwidthSetParent( tr_bandwidth  * b,
163                       tr_bandwidth  * parent )
164{
165    assert( isBandwidth( b ) );
166    assert( b != parent );
167
168    if( b->parent )
169    {
170        assert( isBandwidth( b->parent ) );
171
172        tr_ptrArrayRemoveSorted( b->parent->children, b, comparePointers );
173        b->parent= NULL;
174    }
175
176    if( parent )
177    {
178        assert( isBandwidth( parent ) );
179        assert( parent->parent != b );
180
181        tr_ptrArrayInsertSorted( parent->children, b, comparePointers );
182        b->parent = parent;
183    }
184}
185
186void
187tr_bandwidthHonorParentLimits( tr_bandwidth  * b,
188                               tr_direction    dir,
189                               int             honorParentLimits )
190{
191    assert( isBandwidth( b ) );
192    assert( isDirection( dir ) );
193
194    b->band[dir].honorParentLimits = honorParentLimits != 0;
195}
196
197/***
198****
199***/
200
201void
202tr_bandwidthSetDesiredSpeed( tr_bandwidth  * b,
203                             tr_direction    dir,
204                             double          desiredSpeed )
205{
206    assert( isBandwidth( b ) );
207    assert( isDirection( dir ) );
208
209    b->band[dir].desiredSpeed = desiredSpeed; 
210}
211
212double
213tr_bandwidthGetDesiredSpeed( const tr_bandwidth  * b,
214                             tr_direction          dir )
215{
216    assert( isBandwidth( b ) );
217    assert( isDirection( dir ) );
218
219    return b->band[dir].desiredSpeed;
220}
221
222void
223tr_bandwidthSetLimited( tr_bandwidth  * b,
224                        tr_direction    dir,
225                        int             isLimited )
226{
227    assert( isBandwidth( b ) );
228    assert( isDirection( dir ) );
229
230    b->band[dir].isLimited = isLimited != 0;
231}
232
233int
234tr_bandwidthIsLimited( const tr_bandwidth  * b,
235                       tr_direction          dir )
236{
237    assert( isBandwidth( b ) );
238    assert( isDirection( dir ) );
239
240    return b->band[dir].isLimited != 0;
241}
242
243#if 0
244#define DEBUG_DIRECTION TR_DOWN
245#endif
246
247void
248tr_bandwidthAllocate( tr_bandwidth  * b,
249                      tr_direction    dir,
250                      int             period_msec )
251{
252    const double currentSpeed = tr_bandwidthGetPieceSpeed( b, dir ); /* KiB/s */
253    const double desiredSpeed = b->band[dir].desiredSpeed;           /* KiB/s */
254    const double seconds_per_pulse = period_msec / 1000.0;
255    const double current_bytes_per_pulse = currentSpeed * 1024.0 * seconds_per_pulse;
256    const double desired_bytes_per_pulse = desiredSpeed * 1024.0 * seconds_per_pulse;
257    const double pulses_per_history = (double)HISTORY_MSEC / period_msec;
258    const double min = desired_bytes_per_pulse * 0.90;
259    const double max = desired_bytes_per_pulse * 1.50;
260    const double next_pulse_bytes = desired_bytes_per_pulse * ( pulses_per_history + 1 )
261                                  - ( current_bytes_per_pulse * pulses_per_history );
262    double clamped;
263
264    /* clamp the return value to lessen oscillation */
265    clamped = next_pulse_bytes;
266    clamped = MAX( clamped, min );
267    clamped = MIN( clamped, max );
268    b->band[dir].bytesLeft = clamped;
269
270#ifdef DEBUG_DIRECTION
271if( dir == DEBUG_DIRECTION )
272fprintf( stderr, "bandwidth %p currentSpeed(%5.2f) desiredSpeed(%5.2f), allocating %5.2f (unclamped: %5.2f)\n",
273         b, currentSpeed, desiredSpeed,
274         clamped/1024.0, next_pulse_bytes/1024.0 );
275#endif
276
277    /* notify the io buffers that there's more bandwidth available */
278    if( !b->band[dir].isLimited || ( clamped > 0 ) ) {
279        int i, n=0;
280        short what = dir==TR_UP ? EV_WRITE : EV_READ;
281        struct tr_iobuf ** iobufs = (struct tr_iobuf**) tr_ptrArrayPeek( b->iobufs, &n );
282#ifdef DEBUG_DIRECTION
283if( dir == DEBUG_DIRECTION )
284fprintf( stderr, "bandwidth %p has %d iobufs\n", b, n );
285#endif
286        for( i=0; i<n; ++i )
287            tr_iobuf_enable( iobufs[i], what );
288    }
289
290    /* all children should reallocate too */
291    if( 1 ) {
292        int i, n=0;
293        struct tr_bandwidth ** children = (struct tr_bandwidth**) tr_ptrArrayPeek( b->children, &n );
294        for( i=0; i<n; ++i )
295            tr_bandwidthAllocate( children[i], dir, period_msec );
296    }
297}
298
299/***
300****
301***/
302
303void
304tr_bandwidthAddBuffer( tr_bandwidth        * b,
305                       struct tr_iobuf     * iobuf )
306{
307    assert( isBandwidth( b ) );
308    assert( iobuf );
309
310    tr_ptrArrayInsertSorted( b->iobufs, iobuf, comparePointers );
311}
312
313void
314tr_bandwidthRemoveBuffer( tr_bandwidth        * b,
315                          struct tr_iobuf     * iobuf )
316{
317    assert( isBandwidth( b ) );
318    assert( iobuf );
319
320    tr_ptrArrayRemoveSorted( b->iobufs, iobuf, comparePointers );
321}
322
323/***
324****
325***/
326
327size_t
328tr_bandwidthClamp( const tr_bandwidth  * b,
329                   tr_direction          dir,
330                   size_t                byteCount )
331{
332    assert( isBandwidth( b ) );
333    assert( isDirection( dir ) );
334
335    if( b )
336    {
337        if( b->band[dir].isLimited )
338            byteCount = MIN( byteCount, b->band[dir].bytesLeft );
339
340        if( b->parent && b->band[dir].honorParentLimits )
341            byteCount = tr_bandwidthClamp( b->parent, dir, byteCount );
342    }
343
344    return byteCount;
345}
346
347double
348tr_bandwidthGetRawSpeed( const tr_bandwidth * b, tr_direction dir )
349{
350    assert( isBandwidth( b ) );
351    assert( isDirection( dir ) );
352
353    return getSpeed( &b->band[dir].raw );
354}
355
356double
357tr_bandwidthGetPieceSpeed( const tr_bandwidth * b, tr_direction dir )
358{
359    assert( isBandwidth( b ) );
360    assert( isDirection( dir ) );
361
362    return getSpeed( &b->band[dir].piece );
363}
364
365void
366tr_bandwidthUsed( tr_bandwidth  * b,
367                  tr_direction    dir,
368                  size_t          byteCount,
369                  int             isPieceData )
370{
371    struct tr_band * band;
372
373    assert( isBandwidth( b ) );
374    assert( isDirection( dir ) );
375
376    band = &b->band[dir];
377
378    if( band->isLimited && isPieceData )
379        band->bytesLeft -= MIN( band->bytesLeft, byteCount );
380
381#ifdef DEBUG_DIRECTION
382if( ( dir == DEBUG_DIRECTION ) && band->isLimited && isPieceData )
383fprintf( stderr, "%p consumed %zu bytes of piece data... %zu left\n", b, byteCount, band->bytesLeft );
384#endif
385
386    bytesUsed( &band->raw, byteCount );
387
388    if( isPieceData )
389        bytesUsed( &band->piece, byteCount );
390
391    if( b->parent != NULL )
392        tr_bandwidthUsed( b->parent, dir, byteCount, isPieceData );
393}
Note: See TracBrowser for help on using the repository browser.