Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 33f86995 authored by Tsu Chiang Chuang's avatar Tsu Chiang Chuang
Browse files

Bandwidth microbenchmark test app for ICS.

Change-Id: I6fed5c47818f0fe08b9f2c18f1070d3238a469b6
parent 5465e054
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
# Copyright (C) 2011 The Android Open Source Project
#
# 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.

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests

# Include all test java files.
LOCAL_SRC_FILES := \
	$(call all-java-files-under, src)

LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := BandwidthTests

include $(BUILD_PACKAGE)

include $(call all-makefiles-under,$(LOCAL_PATH))
+38 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project

     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.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.bandwidth.tests" >

    <application >
        <uses-library android:name="android.test.runner" />
    </application>

    <instrumentation
        android:name="com.android.bandwidthtest.BandwidthTestRunner"
        android:targetPackage="com.android.bandwidth.tests"
        android:label="Bandwidth Tests" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.DEVICE_POWER" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>
+217 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011, The Android Open Source Project
 *
 * 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.android.bandwidthtest;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;

import com.android.bandwidthtest.util.BandwidthTestUtil;
import com.android.bandwidthtest.util.ConnectionUtil;

import java.io.File;

/**
 * Test that downloads files from a test server and reports the bandwidth metrics collected.
 */
public class BandwidthTest extends InstrumentationTestCase {

    private static final String LOG_TAG = "BandwidthTest";
    private final static String PROF_LABEL = "PROF_";
    private final static String PROC_LABEL = "PROC_";
    private final static int INSTRUMENTATION_IN_PROGRESS = 2;

