1 /* 2 * Copyright (C) 2011, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bandwidthtest; 18 19 import android.content.Context; 20 import android.net.ConnectivityManager; 21 import android.net.NetworkInfo.State; 22 import android.net.NetworkStats; 23 import android.net.NetworkStats.Entry; 24 import android.net.TrafficStats; 25 import android.net.wifi.WifiManager; 26 import android.os.Bundle; 27 import android.os.Environment; 28 import android.os.Process; 29 import android.os.SystemClock; 30 import android.telephony.TelephonyManager; 31 import android.test.InstrumentationTestCase; 32 import android.test.suitebuilder.annotation.LargeTest; 33 import android.util.Log; 34 35 import com.android.bandwidthtest.util.BandwidthTestUtil; 36 import com.android.bandwidthtest.util.ConnectionUtil; 37 38 import java.io.File; 39 import java.util.HashMap; 40 import java.util.Map; 41 42 /** 43 * Test that downloads files from a test server and reports the bandwidth metrics collected. 44 */ 45 public class BandwidthTest extends InstrumentationTestCase { 46 47 private static final String LOG_TAG = "BandwidthTest"; 48 private final static String PROF_LABEL = "PROF_"; 49 private final static String PROC_LABEL = "PROC_"; 50 private final static int INSTRUMENTATION_IN_PROGRESS = 2; 51 52 private final static String BASE_DIR = 53 Environment.getExternalStorageDirectory().getAbsolutePath(); 54 private final static String TMP_FILENAME = "tmp.dat"; 55 // Download 10.486 * 106 bytes (+ headers) from app engine test server. 56 private final int FILE_SIZE = 10485613; 57 private Context mContext; 58 private ConnectionUtil mConnectionUtil; 59 private TelephonyManager mTManager; 60 private int mUid; 61 private String mSsid; 62 private String mTestServer; 63 private String mDeviceId; 64 private BandwidthTestRunner mRunner; 65 66 67 @Override 68 protected void setUp() throws Exception { 69 super.setUp(); 70 mRunner = (BandwidthTestRunner) getInstrumentation(); 71 mSsid = mRunner.mSsid; 72 mTestServer = mRunner.mTestServer; 73 mContext = mRunner.getTargetContext(); 74 mConnectionUtil = new ConnectionUtil(mContext); 75 mConnectionUtil.initialize(); 76 Log.v(LOG_TAG, "Initialized mConnectionUtil"); 77 mUid = Process.myUid(); 78 mTManager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); 79 mDeviceId = mTManager.getDeviceId(); 80 } 81 82 @Override 83 protected void tearDown() throws Exception { 84 mConnectionUtil.cleanUp(); 85 super.tearDown(); 86 } 87 88 /** 89 * Ensure that downloading on wifi reports reasonable stats. 90 */ 91 @LargeTest 92 public void testWifiDownload() throws Exception { 93 mConnectionUtil.wifiTestInit(); 94 assertTrue("Could not connect to wifi!", setDeviceWifiAndAirplaneMode(mSsid)); 95 downloadFile(); 96 } 97 98 /** 99 * Ensure that downloading on mobile reports reasonable stats. 100 */ 101 @LargeTest 102 public void testMobileDownload() throws Exception { 103 // As part of the setup we disconnected from wifi; make sure we are connected to mobile and 104 // that we have data. 105 assertTrue("Do not have mobile data!", hasMobileData()); 106 downloadFile(); 107 } 108 109 /** 110 * Helper method that downloads a file using http connection from a test server and reports the 111 * data usage stats to instrumentation out. 112 */ 113 protected void downloadFile() throws Exception { 114 NetworkStats pre_test_stats = fetchDataFromProc(mUid); 115 String ts = Long.toString(System.currentTimeMillis()); 116 117 String targetUrl = BandwidthTestUtil.buildDownloadUrl( 118 mTestServer, FILE_SIZE, mDeviceId, ts); 119 TrafficStats.startDataProfiling(mContext); 120 File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME); 121 assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile)); 122 NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext); 123 Log.d(LOG_TAG, prof_stats.toString()); 124 125 NetworkStats post_test_stats = fetchDataFromProc(mUid); 126 NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats); 127 128 // Output measurements to instrumentation out, so that it can be compared to that of 129 // the server. 130 Bundle results = new Bundle(); 131 results.putString("device_id", mDeviceId); 132 results.putString("timestamp", ts); 133 results.putInt("size", FILE_SIZE); 134 AddStatsToResults(PROF_LABEL, prof_stats, results); 135 AddStatsToResults(PROC_LABEL, proc_stats, results); 136 getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); 137 138 // Clean up. 139 assertTrue(cleanUpFile(tmpSaveFile)); 140 } 141 142 /** 143 * Ensure that uploading on wifi reports reasonable stats. 144 */ 145 @LargeTest 146 public void testWifiUpload() throws Exception { 147 mConnectionUtil.wifiTestInit(); 148 assertTrue(setDeviceWifiAndAirplaneMode(mSsid)); 149 uploadFile(); 150 } 151 152 /** 153 * Ensure that uploading on wifi reports reasonable stats. 154 */ 155 @LargeTest 156 public void testMobileUpload() throws Exception { 157 assertTrue(hasMobileData()); 158 uploadFile(); 159 } 160 161 /** 162 * Helper method that downloads a test file to upload. The stats reported to instrumentation out 163 * only include upload stats. 164 */ 165 protected void uploadFile() throws Exception { 166 // Download a file from the server. 167 String ts = Long.toString(System.currentTimeMillis()); 168 String targetUrl = BandwidthTestUtil.buildDownloadUrl( 169 mTestServer, FILE_SIZE, mDeviceId, ts); 170 File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME); 171 assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile)); 172 173 ts = Long.toString(System.currentTimeMillis()); 174 NetworkStats pre_test_stats = fetchDataFromProc(mUid); 175 TrafficStats.startDataProfiling(mContext); 176 assertTrue(BandwidthTestUtil.postFileToServer(mTestServer, mDeviceId, ts, tmpSaveFile)); 177 NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext); 178 Log.d(LOG_TAG, prof_stats.toString()); 179 NetworkStats post_test_stats = fetchDataFromProc(mUid); 180 NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats); 181 182 // Output measurements to instrumentation out, so that it can be compared to that of 183 // the server. 184 Bundle results = new Bundle(); 185 results.putString("device_id", mDeviceId); 186 results.putString("timestamp", ts); 187 results.putInt("size", FILE_SIZE); 188 AddStatsToResults(PROF_LABEL, prof_stats, results); 189 AddStatsToResults(PROC_LABEL, proc_stats, results); 190 getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); 191 192 // Clean up. 193 assertTrue(cleanUpFile(tmpSaveFile)); 194 } 195 196 /** 197 * We want to make sure that if we use wifi and the Download Manager to download stuff, 198 * accounting still goes to the app making the call and that the numbers still make sense. 199 */ 200 @LargeTest 201 public void testWifiDownloadWithDownloadManager() throws Exception { 202 mConnectionUtil.wifiTestInit(); 203 assertTrue(setDeviceWifiAndAirplaneMode(mSsid)); 204 downloadFileUsingDownloadManager(); 205 } 206 207 /** 208 * We want to make sure that if we use mobile data and the Download Manager to download stuff, 209 * accounting still goes to the app making the call and that the numbers still make sense. 210 */ 211 @LargeTest 212 public void testMobileDownloadWithDownloadManager() throws Exception { 213 assertTrue(hasMobileData()); 214 downloadFileUsingDownloadManager(); 215 } 216 217 /** 218 * Helper method that downloads a file from a test server using the download manager and reports 219 * the stats to instrumentation out. 220 */ 221 protected void downloadFileUsingDownloadManager() throws Exception { 222 // If we are using the download manager, then the data that is written to /proc/uid_stat/ 223 // is accounted against download manager's uid, since it uses pre-ICS API. 224 int downloadManagerUid = mConnectionUtil.downloadManagerUid(); 225 assertTrue(downloadManagerUid >= 0); 226 NetworkStats pre_test_stats = fetchDataFromProc(downloadManagerUid); 227 // start profiling 228 TrafficStats.startDataProfiling(mContext); 229 String ts = Long.toString(System.currentTimeMillis()); 230 String targetUrl = BandwidthTestUtil.buildDownloadUrl( 231 mTestServer, FILE_SIZE, mDeviceId, ts); 232 Log.v(LOG_TAG, "Download url: " + targetUrl); 233 File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME); 234 assertTrue(mConnectionUtil.startDownloadAndWait(targetUrl, 500000)); 235 NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext); 236 NetworkStats post_test_stats = fetchDataFromProc(downloadManagerUid); 237 NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats); 238 Log.d(LOG_TAG, prof_stats.toString()); 239 // Output measurements to instrumentation out, so that it can be compared to that of 240 // the server. 241 Bundle results = new Bundle(); 242 results.putString("device_id", mDeviceId); 243 results.putString("timestamp", ts); 244 results.putInt("size", FILE_SIZE); 245 AddStatsToResults(PROF_LABEL, prof_stats, results); 246 AddStatsToResults(PROC_LABEL, proc_stats, results); 247 getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); 248 249 // Clean up. 250 assertTrue(cleanUpFile(tmpSaveFile)); 251 } 252 253 /** 254 * Fetch network data from /proc/uid_stat/uid 255 * 256 * @return populated {@link NetworkStats} 257 */ 258 public NetworkStats fetchDataFromProc(int uid) { 259 String root_filepath = "/proc/uid_stat/" + uid + "/"; 260 File rcv_stat = new File (root_filepath + "tcp_rcv"); 261 int rx = BandwidthTestUtil.parseIntValueFromFile(rcv_stat); 262 File snd_stat = new File (root_filepath + "tcp_snd"); 263 int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat); 264 NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); 265 stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT, 266 NetworkStats.TAG_NONE, rx, 0, tx, 0, 0); 267 return stats; 268 } 269 270 /** 271 * Turn on Airplane mode and connect to the wifi. 272 * 273 * @param ssid of the wifi to connect to 274 * @return true if we successfully connected to a given network. 275 */ 276 public boolean setDeviceWifiAndAirplaneMode(String ssid) { 277 mConnectionUtil.setAirplaneMode(mContext, true); 278 assertTrue(mConnectionUtil.connectToWifi(ssid)); 279 assertTrue(mConnectionUtil.waitForWifiState(WifiManager.WIFI_STATE_ENABLED, 280 ConnectionUtil.LONG_TIMEOUT)); 281 assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_WIFI, 282 State.CONNECTED, ConnectionUtil.LONG_TIMEOUT)); 283 return mConnectionUtil.hasData(); 284 } 285 286 /** 287 * Helper method to make sure we are connected to mobile data. 288 * 289 * @return true if we successfully connect to mobile data. 290 */ 291 public boolean hasMobileData() { 292 assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, 293 State.CONNECTED, ConnectionUtil.LONG_TIMEOUT)); 294 assertTrue("Not connected to mobile", mConnectionUtil.isConnectedToMobile()); 295 assertFalse("Still connected to wifi.", mConnectionUtil.isConnectedToWifi()); 296 return mConnectionUtil.hasData(); 297 } 298 299 /** 300 * Output the {@link NetworkStats} to Instrumentation out. 301 * 302 * @param label to attach to this given stats. 303 * @param stats {@link NetworkStats} to add. 304 * @param results {@link Bundle} to be added to. 305 */ 306 public void AddStatsToResults(String label, NetworkStats stats, Bundle results){ 307 if (results == null || results.isEmpty()) { 308 Log.e(LOG_TAG, "Empty bundle provided."); 309 return; 310 } 311 // Merge stats across all sets. 312 Map<Integer, Entry> totalStats = new HashMap<Integer, Entry>(); 313 for (int i = 0; i < stats.size(); ++i) { 314 Entry statsEntry = stats.getValues(i, null); 315 // We are only interested in the all inclusive stats. 316 if (statsEntry.tag != 0) { 317 continue; 318 } 319 Entry mapEntry = null; 320 if (totalStats.containsKey(statsEntry.uid)) { 321 mapEntry = totalStats.get(statsEntry.uid); 322 switch (statsEntry.set) { 323 case NetworkStats.SET_ALL: 324 mapEntry.rxBytes = statsEntry.rxBytes; 325 mapEntry.txBytes = statsEntry.txBytes; 326 break; 327 case NetworkStats.SET_DEFAULT: 328 case NetworkStats.SET_FOREGROUND: 329 mapEntry.rxBytes += statsEntry.rxBytes; 330 mapEntry.txBytes += statsEntry.txBytes; 331 break; 332 default: 333 Log.w(LOG_TAG, "Invalid state found in NetworkStats."); 334 } 335 } else { 336 totalStats.put(statsEntry.uid, statsEntry); 337 } 338 } 339 // Ouput merged stats to bundle. 340 for (Entry entry : totalStats.values()) { 341 results.putInt(label + "uid", entry.uid); 342 results.putLong(label + "tx", entry.txBytes); 343 results.putLong(label + "rx", entry.rxBytes); 344 } 345 } 346 347 /** 348 * Remove file if it exists. 349 * @param file {@link File} to delete. 350 * @return true if successfully deleted the file. 351 */ 352 private boolean cleanUpFile(File file) { 353 if (file.exists()) { 354 return file.delete(); 355 } 356 return true; 357 } 358 } 359