Android Open Source - Speedometer Ping Task






From Project

Back to project page Speedometer.

License

The source code is released under:

Apache License

If you think the Android project Speedometer listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* Copyright 2012 Google Inc.
 *//  w  w w  .j  a  va2 s. c  o  m
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.wireless.speed.speedometer.measurements;

import com.google.wireless.speed.speedometer.Config;
import com.google.wireless.speed.speedometer.Logger;
import com.google.wireless.speed.speedometer.MeasurementDesc;
import com.google.wireless.speed.speedometer.MeasurementError;
import com.google.wireless.speed.speedometer.MeasurementResult;
import com.google.wireless.speed.speedometer.MeasurementTask;
import com.google.wireless.speed.speedometer.R;
import com.google.wireless.speed.speedometer.SpeedometerApp;
import com.google.wireless.speed.speedometer.util.MeasurementJsonConvertor;
import com.google.wireless.speed.speedometer.util.PhoneUtils;
import com.google.wireless.speed.speedometer.util.Util;

import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.util.Log;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpConnectionParams;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;

/**
 * A callable that executes a ping task using one of three methods
 * @author wenjiezeng@google.com (Steve Zeng)
 *
 */
public class PingTask extends MeasurementTask {
  // Type name for internal use
  public static final String TYPE = "ping";
  // Human readable name for the task
  public static final String DESCRIPTOR = "ping";
  /* Default payload size of the ICMP packet, plus the 8-byte ICMP header resulting in a total of 
   * 64-byte ICMP packet */
  public static final int DEFAULT_PING_PACKET_SIZE = 56;
  public static final int DEFAULT_PING_TIMEOUT = 10;
  
  private Process pingProc = null;
  private String targetIp = null;
  /**
   * Encode ping specific parameters, along with common parameters inherited from MeasurmentDesc
   * @author wenjiezeng@google.com (Steve Zeng)
   *
   */
  public static class PingDesc extends MeasurementDesc {     
    public String pingExe = null;
    // Host address either in the numeric form or domain names
    public String target = null;
    // The payload size in bytes of the ICMP packet    
    public int packetSizeByte = PingTask.DEFAULT_PING_PACKET_SIZE;  
    public int pingTimeoutSec = PingTask.DEFAULT_PING_TIMEOUT;


    public PingDesc(String key, Date startTime,
                    Date endTime, double intervalSec, long count, long priority, 
                    Map<String, String> params) throws InvalidParameterException {
      super(PingTask.TYPE, key, startTime, endTime, intervalSec, count,
          priority, params);  
      initalizeParams(params);
      if (this.target == null || this.target.length() == 0) {
        throw new InvalidParameterException("PingTask cannot be created due "
            + " to null target string");
      }    
    }
    
    @Override
    protected void initalizeParams(Map<String, String> params) {
      if (params == null) {
        return;
      }
      
      this.target = params.get("target");
      
      try {        
        String val = null;
        if ((val = params.get("packet_size_byte")) != null && val.length() > 0 &&
            Integer.parseInt(val) > 0) {
          this.packetSizeByte = Integer.parseInt(val);  
        }
        if ((val = params.get("ping_timeout_sec")) != null && val.length() > 0 &&
            Integer.parseInt(val) > 0) {
          this.pingTimeoutSec = Integer.parseInt(val);  
        }
      } catch (NumberFormatException e) {
        throw new InvalidParameterException("PingTask cannot be created due to invalid params");
      }
    }

    @Override
    public String getType() {
      return PingTask.TYPE;
    }  
  }
  
  @SuppressWarnings("rawtypes")
  public static Class getDescClass() throws InvalidClassException {
    return PingDesc.class;
  }
  
