blob: 65360888346033df05dbc7cea618d75ebf07c90b [file] [log] [blame]
/*
* 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.encryption.tests;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TopHelper;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.SnapshotInputStreamSource;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.FileUtil;
import junit.framework.Assert;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Runs the encryption CPU benchmarks
* <p>
* Runs various disk intensive actions on the device while measuring the CPU usage with the top
* command. This test can be run with an encrypted device or with an unencrypted device, and it is
* important to run both so that the difference between encrypted and unecrypted CPU usage can be
* derived.
* </p>
*/
public class EncryptionCpuTest implements IDeviceTest, IRemoteTest {
/** The amount to trim from either side of the top samples. */
private final static int TOP_TRIM = 5;
/** The block size in bytes for the dd command */
private final static int BLOCK_SIZE = 1024;
/**
* Class used for tests. Includes fields such as name post key and the method for running the
* test.
*/
private class CpuTest {
public String mTestName = null;
public String mKey = null;
private TopHelper mTopHelper = null;
private File mTopLogFile = null;
private Map<String, String> mMetrics = new HashMap<String, String>();
/**
* Run the test.
*
* @throws DeviceNotAvailableException If the device is not available.
*/
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
// Override for the test run.
}
/**
* Helper method for adding all the top statistics to the test metrics.
*
* @param top The {@link TopHelper} object used to measure the CPU usage via the top
* command.
* @param isEncrypted Whether the device is encrypted or not.
*/
protected void addTopStats(TopHelper top, boolean isEncrypted) {
String keySuffix = getKeySuffix(isEncrypted);
List<TopHelper.TopStats> stats = top.getTopStats();
if (stats.size() > TOP_TRIM * 2) {
stats = stats.subList(TOP_TRIM, stats.size() - TOP_TRIM);
addMetric("total_mean" + keySuffix, TopHelper.getTotalAverage(stats).toString());
addMetric("user_mean" + keySuffix, TopHelper.getUserAverage(stats).toString());
addMetric("system_mean" + keySuffix, TopHelper.getSystemAverage(stats).toString());
addMetric("iow_mean" + keySuffix, TopHelper.getIowAverage(stats).toString());
addMetric("irq_mean" + keySuffix, TopHelper.getIrqAverage(stats).toString());
}
}
/**
* Helper method for adding a metric to the test metrics.
*
* @param key The test metric key.
* @param value The value.
*/
protected void addMetric(String key, String value) {
mMetrics.put(key, value);
}
/**
* Gets the test metrics for the test.
*
* @return A mapping of metric key to value pairs for the test.
*/
public Map<String, String> getMetrics() {
return mMetrics;
}
/**
* Creates the {@link TopHelper} and sets up the logging to file.
*/
protected void setupLogging() {
mTopHelper = new TopHelper(mTestDevice);
try {
mTopLogFile = FileUtil.createTempFile("top_", ".txt");
mTopHelper.logToFile(mTopLogFile);
} catch (IOException e) {
CLog.e("Error creating log file: %s", e.getMessage());
}
}
/**
* Starts the {@link TopHelper}.
*/
protected void startLogging() {
mTopHelper.start();
}
/**
* Stops the {@link TopHelper} and adds the log file and metrics to the test results.
* @param listener
* @throws DeviceNotAvailableException
*/
protected void stopLogging(ITestInvocationListener listener)
throws DeviceNotAvailableException {
mTopHelper.cancel();
if (mTopLogFile != null) {
try {
listener.testLog(String.format("top_%s", mKey), LogDataType.TEXT,
new SnapshotInputStreamSource(new FileInputStream(mTopLogFile)));
} catch (FileNotFoundException e) {
CLog.e("Error saving log file: %s", e.getMessage());
}
mTopLogFile.delete();
mTopLogFile = null;
}
InputStreamSource bugreport = mTestDevice.getBugreport();
listener.testLog(String.format("bugreport_%s", mKey), LogDataType.TEXT, bugreport);
addTopStats(mTopHelper, mTestDevice.isDeviceEncrypted());
}
}
/**
* CPU test for measuring CPU usage while pushing a file to the device.
*/
private class PushTest extends CpuTest {
private String mDeviceFilePath = new File(
mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE), "testFile"
).getAbsolutePath();
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
File hostFile = null;
mTestDevice.executeShellCommand(String.format("rm %s", mDeviceFilePath));
try {
hostFile = File.createTempFile("temp", ".dat");
createFileHost(hostFile.getAbsolutePath(), mPushFileSize);
} catch (IOException e) {
CLog.e("Error creating file on host, skipping test.");
if (hostFile != null) {
hostFile.delete();
}
return;
}
setupLogging();
try {
CLog.d("Pushing file");
startLogging();
long startTime = System.currentTimeMillis();
mTestDevice.pushFile(hostFile, mDeviceFilePath);
long elapsedTime = System.currentTimeMillis() - startTime;
CLog.d("Pushing %dkB file to device %s took %d ms", mPushFileSize,
mTestDevice.getSerialNumber(), elapsedTime);
addMetric("push_bw" + getKeySuffix(mTestDevice.isDeviceEncrypted()),
new Double(1000.0 * mPushFileSize / elapsedTime).toString());
} finally {
stopLogging(listener);
hostFile.delete();
mTestDevice.executeShellCommand(String.format("rm %s", mDeviceFilePath));
}
}
}
/**
* CPU test for measuring CPU usage while pulling a file from the device.
*/
private class PullTest extends CpuTest {
private String mDeviceFilePath = new File(
mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE), "testFile"
).getAbsolutePath();
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
File hostFile = null;
mTestDevice.executeShellCommand(String.format("rm %s", mDeviceFilePath));
if (!createFileDevice(mDeviceFilePath, mPullFileSize)) {
CLog.e("Error creating file on device %s, skipping test",
mTestDevice.getSerialNumber());
return;
}
setupLogging();
try {
CLog.d("Pulling file");
startLogging();
long startTime = System.currentTimeMillis();
hostFile = mTestDevice.pullFile(mDeviceFilePath);
long elapsedTime = System.currentTimeMillis() - startTime;
CLog.d("pulling %dkB file from device %s took %d ms", mPullFileSize,
mTestDevice.getSerialNumber(), elapsedTime);
addMetric("pull_bw" + getKeySuffix(mTestDevice.isDeviceEncrypted()),
new Double(1000.0 * mPullFileSize / elapsedTime).toString());
} finally {
stopLogging(listener);
if (hostFile != null) {
hostFile.delete();
}
mTestDevice.executeShellCommand(String.format("rm %s", mDeviceFilePath));
}
}
}
/**
* CPU test for measuring CPU usage while playing back a video.
*/
private class VideoPlaybackTest extends CpuTest {
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
setupLogging();
try {
IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
"com.android.mediaframeworktest", ".MediaRecorderStressTestRunner",
mTestDevice.getIDevice());
runner.setClassName("com.android.mediaframeworktest.stress.MediaPlayerStressTest");
CLog.d("Running video playback instrumentation");
startLogging();
mTestDevice.runInstrumentationTests(runner);
} finally {
stopLogging(listener);
}
}
}
/**
* CPU test for measuring CPU usage while capturing a video.
*/
private class VideoCaptureTest extends CpuTest {
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
setupLogging();
try {
IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
"com.google.android.camera.tests",
"com.android.camera.stress.CameraStressTestRunner",
mTestDevice.getIDevice());
runner.setMethodName("com.android.camera.stress.VideoCapture",
"testBackVideoCapture");
runner.addInstrumentationArg("video_iterations", Integer.toString(1));
runner.addInstrumentationArg("video_duration", Integer.toString(3 * 60 * 1000));
CLog.d("Running video capture instrumentation");
startLogging();
mTestDevice.runInstrumentationTests(runner);
} finally {
stopLogging(listener);
mTestDevice.executeShellCommand("rm -r /sdcard/DCIM/*");
}
}
}
private List<CpuTest> mTestCases = null;
ITestDevice mTestDevice = null;
@Option(name="pull-file-size",
description="The size in kB of the file used in the pull test")
private int mPullFileSize = 256 * 1024;
@Option(name="push-file-size",
description="The size in kB of the file used in the push test")
private int mPushFileSize = 256 * 1024;
/**
* Adds the tests to be run as part of the CPU usage suite.
*/
private void setupTests() {
if (mTestCases != null) {
// assume already set up
return;
}
// Allocate enough space for all AbstractEncryptionCpuTest instances below
mTestCases = new ArrayList<CpuTest>(4);
CpuTest test = new PushTest();
test.mTestName = "PushTest";
test.mKey = "encryption_push_test";
mTestCases.add(test);
test = new PullTest();
test.mTestName = "PullTest";
test.mKey = "encryption_pull_test";
mTestCases.add(test);
test = new VideoPlaybackTest();
test.mTestName = "VideoPlaybackTest";
test.mKey = "encryption_video_playback_test";
mTestCases.add(test);
test = new VideoCaptureTest();
test.mTestName = "VideoCaptureTest";
test.mKey = "encryption_video_record_test";
mTestCases.add(test);
}
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
Assert.assertNotNull(mTestDevice);
setupTests();
for (CpuTest test : mTestCases) {
CLog.d("Running %s", test.mTestName);
listener.testRunStarted(test.mKey, 0);
test.run(listener);
CLog.d("About to report metrics to %s: %s", test.mKey, test.getMetrics());
listener.testRunEnded(0, test.getMetrics());
}
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
mTestDevice = device;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mTestDevice;
}
/**
* Creates a file on the host of a certain size at a specified path.
*
* @param filePath the path to create the file at.
* @param size the size of the file in kB.
* @throws IOException if there was an IOException.
*/
private void createFileHost(String filePath, int size) throws IOException {
CLog.d("Create %d kB file %s", size, filePath);
Process p = null;
try {
p = Runtime.getRuntime().exec(constructDdCommand(filePath, size));
p.waitFor();
if (0 != p.exitValue()) {
CLog.e("dd exited with error code %d", p.exitValue());
throw new IOException(String.format("File %s could not be created. dd exited " +
"with error code %d", filePath, p.exitValue()));
}
} catch (InterruptedException e) {
if (p != null) {
p.destroy();
}
CLog.e("dd interrupted");
throw new IOException(String.format("File %s could not be created. dd was interrupted",
filePath));
}
}
/**
* Creates a file on the device of a certain size at a specified path.
*
* @param filePath the path to create the file at.
* @param size the size of the file in kB.
* @return If the file was created.
* @throws DeviceNotAvailableException if the device was not available.
*/
private boolean createFileDevice(String filePath, int size) throws DeviceNotAvailableException {
CLog.d("Create %d kb file %s on device %s", size, filePath, mTestDevice.getSerialNumber());
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
int timeout = size * 2 * 1000; // Timeout is 2 seconds per kB.
mTestDevice.executeShellCommand(constructDdCommand(filePath, size),
receiver, timeout, 2);
return (receiver.getOutput().contains(
String.format("%d bytes transferred", size * BLOCK_SIZE)));
}
/**
* Constructs the dd command used to create the file, both on the host and on the device.
*
* @param filePath the path to create the file at.
* @param size the size of the file in kB.
* @return the dd command.
*/
private String constructDdCommand(String filePath, int size) {
return String.format("dd if=/dev/urandom of=%s bs=%d count=%d", filePath, BLOCK_SIZE, size);
}
/**
* Returns the key suffix based on the encrypted status of the device.
*
* @param isEncrypted if the device is encrypted.
* @return {@code _encrypted} if the device is encrypted or {@code _unencrypted} if it is not.
*/
private String getKeySuffix(boolean isEncrypted) {
return isEncrypted ? "_encrypted" : "_unencrypted";
}
}