1 /*
2  * Created on May 7, 2007
3  * Created by Paul Gardner
4  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  *
18  */
19 
20 
21 package com.aelitis.azureus.core.speedmanager.impl.v3;
22 
23 import java.util.HashMap;
24 import java.util.Map;
25 
26 import org.gudy.azureus2.core3.config.COConfigurationManager;
27 import org.gudy.azureus2.core3.config.ParameterListener;
28 
29 import com.aelitis.azureus.core.neuronal.NeuralSpeedLimiter;
30 import com.aelitis.azureus.core.speedmanager.SpeedManagerPingSource;
31 import com.aelitis.azureus.core.speedmanager.impl.SpeedManagerAlgorithmProvider;
32 import com.aelitis.azureus.core.speedmanager.impl.SpeedManagerAlgorithmProviderAdapter;
33 import com.aelitis.azureus.core.util.average.Average;
34 import com.aelitis.azureus.core.util.average.AverageFactory;
35 
36 public class
37 SpeedManagerAlgorithmProviderV3
38 	implements SpeedManagerAlgorithmProvider
39 {
40 	private static final String	CONFIG_MIN_UP			= "AutoSpeed Min Upload KBs";
41 	private static final String	CONFIG_MAX_UP			= "AutoSpeed Max Upload KBs";
42 	private static final String	CONFIG_MAX_INC			= "AutoSpeed Max Increment KBs";
43 	private static final String	CONFIG_MAX_DEC			= "AutoSpeed Max Decrement KBs";
44 	private static final String	CONFIG_CHOKE_PING		= "AutoSpeed Choking Ping Millis";
45 	private static final String	CONFIG_DOWNADJ_ENABLE	= "AutoSpeed Download Adj Enable";
46 	private static final String	CONFIG_DOWNADJ_RATIO	= "AutoSpeed Download Adj Ratio";
47 	private static final String	CONFIG_LATENCY_FACTOR	= "AutoSpeed Latency Factor";
48 	private static final String	CONFIG_FORCED_MIN		= "AutoSpeed Forced Min KBs";
49 
50 	private static int					PING_CHOKE_TIME;
51 	private static int					MIN_UP;
52 	private static int					MAX_UP;
53 	private static boolean				ADJUST_DOWNLOAD_ENABLE;
54 	private static float				ADJUST_DOWNLOAD_RATIO;
55 	private static int					MAX_INCREMENT;
56 	private static int					MAX_DECREMENT;
57 	private static int					LATENCY_FACTOR;
58 	private static int					FORCED_MIN_SPEED;
59 
60 	private static final String[]	CONFIG_PARAMS = {
61 		CONFIG_MIN_UP, CONFIG_MAX_UP,
62 		CONFIG_MAX_INC, CONFIG_MAX_DEC,
63 		CONFIG_CHOKE_PING,
64 		CONFIG_DOWNADJ_ENABLE,
65 		CONFIG_DOWNADJ_RATIO,
66 		CONFIG_LATENCY_FACTOR,
67 		CONFIG_FORCED_MIN };
68 
69 
70 	static{
COConfigurationManager.addAndFireParameterListeners( CONFIG_PARAMS, new ParameterListener() { public void parameterChanged( String parameterName ) { PING_CHOKE_TIME = COConfigurationManager.getIntParameter( CONFIG_CHOKE_PING ); MIN_UP = COConfigurationManager.getIntParameter( CONFIG_MIN_UP ) * 1024; MAX_UP = COConfigurationManager.getIntParameter( CONFIG_MAX_UP ) * 1024; MAX_INCREMENT = COConfigurationManager.getIntParameter( CONFIG_MAX_INC ) * 1024; MAX_DECREMENT = COConfigurationManager.getIntParameter( CONFIG_MAX_DEC ) * 1024; ADJUST_DOWNLOAD_ENABLE = COConfigurationManager.getBooleanParameter( CONFIG_DOWNADJ_ENABLE ); String str = COConfigurationManager.getStringParameter( CONFIG_DOWNADJ_RATIO ); LATENCY_FACTOR = COConfigurationManager.getIntParameter( CONFIG_LATENCY_FACTOR ); if ( LATENCY_FACTOR < 1 ){ LATENCY_FACTOR = 1; } FORCED_MIN_SPEED = COConfigurationManager.getIntParameter( CONFIG_FORCED_MIN ) * 1024; if ( FORCED_MIN_SPEED < 1024 ){ FORCED_MIN_SPEED = 1024; } try{ ADJUST_DOWNLOAD_RATIO = Float.parseFloat(str); }catch( Throwable e ){ } } })71 		COConfigurationManager.addAndFireParameterListeners(
72 				CONFIG_PARAMS,
73 				new ParameterListener()
74 				{
75 					public void
76 					parameterChanged(
77 						String parameterName )
78 					{
79 						PING_CHOKE_TIME	= COConfigurationManager.getIntParameter( CONFIG_CHOKE_PING );
80 						MIN_UP			= COConfigurationManager.getIntParameter( CONFIG_MIN_UP ) * 1024;
81 						MAX_UP			= COConfigurationManager.getIntParameter( CONFIG_MAX_UP ) * 1024;
82 						MAX_INCREMENT	= COConfigurationManager.getIntParameter( CONFIG_MAX_INC ) * 1024;
83 						MAX_DECREMENT	= COConfigurationManager.getIntParameter( CONFIG_MAX_DEC ) * 1024;
84 						ADJUST_DOWNLOAD_ENABLE	= COConfigurationManager.getBooleanParameter( CONFIG_DOWNADJ_ENABLE );
85 						String	str 	= COConfigurationManager.getStringParameter( CONFIG_DOWNADJ_RATIO );
86 						LATENCY_FACTOR	= COConfigurationManager.getIntParameter( CONFIG_LATENCY_FACTOR );
87 
88 						if ( LATENCY_FACTOR < 1 ){
89 							LATENCY_FACTOR = 1;
90 						}
91 
92 						FORCED_MIN_SPEED	= COConfigurationManager.getIntParameter( CONFIG_FORCED_MIN ) * 1024;
93 
94 						if ( FORCED_MIN_SPEED < 1024 ){
95 							FORCED_MIN_SPEED = 1024;
96 						}
97 
98 						try{
99 							ADJUST_DOWNLOAD_RATIO = Float.parseFloat(str);
100 						}catch( Throwable e ){
101 						}
102 					}
103 				});
104 
105 	}
106 	private static final int UNLIMITED	= Integer.MAX_VALUE;
107 
108 	private static final int	MODE_RUNNING	= 0;
109 	private static final int	MODE_FORCED_MIN	= 1;
110 	private static final int	MODE_FORCED_MAX	= 2;
111 
112 	private static final int	FORCED_MAX_TICKS	= 30;
113 
114 	private static final int	FORCED_MIN_TICKS		= 60;			// time we'll force low upload to get baseline
115 	private static final int	FORCED_MIN_AT_START_TICK_LIMIT	= 60;	// how long we'll wait on start up before forcing min
116 
117 	private static final int	PING_AVERAGE_HISTORY_COUNT	= 5;
118 
119 	private static final int	IDLE_UPLOAD_SPEED		= 5*1024;		// speed at which upload is treated as "idle"
120 	private static final int	INITIAL_IDLE_AVERAGE	= 100;
121 	private static final int	MIN_IDLE_AVERAGE		= 50;		// any lower than this and small ping variations cause overreaction
122 
123 	private static final int	INCREASING	= 1;
124 	private static final int	DECREASING	= 2;
125 
126 	private SpeedManagerAlgorithmProviderAdapter	adapter;
127 
128 	private NeuralSpeedLimiter limiter;
129 
130 	private Average upload_average				= AverageFactory.MovingImmediateAverage( 5 );
131 	private Average upload_short_average		= AverageFactory.MovingImmediateAverage( 2 );
132 	private Average upload_short_prot_average	= AverageFactory.MovingImmediateAverage( 2 );
133 
134 	private Average	ping_average_history		= AverageFactory.MovingImmediateAverage(PING_AVERAGE_HISTORY_COUNT);
135 
136 	private Average choke_speed_average			= AverageFactory.MovingImmediateAverage( 3 );
137 
138 	private Map							ping_sources;
139 	private volatile int				replacement_contacts;
140 
141 	private int					mode;
142 	private volatile int		mode_ticks;
143 	private int					saved_limit;
144 
145 	private int		direction;
146 	private int		ticks;
147 	private int		idle_ticks;
148 	private int		idle_average;
149 	private boolean	idle_average_set;
150 
151 	private int		max_ping;
152 
153 	private int		max_upload_average;
154 
155 	public
SpeedManagerAlgorithmProviderV3( SpeedManagerAlgorithmProviderAdapter _adapter )156 	SpeedManagerAlgorithmProviderV3(
157 		SpeedManagerAlgorithmProviderAdapter	_adapter )
158 	{
159 		adapter	= _adapter;
160 		limiter = new NeuralSpeedLimiter();
161 	}
162 
163     public void
destroy()164     destroy()
165     {
166     }
167 
168 	public void
updateStats()169 	updateStats()
170 	{
171 		int	current_protocol_speed 	= adapter.getCurrentProtocolUploadSpeed();
172 		int	current_data_speed		= adapter.getCurrentDataUploadSpeed();
173 
174 		int	current_speed = current_protocol_speed + current_data_speed;
175 
176 		upload_average.update( current_speed );
177 
178 		upload_short_average.update( current_speed );
179 
180 		upload_short_prot_average.update( current_protocol_speed );
181 
182 		mode_ticks++;
183 
184 		ticks++;
185 	}
186 
187 	public void
reset()188 	reset()
189 	{
190 		ticks					= 0;
191 		mode					= MODE_RUNNING;
192 		mode_ticks				= 0;
193 		idle_ticks				= 0;
194 		idle_average			= INITIAL_IDLE_AVERAGE;
195 		idle_average_set		= false;
196 		max_upload_average		= 0;
197 		direction				= INCREASING;
198 		max_ping				= 0;
199 		replacement_contacts	= 0;
200 
201 		ping_sources			= new HashMap();
202 
203 		choke_speed_average.reset();
204 		upload_average.reset();
205 		upload_short_average.reset();
206 		upload_short_prot_average.reset();
207 		ping_average_history.reset();
208 	}
209 
210 	public void
pingSourceFound( SpeedManagerPingSource source, boolean is_replacement )211 	pingSourceFound(
212 		SpeedManagerPingSource		source,
213 		boolean						is_replacement )
214 	{
215 		if ( is_replacement ){
216 
217 			replacement_contacts++;
218 		}
219 
220 		synchronized( ping_sources ){
221 
222 			ping_sources.put( source, new pingSource( source ));
223 		}
224 	}
225 
226 	public void
pingSourceFailed( SpeedManagerPingSource source )227 	pingSourceFailed(
228 		SpeedManagerPingSource		source )
229 	{
230 		synchronized( ping_sources ){
231 
232 			ping_sources.remove( source );
233 		}
234 	}
235 
236 	public void
calculate( SpeedManagerPingSource[] sources )237 	calculate(
238 		SpeedManagerPingSource[]	sources )
239 	{
240 		int	min_rtt	= UNLIMITED;
241 
242 		for (int i=0;i<sources.length;i++){
243 
244 			int	rtt =  sources[i].getPingTime();
245 
246 			if ( rtt >= 0 && rtt < min_rtt ){
247 
248 				min_rtt	= rtt;
249 			}
250 		}
251 
252 		String	str = "";
253 
254 		int	ping_total		= 0;
255 		int	ping_count		= 0;
256 
257 		for (int i=0;i<sources.length;i++){
258 
259 			pingSource	ps;
260 
261 			synchronized( ping_sources ){
262 
263 				ps = (pingSource)ping_sources.get( sources[i] );
264 			}
265 
266 			int	rtt =  sources[i].getPingTime();
267 
268 			str += (i==0?"":",") + rtt;
269 
270 				// discount anything 5*min reported unless min is really small, in which case round
271 				// up as we're only trying to catch badly behaved ones
272 
273 			if ( ps != null ){
274 
275 				boolean	good_ping =  rtt < 5 * Math.max( min_rtt, 75 );
276 
277 				ps.pingReceived( rtt, good_ping );
278 
279 				if ( !good_ping ){
280 
281 					rtt = -1;
282 				}
283 			}
284 
285 			if ( rtt != -1 ){
286 
287 				ping_total += rtt;
288 
289 				ping_count++;
290 			}
291 		}
292 
293 		if ( ping_count == 0 ){
294 
295 				// all failed
296 
297 			return;
298 		}
299 
300 		int	ping_average = ping_total/ping_count;
301 
302 			// bias towards min
303 
304 		ping_average = ( ping_average + min_rtt ) / 2;
305 
306 		int	running_average = (int)ping_average_history.update( ping_average );
307 
308 		if ( ping_average > max_ping ){
309 
310 			max_ping	= ping_average;
311 		}
312 
313 		int	up_average = (int)upload_average.getAverage();
314 
315 			// if we're uploading slowly or the current ping rate is better than our current idle average
316 			// then we count this towards establishing the baseline
317 
318 		if ( up_average <= IDLE_UPLOAD_SPEED || ( running_average < idle_average && !idle_average_set )){
319 
320 			idle_ticks++;
321 
322 			if ( idle_ticks >= PING_AVERAGE_HISTORY_COUNT ){
323 
324 				idle_average	= Math.max( running_average, MIN_IDLE_AVERAGE );
325 
326 				log( "New idle average: " + idle_average );
327 
328 				idle_average_set	= true;
329 			}
330 		}else{
331 
332 			if ( up_average > max_upload_average ){
333 
334 				max_upload_average	= up_average;
335 
336 				log( "New max upload:" +  max_upload_average );
337 			}
338 
339 			idle_ticks	= 0;
340 
341 		}
342 
343 		if ( idle_average_set && running_average < idle_average ){
344 
345 				// bump down if we happen to come across lower idle values
346 
347 			idle_average	= Math.max( running_average, MIN_IDLE_AVERAGE );
348 		}
349 
350 		int	current_speed 	= adapter.getCurrentDataUploadSpeed() + adapter.getCurrentProtocolUploadSpeed();
351 		int	current_limit	= adapter.getCurrentUploadLimit();
352 
353 		int	new_limit	= current_limit;
354 
355 		log(
356 				"Pings: " + str + ", average=" + ping_average +", running_average=" + running_average +
357 				",idle_average=" + idle_average + ", speed=" + current_speed + ",limit=" + current_limit +
358 				",choke = " + (int)choke_speed_average.getAverage());
359 
360 
361 
362 		if ( mode == MODE_FORCED_MAX ){
363 
364 			if ( mode_ticks > FORCED_MAX_TICKS ){
365 
366 				mode		= MODE_RUNNING;
367 
368 				current_limit = new_limit	= saved_limit;
369 			}
370 
371 		}else if ( mode == MODE_FORCED_MIN ){
372 
373 			if ( idle_average_set || mode_ticks > FORCED_MIN_TICKS ){
374 
375 				log( "Mode -> running" );
376 
377 				if ( !idle_average_set ){
378 
379 					idle_average	= Math.max( running_average, MIN_IDLE_AVERAGE );
380 
381 					idle_average_set	= true;
382 				}
383 
384 				mode		= MODE_RUNNING;
385 				mode_ticks	= 0;
386 
387 				current_limit = new_limit	= saved_limit;
388 
389 			}else if ( mode_ticks == 5 ){
390 
391 					// we've had 5 secs of min up speed, clear out the ping average now
392 					// to get accurate times
393 
394 				ping_average_history.reset();
395 			}
396 		}
397 
398 		if ( mode == MODE_RUNNING ){
399 
400 			if (	( ticks > FORCED_MIN_AT_START_TICK_LIMIT && !idle_average_set ) ||
401 					( replacement_contacts >= 2 && idle_average_set )){
402 
403 					// we've been running a while but no min set, or we've got some new untested
404 					// contacts - force it
405 
406 				log( "Mode -> forced min" );
407 
408 				mode		= MODE_FORCED_MIN;
409 				mode_ticks	= 0;
410 				saved_limit	= current_limit;
411 
412 				idle_average_set	= false;
413 				idle_ticks			= 0;
414 				replacement_contacts= 0;
415 
416 				new_limit	= FORCED_MIN_SPEED;
417 
418 			}else{
419 
420 				limiter.setDlSpeed(adapter.getCurrentDataDownloadSpeed());
421 				limiter.setUlSpeed(adapter.getCurrentDataUploadSpeed());
422 				limiter.setMaxDlSpeed(adapter.getSpeedManager().getEstimatedDownloadCapacityBytesPerSec().getBytesPerSec());
423 				limiter.setMaxUlSpeed(adapter.getSpeedManager().getEstimatedUploadCapacityBytesPerSec().getBytesPerSec());
424 				limiter.setLatency(ping_average);
425 				limiter.setMinLatency(idle_average);
426 				limiter.setMaxLatency(1500);
427 
428 				if(limiter.shouldLimitDownload()) {
429 					adapter.setCurrentDownloadLimit( (int) limiter.getDownloadLimit() );
430 				} else {
431 					adapter.setCurrentDownloadLimit(0);
432 				}
433 
434 				if(limiter.shouldLimitUpload()) {
435 					adapter.setCurrentUploadLimit( (int) limiter.getUploadLimit() );
436 				} else {
437 					adapter.setCurrentUploadLimit(0);
438 				}
439 			}
440 		}
441 	}
442 
443 	public int
getIdlePingMillis()444 	getIdlePingMillis()
445 	{
446 		return( idle_average );
447 	}
448 
449 	public int
getCurrentPingMillis()450 	getCurrentPingMillis()
451 	{
452 		return( (int)ping_average_history.getAverage());
453 	}
454 
455 	public int
getMaxPingMillis()456 	getMaxPingMillis()
457 	{
458 		return( max_ping );
459 	}
460 
461 		/**
462 		 * Returns the current view of when choking occurs
463 		 * @return speed in bytes/sec
464 		 */
465 
466 	public int
getCurrentChokeSpeed()467 	getCurrentChokeSpeed()
468 	{
469 		return((int)choke_speed_average.getAverage());
470 	}
471 
472 	public int
getMaxUploadSpeed()473 	getMaxUploadSpeed()
474 	{
475 		return( max_upload_average );
476 	}
477 
478 	public boolean
getAdjustsDownloadLimits()479 	getAdjustsDownloadLimits()
480 	{
481 		return( ADJUST_DOWNLOAD_ENABLE );
482 	}
483 
484 	protected void
log( String str )485 	log(
486 		String		str )
487 	{
488 		adapter.log( str );
489 	}
490 
491 	protected class
492 	pingSource
493 	{
494 		private SpeedManagerPingSource	source;
495 
496 		private int		last_good_ping;
497 		private int		bad_pings;
498 
499 		protected
pingSource( SpeedManagerPingSource _source )500 		pingSource(
501 			SpeedManagerPingSource	_source )
502 		{
503 			source	= _source;
504 		}
505 
506 		public void
pingReceived( int time, boolean good_ping )507 		pingReceived(
508 			int		time,
509 			boolean	good_ping )
510 		{
511 			if ( good_ping ){
512 
513 				bad_pings = 0;
514 
515 				last_good_ping	= time;
516 
517 			}else{
518 
519 				bad_pings++;
520 			}
521 
522 				// three strikes and you're out!
523 
524 			if ( bad_pings == 3 ){
525 
526 				source.destroy();
527 			}
528 		}
529 	}
530 }
531