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