/*
* Created on May 12, 2010
* Created by Paul Gardner
*
* Copyright 2010 Vuze, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gudy.azureus2.core3.download.impl;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPeerManagerStats;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AsyncDispatcher;
import org.gudy.azureus2.core3.util.DisplayFormatters;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;
import org.gudy.azureus2.core3.util.TimerEventPeriodic;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.networkmanager.LimitedRateGroup;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.speedmanager.SpeedManager;
import com.aelitis.azureus.core.speedmanager.SpeedManagerLimitEstimate;
import com.aelitis.azureus.core.speedmanager.SpeedManagerPingMapper;
public class
DownloadManagerRateController
{
private static AzureusCore core;
private static SpeedManager speed_manager;
private static Map<PEPeerManager,PMState> pm_map = new HashMap<PEPeerManager, PMState>();
private static TimerEventPeriodic timer;
private static AsyncDispatcher dispatcher = new AsyncDispatcher( "DMCRateController" );
private static boolean enable;
private static boolean enable_limit_handling;
private static int slack_bytes_per_sec;
static{
COConfigurationManager.addAndFireParameterListeners(
new String[]{
"Bias Upload Enable",
"Bias Upload Handle No Limit",
"Bias Upload Slack KBs",
},
new ParameterListener()
{
public void
parameterChanged(
String parameterName)
{
enable = COConfigurationManager.getBooleanParameter( "Bias Upload Enable" );
enable_limit_handling = COConfigurationManager.getBooleanParameter( "Bias Upload Handle No Limit" ) && enable;
slack_bytes_per_sec = COConfigurationManager.getIntParameter( "Bias Upload Slack KBs" )*1024;
}
});
}
private static volatile int rate_limit = 0;
private static LimitedRateGroup
limiter =
new LimitedRateGroup()
{
public String
getName()
{
return( "DMRC" );
}
public int
getRateLimitBytesPerSecond()
{
return( rate_limit );
}
public void
updateBytesUsed(
int used )
{
}
};
private static final int TIMER_MILLIS = 1000;
private static final int WAIT_AFTER_CHOKE_PERIOD = 10*1000;
private static final int WAIT_AFTER_CHOKE_TICKS = WAIT_AFTER_CHOKE_PERIOD/TIMER_MILLIS;
private static final int DEFAULT_UP_LIMIT = 250*1024;
private static final int MAX_UP_DIFF = 15*1024;
private static final int MAX_DOWN_DIFF = 10*1024;
private static final int MIN_DIFF = 2*1024;
private static final int SAMPLE_COUNT = 5;
private static int sample_num;
private static double incomplete_samples;
private static double complete_samples;
private static int ticks_to_sample_start;
private static int last_rate_limit;
private static double last_incomplete_average;
private static double last_complete_average;
private static double last_overall_average;
private static int tick_count = 0;
private static int last_tick_processed = -1;
private static long pm_last_bad_limit;
private static int latest_choke;
private static int wait_until_tick;
public static String
getString()
{
if ( enable ){
String str = "reserved=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( slack_bytes_per_sec );
if ( enable_limit_handling ){
str += ", limit=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( rate_limit );
str += ", last[choke=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( latest_choke ) +
", ratio=" + DisplayFormatters.formatDecimal(last_incomplete_average/last_complete_average, 2) + "]";
return( str );
}else{
return( str );
}
}else{
return( "Disabled" );
}
}
public static void
addPeerManager(
final PEPeerManager pm )
{
dispatcher.dispatch(
new AERunnable()
{
public void
runSupport()
{
if ( core == null ){
core = AzureusCoreFactory.getSingleton();
speed_manager = core.getSpeedManager();
}
boolean is_complete = !pm.hasDownloadablePiece();
PEPeerManagerStats pm_stats = pm.getStats();
long up_bytes = pm_stats.getTotalDataBytesSentNoLan() + pm_stats.getTotalProtocolBytesSentNoLan();
if ( is_complete ){
pm.addRateLimiter( limiter, true );
}
pm_map.put( pm, new PMState( pm, is_complete, up_bytes ));
if ( timer == null ){
timer =
SimpleTimer.addPeriodicEvent(
"DMRC",
TIMER_MILLIS,
new TimerEventPerformer()
{
public void
perform(
TimerEvent event )
{
dispatcher.dispatch(
new AERunnable()
{
public void
runSupport()
{
update();
}
});
}
});
}
}
});
}
public static void
removePeerManager(
final PEPeerManager pm )
{
dispatcher.dispatch(
new AERunnable()
{
public void
runSupport()
{
pm_map.remove( pm );
if ( pm_map.size() == 0 ){
timer.cancel();
timer = null;
rate_limit = 0;
}
}
});
}
private static void
update()
{
tick_count++;
if ( (!enable_limit_handling ) || pm_map.size() == 0 ||
NetworkManager.isSeedingOnlyUploadRate() || NetworkManager.getMaxUploadRateBPSNormal() != 0 ||
core == null || speed_manager == null || speed_manager.getSpeedTester() == null ){
rate_limit = 0;
return;
}
int num_complete = 0;
int num_incomplete = 0;
int num_interesting = 0;
int i_up_total = 0;
int c_up_total = 0;
long mono_now = SystemTime.getMonotonousTime();
for ( Map.Entry<PEPeerManager, PMState> entry: pm_map.entrySet()){
PEPeerManager pm = entry.getKey();
PMState state = entry.getValue();
boolean is_complete = !pm.hasDownloadablePiece();
PEPeerManagerStats pm_stats = pm.getStats();
long up_bytes = pm_stats.getTotalDataBytesSentNoLan() + pm_stats.getTotalProtocolBytesSentNoLan();
long diff = state.setBytesUp( up_bytes );
if ( is_complete ){
num_complete++;
c_up_total += diff;
}else{
num_incomplete++;
i_up_total += diff;
if ( state.isInteresting( mono_now )){
num_interesting++;
}
}
if ( state.isComplete() != is_complete ){
if ( is_complete ){
pm.addRateLimiter( limiter, true );
}else{
pm.removeRateLimiter( limiter, true );
}
state.setComplete( is_complete );
}
}
if ( num_incomplete == 0 || num_complete == 0 || num_interesting == 0 ){
rate_limit = 0;
return;
}
boolean skipped_tick = false;
if ( last_tick_processed != tick_count - 1 ){
pm_last_bad_limit = 0;
latest_choke = 0;
wait_until_tick = 0;
ticks_to_sample_start = 0;
sample_num = 0;
incomplete_samples = 0;
complete_samples = 0;
skipped_tick = true;
}
last_tick_processed = tick_count;
if ( skipped_tick || tick_count < wait_until_tick ){
return;
}
try{
long real_now = SystemTime.getCurrentTime();
SpeedManagerPingMapper mapper = speed_manager.getActiveMapper();
if ( rate_limit == 0 ){
rate_limit = speed_manager.getEstimatedUploadCapacityBytesPerSec().getBytesPerSec();
if ( rate_limit == 0 ){
rate_limit = DEFAULT_UP_LIMIT;
}
}
SpeedManagerLimitEstimate last_bad = mapper.getLastBadUploadLimit();
if ( last_bad != null ){
int last_bad_limit = last_bad.getBytesPerSec();
if ( last_bad_limit != pm_last_bad_limit ){
pm_last_bad_limit = last_bad_limit;
SpeedManagerLimitEstimate[] bad_ups = mapper.getBadUploadHistory();
int total = last_bad.getBytesPerSec();
int count = 1;
for ( SpeedManagerLimitEstimate bad: bad_ups ){
long t = bad.getWhen();
if ( real_now - t <= 30*1000 && bad.getBytesPerSec() != last_bad_limit ){
total += bad.getBytesPerSec();
count++;
}
}
latest_choke = total/count;
int new_rate_limit;
if ( rate_limit == 0 ){
new_rate_limit = latest_choke/2;
}else{
new_rate_limit = rate_limit/2;
}
if ( new_rate_limit < slack_bytes_per_sec ){
new_rate_limit = slack_bytes_per_sec;
}
rate_limit = new_rate_limit;
wait_until_tick = tick_count + WAIT_AFTER_CHOKE_TICKS;
ticks_to_sample_start = 0;
sample_num = 0;
complete_samples = 0;
incomplete_samples = 0;
last_rate_limit = 0;
return;
}
}
if ( ticks_to_sample_start > 0 ){
ticks_to_sample_start--;
}else if ( sample_num < SAMPLE_COUNT ){
complete_samples += c_up_total;
incomplete_samples += i_up_total;
sample_num++;
}else{
double incomplete_average = incomplete_samples / SAMPLE_COUNT;
double complete_average = complete_samples / SAMPLE_COUNT;
double overall_average = ( complete_samples + incomplete_samples ) / SAMPLE_COUNT;
int action = -1;
try{
if ( last_rate_limit == 0 ){
action = 1;
}else{
double overall_change = overall_average - last_overall_average;
if ( overall_change < 0 ){
if ( rate_limit < last_rate_limit ){
// System.out.println( "average decreased" );
action = 1;
}else{
action = 0;
}
}else{
double last_ratio = last_incomplete_average / last_complete_average;
double ratio = incomplete_average / complete_average;
// System.out.println( "rate=" + rate_limit + "/" + last_rate_limit + ", ratio=" + ratio + "/" + last_ratio );
if ( rate_limit < last_rate_limit && ratio >= last_ratio ){
action = -1;
}else if ( rate_limit > last_rate_limit && ratio <= last_ratio ){
double i_up_change = incomplete_average - last_incomplete_average;
if ( i_up_change >= 1024 ){
action = -1;
}else{
action = 1;
}
}else{
action = 1;
}
}
}
}finally{
int new_rate_limit;
if ( action > 0 ){
int ceiling = latest_choke==0?DEFAULT_UP_LIMIT:latest_choke;
int diff = ( ceiling - rate_limit )/4;
if ( diff > MAX_UP_DIFF ){
diff = MAX_UP_DIFF;
}else if ( diff < MIN_DIFF ){
diff = MIN_DIFF;
}
new_rate_limit = rate_limit + diff;
if ( new_rate_limit > 100*1024*1024 ){
new_rate_limit = 100*1024*1024;
}
}else if ( action < 0 ){
int diff = rate_limit/5;
if ( diff > MAX_DOWN_DIFF ){
diff = MAX_DOWN_DIFF;
}else if ( diff < MIN_DIFF ){
diff = MIN_DIFF;
}
new_rate_limit = rate_limit - diff;
if ( new_rate_limit < slack_bytes_per_sec ){
new_rate_limit = slack_bytes_per_sec;
}
}else{
new_rate_limit = rate_limit;
}
last_rate_limit = rate_limit;
last_overall_average = overall_average;
last_complete_average = complete_average;
last_incomplete_average = incomplete_average;
rate_limit = new_rate_limit;
sample_num = 0;
complete_samples = 0;
incomplete_samples = 0;
}
}
}finally{
// System.out.println( "rate=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( rate_limit ) + ", last_choke=" + latest_choke );
}
}
private static class
PMState
{
final private PEPeerManager manager;
private boolean complete;
private long bytes_up;
private boolean interesting;
private long last_interesting_calc;
private
PMState(
PEPeerManager _manager,
boolean _complete,
long _bytes_up )
{
manager = _manager;
complete = _complete;
bytes_up = _bytes_up;
}
private boolean
isComplete()
{
return( complete );
}
private void
setComplete(
boolean c )
{
complete = c;
}
private long
setBytesUp(
long b )
{
long diff = b - bytes_up;
bytes_up = b;
return( diff );
}
private boolean
isInteresting(
long now )
{
boolean calc;
if ( last_interesting_calc == 0 ){
calc = true;
}else if ( !interesting ){
calc = now - last_interesting_calc >= 5*1000;
}else{
calc = now - last_interesting_calc >= 60*1000;
}
if ( calc ){
last_interesting_calc = now;
PEPeerManagerStats stats = manager.getStats();
// not interesting if stalled downloading
long dl_rate = stats.getDataReceiveRate();
if ( dl_rate < 5*1024 ){
interesting = false;
}else{
// not interesting if we have nobody to seed to!
if ( manager.getNbPeersUnchoked() < 3 ){
interesting = false;
}else{
// see if the download has a manually imposed upload limit and if
// we are close to it
int limit = manager.getUploadRateLimitBytesPerSecond();
if ( limit > 0 &&
( stats.getDataSendRate() + stats.getProtocolSendRate() ) >= ( limit - (5*1024))){
interesting = false;
}else{
interesting = true;
}
}
}
}
return( interesting );
}
}
}