  public PingTask(MeasurementDesc desc, Context parent) {
    super(new PingDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec,
      desc.count, desc.priority, desc.parameters), parent);
  }
  
  /**
   * Returns a copy of the PingTask
   */
  @Override
  public MeasurementTask clone() {
    MeasurementDesc desc = this.measurementDesc;
    PingDesc newDesc = new PingDesc(desc.key, desc.startTime, desc.endTime, 
          desc.intervalSec, desc.count, desc.priority, desc.parameters);
    return new PingTask(newDesc, parent);
  }
  
  /* We will use three methods to ping the requested resource in the order of PING_COMMAND, 
   * JAVA_ICMP_PING, and HTTP_PING. If all fails, then we declare the resource unreachable */
  @Override
  public MeasurementResult call() throws MeasurementError {
    PingDesc desc = (PingDesc) measurementDesc;
    try {
      InetAddress addr = InetAddress.getByName(desc.target);
      // All ping methods ping against targetIp rather than desc.target
      targetIp = addr.getHostAddress();
    } catch (UnknownHostException e) {
      throw new MeasurementError("Unknown host " + desc.target);
    }
    
    try {
      Logger.i("running ping command");
      /* Prevents the phone from going to low-power mode where WiFi turns off */
      return executePingCmdTask();
    } catch (MeasurementError e) {
      try {
        Logger.i("running java ping");
        return executeJavaPingTask();
      } catch (MeasurementError ee) {
        Logger.i("running http ping");
        return executeHttpPingTask();
      }
    }
  }
  
  @Override
  public String getType() {
    return PingTask.TYPE;
  }
  
  @Override
  public String getDescriptor() {
    return DESCRIPTOR;
  }
  
  @Override
  public int getProgress() {
    return this.progress;
  }
  
  private MeasurementResult constructResult(ArrayList<Double> rrtVals, double packetLoss,
                                            int packetsSent) {
    double min = Double.MAX_VALUE;
    double max = Double.MIN_VALUE;
    double mdev, avg, filteredAvg;
    double total = 0;
    boolean success = true;
    
    if (rrtVals.size() == 0) {
      return null;
    }
    
    for (double rrt : rrtVals) {
      if (rrt < min) {
        min = rrt;
      }
      if (rrt > max) {
        max = rrt;
      }
      total += rrt;
    }
    
    avg = total / rrtVals.size();
    mdev = Util.getStandardDeviation(rrtVals, avg);
    filteredAvg = filterPingResults(rrtVals, avg);
    
    PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
    
    MeasurementResult result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId,
        phoneUtils.getDeviceProperty(), PingTask.TYPE, System.currentTimeMillis() * 1000,
        success, this.measurementDesc);
    
    result.addResult("target_ip", targetIp);
    result.addResult("mean_rtt_ms", avg);
    result.addResult("min_rtt_ms", min);
    result.addResult("max_rtt_ms", max);
    result.addResult("stddev_rtt_ms", mdev);
    if (filteredAvg != avg) {
      result.addResult("filtered_mean_rtt_ms", filteredAvg);
    }
    result.addResult("packet_loss", packetLoss);
    result.addResult("packets_sent", packetsSent);
    return result;
  }
  
  private void cleanUp(Process proc) {
    try { 
      if (proc != null) {
        proc.destroy();
      }
    } catch (Exception e) { 
      Logger.w("Unable to kill ping process", e);
    }
  }
  
  /* Compute the average of the filtered rtts.
   * The first several ping results are usually extremely large as the device needs to activate
   * the wireless interface and resolve domain names. Such distorted measurements are filtered out
   * 
   */
  private double filterPingResults(final ArrayList<Double> rrts, double avg) {
    double rrtAvg = avg;
    // Our # of results should be less than the # of times we ping
    try {
      ArrayList<Double> filteredResults =
          Util.applyInnerBandFilter(rrts, Double.MIN_VALUE, rrtAvg * Config.PING_FILTER_THRES);
      // Now we compute the average again based on the filtered results
      if (filteredResults != null && filteredResults.size() > 0) {
        rrtAvg = Util.getSum(filteredResults) / filteredResults.size();
      }
    } catch (InvalidParameterException e) {
      Log.wtf(SpeedometerApp.TAG, "This should never happen because rrts is never empty");
    }
    return rrtAvg;
  }
  
  // Runs when SystemState is IDLE
  private MeasurementResult executePingCmdTask() throws MeasurementError {
    Logger.i("Starting executePingCmdTask");
    PingDesc pingTask = (PingDesc) this.measurementDesc;
    String errorMsg = "";
    MeasurementResult measurementResult = null;
    // TODO(Wenjie): Add a exhaustive list of ping locations for different Android phones
    pingTask.pingExe = parent.getString(R.string.ping_executable);
    try {
      String command = Util.constructCommand(pingTask.pingExe, "-i", 
          Config.DEFAULT_INTERVAL_BETWEEN_ICMP_PACKET_SEC,
          "-s", pingTask.packetSizeByte, "-w", pingTask.pingTimeoutSec, "-c", 
          Config.PING_COUNT_PER_MEASUREMENT, targetIp);
      Logger.i("Running: " + command);
      pingProc = Runtime.getRuntime().exec(command);
      
      // Grab the output of the process that runs the ping command
      InputStream is = pingProc.getInputStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(is));

      String line = null;
      int lineCnt = 0;
      ArrayList<Double> rrts = new ArrayList<Double>();
      ArrayList<Integer> receivedIcmpSeq = new ArrayList<Integer>();
      double packetLoss = Double.MIN_VALUE;
      int packetsSent = Config.PING_COUNT_PER_MEASUREMENT;
      // Process each line of the ping output and store the rrt in array rrts.
      while ((line = br.readLine()) != null) {
        // Ping prints a number of 'param=value' pairs, among which we only need the 
        // 'time=rrt_val' pair
        String[] extractedValues = Util.extractInfoFromPingOutput(line);
        if (extractedValues != null) {
          int curIcmpSeq = Integer.parseInt(extractedValues[0]);
          double rrtVal = Double.parseDouble(extractedValues[1]);
  
          // ICMP responses from the system ping command could be duplicate and out of order
          if (!receivedIcmpSeq.contains(curIcmpSeq)) {
            rrts.add(rrtVal);
            receivedIcmpSeq.add(curIcmpSeq);
          }
        }
        
        this.progress = 100 * ++lineCnt / Config.PING_COUNT_PER_MEASUREMENT;
        this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, progress);
        broadcastProgressForUser(progress);
        // Get the number of sent/received pings from the ping command output 
        int[] packetLossInfo = Util.extractPacketLossInfoFromPingOutput(line);
        if (packetLossInfo != null) {
          packetsSent = packetLossInfo[0];
          int packetsReceived = packetLossInfo[1];
          packetLoss = 1 - ((double) packetsReceived / (double) packetsSent);
        }
        
        Logger.i(line);
      }
      // Use the output from the ping command to compute packet loss. If that's not
      // available, use an estimation.
      if (packetLoss == Double.MIN_VALUE) {
        packetLoss = 1 - ((double) rrts.size() / (double) Config.PING_COUNT_PER_MEASUREMENT);
      }
      measurementResult = constructResult(rrts, packetLoss, packetsSent);
      Logger.i(MeasurementJsonConvertor.toJsonString(measurementResult));
    } catch (IOException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    } catch (SecurityException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    } catch (NumberFormatException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";  
    } catch (InvalidParameterException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    } finally {
      // All associated streams with the process will be closed upon destroy()
      cleanUp(pingProc);
    }
    
    if (measurementResult == null) {
      Logger.e("Error running ping: " + errorMsg);
      throw new MeasurementError(errorMsg);
    }
    return measurementResult;
  }

  // Runs when the ping command fails
  private MeasurementResult executeJavaPingTask() throws MeasurementError {
    PingDesc pingTask = (PingDesc) this.measurementDesc;
    long pingStartTime = 0;
    long pingEndTime = 0;
    ArrayList<Double> rrts = new ArrayList<Double>();
    String errorMsg = "";
    MeasurementResult result = null;

    try {       
      int timeOut = (int) (1000 * (double) pingTask.pingTimeoutSec /
            Config.PING_COUNT_PER_MEASUREMENT);
      int successfulPingCnt = 0;
      long totalPingDelay = 0;
      for (int i = 0; i < Config.PING_COUNT_PER_MEASUREMENT; i++) {
        pingStartTime = System.currentTimeMillis();
        boolean status = InetAddress.getByName(targetIp).isReachable(timeOut);
        pingEndTime = System.currentTimeMillis();
        long rrtVal = pingEndTime - pingStartTime;
        if (status) {
          totalPingDelay += rrtVal;
          rrts.add((double) rrtVal);
        }
        this.progress = 100 * i / Config.PING_COUNT_PER_MEASUREMENT;
        broadcastProgressForUser(progress);
      }
      Logger.i("java ping succeeds");
      double packetLoss = 1 - ((double) rrts.size() / (double) Config.PING_COUNT_PER_MEASUREMENT);
      result = constructResult(rrts, packetLoss, Config.PING_COUNT_PER_MEASUREMENT);
    } catch (IllegalArgumentException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    } catch (IOException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    } 
    if (result != null) {
      return result;
    } else {
      Logger.i("java ping fails");
      throw new MeasurementError(errorMsg);
    }
  }
  
  /** 
   * Use the HTTP Head method to emulate ping. The measurement from this method can be 
   * substantially (2x) greater than the first two methods and inaccurate. This is because, 
   * depending on the implementing of the destination web server, either a quick HTTP
   * response is replied or some actual heavy lifting will be done in preparing the response
   * */
  private MeasurementResult executeHttpPingTask() throws MeasurementError {
    long pingStartTime = 0;
    long pingEndTime = 0;
    ArrayList<Double> rrts = new ArrayList<Double>();
    PingDesc pingTask = (PingDesc) this.measurementDesc;
    String errorMsg = "";
    MeasurementResult result = null;

    try {
      long totalPingDelay = 0;
      
      HttpClient client = AndroidHttpClient.newInstance(Util.prepareUserAgent(this.parent));
      HttpHead headMethod = new HttpHead("http://" + targetIp);
      headMethod.addHeader(new BasicHeader("Connection", "close"));
      headMethod.setParams(new BasicHttpParams().setParameter(
          CoreConnectionPNames.CONNECTION_TIMEOUT, 1000));
      
      int timeOut = (int) (1000 * (double) pingTask.pingTimeoutSec /
          Config.PING_COUNT_PER_MEASUREMENT);
      HttpConnectionParams.setConnectionTimeout(headMethod.getParams(), timeOut);
                      
      for (int i = 0; i < Config.PING_COUNT_PER_MEASUREMENT; i++) {
        pingStartTime = System.currentTimeMillis();
        HttpResponse response = client.execute(headMethod);  
        pingEndTime = System.currentTimeMillis();
        rrts.add((double) (pingEndTime - pingStartTime));
        this.progress = 100 * i / Config.PING_COUNT_PER_MEASUREMENT;
        broadcastProgressForUser(progress);
      }
      Logger.i("HTTP get ping succeeds");
      double packetLoss = 1 - ((double) rrts.size() / (double) Config.PING_COUNT_PER_MEASUREMENT);
      result = constructResult(rrts, packetLoss, Config.PING_COUNT_PER_MEASUREMENT);
    } catch (MalformedURLException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    } catch (IOException e) {
      Logger.e(e.getMessage());
      errorMsg += e.getMessage() + "\n";
    }
    if (result != null) {
      return result;
    } else {
      Logger.i("HTTP get ping fails");
      throw new MeasurementError(errorMsg);
    }
  }
  
  @Override
  public String toString() {
    PingDesc desc = (PingDesc) measurementDesc;
    return "[Ping]\n  Target: " + desc.target + "\n  Interval (sec): " + desc.intervalSec 
        + "\n  Next run: " + desc.startTime;
  }
  
  @Override
  public void stop() {
    cleanUp(pingProc);
  }
}




