/* Copyright 2012 Google Inc.
*
* 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.mobiperf;
import android.util.StringBuilderPrinter;
import com.mobiperf.measurements.DnsLookupTask;
import com.mobiperf.measurements.DnsLookupTask.DnsLookupDesc;
import com.mobiperf.measurements.HttpTask;
import com.mobiperf.measurements.HttpTask.HttpDesc;
import com.mobiperf.measurements.PingTask;
import com.mobiperf.measurements.PingTask.PingDesc;
import com.mobiperf.measurements.TracerouteTask;
import com.mobiperf.measurements.TracerouteTask.TracerouteDesc;
import com.mobiperf.measurements.UDPBurstTask;
import com.mobiperf.measurements.UDPBurstTask.UDPBurstDesc;
import com.mobiperf.measurements.TCPThroughputTask;
import com.mobiperf.measurements.TCPThroughputTask.TCPThroughputDesc;
import com.mobiperf.util.MeasurementJsonConvertor;
import com.mobiperf.util.PhoneUtils;
import com.mobiperf.util.Util;
import java.util.Formatter;
import java.util.HashMap;
/**
* POJO that represents the result of a measurement
* @see MeasurementDesc
*/
public class MeasurementResult {
private String deviceId;
private DeviceProperty properties;
private long timestamp;
private boolean success;
private String taskKey;
private String type;
private MeasurementDesc parameters;
private HashMap<String, String> values;
/**
* @param deviceProperty
* @param type
* @param timestamp
* @param success
* @param measurementDesc
*/
public MeasurementResult(String id, DeviceProperty deviceProperty, String type,
long timeStamp, boolean success, MeasurementDesc measurementDesc) {
super();
this.taskKey = measurementDesc.key;
this.deviceId = id;
this.type = type;
this.properties = deviceProperty;
this.timestamp = timeStamp;
this.success = success;
this.parameters = measurementDesc;
this.parameters.parameters = null;
this.values = new HashMap<String, String>();
}
/* Returns the type of this result */
public String getType() {
return parameters.getType();
}
/* Add the measurement results of type String into the class */
public void addResult(String resultType, Object resultVal) {
this.values.put(resultType, MeasurementJsonConvertor.toJsonString(resultVal));
}
/* Returns a string representation of the result */
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
StringBuilderPrinter printer = new StringBuilderPrinter(builder);
Formatter format = new Formatter();
try {
if (type == PingTask.TYPE) {
getPingResult(printer, values);
} else if (type == HttpTask.TYPE) {
getHttpResult(printer, values);
} else if (type == DnsLookupTask.TYPE) {
getDnsResult(printer, values);
} else if (type == TracerouteTask.TYPE) {
getTracerouteResult(printer, values);
} else if (type == UDPBurstTask.TYPE) {
getUDPBurstResult(printer, values);
} else if (type == TCPThroughputTask.TYPE) {
getTCPThroughputResult(printer, values);
} else {
Logger.e("Failed to get results for unknown measurement type " + type);
}
return builder.toString();
} catch (NumberFormatException e) {
Logger.e("Exception occurs during constructing result string for user", e);
} catch (ClassCastException e) {
Logger.e("Exception occurs during constructing result string for user", e);
} catch (Exception e) {
Logger.e("Exception occurs during constructing result string for user", e);
}
return "Measurement has failed";
}
private void getPingResult(StringBuilderPrinter printer, HashMap<String, String> values) {
PingDesc desc = (PingDesc) parameters;
printer.println("[Ping]");
printer.println("Target: " + desc.target);
String ipAddress = removeQuotes(values.get("target_ip"));
// TODO: internationalize 'Unknown'.
if (ipAddress == null) {
ipAddress = "Unknown";
}
printer.println("IP address: " + ipAddress);
printer.println("Timestamp: " + Util.getTimeStringFromMicrosecond(properties.timestamp));
printIPTestResult(printer);
if (success) {
float packetLoss = Float.parseFloat(values.get("packet_loss"));
int count = Integer.parseInt(values.get("packets_sent"));
printer.println("\n" + count + " packets transmitted, " + (int) (count * (1 - packetLoss)) +
" received, " + (packetLoss * 100) + "% packet loss");
float value = Float.parseFloat(values.get("mean_rtt_ms"));
printer.println("Mean RTT: " + String.format("%.1f", value) + " ms");
value = Float.parseFloat(values.get("min_rtt_ms"));
printer.println("Min RTT: " + String.format("%.1f", value) + " ms");
value = Float.parseFloat(values.get("max_rtt_ms"));
printer.println("Max RTT: " + String.format("%.1f", value) + " ms");
value = Float.parseFloat(values.get("stddev_rtt_ms"));
printer.println("Std dev: " + String.format("%.1f", value) + " ms");
} else {
printer.println("Failed");
}
}
private void getHttpResult(StringBuilderPrinter printer, HashMap<String, String> values) {
HttpDesc desc = (HttpDesc) parameters;
printer.println("[HTTP]");
printer.println("URL: " + desc.url);
printer.println("Timestamp: " + Util.getTimeStringFromMicrosecond(properties.timestamp));
printIPTestResult(printer);
if (success) {
int headerLen = Integer.parseInt(values.get("headers_len"));
int bodyLen = Integer.parseInt(values.get("body_len"));
int time = Integer.parseInt(values.get("time_ms"));
printer.println("");
printer.println("Downloaded " + (headerLen + bodyLen) + " bytes in " + time + " ms");
printer.println("Bandwidth: " + (headerLen + bodyLen) * 8 / time + " Kbps");
} else {
printer.println("Download failed, status code " + values.get("code"));
}
}
private void getDnsResult(StringBuilderPrinter printer, HashMap<String, String> values) {
DnsLookupDesc desc = (DnsLookupDesc) parameters;
printer.println("[DNS Lookup]");
printer.println("Target: " + desc.target);
printer.println("Timestamp: " + Util.getTimeStringFromMicrosecond(properties.timestamp));
printIPTestResult(printer);
if (success) {
String ipAddress = removeQuotes(values.get("address"));
if (ipAddress == null) {
ipAddress = "Unknown";
}
printer.println("\nAddress: " + ipAddress);
int time = Integer.parseInt(values.get("time_ms"));
printer.println("Lookup time: " + time + " ms");
} else {
printer.println("Failed");
}
}
private void getTracerouteResult(StringBuilderPrinter printer, HashMap<String, String> values) {
TracerouteDesc desc = (TracerouteDesc) parameters;
printer.println("[Traceroute]");
printer.println("Target: " + desc.target);
printer.println("Timestamp: " + Util.getTimeStringFromMicrosecond(properties.timestamp));
printIPTestResult(printer);
if (success) {
// Manually inject a new line
printer.println(" ");
int hops = Integer.parseInt(values.get("num_hops"));
int hop_str_len = String.valueOf(hops + 1).length();
for (int i = 0; i < hops; i++) {
String key = "hop_" + i + "_addr_1";
String ipAddress = removeQuotes(values.get(key));
if (ipAddress == null) {
ipAddress = "Unknown";
}
String hop_str = String.valueOf(i+1);
String hopInfo = hop_str;
for (int j = 0; j < hop_str_len + 1 - hop_str.length(); ++j) {
hopInfo += " ";
}
hopInfo += ipAddress;
// Maximum IP address length is 15.
for (int j = 0; j < 16 - ipAddress.length(); ++j) {
hopInfo += " ";
}
key = "hop_" + i + "_rtt_ms";
// The first and last character of this string are double quotes.
String timeStr = removeQuotes(values.get(key));
if (timeStr == null) {
timeStr = "Unknown";
}
float time = Float.parseFloat(timeStr);
printer.println(hopInfo + String.format("%6.2f", time) + " ms");
}
} else {
printer.println("Failed");
}
}
private void getUDPBurstResult(StringBuilderPrinter printer, HashMap<String, String> values) {
UDPBurstDesc desc = (UDPBurstDesc) parameters;
if (desc.dirUp) {
printer.println("[UDPBurstUp]");
} else {
printer.println("[UDPBurstDown]");
}
printer.println("Target: " + desc.target);
printer.println("IP addr: " + values.get("target_ip"));
if (success) {
printer.println("Timestamp: " +
Util.getTimeStringFromMicrosecond(properties.timestamp));
printIPTestResult(printer);
printer.println("Packet size: " + desc.packetSizeByte + "B");
printer.println("Number of packets to be sent: " + desc.udpBurstCount);
printer.println("Interval between packets: " + desc.udpInterval + "ms");
String lossRatio = String.format("%.2f"
, Double.parseDouble(values.get("loss_ratio")) * 100);
String outOfOrderRatio = String.format("%.2f"
, Double.parseDouble(values.get("out_of_order_ratio")) * 100);
printer.println("\nLoss ratio: " + lossRatio + "%");
printer.println("Out of order ratio: " + outOfOrderRatio + "%");
printer.println("Jitter: " + values.get("jitter") + "ms");
} else {
printer.println("Failed");
}
}
private void getTCPThroughputResult(StringBuilderPrinter printer,
HashMap<String, String> values) {
TCPThroughputDesc desc = (TCPThroughputDesc) parameters;
if (desc.dir_up) {
printer.println("[TCP Uplink]");
} else {
printer.println("[TCP Downlink]");
}
printer.println("Target: " + desc.target);
printer.println("Timestamp: " +
Util.getTimeStringFromMicrosecond(properties.timestamp));
printIPTestResult(printer);
if (success) {
printer.println("");
// Display result with precision up to 2 digit
String speedInJSON = values.get("tcp_speed_results");
String dataLimitExceedInJSON = values.get("data_limit_exceeded");
String displayResult = "";
double tp = desc.calMedianSpeedFromTCPThroughputOutput(speedInJSON);
double KB = Math.pow(2, 10);
if (tp < 0) {
displayResult = "No results available.";
} else if (tp > KB*KB) {
displayResult = "Speed: " + String.format("%.2f",tp/(KB*KB)) + " Gbps";
} else if (tp > KB ) {
displayResult = "Speed: " + String.format("%.2f",tp/KB) + " Mbps";
} else {
displayResult = "Speed: " + String.format("%.2f", tp) + " Kbps";
}
// Append notice for exceeding data limit
if (dataLimitExceedInJSON.equals("true")) {
displayResult += "\n* Task finishes earlier due to exceeding " +
"maximum number of "+ ((desc.dir_up) ? "transmitted" : "received") +
" bytes";
}
printer.println(displayResult);
} else {
printer.println("Failed");
}
}
/**
* Removes the quotes surrounding the string. If |str| is null, returns null.
*/
private String removeQuotes(String str) {
return str != null ? str.replaceAll("^\"|\"", "") : null;
}
/**
* Print ip connectivity and hostname resolvability result
*/
private void printIPTestResult (StringBuilderPrinter printer) {
printer.println("IPv4/IPv6 Connectivity: " + properties.ipConnectivity);
printer.println("IPv4/IPv6 Domain Name Resolvability: "
+ properties.dnResolvability);
}
}