    private final static String BASE_DIR =
            Environment.getExternalStorageDirectory().getAbsolutePath();
    private final static String TMP_FILENAME = "tmp.dat";
    // Download 10.486 * 106 bytes (+ headers) from app engine test server.
    private final int FILE_SIZE = 10485613;
    private Context mContext;
    private ConnectionUtil mConnectionUtil;
    private TelephonyManager mTManager;
    private int mUid;
    private String mSsid;
    private String mTestServer;
    private String mDeviceId;
    private BandwidthTestRunner mRunner;


    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mRunner = (BandwidthTestRunner) getInstrumentation();
        mSsid = mRunner.mSsid;
        mTestServer = mRunner.mTestServer;
        mContext = mRunner.getTargetContext();
        mConnectionUtil = new ConnectionUtil(mContext);
        mConnectionUtil.initialize();
        Log.v(LOG_TAG, "Initialized mConnectionUtil");
        mUid = Process.myUid();
        mTManager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
        mDeviceId = mTManager.getDeviceId();
    }

    @Override
    protected void tearDown() throws Exception {
        mConnectionUtil.cleanUp();
        super.tearDown();
    }

    /**
     * Ensure that downloading on wifi reports reasonable stats.
     */
    @LargeTest
    public void testWifiDownload() {
        assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
        NetworkStats pre_test_stats = fetchDataFromProc(mUid);
        String ts = Long.toString(System.currentTimeMillis());

        String targetUrl = BandwidthTestUtil.buildDownloadUrl(
                mTestServer, FILE_SIZE, mDeviceId, ts);
        TrafficStats.startDataProfiling(mContext);
        File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
        assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile));
        NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);

        NetworkStats post_test_stats = fetchDataFromProc(mUid);
        NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);

        // Output measurements to instrumentation out, so that it can be compared to that of
        // the server.
        Bundle results = new Bundle();
        results.putString("device_id", mDeviceId);
        results.putString("timestamp", ts);
        results.putInt("size", FILE_SIZE);
        AddStatsToResults(PROF_LABEL, prof_stats, results);
        AddStatsToResults(PROC_LABEL, proc_stats, results);
        getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);

        // Clean up.
        assertTrue(cleanUpFile(tmpSaveFile));
    }

    /**
     * We want to make sure that if we use the Download Manager to download stuff,
     * accounting still goes to the app making the call and that the numbers still make sense.
     */
    @LargeTest
    public void testWifiDownloadWithDownloadManager() {
        assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
        // If we are using the download manager, then the data that is written to /proc/uid_stat/
        // is accounted against download manager's uid, since it uses pre-ICS API.
        int downloadManagerUid = mConnectionUtil.downloadManagerUid();
        assertTrue(downloadManagerUid >= 0);
        NetworkStats pre_test_stats = fetchDataFromProc(downloadManagerUid);
        // start profiling
        TrafficStats.startDataProfiling(mContext);
        String ts = Long.toString(System.currentTimeMillis());
        String targetUrl = BandwidthTestUtil.buildDownloadUrl(
                mTestServer, FILE_SIZE, mDeviceId, ts);
        Log.v(LOG_TAG, "Download url: " + targetUrl);
        File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
        assertTrue(mConnectionUtil.startDownloadAndWait(targetUrl, 500000));
        NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
        NetworkStats post_test_stats = fetchDataFromProc(downloadManagerUid);
        NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);

        // Output measurements to instrumentation out, so that it can be compared to that of
        // the server.
        Bundle results = new Bundle();
        results.putString("device_id", mDeviceId);
        results.putString("timestamp", ts);
        results.putInt("size", FILE_SIZE);
        AddStatsToResults(PROF_LABEL, prof_stats, results);
        AddStatsToResults(PROC_LABEL, proc_stats, results);
        getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);

        // Clean up.
        assertTrue(cleanUpFile(tmpSaveFile));
    }

    /**
     * Fetch network data from /proc/uid_stat/uid
     * @return populated {@link NetworkStats}
     */
    public NetworkStats fetchDataFromProc(int uid) {
        String root_filepath = "/proc/uid_stat/" + uid + "/";
        File rcv_stat = new File (root_filepath + "tcp_rcv");
        int rx = BandwidthTestUtil.parseIntValueFromFile(rcv_stat);
        File snd_stat = new File (root_filepath + "tcp_snd");
        int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat);
        NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
        stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.TAG_NONE, rx, 0, tx, 0);
        return stats;
    }

    /**
     * Turn on Airplane mode and connect to the wifi
     * @param ssid of the wifi to connect to
     * @return true if we successfully connected to a given network.
     */
    public boolean setDeviceWifiAndAirplaneMode(String ssid) {
        mConnectionUtil.setAirplaneMode(mContext, true);
        assertTrue(mConnectionUtil.connectToWifi(ssid));
        assertTrue(mConnectionUtil.waitForWifiState(WifiManager.WIFI_STATE_ENABLED,
                ConnectionUtil.LONG_TIMEOUT));
        return mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
                ConnectionUtil.LONG_TIMEOUT);
    }

    /**
     * Output the {@link NetworkStats} to Instrumentation out.
     * @param label to attach to this given stats.
     * @param stats {@link NetworkStats} to add.
     * @param results {@link Bundle} to be added to.
     */
    public void AddStatsToResults(String label, NetworkStats stats, Bundle results){
        if (results == null || results.isEmpty()) {
            Log.e(LOG_TAG, "Empty bundle provided.");
            return;
        }
        for (int i = 0; i < stats.size(); ++i) {
            android.net.NetworkStats.Entry entry = stats.getValues(i, null);
            results.putInt(label + "uid", entry.uid);
            results.putString(label + "iface", entry.iface);
            results.putInt(label + "tag", entry.tag);
            results.putLong(label + "tx", entry.txBytes);
            results.putLong(label + "rx", entry.rxBytes);
        }
    }

    /**
     * Remove file if it exists.
     * @param file {@link File} to delete.
     * @return true if successfully deleted the file.
     */
    private boolean cleanUpFile(File file) {
        if (file.exists()) {
            return file.delete();
        }
        return true;
    }
}
 No newline at end of file
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011, The Android Open Source Project
 *
 * 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.android.bandwidthtest;

import android.os.Bundle;
import android.test.InstrumentationTestRunner;

