package com.aelitis.azureus.core.speedmanager.impl.v2; /** * Created on Jun 14, 2007 * Created by Alan Snyder * Copyright (C) 2007 Aelitis, All Rights Reserved. * <p/> * 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; either version 2 * of the License, or (at your option) any later version. * 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. * <p/> * AELITIS, SAS au capital de 63.529,40 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. */ /** * Classifies the ping-times and then maps them against the a * grid of upload and download rates. * * Create a two dimensional map of upload and download rates. Map onto this * space ping times. * * The mesh size will be smaller near zero, and larger higher up. * * 0 - 100 kByte/sec - 10 kBytes mesh size. * 100 - 500 kBytes/sec - 50 kBytes mesh size. * 500 - 5000 kBytes/sec - 100 kBytes mesh size. * Anything above 5 MBytes/sec is one region. * */ public class PingSpaceMapper { //ToDo: use the SpeedManagerPingMapper interface and move this up a level. GridRegion[][] gridRegion; //here upIndex,downIndex int lastDownloadBitsPerSec; int lastUploadBitsPerSec; int goodPingInMilliSec; int badPingInMilliSec; int totalPointsInMap = 0; private static final int maxMeshIndex=70; /** * Create a grid and define good and bad ping times. * @param _goodPingInMilliSec - * @param _badPingInMilliSec - */ public PingSpaceMapper(int _goodPingInMilliSec, int _badPingInMilliSec){ createNewGrid(); goodPingInMilliSec = _goodPingInMilliSec; badPingInMilliSec = _badPingInMilliSec; } private void createNewGrid() { //create the mesh. We will have 70 by 70 grid. Even though we only use 63. gridRegion = null; gridRegion = new GridRegion[maxMeshIndex][maxMeshIndex]; for(int upIndex=0;upIndex<maxMeshIndex;upIndex++){ for(int downIndex=0;downIndex<maxMeshIndex;downIndex++){ gridRegion[upIndex][downIndex] = new GridRegion(); } } } /** * We have a hard coded mesh. * 0-9999 = 0, 10000- * * * @param bitsPerSec - * @return - mesh index. */ private int convertBitsPerSec2meshIndex(int bitsPerSec){ if( bitsPerSec<0){ return 0; } int bytesPerSec = bitsPerSec/1024; if( bytesPerSec<100){ return bytesPerSec/10; }else if(bytesPerSec<500){ return 10 + ((bytesPerSec-100)/50); }else if(bytesPerSec<5000){ return 10 + 8 + ((bytesPerSec-500)/100); } //return max mesh index. return 10 + 8 + 45; }//convertBitsPerSec2meshIndex /** * The reverse of bit/sec -> mesh index calculation. * @param meshIndex - value between 0 and 70 * @return lowest BitsPerSecond that meets that criteria. */ private int convertMeshIndex2bitsPerSec(int meshIndex){ int bytesPerSec=0; if(meshIndex<=0){ return 0; } if(meshIndex<=10){ bytesPerSec = meshIndex*10; }else if(meshIndex<=18){ bytesPerSec = 100 + (meshIndex-10) * 50; }else{ bytesPerSec = 500 + (meshIndex-18) * 100; } return bytesPerSec*1024; }//convertMeshIndex2bitsPerSec public void setCurrentTransferRates(int downloadBitPerSec, int uploadBitsPerSec){ lastDownloadBitsPerSec = downloadBitPerSec; lastUploadBitsPerSec = uploadBitsPerSec; } public void addMetricToMap(int metric){ int downIndex = convertBitsPerSec2meshIndex(lastDownloadBitsPerSec); int upIndex = convertBitsPerSec2meshIndex(lastUploadBitsPerSec); totalPointsInMap++; if( metric<goodPingInMilliSec ){ gridRegion[upIndex][downIndex].incrementMetricCount( GridRegion.INDEX_PING_GOOD ); }else if( metric<badPingInMilliSec ){ gridRegion[upIndex][downIndex].incrementMetricCount( GridRegion.INDEX_PING_NEUTRAL ); }else{ gridRegion[upIndex][downIndex].incrementMetricCount( GridRegion.INDEX_PING_BAD ); } }//addMetricToMap /** * Start accumlating data from scratch. */ public void reset(){ totalPointsInMap=0; createNewGrid(); } public static final int RESULT_UPLOAD_INDEX = 0; public static final int RESULT_DOWNLOAD_INDEX = 1; private Result getHighestMeshIndexWithGoodPing(){ Result[] retVal = calculate(); return retVal[GOOD_PING_INDEX]; } private Result getHighestMeshIndexWithAnyPing(){ Result[] retVal = calculate(); return retVal[ANY_PING_INDEX]; } /** * Try to determine if a chocking ping occured during this test. * @param isDownloadTest - set true if this is a download_search_test. set false if upload search test. * @return - true if it appears a chocking ping occured. */ public boolean hadChockingPing(boolean isDownloadTest){ Result[] res = calculate(); int goodPingIndex; int highPingIndex; if( isDownloadTest ){ goodPingIndex = res[GOOD_PING_INDEX].getDownloadIndex(); highPingIndex = res[ANY_PING_INDEX].getDownloadIndex(); }else{ goodPingIndex = res[GOOD_PING_INDEX].getUploadIndex(); highPingIndex = res[ANY_PING_INDEX].getUploadIndex(); } return (highPingIndex>goodPingIndex); } static final int GOOD_PING_INDEX = 0; static final int ANY_PING_INDEX = 1; /** * Look at the Map and find the highest index for each catagory. * @return Result[2], where index 0 is goodPing, index 1 is anyPing */ private Result[] calculate(){ Result[] retVal = new Result[2]; retVal[GOOD_PING_INDEX] = new Result(); retVal[ANY_PING_INDEX] = new Result(); for(int upIndex=0;upIndex<maxMeshIndex;upIndex++){ for(int downIndex=0;downIndex<maxMeshIndex;downIndex++) { //Register this grid point if it has more good then bad pings. float rating = gridRegion[upIndex][downIndex].getRating(); if( rating>0.0f ){ retVal[GOOD_PING_INDEX].checkAndUpdate(upIndex,downIndex); } //Register this grid point if it has any ping values. int count = gridRegion[upIndex][downIndex].getTotal(); if( count>0 ){ retVal[ANY_PING_INDEX].checkAndUpdate(upIndex,downIndex); } }//for }//for return retVal; } /** * Make a guess at the upload capacity based on metric data. * @return - */ public int guessUploadLimit(){ Result result = getHighestMeshIndexWithGoodPing(); int upMeshIndex = result.getUploadIndex(); return convertMeshIndex2bitsPerSec( upMeshIndex ); } /** * Make a guess at the download capacity based on metric data. * @return - */ public int guessDownloadLimit(){ Result result = getHighestMeshIndexWithGoodPing(); int downMeshIndex = result.getDownloadIndex(); return convertMeshIndex2bitsPerSec( downMeshIndex ); } /** * Class to return a result. */ static class Result{ int highestUploadIndex=0; int highestDownloadIndex=0; /** * If the input index is higher then stored, update it with the new value. * @param uploadIndex - * @param downloadIndex - */ public void checkAndUpdate(int uploadIndex, int downloadIndex){ if( uploadIndex>highestUploadIndex ){ highestUploadIndex=uploadIndex; } if( downloadIndex>highestDownloadIndex ){ highestDownloadIndex=downloadIndex; } } public int getUploadIndex(){ return highestUploadIndex; } public int getDownloadIndex(){ return highestDownloadIndex; } }//class Result /** * A region on the grid for accumulating counts. */ static class GridRegion{ public static final int INDEX_PING_GOOD = 0; public static final int INDEX_PING_NEUTRAL = 1; public static final int INDEX_PING_BAD = 2; int pingCount[] = new int[3]; int uploadBound[] = new int[2]; int downloadBound[] = new int[2]; public void incrementMetricCount( int pingIndex){ if(pingIndex>=0 && pingIndex<=3){ ++pingCount[pingIndex]; } }//incrementMetricCount public float getRating(){ int total = getTotal(); if( total==0 ){ return 0.0f; } float score = (pingCount[INDEX_PING_GOOD] + 0.3f * pingCount[INDEX_PING_NEUTRAL] - pingCount[INDEX_PING_BAD]); return ( score / (float) total ); }//getRating public int getTotal(){ return pingCount[INDEX_PING_GOOD] +pingCount[INDEX_PING_NEUTRAL] +pingCount[INDEX_PING_BAD]; }//getTotal }//class }