Java Source Code List

com.google.wireless.speed.speedometer.AboutActivity.java
com.google.wireless.speed.speedometer.AccountSelector.java
com.google.wireless.speed.speedometer.BatteryCapPowerManager.java
com.google.wireless.speed.speedometer.Checkin.java
com.google.wireless.speed.speedometer.Config.java
com.google.wireless.speed.speedometer.DeviceInfo.java
com.google.wireless.speed.speedometer.DeviceProperty.java
com.google.wireless.speed.speedometer.Logger.java
com.google.wireless.speed.speedometer.MeasurementCreationActivity.java
com.google.wireless.speed.speedometer.MeasurementDesc.java
com.google.wireless.speed.speedometer.MeasurementError.java
com.google.wireless.speed.speedometer.MeasurementResult.java
com.google.wireless.speed.speedometer.MeasurementScheduleConsoleActivity.java
com.google.wireless.speed.speedometer.MeasurementScheduler.java
com.google.wireless.speed.speedometer.MeasurementSkippedException.java
com.google.wireless.speed.speedometer.MeasurementTask.java
com.google.wireless.speed.speedometer.ResultsConsoleActivity.java
com.google.wireless.speed.speedometer.SpeedometerApp.java
com.google.wireless.speed.speedometer.SpeedometerPreferenceActivity.java
com.google.wireless.speed.speedometer.SplashScreenActivity.java
com.google.wireless.speed.speedometer.SystemConsoleActivity.java
com.google.wireless.speed.speedometer.UpdateIntent.java
com.google.wireless.speed.speedometer.WatchdogBootReceiver.java
com.google.wireless.speed.speedometer.measurements.DnsLookupTask.java
com.google.wireless.speed.speedometer.measurements.HttpTask.java
com.google.wireless.speed.speedometer.measurements.PingTask.java
com.google.wireless.speed.speedometer.measurements.TracerouteTask.java
com.google.wireless.speed.speedometer.measurements.UDPBurstTask.java
com.google.wireless.speed.speedometer.util.MeasurementJsonConvertor.java
com.google.wireless.speed.speedometer.util.PhoneUtils.java
com.google.wireless.speed.speedometer.util.Util.java