public class BandwidthTestRunner extends InstrumentationTestRunner {
    public String mSsid = "wifi-ssid";
    public String mTestServer = "http://www.sometestserver.com";

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        String ssidStr= (String) icicle.get("ssid");
        if (ssidStr != null) {
            mSsid = ssidStr;
        }
        String serverStr = (String) icicle.get("server");
        if (serverStr != null) {
            mTestServer = serverStr;
        }
    }
}
+257 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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.android.bandwidthtest;

import android.net.NetworkInfo.State;
import android.util.Log;

import java.util.List;
import java.util.ArrayList;

/**
 * Data structure to keep track of the network state transitions.
 */
public class NetworkState {
    /**
     * Desired direction of state transition.
     */
    public enum StateTransitionDirection {
        TO_DISCONNECTION, TO_CONNECTION, DO_NOTHING
    }
    private final String LOG_TAG = "NetworkState";
    private List<State> mStateDepository;
    private State mTransitionTarget;
    private StateTransitionDirection mTransitionDirection;
    private String mReason = null;         // record mReason of state transition failure

    public NetworkState() {
        mStateDepository = new ArrayList<State>();
        mTransitionDirection = StateTransitionDirection.DO_NOTHING;
        mTransitionTarget = State.UNKNOWN;
    }

    public NetworkState(State currentState) {
        mStateDepository = new ArrayList<State>();
        mStateDepository.add(currentState);
        mTransitionDirection = StateTransitionDirection.DO_NOTHING;
        mTransitionTarget = State.UNKNOWN;
    }

    /**
     * Reinitialize the network state
     */
    public void resetNetworkState() {
        mStateDepository.clear();
        mTransitionDirection = StateTransitionDirection.DO_NOTHING;
        mTransitionTarget = State.UNKNOWN;
    }

    /**
     * Set the transition criteria
     * @param initState initial {@link State}
     * @param transitionDir explicit {@link StateTransitionDirection}
     * @param targetState desired {@link State}
     */
    public void setStateTransitionCriteria(State initState, StateTransitionDirection transitionDir,
            State targetState) {
        if (!mStateDepository.isEmpty()) {
            mStateDepository.clear();
        }
        mStateDepository.add(initState);
        mTransitionDirection = transitionDir;
        mTransitionTarget = targetState;
        Log.v(LOG_TAG, "setStateTransitionCriteria: " + printStates());
    }

    /**
     * Record the current state of the network
     * @param currentState  the current {@link State}
     */
    public void recordState(State currentState) {
        mStateDepository.add(currentState);
    }

    /**
     * Verify the state transition
     * @return true if the requested transition completed successfully.
     */
    public boolean validateStateTransition() {
        Log.v(LOG_TAG, String.format("Print state depository: %s", printStates()));
        switch (mTransitionDirection) {
            case DO_NOTHING:
                Log.v(LOG_TAG, "No direction requested, verifying network states");
                return validateNetworkStates();
            case TO_CONNECTION:
                Log.v(LOG_TAG, "Transition to CONNECTED");
                return validateNetworkConnection();
            case TO_DISCONNECTION:
                Log.v(LOG_TAG, "Transition to DISCONNECTED");
                return validateNetworkDisconnection();
            default:
                Log.e(LOG_TAG, "Invalid transition direction.");
                return false;
        }
    }

    /**
     * Verify that network states are valid
     * @return false if any of the states are invalid
     */
    private boolean validateNetworkStates() {
        if (mStateDepository.isEmpty()) {
            Log.v(LOG_TAG, "no state is recorded");
            mReason = "no state is recorded.";
            return false;
        } else if (mStateDepository.size() > 1) {
            Log.v(LOG_TAG, "no broadcast is expected, instead broadcast is probably received");
            mReason = "no broadcast is expected, instead broadcast is probably received";
            return false;
        } else if (mStateDepository.get(0) != mTransitionTarget) {
            Log.v(LOG_TAG, String.format("%s is expected, but it is %s",
                    mTransitionTarget.toString(),
                    mStateDepository.get(0).toString()));
            mReason = String.format("%s is expected, but it is %s",
                    mTransitionTarget.toString(),
                    mStateDepository.get(0).toString());
            return false;
        }
        return true;
    }

