package com.samknows.tests; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import android.util.Pair; import com.samknows.libcore.SKPorting; import com.samknows.measurement.util.SKDateFormat; import org.json.JSONObject; /* NOTES: See also https://svn.samknows.com/svn/tests/http_server/trunk/docs/protocol.txt ... where the protocol for the new service is defined. create socket connect ** Start by trying to do this in JUST ONE THREAD! ** boolean bQuit = false Thread 1: write header : Example header as follows: */ // POST /?CONTROL=1&UNITID=1&SESSIONID=281541010&NUM_CONNECTIONS=2&CONNECTION=1&AGGREGATE_WARMUP=0&RESULTS_INTERVAL_PERIOD=10&RESULT_NUM_INTERVALS=1&TEST_DATA_CAP=4294967295&TRANSFER_MAX_SIZE=4294967295&WARMUP_SAMPLE_TIME=5000&NUM_WARMUP_SAMPLES=1&MAX_WARMUP_SIZE=4294967295&MAX_WARMUP_SAMPLES=1&WARMUP_FAIL_ON_MAX=0&WARMUP_TOLERANCE=5 HTTP/1.1 // Host: n1-the1.samknows.com:6500 // Accept: */* // Content-Length: 4294967295 // Content-Type: application/x-www-form-urlencoded // Expect: 100-continue /* } The server expects a query string with a "CONTROL" field. It's value should be set to "1.0", which specifies the version of the protocol. - UNITID Mandatory. - SESSIONID Mandatory. - NUM_CONNECTIONS Mandatory. - CONNECTION Mandatory. - WARMUP_SAMPLE_TIME Mandatory for POST requests. - NUM_WARMUP_SAMPLES Mandatory for POST requests. - MAX_WARMUP_SAMPLES Mandatory for POST requests. - WARMUP_TOLERANCE Mandatory for POST requests. - RESULTS_INTERVAL_PERIOD Mandatory for POST requests. - RESULT_NUM_INTERVALS Mandatory for POST requests. - AGGREGATE_WARMUP Optional. Default: false. - TEST_DATA_CAP Optional. Default: no limit. - TRANSFER_MAX_SIZE Optional. Default: no limit. - MAX_WARMUP_SIZE Optional. Default: no limit. - WARMUP_FAIL_ON_MAX Optional. Default: false. - TRACE_INTERVAL Optional. Default: no tracing. - TCP_CONG Optional. Default: server system default. The values must be sent to the server (e.g TEST_DATA_CAP). Once the server reaches the limits it will send the result up to that point. This makes the limit "maximum bytes to *RECEIVE*". To keep it as "maximum bytes to *SENT*" you could keep your own counter and close the connection when you have sent a fixed amount of bytes... } while (bQuit == false) { write(socket, buffer...) } Thread 2: while (bQuit == false) { if (read (socket, intobuffer, timeout)) succeeds: { Extract upload speed results from server response, which will be in this format: SAMKNOWS_HTTP_REPLY\n VERSION: <major>.<minor>\n RESULT: <OK|FAIL>\n END_TIME: <end time> SECTION: WARMUP\n NUM_WARMUP: <num>\n WARMUP_SESSION <seconds> <nanoseconds> <bytes>\n WARMUP_SESSION <seconds> <nanoseconds> <bytes>\n SECTION: MEASUR\n NUM_MEASUR: <num>\n MEASUR_SESSION <seconds> <nanoseconds> <bytes>\n MEASUR_SESSION <seconds> <nanoseconds> <bytes>\n In terms of sample data etc., the server will give you something like this: ... NUM_MEASUR: 2\n MEASUR_SESSION 5 0 5000000\n MEASUR_SESSION 10 0 15000000\n ... ... It's the client job to calculate that during the first five seconds the speed has been 5,000,000 / 5 = 1,000,000 bytes/sec; and during the next 10 seconds 15,000,000 / 10 = 1,500,000 bytes/sec. Meaning that the speed between seconds 5 and 10 has been (15,000,000 - 5,000,000) / (10 - 5) = 2,000,000 bytes/sec. ... send this "final upload speed result value from the server" to the application. bQuit = true; } } */ public abstract class HttpTest extends SKAbstractBaseTest implements Runnable { // @property (weak) SKTransferOperation *mpParentTransferOperation; // @property int mSocketFd; public enum UploadStrategy {ACTIVE, PASSIVE} // Socket timeout parameters private final int CONNECTIONTIMEOUT = 10000; // 10 seconds connection timeout private final int READTIMEOUT = 10000; // 10 seconds read timeout private final int WRITETIMEOUT = 10000; // 10 seconds write timeout // Http Status codes protected final int HTTPOK = 200; private final int HTTPCONTINUE = 100; // error codes and constraints protected final int BYTESREADERR = -1; // Error occurred while reading from socket private final int MAXNTHREADS = 100; // Max number of threads // Parameters name for the setParameter function. // HTTP test types. Static because called from constructors. protected final static String _DOWNSTREAM = "downstream"; protected final static String _UPSTREAM = "upstream"; // Parameters names for use in Settings XML files //private static final String DOWNSTREAM = "downStream"; //private static final String UPSTREAM = "upStream"; private static final String UPLOADSTRATEGY = "strategy"; // Use server side calculations, different type of server required public static final String WARMUPMAXTIME = "warmupMaxTime"; // Max warmup time in uSecs private static final String WARMUPMAXBYTES = "warmupMaxBytes"; // Max warmup bytes allowed to be transmitted public static final String TRANSFERMAXTIME = "transferMaxTime"; // Max transfer time in uSecs. Metrics, measured during this time period contribute to final result private static final String TRANSFERMAXBYTES = "transferMaxBytes"; // Max transfer bytes allowed to be transmitted public static final String NTHREADS = "numberOfThreads"; // Max number of threads allowed private static final String NTHREADSLOWERCASE = "numberofthreads"; // Max number of threads allowed public static final String BUFFERSIZE = "bufferSize"; // Socket receive buffer size public static final String SENDBUFFERSIZE = "sendBufferSize"; // Socket send buffer size private static final String RECEIVEBUFFERSIZE = "receiveBufferSize";// Socket receive buffer size private static final String POSTDATALENGTH = "postDataLength"; // ??? public static final String SENDDATACHUNK = "sendDataChunk"; // Application send buffer size // Messages regarding the status of the test private final String HTTPGETRUN = "Running download test"; private final String HTTPGETDONE = "Download test completed"; private final String HTTPPOSTRUN = "Running upload test"; private final String HTTPPOSTDONE = "Upload completed"; // Test strings for public use. JSON related public static final String DOWNSTREAMSINGLE = "JHTTPGET"; public static final String DOWNSTREAMMULTI = "JHTTPGETMT"; public static final String UPSTREAMSINGLE = "JHTTPPOST"; public static final String UPSTREAMMULTI = "JHTTPPOSTMT"; // "Direction" for constructor public static final String cReasonResetDownload = "Reset Download"; public static final String cReasonResetUpload = "Reset Upload"; public static final String cReasonUploadEnd = "Upload End"; // Create an interface class, which will allow us to inject a test socket for mock testing. // Usage: Call open, then the set/get methods - then connect! // Then call getInputStream/getOutputStream... // Then call close() public interface ISKHttpSocketFactory { // Instance of factory used to "open" the socket, as a wrapper around the open method. // If null, we use the standard SKHttpSocket. ISKHttpSocket newSocket(); } public interface ISKHttpSocket { void open(); void setTcpNoDelay(boolean on) throws SocketException; void setReceiveBufferSize(int size) throws SocketException; int getReceiveBufferSize() throws SocketException; void setSendBufferSize(int size) throws SocketException; int getSendBufferSize() throws SocketException; void setSoTimeout(int timeout) throws SocketException; //ipAddress = sockAddr.getAddress().getHostAddress(); // Returns the ipaddress... String connect(String target, int port, int timeout) throws IOException; InputStream getInputStream() throws IOException; OutputStream getOutputStream() throws IOException; void close() throws IOException; } // Define a real instantiation of the ISKHttpSocket interface, which is used for "real" testing. public class SKHttpSocket implements ISKHttpSocket { private Socket socket = null; public SKHttpSocket() { } public void open() { socket = new Socket(); } public void setTcpNoDelay(boolean on) throws SocketException { if (socket == null) { SKPorting.sAssert(false); return; } socket.setTcpNoDelay(on); } public synchronized void setReceiveBufferSize(int size) throws SocketException { if (socket == null) { SKPorting.sAssert(false); return; } socket.setReceiveBufferSize(size); } public int getReceiveBufferSize() throws SocketException { if (socket == null) { SKPorting.sAssert(false); return 0; } return socket.getReceiveBufferSize(); } public synchronized void setSendBufferSize(int size) throws SocketException { if (socket == null) { SKPorting.sAssert(false); return; } socket.setSendBufferSize(size); } public synchronized int getSendBufferSize() throws SocketException { if (socket == null) { SKPorting.sAssert(false); return 0; } return socket.getSendBufferSize(); } public void setSoTimeout(int timeout) throws SocketException { if (socket == null) { SKPorting.sAssert(false); return; } socket.setSoTimeout(timeout); } // Returns the ipaddress... public String connect(String target, int port, int timeout) throws IOException { InetSocketAddress sockAddr = new InetSocketAddress(target, port); if (sockAddr == null) { SKPorting.sAssert(false); return ""; } socket.connect(sockAddr, timeout); // // 10 seconds connection timeout return sockAddr.getAddress().getHostAddress(); } public InputStream getInputStream() throws IOException { if (socket == null) { SKPorting.sAssert(false); return null; } return socket.getInputStream(); } public OutputStream getOutputStream() throws IOException { if (socket == null) { SKPorting.sAssert(false); return null; } return socket.getOutputStream(); } public void close() throws IOException { if (socket == null) { SKPorting.sAssert(false); return; } try { socket.close(); } catch (IOException e1) { SKPorting.sAssert(false); throw e1; } finally { socket = null; } } // public long getStartTimeNanoseconds() { // return System.nanoTime(); // } // // public long getTimeNowNanoseconds() { // return System.nanoTime(); // } // // public InetAddress getInetAddressByName(String host) throws UnknownHostException { // return InetAddress.getByName(host); // } } private String TAG(Object param) { return param.getClass().getSimpleName(); } /* TAG is to be passed to SKLogger class. It outputs the human readable class name of the message logger */ /* Abstract methods to be implemented in derived classes */ protected abstract boolean transfer(ISKHttpSocket socket, int threadIndex); /* Generate main traffic for metrics measurements */ protected abstract boolean warmup(ISKHttpSocket socket, int threadIndex); /* Generate initial traffic for setting optimal TCP parameters */ //private abstract int getWarmupBytesPerSecond(); /* Initial traffic speed */ //private abstract int getTransferBytesPerSecond(); /* Main traffic speed */ private final Boolean mThreadsGuard = new Boolean(true); private Thread[] mThreads = null; /* Array of all running threads */ /* Time helper functions */ protected long sGetMicroTime() { return System.nanoTime() / 1000L; } private long sGetMilliTime() { return System.nanoTime() / 1000000L; } protected HttpTest(String direction, List<Param> params) { /* Constructor. Accepts list of Param objects, each representing a certain parameter read from settings XML file */ setDirection(direction); /* Legacy. To be removed */ sLatestSpeedReset(downstream ? cReasonResetDownload : cReasonResetUpload); setParams(params); /* Initialisation */ //SKLogger.d(this, "CREATING HTTP TEST - LOG TEST!"); } private void setParams(List<Param> params) { /* Initialisation helper function */ initialised = true; try { for (Param param : params) { String value = param.getValue(); if (param.contains(TARGET)) { target = value; } else if (param.contains(PORT)) { port = Integer.parseInt(value); } else if (param.contains(FILE)) { file = value; } else if (param.contains(WARMUPMAXTIME)) { mWarmupMaxTimeMicro = Integer.parseInt(value); } else if (param.contains(WARMUPMAXBYTES)) { mWarmupMaxBytes = Integer.parseInt(value); } else if (param.contains(TRANSFERMAXTIME)) { mTransferMaxTimeMicro = Integer.parseInt(value); } else if (param.contains(TRANSFERMAXBYTES)) { mTransferMaxBytes = Integer.parseInt(value); } else if (param.contains(NTHREADS)) { nThreads = Integer.parseInt(value); } else if (param.contains(NTHREADSLOWERCASE)) { nThreads = Integer.parseInt(value); } else if (param.contains(UPLOADSTRATEGY)) { uploadStrategyServerBased = UploadStrategy.ACTIVE; /* If strategy parameter is present ActiveServerload class is used */ } else if (param.contains(BUFFERSIZE)) { downloadBufferSize = Integer.parseInt(value); } else if (param.contains(SENDBUFFERSIZE)) { socketBufferSize = Integer.parseInt(value); } else if (param.contains(RECEIVEBUFFERSIZE)) { desiredReceiveBufferSize = Integer.parseInt(value); downloadBufferSize = Integer.parseInt(value); } else if (param.contains(SENDDATACHUNK)) { uploadBufferSize = Integer.parseInt(value); } else if (param.contains(POSTDATALENGTH)) { postDataLength = Integer.parseInt(value); } else { SKPorting.sAssertE(this, "setParams()"); initialised = false; break; } } } catch (NumberFormatException nfe) { SKPorting.sAssert(false); initialised = false; } } @Override public int getNetUsage() { /* Total number of bytes transfered */ return (int) (getTotalTransferBytes() + getTotalWarmUpBytes()); } // @SuppressLint("NewApi") @Override public boolean isReady() { /* Test sanity checker. Virtual */ if (!initialised) return false; if (target.length() == 0) { setError("Target empty"); return false; } if (port == 0) { setError("Port is zero"); return false; } if (mWarmupMaxTimeMicro == 0 && mWarmupMaxBytes == 0) { setError("No warmup parameter defined"); return false; } if (mTransferMaxTimeMicro == 0 && mTransferMaxBytes == 0) { setError("No transfer parameter defined"); return false; } if (downstream && downloadBufferSize == 0) { setError("Buffer size missing for download"); return false; } if (nThreads < 1 && nThreads > MAXNTHREADS) { setError("Number of threads error, current is: " + nThreads + ". Min " + 1 + " Max " + MAXNTHREADS + "."); return false; } return true; } // void sendTestPing(String token) { // // // DatagramSocket socket = null; // try { // socket = new DatagramSocket(); // // try { // InetAddress address = InetAddress.getByName("192.168.2.105"); // byte[] buf = token.getBytes(Charset.forName("UTF-8")); // DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 90); // socket.send(packet); // } catch (Exception e) { // SKLogger.sAssert(false); // } // } catch (SocketException e2) { // socket.close(); // SKLogger.sAssert(false); // } finally { // if (socket != null) { // socket.close(); // } // } // } @Override public boolean isSuccessful() { return testStatus.equals("OK"); } /* Returns test run result */ // public int getSendBufferSize() { return sendBufferSize; } // public int getReceiveBufferSize() { return receiveBufferSize; } // public String getInfo() { return infoString; } // Socket factory to support testing! ISKHttpSocketFactory mThisSocketFactory = null; @Override public void runBlockingTestToFinishInThisThread() { /* Execute test */ //smDebugSocketSendTimeMicroseconds.clear(); //Context context = SKApplication.getAppInstance().getBaseContext(); //sendTestPing("TIMING_Start"); if (downstream) { //SKLogger.d(this, "DOWNLOAD HTTP TEST - execute()"); infoString = HTTPGETRUN; } else { //SKLogger.d(this, "UPLOAD HTTP TEST - execute()"); infoString = HTTPPOSTRUN; } setStateToRunning(); mThreads = new Thread[nThreads]; for (int i = 0; i < nThreads; i++) { mThreads[i] = new Thread(this); } for (int i = 0; i < nThreads; i++) { mThreads[i].start(); } try { for (int i = 0; i < nThreads; i++) { mThreads[i].join(); } } catch (Exception e) { setErrorIfEmpty("Thread join exception: ", e); SKPorting.sAssertE(this, "Thread join exception()"); testStatus = "FAIL"; } if (downstream) { infoString = HTTPGETDONE; } else { infoString = HTTPPOSTDONE; } //sendTestPing("TIMING_Stop"); if (getTotalTransferBytes() == 0) { // 30/03/2015 - note that if transferBytes is ZERO, we must also tag this with "success": false error.set(true); } finish(); } public void runBlockingTestToFinishInThisThread(ISKHttpSocketFactory withThisSocketFactory) { /* Execute test */ if (mThisSocketFactory != null) { SKPorting.sAssert(false); } else { mThisSocketFactory = withThisSocketFactory; } runBlockingTestToFinishInThisThread(); } private ISKHttpSocket makeSocket() { /* Socket initialiser */ //SKLogger.d(this, "HTTP TEST - makeSocket()"); ISKHttpSocket retSocket = null; try { // Are we using a socket factory? if (mThisSocketFactory != null) { // Yes, use a specific socket factory (for testing!) retSocket = mThisSocketFactory.newSocket(); } else { // No, use the built-in socket. retSocket = new SKHttpSocket(); } retSocket.open(); retSocket.setTcpNoDelay(noDelay); if (0 != desiredReceiveBufferSize) { retSocket.setReceiveBufferSize(desiredReceiveBufferSize); } receiveBufferSize = retSocket.getReceiveBufferSize(); // Experimentation shows a *much* better settling-down on upload speed, // if we force a 32K send buffer size in bytes, rather than relying // on the default send buffer size. // When forcing value in bytes, you must actually divide by two! // https://code.google.com/p/android/issues/detail?id=13898 // desiredSendBufferSize = 32768 / 2; // (2 ^ 15) / 2 if (0 != socketBufferSize) { retSocket.setSendBufferSize(socketBufferSize); } sendBufferSize = retSocket.getSendBufferSize(); if (downstream) { // Read / download retSocket.setSoTimeout(READTIMEOUT); } else { retSocket.setSoTimeout(getSocketTimeoutMilliseconds()); //retSocket.setSoTimeout(1); } ipAddress = retSocket.connect(target, port, CONNECTIONTIMEOUT); // // 10 seconds connection timeout SKPorting.sAssert(ipAddress.length() > 0); //SKLogger.d(this, "HTTP TEST - getSocket() completed OK"); } catch (Exception e) { SKPorting.sAssertE(this, "getSocket()", e); retSocket = null; } return retSocket; } private Long mTimestampSeconds = SKAbstractBaseTest.sGetUnixTimeStampSeconds(); @Override public synchronized void finish() { mTimestampSeconds = SKAbstractBaseTest.sGetUnixTimeStampSeconds(); status = STATUS.DONE; } @Override public long getTimestamp() { return mTimestampSeconds; } @Override public void setTimestamp(long timestamp) { mTimestampSeconds = timestamp; } @Override public JSONObject getJSONResult() { //SKLogger.d(this, "HTTP TEST - output()"); Map<String, Object> output = new HashMap<>(); // string id output.put(JsonData.JSON_TYPE, getStringID()); // time output.put(JsonData.JSON_TIMESTAMP, mTimestampSeconds); output.put(JsonData.JSON_DATETIME, SKDateFormat.sGetDateAsIso8601String(new java.util.Date(mTimestampSeconds * 1000))); long transferBytes = getTotalTransferBytes(); //SKLogger.d(this, "HTTP TEST - output(), transferBytes=" + transferBytes); if (transferBytes == 0) { // 30/03/2015 - note that if transferBytes is ZERO, we must also tag this with "success": false error.set(true); } // status if (error.get()) { output.put(JsonData.JSON_SUCCESS, false); } else { output.put(JsonData.JSON_SUCCESS, true); } // target output.put(JsonData.JSON_TARGET, target); // target ip address output.put(JsonData.JSON_TARGET_IPADDRESS, ipAddress); // transfer time output.put(JsonData.JSON_TRANFERTIME, getTransferTimeMicro()); // transfer bytes output.put(JsonData.JSON_TRANFERBYTES, totalTransferBytes); // byets_sec output.put(JsonData.JSON_BYTES_SEC, Math.max(0, getTransferBytesPerSecond())); // warmup time output.put(JsonData.JSON_WARMUPTIME, getWarmUpTimeMicro()); // warmup bytes output.put(JsonData.JSON_WARMUPBYTES, getTotalWarmUpBytes()); // number of threads output.put(JsonData.JSON_NUMBER_OF_THREADS, nThreads); JSONObject json_output = new JSONObject(output); return json_output; } /* The following set of methods relates to a communication with the external UI TODO move prototypes to test */ static private final AtomicReference<Double> sLatestSpeedForExternalMonitorBytesPerSecond = new AtomicReference<>(0.0); static private AtomicReference<Double> sBytesPerSecondLast = new AtomicReference<>(0.0); private static String sLatestSpeedForExternalMonitorTestId = ""; private static void sLatestSpeedReset(String theReasonId) { sLatestSpeedForExternalMonitorBytesPerSecond.set(0d); sBytesPerSecondLast.set(0d); sLatestSpeedForExternalMonitorTestId = theReasonId; } public static void sLatestSpeedReset() { sLatestSpeedReset(cReasonResetDownload); } // Report-back a running average, to keep the UI moving... // Returns -1 if sample time too short. public static Pair<Double, String> sGetLatestSpeedForExternalMonitorAsMbps() { // use moving average of the last 2 items! double bytesPerSecondToUse = sBytesPerSecondLast.get() + sLatestSpeedForExternalMonitorBytesPerSecond.get(); bytesPerSecondToUse /= 2; double mbps1000Based = Conversions.sConvertBytesPerSecondToMbps1000Based(bytesPerSecondToUse); return new Pair<>(mbps1000Based, sLatestSpeedForExternalMonitorTestId); } public static void sSetLatestSpeedForExternalMonitor(Double bytesPerSecond, String testId) { sBytesPerSecondLast = sLatestSpeedForExternalMonitorBytesPerSecond; if (bytesPerSecond == 0) { //SKLogger.sAssert(testId.equals(cReasonUploadEnd)); } sLatestSpeedForExternalMonitorBytesPerSecond.set(bytesPerSecond); sLatestSpeedForExternalMonitorTestId = testId; } protected final int extMonitorUpdateInterval = 500000; protected void sSetLatestSpeedForExternalMonitorInterval(long pause, String id, Double transferSpeed) { long updateTime = /*timeElapsedSinceLastExternalMonitorUpdate.get() == 0 ? pause * 5 : */ pause; /* first update is delayed 3 times of a given pause */ if (timeElapsedSinceLastExternalMonitorUpdate.get() == 0) { timeElapsedSinceLastExternalMonitorUpdate.set(sGetMicroTime()); /* record update time */ } if (sGetMicroTime() - timeElapsedSinceLastExternalMonitorUpdate.get() > updateTime/*uSec*/) { /* update should be called only if 'pause' is expired */ double currentSpeed; try { currentSpeed = transferSpeed; /* current speed could be for warm up, transfer or possibly others processes */ } catch (Exception e) { currentSpeed = 0.0; } sSetLatestSpeedForExternalMonitor(currentSpeed /*/ 1000000.0*/, id); /* update speed parameter + indicative ID */ timeElapsedSinceLastExternalMonitorUpdate.set(sGetMicroTime()); /* set new update time */ // SKLogger.d(TAG(this), "External Monitor updated at " + (new java.text.SimpleDateFormat("HH:mm:ss.SSS")).format(new java.util.Date()) + // " as " + ( currentSpeed / 1000000.0) + // " thread: " + getThreadIndex());//haha remove in production } } /* This is the end of the block related to communication with UI */ protected boolean isWarmupDone(int bytes) { //SKLogger.d(this, "isWarmupDone("+ bytes+")"); boolean timeExceeded = false; boolean bytesExceeded = false; if (bytes == BYTESREADERR) { /* if there is an error the test must stop and report it */ setErrorIfEmpty("read error"); bytes = 0; /* do not modify the bytes counters ??? */ error.set(true); return true; } if (mWarmupMicroDuration.get() != 0) /* if some other thread has already finished warmup there is no need to proceed */ return true; addTotalWarmUpBytes(bytes); /* increment atomic total bytes counter */ if (mStartWarmupMicro.get() == 0) { mStartWarmupMicro.set(sGetMicroTime()); /* record start up time should be recorded only by one thread */ } setWarmUpTimeMicro(sGetMicroTime() - mStartWarmupMicro.get()); /* current warm up time should be atomic*/ if (mWarmupMaxTimeMicro > 0) { /*if warmup max time is set and time has exceeded its values set time warmup to true */ timeExceeded = (mWarmupTimeMicro.get() >= mWarmupMaxTimeMicro); } if (mWarmupMaxBytes > 0) { /* if warmup max bytes is set and bytes counter exceeded its value set bytesWarmup to true */ bytesExceeded = (getTotalWarmUpBytes() >= mWarmupMaxBytes); } if (timeExceeded) { /* if maximum warmup time is reached */ if (mWarmupMicroDuration.get() == 0) { mWarmupMicroDuration.set(sGetMicroTime() - mStartWarmupMicro.get()); /* Register the time duration up to this moment */ } warmupDoneCounter.addAndGet(1); /* and increment warmup counter */ return true; } if (bytesExceeded) { /* if max warmup bytes transferred */ if (mWarmupMicroDuration.get() == 0) { mWarmupMicroDuration.set(sGetMicroTime() - mStartWarmupMicro.get()); /* Register the time duration up to this moment */ } warmupDoneCounter.addAndGet(1); /* and increment warmup counter */ return true; } return false; } protected boolean isTransferDone(int bytes) { boolean timeExceeded = false; boolean bytesExceeded = false; //SKLogger.d(this, "isTransferDone("+ bytes+")"); //boolean ret = false; if (bytes == BYTESREADERR) { /* if there is an error the test must stop and report it */ setErrorIfEmpty("read error"); bytes = 0; /* do not modify the bytes counters ??? */ error.set(true); SKPorting.sAssertE(this, "isTransferDone, bytes == BYTESREADERR!"); return true; } if (mTransferMicroDuration.get() != 0) { /* if some other thread has already finished warmup there is no need to proceed */ //SKLogger.d(this, "isTransferDone, mTransferMicroDuration != 0"); return true; } addTotalTransferBytes(bytes); /* increment atomic total bytes counter */ /* record start up time should be recorded only by one thread */ mStartTransferMicro.compareAndSet(0, sGetMicroTime()); //SKLogger.d(TAG(this), "Setting transfer start == " + mStartTransferMicro.get() + " by thread: " + this.getThreadIndex());//TODO remove in production setTransferTimeMicro(sGetMicroTime() - mStartTransferMicro.get()); /* How much time transfer took up to now */ if (mTransferMaxTimeMicro > 0) { /* If transfer time is more than max time, then transfer is done */ //SKLogger.d(this, "transfer Time so far milli =" + getTransferTimeMicro()/1000); timeExceeded = (getTransferTimeMicro() >= mTransferMaxTimeMicro); } //SKLogger.d(this, "transfer Bytes so far =" + getTotalTransferBytes()); if (mTransferMaxBytes > 0) { bytesExceeded = (getTotalTransferBytes() >= mTransferMaxBytes); } if (getTotalTransferBytes() > 0) { testStatus = "OK"; } if (timeExceeded) { /* if maximum transfer time is reached */ /* Register the time duration up to this moment */ mTransferMicroDuration.compareAndSet(0, sGetMicroTime() - mStartTransferMicro.get()); transferDoneCounter.addAndGet(1); /* and increment transfer counter */ //SKLogger.d(this, "isTransferDone, timeExceeded"); return true; } if (bytesExceeded) { /* if max transfer bytes transferred */ mTransferMicroDuration.compareAndSet(0, sGetMicroTime() - mStartTransferMicro.get()); //SKLogger.d(this, "isTransferDone, bytesExceeded"); transferDoneCounter.addAndGet(1); /* and increment transfer counter */ return true; } //SKLogger.d(this, "isTransferDone, still waiting..."); return false; } protected int getThreadIndex() { int threadIndex = 0; synchronized (mThreadsGuard) { boolean bFound = false; int i; for (i = 0; i < mThreads.length; i++) { if (Thread.currentThread() == mThreads[i]) { threadIndex = i; bFound = true; break; } } if (bFound == false) { SKPorting.sAssertE(this, "getThreadIndex()"); } } return threadIndex; } protected OutputStream getOutput(ISKHttpSocket socket) { OutputStream conn = null; boolean err = false; /*Initially there is not error */ if (socket != null) { try { conn = socket.getOutputStream(); /* Try to get output stream */ } catch (IOException io) { err = true; /* Fails */ SKPorting.sAssertE(this, "getOutput() ... thread: " + this.getThreadIndex(), io); } } else { /* if socket is null - fails */ err = true; SKPorting.sAssertE(this, "getOutput(), socket is null! ... thread: " + this.getThreadIndex()); } if (err) { /* return null if there is an error */ SKPorting.sAssertE(this, "Error occurred while getting output connection, returning null... thread: " + this.getThreadIndex()); return null; } return conn; /* return output stream */ } protected InputStream getInput(ISKHttpSocket socket) { InputStream conn = null; boolean err = false; /*Initially there is not error */ if (socket != null) { try { conn = socket.getInputStream(); /* Try to get output stream */ } catch (IOException io) { err = true; /* Fails */ SKPorting.sAssertE(this, "getInput() ... thread: " + this.getThreadIndex(), io); } } else { /* if socket is null - fails */ err = true; SKPorting.sAssertE(this, "getOutput(), socket is null! ... thread: " + this.getThreadIndex()); } if (err) { /* return null if there is an error */ SKPorting.sAssertE(this, "Error occurred while getting input connection, returning null... thread: " + this.getThreadIndex()); return null; } return conn; /* return output stream */ } //public void setDownstream() { downstream = true; } //public void setUpstream() { downstream = false; } private void setDirection(String d) { if (d.equalsIgnoreCase(_DOWNSTREAM)) { downstream = true; } else if (d.equalsIgnoreCase(_UPSTREAM)) { downstream = false; } } public boolean isProgressAvailable() {//TODO check with new interface boolean ret = false; if (mTransferMaxTimeMicro > 0) { ret = true; } else if (mTransferMaxBytes > 0) { ret = true; } return ret; } // Return progress as integer value from 0 to 100. public int getProgress0To100() { double ret = 0; if (mStartWarmupMicro.get() == 0) { return 0; } if (mTransferMaxTimeMicro != 0) { long currTime = sGetMicroTime() - mStartWarmupMicro.get(); ret = (double) currTime / (mWarmupMaxTimeMicro + mTransferMaxTimeMicro); } else { long currBytes = getTotalWarmUpBytes() + getTotalTransferBytes(); ret = (double) currBytes / (mWarmupMaxBytes + mTransferMaxBytes); } int result = (int) (ret * 100); if (result < 0) { return 0; } if (result > 100) { return 100; } SKPorting.sAssert(result >= 0); SKPorting.sAssert(result <= 100); return result; } protected void closeConnection(ISKHttpSocket socket) { /* Closes connections and winds socket out*/ //SKLogger.d(this, "closeConnection()"); /* * Should be run inside thread */ if (socket != null) { OutputStream outputStream = null; InputStream inputStream = null; try { outputStream = socket.getOutputStream(); inputStream = socket.getInputStream(); } catch (IOException e) { SKPorting.sAssertE(this, "closeConnection(), e", e); } if (inputStream != null) { //SKLogger.d(this, "inputStream.close()"); try { inputStream.close(); } catch (IOException ioe) { SKPorting.sAssertE(this, "closeConnection(), ioe", ioe); } } if (outputStream != null) { //SKLogger.d(this, "outputStream.close()"); try { outputStream.close(); } catch (IOException ioe2) { SKPorting.sAssertE(this, "closeConnection(), ioe2", ioe2); } } try { //SKLogger.d(this, "socket.close()"); socket.close(); } catch (IOException ioe3) { SKPorting.sAssertE(this, "closeConnection(), ioe2", ioe3); } } } @Override public void run() { boolean result = false; int threadIndex = getThreadIndex(); ISKHttpSocket socket = makeSocket(); if (socket == null) { SKPorting.sAssertE(TAG(this), "Socket initiation failed, thread: " + threadIndex); return; } result = warmup(socket, threadIndex); if (!result) { closeConnection(socket); return; } result = transfer(socket, threadIndex); closeConnection(socket); } /* * Atomic variables used as aggregate counters or (errors, etc. ) indicators updated from concurrently running threads */ private final AtomicLong totalWarmUpBytes = new AtomicLong(0); /* Total num of bytes transmitted during warmup period */ private final AtomicLong totalTransferBytes = new AtomicLong(0); /* Total num of bytes transmitted during trnasfer period */ private final AtomicBoolean error = new AtomicBoolean(false); /* Global error indicator */ protected AtomicBoolean getError() { return error; } /* * Accessors to atomic variables */ protected long getTotalWarmUpBytes() { return totalWarmUpBytes.get(); } protected long getTotalTransferBytes() { return totalTransferBytes.get(); } protected long getWarmUpTimeMicro() { return mWarmupTimeMicro.get(); } private long getWarmUpTimeDurationMicro() { return mWarmupMicroDuration.get(); } protected long getTransferTimeMicro() { return transferTimeMicroseconds.get(); } private long getTransferTimeDurationMicro() { return mTransferMicroDuration.get(); } private long getStartTransferMicro() { return mStartTransferMicro.get(); } private long getStartWarmupMicro() { return mStartWarmupMicro.get(); } protected void addTotalTransferBytes(long bytes) { totalTransferBytes.addAndGet(bytes); } protected void resetTotalTransferBytesToZero() { totalTransferBytes.set(0L); } private void addTotalWarmUpBytes(long bytes) { totalWarmUpBytes.addAndGet(bytes); } private void setWarmUpTimeMicro(long uTime) { mWarmupTimeMicro.set(uTime); } private void setTransferTimeMicro(long uTime) { transferTimeMicroseconds.set(uTime); } private String infoString = ""; private String ipAddress = ""; // Flag to indicate if upload buffer randomisation is required... private final boolean randomEnabled = true; protected boolean getRandomEnabled() { return randomEnabled; } //boolean warmUpDone = false; private int postDataLength = 0; protected int getPostDataLength() { return postDataLength; } // warmup variables private final AtomicLong mStartWarmupMicro = new AtomicLong(0); /* Point in time when warm up process starts, uSecs */ private final AtomicLong mWarmupMicroDuration = new AtomicLong(0); /* Total duration of warm up period, uSecs */ private final AtomicLong mWarmupTimeMicro = new AtomicLong(0); /* Time elapsed since warm up process started, uSecs */ private final AtomicInteger warmupDoneCounter = new AtomicInteger(0); /* Counter shows how many threads completed warm up process */ private long mWarmupMaxTimeMicro = 0; /* Max time warm up is allowed to continue, uSecs */ protected long getWarmupMaxTimeMicro() { return mWarmupMaxTimeMicro; } private int mWarmupMaxBytes = 0; /* Max bytes warm up is allowed to send */ protected int getWarmupMaxBytes() { return mWarmupMaxBytes; } // transfer variables private final AtomicLong mStartTransferMicro = new AtomicLong(0); /* Point in time when transfer process starts, uSecs */ private final AtomicLong mTransferMicroDuration = new AtomicLong(0); /* Total duration of transfer period, uSecs */ private final AtomicLong transferTimeMicroseconds = new AtomicLong(0); /* Time elapsed since transfer process started, uSecs */ private final AtomicInteger transferDoneCounter = new AtomicInteger(0); /* Counter shows how many threads completed trnasfer process */ private long mTransferMaxTimeMicro = 0; /* Max time transfer is allowed to continue, uSecs*/ protected long getTransferMaxTimeMicro() { return mTransferMaxTimeMicro; } private int mTransferMaxBytes = 0; /* Max bytes transfer is allowed to send */ protected int getTransferMaxBytes() { return mTransferMaxBytes; } //external monitor variables private final AtomicLong timeElapsedSinceLastExternalMonitorUpdate = new AtomicLong(0); /* Time elapsed since external monitor counter was updated last time, uSecs */ // Various HTTP tests variables private int nThreads; /* Number of send/receive threads */ //various buffers private int downloadBufferSize = 0; protected int getDownloadBufferSize() { return downloadBufferSize; } private int desiredReceiveBufferSize = 0; private int socketBufferSize = 0; private int uploadBufferSize = 0; protected int getUploadBufferSize() { return uploadBufferSize; } //private int connectionCounter = 0; private int receiveBufferSize = 0; private int sendBufferSize = 0; protected int getThreadsNum() { return nThreads; } /* Accessor for number of threads */ private final boolean noDelay = false; private String testStatus = "FAIL"; /* Test status, could be 'OK' or 'FAIL' */ protected String getTestStatus() { return testStatus; } protected void setTestStatus(String value) { testStatus = value; } // Connection variables private String target = ""; public String getTarget() { return target; } private String file = ""; protected String getFile() { return file; } private int port = 0; protected int getPort() { return port; } private UploadStrategy uploadStrategyServerBased = UploadStrategy.PASSIVE; /* Upload type selection strategy: simple upload or with server side measurements */ private boolean downstream = true; private static double sGetBytesPerSecondWithMicroDuration(long durationMicro, long btsTotal) { double btsPerSec = 0; if (durationMicro != 0) { double timeSeconds = ((double) durationMicro) / 1000000.0; btsPerSec = (((double) btsTotal) / timeSeconds); } //SKLogger.d(TAG(this), "getWarmupSpeedBytesPerSecond, using CLIENT value = " + btsPerSec);//HAHA remove in production return btsPerSec; } protected double getWarmupBytesPerSecond() { long btsTotal = getTotalWarmUpBytes(); long durationMicro = getWarmUpTimeDurationMicro() == 0 ? (sGetMicroTime() - getStartWarmupMicro()) : getWarmUpTimeDurationMicro(); return sGetBytesPerSecondWithMicroDuration(durationMicro, btsTotal); } // Returns -1 if not enough time has passed for sensible measurement. public double getTransferBytesPerSecond() { long btsTotal = getTotalTransferBytes(); long durationMicro = getTransferTimeDurationMicro() == 0 ? (sGetMicroTime() - getStartTransferMicro()) : getTransferTimeDurationMicro(); // double durationSeconds = ((double)durationMicro) / 1000000.0; //if (durationMicro < 1000000.0) // Anything if (durationMicro == 0) // Anything { // Not yet possible to return a valid result! return -1.0; } return sGetBytesPerSecondWithMicroDuration(durationMicro, btsTotal); } private int mSocketTimeoutMilliseconds = WRITETIMEOUT; private int getSocketTimeoutMilliseconds() { return mSocketTimeoutMilliseconds; } public void setSocketTimeoutMilliseconds(int value) { mSocketTimeoutMilliseconds = value; } private boolean mbIgnoreSocketTimeout = false; public boolean getIgnoreSocketTimeout() { return mbIgnoreSocketTimeout; } public void setIgnoreSocketTimeout(boolean value) { mbIgnoreSocketTimeout = value; } }