Back to project page Speedometer.
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.
/* Copyright 2012 Google Inc. */*from w w w. ja v a 2s .co 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.util.Log; 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.UnknownHostException; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Map; /** * A Callable task that handles Traceroute measurements * @author wenjiezeng@google.com (Steve Zeng) * */ public class TracerouteTask extends MeasurementTask { // Type name for internal use public static final String TYPE = "traceroute"; // Human readable name for the task public static final String DESCRIPTOR = "traceroute"; /* 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; public static final int DEFAULT_MAX_HOP_CNT = 30; // Used to compute progress for user public static final int EXPECTED_HOP_CNT = 20; public static final int DEFAULT_PINGS_PER_HOP = 3; private Process pingProc = null; private boolean stopRequested = false; /** * The description of the Traceroute measurement */ public static class TracerouteDesc extends MeasurementDesc { // the host name or IP address to use as the target of the traceroute. public String target; // the packet per ICMP ping in the unit of bytes private int packetSizeByte; // the number of seconds we wait for a ping response. private int pingTimeoutSec; // the interval between successive pings in seconds private double pingIntervalSec; // the number of pings we use for each ttl value private int pingsPerHop; // the total number of pings will send before we declarethe traceroute fails private int maxHopCount; // the location of the ping binary. Only used internally private String pingExe; public TracerouteDesc(String key, Date startTime, Date endTime, double intervalSec, long count, long priority, Map<String, String> params) throws InvalidParameterException { super(TracerouteTask.TYPE, key, startTime, endTime, intervalSec, count, priority, params); initalizeParams(params); if (target == null || target.length() == 0) { throw new InvalidParameterException("Target of traceroute cannot be null"); } } @Override public String getType() { return TracerouteTask.TYPE; } @Override protected void initalizeParams(Map<String, String> params) { if (params == null) { return; } // HTTP specific parameters according to the design document this.target = params.get("target"); try { String val; if ((val = params.get("packet_size_byte")) != null && val.length() > 0 && Integer.parseInt(val) > 0) { this.packetSizeByte = Integer.parseInt(val); } else { this.packetSizeByte = TracerouteTask.DEFAULT_PING_PACKET_SIZE; } if ((val = params.get("ping_timeout_sec")) != null && val.length() > 0 && Integer.parseInt(val) > 0) { this.pingTimeoutSec = Integer.parseInt(val); } else { this.pingTimeoutSec = TracerouteTask.DEFAULT_PING_TIMEOUT; } if ((val = params.get("ping_interval_sec")) != null && val.length() > 0 && Integer.parseInt(val) > 0) { this.pingIntervalSec = Integer.parseInt(val); } else { this.pingIntervalSec = Config.DEFAULT_INTERVAL_BETWEEN_ICMP_PACKET_SEC; } if ((val = params.get("pings_per_hop")) != null && val.length() > 0 && Integer.parseInt(val) > 0) { this.pingsPerHop = Integer.parseInt(val); } else { this.pingsPerHop = TracerouteTask.DEFAULT_PINGS_PER_HOP; } if ((val = params.get("max_hop_count")) != null && val.length() > 0 && Integer.parseInt(val) > 0) { this.maxHopCount = Integer.parseInt(val); } else { this.maxHopCount = TracerouteTask.DEFAULT_MAX_HOP_CNT; } } catch (NumberFormatException e) { throw new InvalidParameterException("PingTask cannot be created due to invalid params"); } } } public TracerouteTask(MeasurementDesc desc, Context parent) { super(new TracerouteDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority, desc.parameters), parent); } /** * Returns a copy of the TracerouteTask */ @Override public MeasurementTask clone() { MeasurementDesc desc = this.measurementDesc; TracerouteDesc newDesc = new TracerouteDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority, desc.parameters); return new TracerouteTask(newDesc, parent); } @Override public MeasurementResult call() throws MeasurementError { TracerouteDesc task = (TracerouteDesc) this.measurementDesc; int maxHopCount = task.maxHopCount; int ttl = 1; String hostIp = null; // TODO(Wenjie): Add a exhaustive list of ping locations for different Android phones task.pingExe = parent.getString(R.string.ping_executable); String target = task.target; boolean success = false; ArrayList<HopInfo> hopHosts = new ArrayList<HopInfo>(); Logger.d("Starting traceroute on host " + task.target); try { hostIp = InetAddress.getByName(target).getHostAddress(); } catch (UnknownHostException e) { Logger.e("Cannont resolve host " + target); throw new MeasurementError("target " + target + " cannot be resolved"); } MeasurementResult result = null; while (maxHopCount-- >= 0 && !stopRequested) { /* Current traceroute implementation sends out three ICMP probes per TTL. * One ping every 0.2s is the lower bound before some platforms requires * root to run ping. We ping once every time to get a rough rtt as we cannot * get the exact rtt from the output of the ping command with ttl being set * */ String command = Util.constructCommand(task.pingExe, "-n", "-t", ttl, "-s", task.packetSizeByte, "-c 1 ", target); try { double rtt = 0; long t1; HashSet<String> hostsAtThisDistance = new HashSet<String>(); for (int i = 0; i < task.pingsPerHop; i++) { t1 = System.currentTimeMillis(); pingProc = Runtime.getRuntime().exec(command); rtt += System.currentTimeMillis() - t1; // Grab the output of the process that runs the ping command InputStream is = pingProc.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); /* Process each line of the ping output and extracts the intermediate hops into * hostAtThisDistance */ processPingOutput(br, hostsAtThisDistance, hostIp); cleanUp(pingProc); try { Thread.sleep((long) (task.pingIntervalSec * 1000)); } catch (InterruptedException e) { Logger.i("Sleep interrupted between ping intervals"); } } rtt = rtt / task.pingsPerHop; hopHosts.add(new HopInfo(hostsAtThisDistance, rtt)); // Process the extracted IPs of intermediate hops StringBuffer progressStr = new StringBuffer(ttl + ": "); for (String ip : hostsAtThisDistance) { // If we have reached the final destination hostIp, print it out and clean up if (ip.compareTo(hostIp) == 0) { Logger.i(ttl + ": " + hostIp); Logger.i(" Finished! " + target + " reached in " + ttl + " hops"); success = true; PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils(); result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId, phoneUtils.getDeviceProperty(), TracerouteTask.TYPE, System.currentTimeMillis() * 1000, success, this.measurementDesc); result.addResult("num_hops", ttl); for (int i = 0; i < hopHosts.size(); i++) { HopInfo hopInfo = hopHosts.get(i); int hostIdx = 1; for (String host : hopInfo.hosts) { result.addResult("hop_" + i + "_addr_" + hostIdx++, host); } result.addResult("hop_" + i + "_rtt_ms", String.format("%.3f", hopInfo.rtt)); } Logger.i(MeasurementJsonConvertor.toJsonString(result)); return result; } else { // Otherwise, we aggregate various hosts at a given hop distance for printout progressStr.append(ip + " | "); } } // Remove the trailing separators progressStr.delete(progressStr.length() - 3, progressStr.length()); Logger.i(progressStr.toString()); } catch (SecurityException e) { Logger.e("Does not have the permission to run ping on this device"); } catch (IOException e) { Logger.e("The ping program cannot be executed"); Logger.e(e.getMessage()); } finally { cleanUp(pingProc); } ttl++; this.progress = (int) (100 * ttl / (double) TracerouteTask.EXPECTED_HOP_CNT); this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, progress); broadcastProgressForUser(progress); } throw new MeasurementError("cannot perform traceroute to " + task.target); } @SuppressWarnings("rawtypes") public static Class getDescClass() throws InvalidClassException { return TracerouteDesc.class; } @Override public String getType() { return TracerouteTask.TYPE; } @Override public String getDescriptor() { return DESCRIPTOR; } private void cleanUp(Process proc) { if (proc != null) { // destroy() closes all open streams proc.destroy(); } } private void processPingOutput(BufferedReader br, HashSet<String> hostsAtThisDistance, String hostIp) throws IOException { String line = null; while ((line = br.readLine()) != null) { Logger.d(line); if (line.startsWith("From")) { String ip = getHostIp(line); if (ip != null && ip.compareTo(hostIp) != 0) { hostsAtThisDistance.add(ip); } } else if (line.contains("time=")) { hostsAtThisDistance.add(hostIp); } } } /* TODO(Wenjie): The current search for valid IPs assumes the IP string is not a proper * substring of the space-separated tokens. For more robust searching in case different * outputs from ping due to its different versions, we need to refine the search * by testing weather any substring of the tokens contains a valid IP */ private String getHostIp(String line) { String[] tokens = line.split(" "); // In most cases, the second element in the array is the IP String tempIp = tokens[1]; if (isValidIpv4Addr(tempIp)) { return tempIp; } else { for (int i = 0; i < tokens.length; i++) { if (i == 1) { // Examined already continue; } else { if (isValidIpv4Addr(tokens[i])) { return tokens[i]; } } } } return null; } // Tells whether the string is an valid IPv4 address private boolean isValidIpv4Addr(String ip) { String[] tokens = ip.split("\\."); if (tokens.length == 4) { for (int i = 0; i < 4; i++) { try { int val = Integer.parseInt(tokens[i]); if (val < 0 || val > 255) { return false; } } catch (NumberFormatException e) { Logger.d(ip + " is not a valid IP address"); return false; } } return true; } return false; } private class HopInfo { // The hosts at a given hop distance public HashSet<String> hosts; // The average RRT for this hop distance public double rtt; protected HopInfo(HashSet<String> hosts, double rtt) { this.hosts = hosts; this.rtt = rtt; } } @Override public String toString() { TracerouteDesc desc = (TracerouteDesc) measurementDesc; return "[Traceroute]\n Target: " + desc.target + "\n Interval (sec): " + desc.intervalSec + "\n Next run: " + desc.startTime; } @Override public void stop() { stopRequested = true; cleanUp(pingProc); } }