    /**
     * Verify the network state to disconnection
     * @return false if any of the state transitions were not valid
     */
    private boolean validateNetworkDisconnection() {
        // Transition from CONNECTED -> DISCONNECTED: CONNECTED->DISCONNECTING->DISCONNECTED
        StringBuffer str = new StringBuffer ("States: ");
        str.append(printStates());
        if (mStateDepository.get(0) != State.CONNECTED) {
            str.append(String.format(" Initial state should be CONNECTED, but it is %s.",
                    mStateDepository.get(0)));
            mReason = str.toString();
            return false;
        }
        State lastState = mStateDepository.get(mStateDepository.size() - 1);
        if ( lastState != mTransitionTarget) {
            str.append(String.format(" Last state should be DISCONNECTED, but it is %s",
                    lastState));
            mReason = str.toString();
            return false;
        }
        for (int i = 1; i < mStateDepository.size() - 1; i++) {
            State preState = mStateDepository.get(i-1);
            State curState = mStateDepository.get(i);
            if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) ||
                    (curState == State.DISCONNECTED))) {
                continue;
            } else if ((preState == State.DISCONNECTING) && (curState == State.DISCONNECTED)) {
                continue;
            } else if ((preState == State.DISCONNECTED) && (curState == State.DISCONNECTED)) {
                continue;
            } else {
                str.append(String.format(" Transition state from %s to %s is not valid",
                        preState.toString(), curState.toString()));
                mReason = str.toString();
                return false;
            }
        }
        mReason = str.toString();
        return true;
    }

    /**
     * Verify the network state to connection
     * @return false if any of the state transitions were not valid
     */
    private boolean validateNetworkConnection() {
        StringBuffer str = new StringBuffer("States ");
        str.append(printStates());
        if (mStateDepository.get(0) != State.DISCONNECTED) {
            str.append(String.format(" Initial state should be DISCONNECTED, but it is %s.",
                    mStateDepository.get(0)));
            mReason = str.toString();
            return false;
        }
        State lastState = mStateDepository.get(mStateDepository.size() - 1);
        if ( lastState != mTransitionTarget) {
            str.append(String.format(" Last state should be %s, but it is %s", mTransitionTarget,
                    lastState));
            mReason = str.toString();
            return false;
        }
        for (int i = 1; i < mStateDepository.size(); i++) {
            State preState = mStateDepository.get(i-1);
            State curState = mStateDepository.get(i);
            if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) ||
                    (curState == State.CONNECTED) || (curState == State.DISCONNECTED))) {
                continue;
            } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) {
                continue;
            } else if ((preState == State.CONNECTED) && (curState == State.CONNECTED)) {
                continue;
            } else {
                str.append(String.format(" Transition state from %s to %s is not valid.",
                        preState.toString(), curState.toString()));
                mReason = str.toString();
                return false;
            }
        }
        mReason = str.toString();
        return true;
    }

    /**
     * Fetch the different network state transitions
     * @return {@link List} of {@link State}
     */
    public List<State> getTransitionStates() {
        return mStateDepository;
    }

    /**
     * Fetch the reason for network state transition failure
     * @return the {@link String} for the failure
     */
    public String getFailureReason() {
        return mReason;
    }

    /**
     * Print the network state
     * @return {@link String} representation of the network state
     */
    public String printStates() {
        StringBuilder stateBuilder = new StringBuilder();
        for (int i = 0; i < mStateDepository.size(); i++) {
            stateBuilder.append(" ").append(mStateDepository.get(i).toString()).append("->");
        }
        return stateBuilder.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("mTransitionDirection: ").append(mTransitionDirection.toString()).
        append("; ").append("states:").
        append(printStates()).append("; ");
        return builder.toString();
    }
}
Loading