From: Android (Google) Code Review Date: Tue, 2 Jun 2009 17:54:57 +0000 (-0700) Subject: am 3d44de40: Merge change 1918 into donut X-Git-Tag: android-x86-2.2~34 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=3ab8f4e41cef2ef2bed51963fd5f8534ad8861ff;hp=1ded028acecd41f7fd10fc63fb859ecdad0e1ff4;p=android-x86%2Fsystem-extras.git am 3d44de40: Merge change 1918 into donut Merge commit '3d44de40d56dd4da750c453814ab2bf6fc46273d' * commit '3d44de40d56dd4da750c453814ab2bf6fc46273d': Load test for the sdcard. --- diff --git a/tests/sdcard/Android.mk b/tests/sdcard/Android.mk new file mode 100644 index 00000000..d1e06f25 --- /dev/null +++ b/tests/sdcard/Android.mk @@ -0,0 +1,37 @@ +# Copyright (C) 2009 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. +# +# Build control file for Bionic's test programs +# define the BIONIC_TESTS environment variable to build the test programs +# + +ifdef SDCARD_TESTS + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES = \ + stopwatch.cpp \ + sysutil.cpp \ + sdcard_perf_test.cpp \ + testcase.cpp + +LOCAL_MODULE := sdcard_perf_test +LOCAL_MODULE_TAGS := eng tests +LOCAL_SHARED_LIBRARIES := libutils libhardware_legacy + +include $(BUILD_EXECUTABLE) + +endif # SDCARD_TESTS diff --git a/tests/sdcard/plot_sdcard.py b/tests/sdcard/plot_sdcard.py new file mode 100755 index 00000000..10ee00ba --- /dev/null +++ b/tests/sdcard/plot_sdcard.py @@ -0,0 +1,200 @@ +#!/usr/bin/python2.5 +# +# Copyright 2009 Google Inc. All Rights Reserved. + +"""plot_sdcard: A module to plot the results of an sdcard perf test. + +Requires Gnuplot python v 1.8 + +Typical usage: + +python +>>> import plot_sdcard as p +>>> (metadata, data) = p.parse('/tmp/data.txt') +>>> p.plotIterations(metadata, data) +>>> p.plotTimes(metadata, data) + +""" + +#TODO: provide a main so we can pipe the result from the run +#TODO: more comments... + +import Gnuplot +from numpy import * +import sys +import re +from itertools import izip + +class DataSet(object): + def __init__(self, line): + res = re.search('# StopWatch ([\w]+) total/cumulative duration ([0-9.]+)\. Samples: ([0-9]+)', line) + self.time = [] + self.data = [] + self.name = res.group(1) + self.duration = float(res.group(2)) + self.iteration = int(res.group(3)) + print "Name: %s Duration: %f Iterations: %d" % (self.name, self.duration, self.iteration) + self.summary = re.match('([a-z_]+)_total', self.name) + + def __repr__(self): + return str(zip(self.time, self.data)) + + def add(self, time, value): + self.time.append(time) + self.data.append(value) + + def rescaleTo(self, length): + factor = len(self.data) / length + + if factor > 1: + new_time = [] + new_data = [] + accum = 0.0 + idx = 1 + for t,d in izip(self.time, self.data): + accum += d + if idx % factor == 0: + new_time.append(t) + new_data.append(accum / factor) + accum = 0 + idx += 1 + self.time = new_time + self.data = new_data + + +class Metadata(object): + def __init__(self): + self.kernel = '' + self.command_line = '' + self.sched = '' + self.name = '' + self.fadvise = '' + self.iterations = 0 + self.duration = 0.0 + self.complete = False + + def parse(self, line): + if line.startswith('# Kernel:'): + self.kernel = re.search('Linux version ([0-9.]+-[0-9]+)', line).group(1) + elif line.startswith('# Command:'): + self.command_line = re.search('# Command: [/\w_]+ (.*)', line).group(1) + self.command_line = self.command_line.replace(' --', '-') + self.command_line = self.command_line.replace(' -d', '') + self.command_line = self.command_line.replace('--test=', '') + elif line.startswith('# Iterations'): + self.iterations = int(re.search('# Iterations: ([0-9]+)', line).group(1)) + elif line.startswith('# Fadvise'): + self.fadvise = int(re.search('# Fadvise: ([\w]+)', line).group(1)) + elif line.startswith("# Sched"): + self.sched = re.search('# Sched features: ([\w]+)', line).group(1) + self.complete = True + + def asTitle(self): + return "%s-duration:%f\\n-%s\\n%s" % (self.kernel, self.duration, self.command_line, self.sched) + + def updateWith(self, dataset): + self.duration = max(self.duration, dataset.duration) + self.name = dataset.name + + +def plotIterations(metadata, data): + gp = Gnuplot.Gnuplot(persist = 1) + gp('set data style lines') + gp.clear() + gp.xlabel("iterations") + gp.ylabel("duration in second") + gp.title(metadata.asTitle()) + styles = {} + line_style = 1 + + for dataset in data: + dataset.rescaleTo(metadata.iterations) + x = arange(len(dataset.data), dtype='int_') + if not dataset.name in styles: + styles[dataset.name] = line_style + line_style += 1 + d = Gnuplot.Data(x, dataset.data, + title=dataset.name, + with_='lines ls %d' % styles[dataset.name]) + else: # no need to repeat a title that exists already. + d = Gnuplot.Data(x, dataset.data, + with_='lines ls %d' % styles[dataset.name]) + + gp.replot(d) + gp.hardcopy('/tmp/%s-%s-%f.png' % (metadata.name, metadata.kernel, metadata.duration), terminal='png') + +def plotTimes(metadata, data): + gp = Gnuplot.Gnuplot(persist = 1) + gp('set data style impulses') + gp('set xtics 1') + gp.clear() + gp.xlabel("seconds") + gp.ylabel("duration in second") + gp.title(metadata.asTitle()) + styles = {} + line_style = 1 + + for dataset in data: + #dataset.rescaleTo(metadata.iterations) + x = array(dataset.time, dtype='float_') + if not dataset.name in styles: + styles[dataset.name] = line_style + line_style += 1 + d = Gnuplot.Data(x, dataset.data, + title=dataset.name, + with_='impulses ls %d' % styles[dataset.name]) + else: # no need to repeat a title that exists already. + d = Gnuplot.Data(x, dataset.data, + with_='impulses ls %d' % styles[dataset.name]) + + gp.replot(d) + gp.hardcopy('/tmp/%s-%s-%f.png' % (metadata.name, metadata.kernel, metadata.duration), terminal='png') + + +def parse(filename): + f = open(filename, 'r') + + metadata = Metadata() + data = [] # array of dataset + dataset = None + + for num, line in enumerate(f): + try: + line = line.strip() + if not line: continue + + if not metadata.complete: + metadata.parse(line) + continue + + if re.match('[a-z_]', line): + continue + + if line.startswith('# StopWatch'): # Start of a new dataset + if dataset: + if dataset.summary: + metadata.updateWith(dataset) + else: + data.append(dataset) + + dataset = DataSet(line) + continue + + if line.startswith('#'): + continue + + # must be data at this stage + try: + (time, value) = line.split(None, 1) + except ValueError: + print "skipping line %d: %s" % (num, line) + continue + + if dataset and not dataset.summary: + dataset.add(float(time), float(value)) + + except Exception, e: + print "Error parsing line %d" % num, sys.exc_info()[0] + raise + data.append(dataset) + return (metadata, data) diff --git a/tests/sdcard/sdcard_perf_test.cpp b/tests/sdcard/sdcard_perf_test.cpp new file mode 100644 index 00000000..28069b9a --- /dev/null +++ b/tests/sdcard/sdcard_perf_test.cpp @@ -0,0 +1,607 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stopwatch.h" +#include "sysutil.h" +#include "testcase.h" + +// Stress test for the sdcard. Use this to generate some load on the +// sdcard and collect performance statistics. The ouput is either a +// human readable report or the raw timing samples that can be +// processed using another tool. +// +// Needs debugfs: +// adb root; +// adb shell mount -t debugfs none /sys/kernel/debug +// +// The following tests are defined (value of the --test flag): +// write: Open a file write some random data and close. +// read: Open a file read it and close. +// read_write: Combine readers and writers. +// open_create: Open|create an non existing file. +// +// For each run you can control how many processes will run the test in +// parallel to simulate a real load (--procnb flag) +// +// For each process, the test selected will be executed many time to +// get a meaningful average/min/max (--iterations flag) +// +// Use --dump to also get the raw data. +// +// For read/write tests, size is the number of Kbytes to use. +// +// To build: mmm system/extras/tests/sdcard/Android.mk SDCARD_TESTS=1 +// +// Examples: +// adb shell /system/bin/sdcard_perf_test --test=read --size=1000 --chunk-size=100 --procnb=1 --iterations=10 --dump > /tmp/data.txt +// adb shell /system/bin/sdcard_perf_test --test=write --size=1000 --chunk-size=100 --procnb=1 --iterations=100 --dump > /tmp/data.txt +// +// To watch the memory: cat /proc/meminfo +// If the phone crashes, look at /proc/last_kmsg on reboot. +// +// TODO: It would be cool if we could play with various fadvise() +// strategies in here to see how tweaking read-ahead changes things. +// + +extern char *optarg; +extern int optind, opterr, optopt; + +// TODO: No clue where fadvise is. Disabled for now. +#define FADVISE(fd, off, len, advice) (void)0 + +#ifndef min +#define min(a,b) (((a)>(b))?(b):(a)) +#endif + +namespace { +using android::kernelVersion; +using android::kMinKernelVersionBufferSize; +using android::schedFeatures; +using android::kMinSchedFeaturesBufferSize; +using android_test::StopWatch; +using android::writePidAndWaitForReply; +using android::waitForChildrenAndSignal; +using android::waitForChildrenOrExit; +using android_test::TestCase; + +const char *kAppName = "sdcard_perf_test"; +const char *kTestDir = "/sdcard/perf"; +const bool kVerbose = false; + +// Used by getopt to parse the command line. +struct option long_options[] = { + {"size", required_argument, 0, 's'}, + {"chunk-size", required_argument, 0, 'S'}, + {"iterations", required_argument, 0, 'i'}, + {"procnb", required_argument, 0, 'p'}, + {"test", required_argument, 0, 't'}, + {"dump", no_argument, 0, 'd'}, + {"cpu-scaling", no_argument, 0, 'c'}, + {"sync", required_argument, 0, 'f'}, + {"truncate", no_argument, 0, 'e'}, + {"no-new-fair-sleepers", no_argument, 0, 'z'}, + {"no-normalized-sleepers", no_argument, 0, 'Z'}, + {"fadvise", required_argument, 0, 'a'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +void usage() +{ + printf("sdcard_perf_test --test=write|read|read_write|open_create [options]\n\n" + " -t --test: Select the test.\n" + " -s --size: Size in kbytes of the data.\n" + " -S --chunk-size: Size of a chunk. Default to size ie 1 chunk.\n" + " Data will be written/read using that chunk size.\n" + " -i --iterations: Number of time a process should carry its task.\n" + " -p --procnb: Number of processes to use.\n" + " -d --dump: Print the raw timing on stdout.\n" + " -c --cpu-scaling: Enable cpu scaling.\n" + " -s --sync: fsync|sync Use fsync or sync in write test. Default: no sync call.\n" + " -e --truncate: Truncate to size the file on creation.\n" + " -z --no-new-fair-sleepers: Turn them off. You need to mount debugfs.\n" + " -Z --no-normalized-sleepers: Turn them off. You need to mount debugfs.\n" + " -a --fadvise: Specify an fadvise policy (not supported).\n" + ); +} + +// Print command line, pid, kernel version, OOM adj and scheduler. +void printHeader(int argc, char **argv, const TestCase& testCase) +{ + printf("# Command: "); + for (int i = 0; i < argc; ++i) + { + printf("%s ", argv[i]); + } + printf("\n"); + + printf("# Pid: %d\n", getpid()); + + { + char buffer[kMinKernelVersionBufferSize] = {0, }; + if (kernelVersion(buffer, sizeof(buffer)) > 0) + { + printf("# Kernel: %s", buffer); + } + } + + // Earlier on, running this test was crashing the phone. It turned + // out that it was using too much memory but its oom_adj value was + // -17 which means disabled. -16 is the system_server and 0 is + // typically what applications run at. The issue is that adb runs + // at -17 and so is this test. We force oom_adj to 0 unless the + // oom_adj option has been used. + // TODO: We talked about adding an option to control oom_adj, not + // sure if we still need that. + int oomAdj = android::pidOutOfMemoryAdj(); + + printf("# Oom_adj: %d ", oomAdj); + if (oomAdj < 0) + { + android::setPidOutOfMemoryAdj(0); + printf("adjuted to %d", android::pidOutOfMemoryAdj()); + } + printf("\n"); + + { + char buffer[kMinSchedFeaturesBufferSize] = {0, }; + if (schedFeatures(buffer, sizeof(buffer)) > 0) + { + printf("# Sched features: %s", buffer); + } + } + printf("# Fadvise: %s\n", testCase.fadviseAsStr()); +} + +// Remove all the files under kTestDir and clear the caches. +void cleanup() { + android::resetDirectory(kTestDir); + android::syncAndDropCaches(); // don't pollute runs. +} + +// @param argc, argv have a wild guess. +// @param[out] testCase Structure built from the cmd line args. +void parseCmdLine(int argc, char **argv, TestCase *testCase)\ +{ + int c; + + while(true) + { + // getopt_long stores the option index here. + int option_index = 0; + + c = getopt_long (argc, argv, + "hS:s:i:p:t:dcf:ezZa:", + long_options, + &option_index); + // Detect the end of the options. + if (c == -1) break; + + switch (c) + { + case 's': + testCase->setDataSize(atoi(optarg) * 1024); + break; + case 'S': + testCase->setChunkSize(atoi(optarg) * 1024); + break; + case 'i': + testCase->setIter(atoi(optarg)); + printf("# Iterations: %d\n", testCase->iter()); + break; + case 'p': + testCase->setNproc(atoi(optarg)); + printf("# Proc nb: %d\n", testCase->nproc()); + break; + case 't': + testCase->setTypeFromName(optarg); + printf("# Test name %s\n", testCase->name()); + break; + case 'd': + testCase->setDump(); + break; + case 'c': + printf("# Cpu scaling is on\n"); + testCase->setCpuScaling(); + break; + case 'f': + if (strcmp("sync", optarg) == 0) { + testCase->setSync(TestCase::SYNC); + } else if (strcmp("fsync", optarg) == 0) { + testCase->setSync(TestCase::FSYNC); + } + break; + case 'e': // e for empty + printf("# Will truncate to size\n"); + testCase->setTruncateToSize(); + break; + case 'z': // no new fair sleepers + testCase->setNewFairSleepers(false); + break; + case 'Z': // no normalized sleepers + testCase->setNormalizedSleepers(false); + break; + case 'a': // fadvise + testCase->setFadvise(optarg); + break; + case 'h': + usage(); + exit(0); + default: + fprintf(stderr, "Unknown option %s\n", optarg); + exit(EXIT_FAILURE); + } + } +} + +// ---------------------------------------------------------------------- +// READ TEST + +// Read a file. We use a new file each time to avoid any caching +// effect that would happen if we were reading the same file each +// time. +// @param chunk buffer large enough where the chunk read are written. +// @param idx iteration number. +// @param testCase has all the timers and paramters to run the test. + +bool readData(char *const chunk, const int idx, TestCase *testCase) +{ + char filename[80] = {'\0',}; + + sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid()); + + testCase->openTimer()->start(); + int fd = open(filename, O_RDONLY); + testCase->openTimer()->stop(); + + if (fd < 0) + { + fprintf(stderr, "Open read only failed."); + return false; + } + FADVISE(fd, 0, 0, testCase->fadvise()); + + size_t left = testCase->dataSize(); + pid_t *pid = (pid_t*)chunk; + while (left > 0) + { + char *dest = chunk; + size_t chunk_size = testCase->chunkSize(); + + if (chunk_size > left) + { + chunk_size = left; + left = 0; + } + else + { + left -= chunk_size; + } + + testCase->readTimer()->start(); + while (chunk_size > 0) + { + ssize_t s = read(fd, dest, chunk_size); + if (s < 0) + { + fprintf(stderr, "Failed to read.\n"); + close(fd); + return false; + } + chunk_size -= s; + dest += s; + } + testCase->readTimer()->stop(); + } + close(fd); + if (testCase->pid() != *pid) + { + fprintf(stderr, "Wrong pid found @ read block %x != %x\n", testCase->pid(), *pid); + return false; + } + else + { + return true; + } +} + + +bool testRead(TestCase *testCase) { + // Setup the testcase by writting some dummy files. + const size_t size = testCase->dataSize(); + size_t chunk_size = testCase->chunkSize(); + char *const chunk = new char[chunk_size]; + + memset(chunk, 0xaa, chunk_size); + *((pid_t *)chunk) = testCase->pid(); // write our pid at the beginning of each chunk + + size_t iter = testCase->iter(); + + // since readers are much faster we increase the number of + // iteration to last longer and have concurrent read/write + // thoughout the whole test. + if (testCase->type() == TestCase::READ_WRITE) + { + iter *= TestCase::kReadWriteFactor; + } + + for (size_t i = 0; i < iter; ++i) + { + char filename[80] = {'\0',}; + + sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid()); + int fd = open(filename, O_RDWR | O_CREAT); + + size_t left = size; + while (left > 0) + { + if (chunk_size > left) + { + chunk_size = left; + } + ssize_t written = write(fd, chunk, chunk_size); + if (written < 0) + { + fprintf(stderr, "Write failed %d %s.", errno, strerror(errno)); + return false; + } + left -= written; + } + close(fd); + } + if (kVerbose) printf("Child %d all chunk written\n", testCase->pid()); + + android::syncAndDropCaches(); + testCase->signalParentAndWait(); + + // Start the read test. + testCase->testTimer()->start(); + for (size_t i = 0; i < iter; ++i) + { + if (!readData(chunk, i, testCase)) + { + return false; + } + } + testCase->testTimer()->stop(); + + delete [] chunk; + return true; +} + +// ---------------------------------------------------------------------- +// WRITE TEST + +bool writeData(const char *const chunk, const int idx, TestCase *testCase) { + char filename[80] = {'\0',}; + + sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid()); + testCase->openTimer()->start(); + int fd = open(filename, O_RDWR | O_CREAT); // no O_TRUNC, see header comment + testCase->openTimer()->stop(); + + if (fd < 0) + { + fprintf(stderr, "Open write failed."); + return false; + } + FADVISE(fd, 0, 0, testCase->fadvise()); + + if (testCase->truncateToSize()) + { + testCase->truncateTimer()->start(); + ftruncate(fd, testCase->dataSize()); + testCase->truncateTimer()->stop(); + } + + size_t left = testCase->dataSize(); + while (left > 0) + { + const char *dest = chunk; + size_t chunk_size = testCase->chunkSize(); + + if (chunk_size > left) + { + chunk_size = left; + left = 0; + } + else + { + left -= chunk_size; + } + + + testCase->writeTimer()->start(); + while (chunk_size > 0) + { + ssize_t s = write(fd, dest, chunk_size); + if (s < 0) + { + fprintf(stderr, "Failed to write.\n"); + close(fd); + return false; + } + chunk_size -= s; + dest += s; + } + testCase->writeTimer()->stop(); + } + + if (TestCase::FSYNC == testCase->sync()) + { + testCase->syncTimer()->start(); + fsync(fd); + testCase->syncTimer()->stop(); + } + else if (TestCase::SYNC == testCase->sync()) + { + testCase->syncTimer()->start(); + sync(); + testCase->syncTimer()->stop(); + } + close(fd); + return true; +} + +bool testWrite(TestCase *testCase) +{ + const size_t size = testCase->dataSize(); + size_t chunk_size = testCase->chunkSize(); + char *data = new char[chunk_size]; + + memset(data, 0xaa, chunk_size); + // TODO: write the pid at the beginning like in the write + // test. Have a mode where we check the write was correct. + testCase->signalParentAndWait(); + + testCase->testTimer()->start(); + for (size_t i = 0; i < testCase->iter(); ++i) + { + if (!writeData(data, i, testCase)) + { + return false; + } + } + testCase->testTimer()->stop(); + delete [] data; + return true; +} + + +// ---------------------------------------------------------------------- +// READ WRITE + +// Mix of read and write test. Even PID run the write test. Odd PID +// the read test. Not fool proof but work most of the time. +bool testReadWrite(TestCase *testCase) +{ + if (getpid() & 0x1) { + return testRead(testCase); + } else { + return testWrite(testCase); + } +} + +// ---------------------------------------------------------------------- +// OPEN CREATE TEST + +bool testOpenCreate(TestCase *testCase) +{ + char filename[80] = {'\0',}; + + testCase->signalParentAndWait(); + testCase->testTimer()->start(); + + for (size_t i = 0; i < testCase->iter(); ++i) + { + sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid()); + + int fd = open(filename, O_RDWR | O_CREAT); + FADVISE(fd, 0, 0, testCase->fadvise()); + + if (testCase->truncateToSize()) + { + ftruncate(fd, testCase->dataSize()); + } + if (fd < 0) + { + return false; + } + close(fd); + } + testCase->testTimer()->stop(); + return true; +} + +} // anonymous namespace + +int main(int argc, char **argv) +{ + android_test::TestCase testCase(kAppName); + + cleanup(); + + parseCmdLine(argc, argv, &testCase); + printHeader(argc, argv, testCase); + + printf("# File size %d kbytes\n", testCase.dataSize() / 1024); + printf("# Chunk size %d kbytes\n", testCase.chunkSize() / 1024); + printf("# Sync: %s\n", testCase.syncAsStr()); + + if (!testCase.cpuScaling()) + { + android::disableCpuScaling(); + } + + switch(testCase.type()) { + case TestCase::WRITE: + testCase.mTestBody = testWrite; + break; + case TestCase::READ: + testCase.mTestBody = testRead; + break; + case TestCase::OPEN_CREATE: + testCase.mTestBody = testOpenCreate; + break; + case TestCase::READ_WRITE: + testCase.mTestBody = testReadWrite; + break; + default: + fprintf(stderr, "Unknown test type %s", testCase.name()); + exit(EXIT_FAILURE); + } + + testCase.createTimers(); + + bool ok; + + ok = testCase.runTest(); + + cleanup(); + if (!ok) + { + printf("error %d %s", errno, strerror(errno)); + exit(EXIT_FAILURE); + } + else + { + exit(EXIT_SUCCESS); + } +} diff --git a/tests/sdcard/stopwatch.cpp b/tests/sdcard/stopwatch.cpp new file mode 100644 index 00000000..12fe8f1f --- /dev/null +++ b/tests/sdcard/stopwatch.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#include +#include +#include +#include "stopwatch.h" + +#define SNPRINTF_OR_RETURN(str, size, format, ...) { \ + int len = snprintf((str), (size), (format), ## __VA_ARGS__); \ + if (len < 0) return; \ + if (len > static_cast(size)) { \ + fprintf(stderr, "Not enough space\n"); \ + return; \ + } else { \ + (size) -= len; (str) += len; \ + } \ + } + +namespace { +const bool kVerbose = false; +bool printRaw = false; +} + +namespace android_test { + +StopWatch::StopWatch(const char *name, size_t capacity) + : mName(strdup(name)), mNum(0), mData(NULL), mDataLen(0), mCapacity(capacity * 2), + mSizeKbytes(0), mAlreadyPrinted(false), mPrintRaw(false), + mDuration(0.0), + mMinDuration(0.0), mMinIdx(0), + mMaxDuration(0.0), mMaxIdx(0), + mDeltas(NULL), mUsed(false) +{ + mStart.tv_sec = 0; + mStart.tv_nsec = 0; + mData = (Measurement *) malloc(mCapacity * sizeof(Measurement)); +} + +StopWatch::~StopWatch() +{ + if (mUsed && !mAlreadyPrinted) + { + fprintf(stderr, "Discarding data for %s\n", mName); + } + free(mData); + free(mName); + delete [] mDeltas; +} + +void StopWatch::start() +{ + checkCapacity(); + clock_gettime(CLOCK_MONOTONIC, &mData[mDataLen].mTime); + mData[mDataLen].mIsStart = true; + if (!mUsed) + { + mStart = mData[mDataLen].mTime; // mDataLen should be 0 + mUsed = true; + } + ++mNum; + ++mDataLen; +} + +void StopWatch::stop() +{ + checkCapacity(); + clock_gettime(CLOCK_MONOTONIC, &mData[mDataLen].mTime); + mData[mDataLen].mIsStart = false; + ++mDataLen; +} + +void StopWatch::setPrintRawMode(bool raw) +{ + printRaw = raw; +} + + +void StopWatch::sprint(char **str, size_t *size) +{ + if (kVerbose) fprintf(stderr, "printing\n"); + mAlreadyPrinted = true; + if (0 == mDataLen) + { + return; + } + if (mDataLen > 0 && mData[mDataLen - 1].mIsStart) + { + stop(); + } + if (kVerbose) SNPRINTF_OR_RETURN(*str, *size, "# Got %d samples for %s\n", mDataLen, mName); + processSamples(); + + SNPRINTF_OR_RETURN(*str, *size, "# StopWatch %s total/cumulative duration %f. Samples: %d\n", + mName, mDuration, mNum); + printThroughput(str, size); + printAverageMinMax(str, size); + + if (printRaw) + { + // print comment header and summary values. + + SNPRINTF_OR_RETURN(*str, *size, "# Name Iterations Duration Min MinIdx Max MaxIdx SizeMbytes\n"); + SNPRINTF_OR_RETURN(*str, *size, "%s %d %f %f %d %f %d %d\n", mName, mNum, mDuration, + mMinDuration, mMinIdx, mMaxDuration, mMaxIdx, mSizeKbytes); + // print each duration sample + for (size_t i = 0; i < mDataLen / 2; ++i) + { + long second = mData[i * 2].mTime.tv_sec - mStart.tv_sec; + long nano = mData[i * 2].mTime.tv_nsec - mStart.tv_nsec; + + SNPRINTF_OR_RETURN(*str, *size, "%f %f\n", double(second) + double(nano) / 1.0e9, mDeltas[i]); + } + } + +} + +// Normally we should have enough capacity but if we have to +// reallocate the measurement buffer (e.g start and stop called more +// than once in an iteration) we let the user know. She should provide +// a capacity when building the StopWatch. +void StopWatch::checkCapacity() +{ + if (mDataLen >= mCapacity) + { + mCapacity *= 2; + fprintf(stderr, "# Increased capacity to %d for %s. Measurement affected.\n", + mCapacity, mName); + mData = (Measurement *)realloc(mData, mCapacity * sizeof(Measurement)); + } +} + + +// Go over all the samples and compute the diffs between a start and +// stop pair. The diff is accumulated in mDuration and inserted in +// mDeltas. +// The min and max values for a diff are also tracked. +void StopWatch::processSamples() +{ + if (kVerbose) fprintf(stderr, "processing samples\n"); + mDeltas= new double[mDataLen / 2]; + + for (size_t i = 0; i < mDataLen; i += 2) // even: start odd: stop + { + long second = mData[i + 1].mTime.tv_sec - mData[i].mTime.tv_sec; + long nano = mData[i + 1].mTime.tv_nsec - mData[i].mTime.tv_nsec; + + mDeltas[i / 2] = double(second) + double(nano) / 1.0e9; + } + + for (size_t i = 0; i < mDataLen / 2; ++i) + { + if (0 == i) + { + mMinDuration = mMaxDuration = mDeltas[i]; + } + else + { + if (mMaxDuration < mDeltas[i]) + { + mMaxDuration = mDeltas[i]; + mMaxIdx = i; + } + if (mMinDuration > mDeltas[i]) + { + mMinDuration = mDeltas[i]; + mMinIdx = i; + } + } + mDuration += mDeltas[i]; + } +} + +double StopWatch::timespecToDouble(const struct timespec& time) +{ + double val = double(time.tv_nsec) / 1.0e9 + double(time.tv_sec); + return val < 0.0 ? -val : val; // sometimes 0.00 is -0.00 +} + + +// If we have only 2 values, don't bother printing anything. +void StopWatch::printAverageMinMax(char **str, size_t *size) +{ + if (mDataLen > 2) // if there is only one sample, avg, min, max are trivial. + { + SNPRINTF_OR_RETURN(*str, *size, "# Average %s duration %f s/op\n", mName, mDuration / mNum); + SNPRINTF_OR_RETURN(*str, *size, "# Min %s duration %f [%d]\n", mName, mMinDuration, mMinIdx); + SNPRINTF_OR_RETURN(*str, *size, "# Max %s duration %f [%d]\n", mName, mMaxDuration, mMaxIdx); + } +} + +void StopWatch::printThroughput(char **str, size_t *size) +{ + if (0 != mSizeKbytes) + { + SNPRINTF_OR_RETURN(*str, *size, "# Size: %d Kbytes Total: %d\n", mSizeKbytes, mNum); + SNPRINTF_OR_RETURN(*str, *size, "# Speed %f Kbyte/s\n", double(mSizeKbytes) * mNum / mDuration); + } +} +} // namespace android_test diff --git a/tests/sdcard/stopwatch.h b/tests/sdcard/stopwatch.h new file mode 100644 index 00000000..4d1a7949 --- /dev/null +++ b/tests/sdcard/stopwatch.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ANDROID_NATIVETEST_SYSTEM_EXTRAS_TESTS_SDCARD_STOPWATCH_H_ +#define ANDROID_NATIVETEST_SYSTEM_EXTRAS_TESTS_SDCARD_STOPWATCH_H_ + +#include +#include +#include + +namespace android_test { + +// StopWatch class to collect execution statistics. +// +// Once the watch has been created, start and stop can be called to +// capture an event duration. +// +// On completion, use 'sprint' to retrieve the data. +// +// If StopWatch::setPrintRawMode(true) has been called, the raw +// samples also are printed. +// The print method is thread safe to avoid mixing the result of +// watches on different threads. For processes, use different files +// that you concat after the run. +// +// If the time measure is associated with some volume of data, use +// setMbytes, the print method will compute the average throughput +// based on that value. +// +// To capture the time accurately and since the runs are not too long, +// we collect the raw start and stop time in an array that get +// processed once all the measurements have been done. +// +// Typical Usage: +// ============== +// +// StopWatch watch("my name", 20); +// +// for (int i = 0; i < 20; ++i) { +// watch.start(); +// doMyStuff(); +// watch.stop(); +// } +// char buffer[4096]; +// char *str = buffer; +// size_t size = sizeof(buffer); +// watch.sprint(&str, &size); +// + +class StopWatch { + public: + // Time of the snapshot and its nature (start of the interval or end of it). + struct Measurement { + struct timespec mTime; + bool mIsStart; + }; + static const size_t kUseDefaultCapacity = 20; + + // Create a stop watch. Default capacity == 2 * interval_nb + // @param name To be used when the results are displayed. No + // spaces, use _ instead. + // @param capacity Hint about the number of sampless that will be + // measured (1 sample == 1 start + 1 stop). Used + // to size the internal storage, when the capacity + // is reached, it is doubled. + StopWatch(const char *name, size_t capacity = kUseDefaultCapacity); + ~StopWatch(); + + // A StopWatch instance measures time intervals. Use setDataSize + // if some volume of data is processed during these intervals, to + // get the average throughput (in kbytes/s) printed. + void setDataSize(size_t size_in_bytes) { mSizeKbytes = size_in_bytes / 1000; } + + // Starts and stops the timer. The time between the 2 calls is an + // interval whose duration will be reported in sprint. + void start(); + void stop(); + + // Print a summary of the measurement and optionaly the raw data. + // The summary is commented out using a leading '#'. The raw data + // is a pair (time, duration). The 1st sample is always at time + // '0.0'. + // @param str[inout] On entry points to the begining of a buffer + // where to write the data. On exit points pass the last byte + // written. + // @param size[inout] On entry points to the size of the buffer + // pointed by *str. On exit *size is the amount of free space left + // in the buffer. If there was not enough space the data is truncated + // and a warning is printed. + void sprint(char **str, size_t *size); + + // @return true if at least one interval was timed. + bool used() const { return mUsed; } + + // Affects all the timers. Instructs all the timers to print the + // raw data as well as the summary. + static void setPrintRawMode(bool printRaw); + + private: + void checkCapacity(); + double timespecToDouble(const struct timespec& time); + void printAverageMinMax(char **str, size_t *size); + void printThroughput(char **str, size_t *size); + // Allocate mDeltas and fill it in. Search for the min and max. + void processSamples(); + + char *const mName; // Name of the test. + struct timespec mStart; + size_t mNum; // # of intervals == # of start() calls. + struct Measurement *mData; + size_t mDataLen; + size_t mCapacity; + int mSizeKbytes; + + bool mAlreadyPrinted; + bool mPrintRaw; + + double mDuration; + double mMinDuration; + size_t mMinIdx; + double mMaxDuration; + size_t mMaxIdx; + double *mDeltas; + + bool mUsed; +}; + +} // namespace android_test + +#endif // ANDROID_NATIVETEST_SYSTEM_EXTRAS_TESTS_SDCARD_STOPWATCH_H_ diff --git a/tests/sdcard/sysutil.cpp b/tests/sdcard/sysutil.cpp new file mode 100644 index 00000000..0182590a --- /dev/null +++ b/tests/sdcard/sysutil.cpp @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sysutil.h" + +namespace { +const int kError = -1; +// Max number of retries on EAGAIN and EINTR. Totally arbitrary. +const int kMaxAttempts = 8; + +// How long to wait after a cache purge. A few seconds (arbitrary). +const int kCachePurgeSleepDuration = 2; // seconds + +const bool kSilentIfMissing = false; + +const char *kKernelVersion = "/proc/version"; +const char *kScalingGovernorFormat = "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor"; +const char *kDropCaches = "/proc/sys/vm/drop_caches"; +const char *kSchedFeatures = "/sys/kernel/debug/sched_features"; +const char *kNewFairSleepers = "NEW_FAIR_SLEEPERS"; +const char *kNoNewFairSleepers = "NO_NEW_FAIR_SLEEPERS"; +const char *kNormalizedSleepers = "NORMALIZED_SLEEPER"; // no 's' at the end +const char *kNoNormalizedSleepers = "NO_NORMALIZED_SLEEPER"; + +const char *kDebugfsWarningMsg = "Did you 'adb root; adb shell mount -t debugfs none /sys/kernel/debug' ?"; + +// TODO: Surely these file utility functions must exist already. A +// quick grep did not turn up anything. Look harder later. + +void printErrno(const char *msg, const char *filename) +{ + fprintf(stderr, "# %s %s %d %s\n", msg, filename, errno, strerror(errno)); +} + +// Read a C-string from a file. If the buffer is too short, an error +// message will be printed on stderr. +// @param filename Of the file to read. +// @param start Buffer where the data should be written to. +// @param size The size of the buffer pointed by str. Must be >= 1. +// @return The number of characters read (not including the trailing'\0' used +// to end the string) or -1 if there was an error. +int readStringFromFile(const char *filename, char *const start, size_t size, bool must_exist=true) +{ + if (NULL == start || size == 0) + { + return 0; + } + char *end = start; + int fd = open(filename, O_RDONLY); + + if (fd < 0) + { + if (ENOENT != errno || must_exist) + { + printErrno("Failed to open", filename); + } + return kError; + } + + bool eof = false; + bool error = false; + int attempts = 0; + + --size; // reserve space for trailing '\0' + + while (size > 0 && !error && !eof && attempts < kMaxAttempts) + { + ssize_t s; + + s = read(fd, end, size); + + if (s < 0) + { + error = EAGAIN != errno && EINTR != errno; + if (error) + { + printErrno("Failed to read", filename); + } + } + else if (0 == s) + { + eof = true; + } + else + { + end += s; + size -= s; + } + ++attempts; + } + + close(fd); + + if (error) + { + return kError; + } + else + { + *end = '\0'; + if (!eof) + { + fprintf(stderr, "Buffer too small for %s\n", filename); + } + return end - start; + } +} + +// Write a C string ('\0' terminated) to a file. +// +int writeStringToFile(const char *filename, const char *start, bool must_exist=true) +{ + int fd = open(filename, O_WRONLY); + + + if (fd < 0) + { + if (ENOENT != errno || must_exist) + { + printErrno("Failed to open", filename); + } + return kError; + } + + const size_t len = strlen(start); + size_t size = len; + bool error = false; + int attempts = 0; + + while (size > 0 && !error && attempts < kMaxAttempts) + { + ssize_t s = write(fd, start, size); + + if (s < 0) + { + error = EAGAIN != errno && EINTR != errno; + if (error) + { + printErrno("Failed to write", filename); + } + } + else + { + start += s; + size -= s; + } + ++attempts; + } + close(fd); + + if (error) + { + return kError; + } + else + { + if (size > 0) + { + fprintf(stderr, "Partial write to %s (%d out of %d)\n", + filename, size, len); + } + return len - size; + } +} + +int writeIntToFile(const char *filename, long value) +{ + char buffer[16] = {0,}; + sprintf(buffer, "%ld", value); + return writeStringToFile(filename, buffer); +} + +// @return a message describing the reason why the child exited. The +// message is in a shared buffer, not thread safe, erased by +// subsequent calls. +const char *reasonChildExited(int status) +{ + static char buffer[80]; + + if (WIFEXITED(status)) + { + snprintf(buffer, sizeof(buffer), "ok (%d)", WEXITSTATUS(status)); + } + else if (WIFSIGNALED(status)) + { + snprintf(buffer, sizeof(buffer), "signaled (%d %s)", WTERMSIG(status), strsignal(WTERMSIG(status))); + } + else + { + snprintf(buffer, sizeof(buffer), "stopped?"); + } + return buffer; +} + + +} // anonymous namespace + +namespace android { + +int kernelVersion(char *str, size_t size) +{ + return readStringFromFile(kKernelVersion, str, size); +} + +int pidOutOfMemoryAdj() +{ + char filename[FILENAME_MAX]; + + snprintf(filename, sizeof(filename), "/proc/%d/oom_adj", getpid()); + + char value[16]; + if (readStringFromFile(filename, value, sizeof(value)) == -1) + { + return -127; + } + else + { + return atoi(value); + } +} + +void setPidOutOfMemoryAdj(int level) +{ + char filename[FILENAME_MAX]; + + snprintf(filename, sizeof(filename), "/proc/%d/oom_adj", getpid()); + writeIntToFile(filename, level); +} + +void disableCpuScaling() +{ + for (int cpu = 0; cpu < 16; ++cpu) // 16 cores mobile phones, abestos pockets recommended. + { + char governor[FILENAME_MAX]; + sprintf(governor, kScalingGovernorFormat, cpu); + + if (writeStringToFile(governor, "performance", kSilentIfMissing) < 0) + { + if (cpu > 0 && errno == ENOENT) + { + break; // cpu1 or above not found, ok since we have cpu0. + } + fprintf(stderr, "Failed to write to scaling governor file for cpu %d: %d %s", + cpu, errno, strerror(errno)); + break; + } + } +} + +int schedFeatures(char *str, size_t size) +{ + return readStringFromFile(kSchedFeatures, str, size); +} + +bool newFairSleepers() +{ + char value[256] = {0,}; + + if (readStringFromFile(kSchedFeatures, value, sizeof(value)) == -1) + { + printErrno(kDebugfsWarningMsg, kSchedFeatures); + return false; + } + return strstr(value, "NO_NEW_FAIR_SLEEPERS") == NULL; +} + +void setNewFairSleepers(bool on) +{ + int retcode; + + if (on) + { + retcode = writeStringToFile(kSchedFeatures, kNewFairSleepers); + } + else + { + retcode = writeStringToFile(kSchedFeatures, kNoNewFairSleepers); + } + if (retcode < 0) + { + fprintf(stderr, "# %s\n", kDebugfsWarningMsg); + } +} + +bool normalizedSleepers() +{ + char value[256] = {0,}; + + if (readStringFromFile(kSchedFeatures, value, sizeof(value)) == -1) + { + printErrno(kDebugfsWarningMsg, kSchedFeatures); + return false; + } + return strstr(value, "NO_NEW_FAIR_SLEEPERS") == NULL; +} + +void setNormalizedSleepers(bool on) +{ + int retcode; + + if (on) + { + retcode = writeStringToFile(kSchedFeatures, kNormalizedSleepers); + } + else + { + retcode = writeStringToFile(kSchedFeatures, kNoNormalizedSleepers); + } + if (retcode < 0) + { + fprintf(stderr, "# %s\n", kDebugfsWarningMsg); + } +} + +pid_t forkOrExit() +{ + pid_t childpid = fork(); + + if (-1 == childpid) + { + fprintf(stderr, "Fork failed: %d %s", errno, strerror(errno)); + exit(EXIT_FAILURE); + } + return childpid; +} + +void waitForChildrenOrExit(int num) +{ + while (num > 0) + { + int status; + pid_t pid = wait(&status); + if (-1 == pid) + { + fprintf(stderr, "Wait failed\n"); + } + else + { + if (!WIFEXITED(status)) + { + fprintf(stderr, "Child pid %d did not exit cleanly %s\n", + pid, reasonChildExited(status)); + exit(EXIT_FAILURE); + } + } + --num; + } +} + +// Sync and cache cleaning functions. In the old hpux days I was told +// to always call *sync twice. The same advice seems to be still true +// today so *sync is called twice. +// Also we wait 'a little' to give a chance to background threads to +// purge their caches. +void syncAndDropCaches(int code) +{ + sync(); + sync(); + writeIntToFile(kDropCaches, code); + sleep(kCachePurgeSleepDuration); +} + + +void fsyncAndDropCaches(int fd, int code) +{ + fsync(fd); + fsync(fd); + writeIntToFile(kDropCaches, code); + sleep(kCachePurgeSleepDuration); +} + + +void resetDirectory(const char *directory) +{ + DIR *dir = opendir(directory); + + if (NULL != dir) + { + struct dirent *entry; + char name_buffer[PATH_MAX]; + + while((entry = readdir(dir))) + { + if (0 == strcmp(entry->d_name, ".") + || 0 == strcmp(entry->d_name, "..") + || 0 == strcmp(entry->d_name, "lost+found")) + { + continue; + } + strcpy(name_buffer, directory); + strcat(name_buffer, "/"); + strcat(name_buffer, entry->d_name); + unlink(name_buffer); + } + closedir(dir); + } else { + mkdir(directory, S_IRWXU); + } +} + + +// IPC +bool writePidAndWaitForReply(int writefd, int readfd) +{ + if (writefd > readfd) + { + fprintf(stderr, "Called with args in wrong order!!\n"); + return false; + } + pid_t pid = getpid(); + char *start = reinterpret_cast(&pid); + size_t size = sizeof(pid); + bool error = false; + int attempts = 0; + + while (size > 0 && !error && attempts < kMaxAttempts) + { + ssize_t s = write(writefd, start, size); + + if (s < 0) + { + error = EAGAIN != errno && EINTR != errno; + if (error) + { + printErrno("Failed to write", "parent"); + } + } + else + { + start += s; + size -= s; + } + ++attempts; + } + + if (error || 0 != size) + { + return false; + } + + bool eof = false; + char dummy; + size = sizeof(dummy); + error = false; + attempts = 0; + + while (size > 0 && !error && !eof && attempts < kMaxAttempts) + { + ssize_t s; + + s = read(readfd, &dummy, size); + + if (s < 0) + { + error = EAGAIN != errno && EINTR != errno; + if (error) + { + printErrno("Failed to read", "parent"); + } + } + else if (0 == s) + { + eof = true; + } + else + { + size -= s; + } + ++attempts; + } + if (error || 0 != size) + { + return false; + } + return true; +} + + + +bool waitForChildrenAndSignal(int mProcessNb, int readfd, int writefd) +{ + if (readfd > writefd) + { + fprintf(stderr, "Called with args in wrong order!!\n"); + return false; + } + + bool error; + int attempts; + size_t size; + + for (int p = 0; p < mProcessNb; ++p) + { + bool eof = false; + pid_t pid; + char *end = reinterpret_cast(&pid); + + error = false; + attempts = 0; + size = sizeof(pid); + + while (size > 0 && !error && !eof && attempts < kMaxAttempts) + { + ssize_t s; + + s = read(readfd, end, size); + + if (s < 0) + { + error = EAGAIN != errno && EINTR != errno; + if (error) + { + printErrno("Failed to read", "child"); + } + } + else if (0 == s) + { + eof = true; + } + else + { + end += s; + size -= s; + } + ++attempts; + } + + if (error || 0 != size) + { + return false; + } + } + + for (int p = 0; p < mProcessNb; ++p) + { + char dummy; + + error = false; + attempts = 0; + size = sizeof(dummy); + + while (size > 0 && !error && attempts < kMaxAttempts) + { + ssize_t s = write(writefd, &dummy, size); + + if (s < 0) + { + error = EAGAIN != errno && EINTR != errno; + if (error) + { + printErrno("Failed to write", "child"); + } + } + else + { + size -= s; + } + ++attempts; + } + + if (error || 0 != size) + { + return false; + } + } + return true; +} + +} // namespace android diff --git a/tests/sdcard/sysutil.h b/tests/sdcard/sysutil.h new file mode 100644 index 00000000..3df79c12 --- /dev/null +++ b/tests/sdcard/sysutil.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ANDROID_NATIVETEST_SYSTEM_EXTRAS_TESTS_SDCARD_SYSUTIL_H_ +#define ANDROID_NATIVETEST_SYSTEM_EXTRAS_TESTS_SDCARD_SYSUTIL_H_ + +#include +namespace android { + +// Collection of functions to access the system: +// .kernelVersion Retrieve the full kernel description. +// .pidOutOfMemoryAdj Get and set the OOM adj value. +// .setPidOutOfMemoryAdj +// .schedFeatures Manipulate the scheduler using debugfs. +// .newFairSleepers +// .setNewFairSleepers +// .disableCpuScaling Set cpu scaling to 'performance'. +// .forkOrExit Fork a child or exit. +// .syncAnddropCaches Call sync an drop page/dentries/inodes caches. +// .fsyncAnddropCaches Call fsync an drop page/dentries/inodes caches. +// .resetDirectory Delete (non-recursive) files in a directory. +// +// IPC function to synchonize a processes with their parent. +// .writePidAndWaitForReply To instruct the parent the child is ready. +// Blocks until the parent signals back. +// .waitForChildrenAndSignal Blocks until all the children have called +// writePidAndWaitForReply. +// Then unblock all the children. +// .waitForChildrenOrExit Wait and exit if a child exit with errors. +// + +// Minimum size for the buffer to retrieve the kernel version. +static const size_t kMinKernelVersionBufferSize = 256; + +// @param str points to the buffer where the kernel version should be +// added. Must be at least kMinKernelVersionBufferSize chars. +// @param size of the buffer pointed by str. +// @return If successful the number of characters inserted in the +// buffer (not including the trailing '\0' byte). -1 otherwise. +int kernelVersion(char *str, size_t size); + + +// Return the out of memory adj for this process. /proc//oom_adj. +// @return the oom_adj of the current process. Typically: +// 0: a regular process. Should die on OOM. +// -16: system_server level. +// -17: disable, this process will never be killed. +// -127: error. +int pidOutOfMemoryAdj(); +void setPidOutOfMemoryAdj(int level); + +// Disable cpu scaling. +void disableCpuScaling(); + + +// Minimum size for the buffer to retrieve the sched features. +static const size_t kMinSchedFeaturesBufferSize = 256; + +// @param str points to the buffer where the sched features should be +// added. Must be at least kMinSchedFeaturesBufferSize chars. +// @param size of the buffer pointed by str. +// @return If successful the number of characters inserted in the +// buffer (not including the trailing '\0' byte). -1 otherwise. +int schedFeatures(char *str, size_t size); + +// @return true if NEW_FAIR_SLEEPERS is set, false if NO_NEW_FAIR_SLEEPERS is set. +bool newFairSleepers(); + +// Turns NEW_FAIR_SLEEPERS on or off. +void setNewFairSleepers(bool on); + +// @return true if NORMALIZED_SLEEPERS is set, false if NO_NORMALIZED_SLEEPERS is set. +bool normalizedSleepers(); + +// Turns NORMALIZED_SLEEPERS on or off. +void setNormalizedSleepers(bool on); + +// Filesystem + +// Sync and drop caches. Sync is needed because dirty objects are not +// freable. +// @param code: +// * 1 To free pagecache. +// * 2 To free dentries and inodes. +// * 3 To free pagecache, dentries and inodes. +void syncAndDropCaches(int code = 3); + +// Fsync the given fd and drop caches. Fsync is needed because dirty +// objects are not freable. +// @param code: +// * 1 To free pagecache. +// * 2 To free dentries and inodes. +// * 3 To free pagecache, dentries and inodes. +void fsyncAndDropCaches(int fd, int code = 3); + +// Delete all the files in the given directory. If the directory does +// not exist, it is created. Use this at the beginning of a test to +// make sure you have a clean existing directory, previous run may +// have crashed and left clutter around. +void resetDirectory(const char *directory); + +// IPC + +// Try to fork. exit on failure. +pid_t forkOrExit(); + +// Signal our parent we are alive and ready by sending our pid. +// Then do a blocking read for parent's reply. +bool writePidAndWaitForReply(int writefd, int readfd); + +// Wait for all the children to report their pids. +// Then unblock them. +bool waitForChildrenAndSignal(int mProcessNb, int readfd, int writefd); + +// Wait for 'num' children to complete. +// If a child did not exit cleanly, exit. +void waitForChildrenOrExit(int num); + +} // namespace android + +#endif // ANDROID_NATIVETEST_SYSTEM_EXTRAS_TESTS_SDCARD_SYSUTIL_H_ diff --git a/tests/sdcard/testcase.cpp b/tests/sdcard/testcase.cpp new file mode 100644 index 00000000..0de436f7 --- /dev/null +++ b/tests/sdcard/testcase.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "testcase.h" +#include // wake lock +#include +#include +#include +#include + +namespace { +const bool kVerbose = false; +} + +namespace android_test { + +TestCase::TestCase(const char *appName) + : mTestBody(NULL), mAppName(appName), mDataSize(1000 * 1000), + mChunkSize(mDataSize), mIter(20), mNproc(1), + mType(UNKNOWN_TEST), mDump(false), mCpuScaling(false), + mSync(NO_SYNC), mFadvice(POSIX_FADV_NORMAL), mTruncateToSize(false), + mTestTimer(NULL) +{ + // Make sure the cpu and phone are fully awake. The + // FULL_WAKE_LOCK was used by java apps and don't do + // anything. Also the partial wake lock is a nop if the phone + // is plugged in via USB. + acquire_wake_lock(PARTIAL_WAKE_LOCK, mAppName); + mPid = getpid(); + setNewFairSleepers(true); + setNormalizedSleepers(true); + if (pipe(mIpc) < 0) + { + fprintf(stderr, "pipe failed\n"); + exit(1); + } + if (pipe(mIpc + 2) < 0) + { + fprintf(stderr, "pipe failed\n"); + exit(1); + } +} + +TestCase::~TestCase() +{ + release_wake_lock(mAppName); + for (int i = 0; i < 4; ++i) close(mIpc[i]); + delete mTestTimer; + delete mOpenTimer; +} + + +bool TestCase::runTest() +{ + if (UNKNOWN_TEST == mType) + { + fprintf(stderr, "No test set."); + return false; + } + for (size_t p = 0; p < mNproc; ++p) + { + pid_t childpid = android::forkOrExit(); + + if (0 == childpid) { // I am a child, run the test. + mPid = getpid(); + close(mIpc[READ_FROM_CHILD]); + close(mIpc[WRITE_TO_CHILD]); + + if (kVerbose) printf("Child pid: %d\n", mPid); + if (!mTestBody(this)) { + printf("Test failed\n"); + } + char buffer[32000] = {0,}; + char *str = buffer; + size_t size_left = sizeof(buffer); + + testTimer()->sprint(&str, &size_left); + if(openTimer()->used()) openTimer()->sprint(&str, &size_left); + if(readTimer()->used()) readTimer()->sprint(&str, &size_left); + if(writeTimer()->used()) writeTimer()->sprint(&str, &size_left); + if(syncTimer()->used()) syncTimer()->sprint(&str, &size_left); + if(truncateTimer()->used()) truncateTimer()->sprint(&str, &size_left); + + write(mIpc[TestCase::WRITE_TO_PARENT], buffer, str - buffer); + + + close(mIpc[WRITE_TO_PARENT]); + close(mIpc[READ_FROM_PARENT]); + exit(EXIT_SUCCESS); + } + } + // I am the parent process + close(mIpc[WRITE_TO_PARENT]); + close(mIpc[READ_FROM_PARENT]); + + // Block until all the children have reported for + // duty. Unblock them so they start the work. + if (!android::waitForChildrenAndSignal(mNproc, mIpc[READ_FROM_CHILD], mIpc[WRITE_TO_CHILD])) + { + exit(1); + } + + // Process the output of each child. + // TODO: handle EINTR + char buffer[32000] = {0,}; + while(read(mIpc[READ_FROM_CHILD], buffer, sizeof(buffer)) != 0) + { + printf("%s", buffer); + fflush(stdout); + memset(buffer, 0, sizeof(buffer)); + } + // Parent is waiting for children to exit. + android::waitForChildrenOrExit(mNproc); + return true; +} + +void TestCase::setIter(size_t iter) +{ + mIter = iter; +} + +void TestCase::createTimers() +{ + char total_time[80]; + + snprintf(total_time, sizeof(total_time), "%s_total", mName); + mTestTimer = new StopWatch(total_time, 1); + mTestTimer->setDataSize(dataSize()); + + mOpenTimer = new StopWatch("open", iter() * kReadWriteFactor); + + mReadTimer = new StopWatch("read", iter() * dataSize() / chunkSize() * kReadWriteFactor); + mReadTimer->setDataSize(dataSize()); + + mWriteTimer = new StopWatch("write", iter() * dataSize() / chunkSize()); + mWriteTimer->setDataSize(dataSize()); + + mSyncTimer = new StopWatch("sync", iter()); + + mTruncateTimer = new StopWatch("truncate", iter()); +} + +bool TestCase::setTypeFromName(const char *test_name) +{ + strcpy(mName, test_name); + if (strcmp(mName, "write") == 0) mType = WRITE; + if (strcmp(mName, "read") == 0) mType = READ; + if (strcmp(mName, "read_write") == 0) mType = READ_WRITE; + if (strcmp(mName, "open_create") == 0) mType = OPEN_CREATE; + + return UNKNOWN_TEST != mType; +} + +void TestCase::setSync(Sync s) +{ + mSync = s; +} + +const char *TestCase::syncAsStr() const +{ + return mSync == NO_SYNC ? "disabled" : (mSync == FSYNC ? "fsync" : "sync"); +} + +void TestCase::setFadvise(const char *advice) +{ + mFadvice = POSIX_FADV_NORMAL; + if (strcmp(advice, "sequential") == 0) + { + mFadvice = POSIX_FADV_SEQUENTIAL; + } + else if (strcmp(advice, "random") == 0) + { + mFadvice = POSIX_FADV_RANDOM; + } + else if (strcmp(advice, "noreuse") == 0) + { + mFadvice = POSIX_FADV_NOREUSE; + } + else if (strcmp(advice, "willneed") == 0) + { + mFadvice = POSIX_FADV_WILLNEED; + } + else if (strcmp(advice, "dontneed") == 0) + { + mFadvice = POSIX_FADV_DONTNEED; + } +} + +const char *TestCase::fadviseAsStr() const +{ + switch (mFadvice) { + case POSIX_FADV_NORMAL: return "fadv_normal"; + case POSIX_FADV_SEQUENTIAL: return "fadv_sequential"; + case POSIX_FADV_RANDOM: return "fadv_random"; + case POSIX_FADV_NOREUSE: return "fadv_noreuse"; + case POSIX_FADV_WILLNEED: return "fadv_willneed"; + case POSIX_FADV_DONTNEED: return "fadv_dontneed"; + default: return "fadvice_unknown"; + } +} + + +} // namespace android_test diff --git a/tests/sdcard/testcase.h b/tests/sdcard/testcase.h new file mode 100644 index 00000000..66af9d63 --- /dev/null +++ b/tests/sdcard/testcase.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#ifndef SYSTEM_EXTRAS_TESTS_SDCARD_TESTCASE_H_ +#define SYSTEM_EXTRAS_TESTS_SDCARD_TESTCASE_H_ + +#include +#include "stopwatch.h" +#include "sysutil.h" + +namespace android_test { + +// Class to group test parameters and implementation. +// Takes care of forking child processes and wait for them. + +class TestCase { + public: + enum Type {UNKNOWN_TEST, WRITE, READ, OPEN_CREATE, READ_WRITE}; + enum Pipe {READ_FROM_CHILD = 0, WRITE_TO_PARENT, READ_FROM_PARENT, WRITE_TO_CHILD}; + enum Sync {NO_SYNC, FSYNC, SYNC}; + + // Reads takes less time than writes. This is a basic + // approximation of how much longer the read tasks must run to + // terminate roughly at the same time as the write tasks. + const static int kReadWriteFactor = 5; + + TestCase(const char *appName); + + ~TestCase(); + + size_t iter() const { return mIter; } + void setIter(size_t iter); + + size_t nproc() const { return mNproc; } + void setNproc(size_t val) { mNproc = val; } + + size_t dataSize() const { return mDataSize; } + void setDataSize(size_t val) { mDataSize = val; } + + size_t chunkSize() const { return mChunkSize; } + void setChunkSize(size_t val) { mChunkSize = val; } + + bool newFairSleepers() const { return mNewFairSleepers; } + void setNewFairSleepers(bool val) { + mNewFairSleepers = val; + android::setNewFairSleepers(val); + } + + bool normalizedSleepers() const { return mNormalizedSleepers; } + void setNormalizedSleepers(bool val) { + mNormalizedSleepers = val; + android::setNormalizedSleepers(val); + } + + Sync sync() const { return mSync; } + void setSync(Sync s); + const char *syncAsStr() const; + + bool cpuScaling() const { return mCpuScaling; } + void setCpuScaling() { mCpuScaling = true; } + + bool truncateToSize() const { return mTruncateToSize; } + void setTruncateToSize() { mTruncateToSize = true; } + + int fadvise() { return mFadvice; } + void setFadvise(const char *advice); + const char *fadviseAsStr() const; + + // Print the samples. + void setDump() { StopWatch::setPrintRawMode(true); } + + StopWatch *testTimer() { return mTestTimer; } + StopWatch *openTimer() { return mOpenTimer; } + StopWatch *readTimer() { return mReadTimer; } + StopWatch *writeTimer() { return mWriteTimer; } + StopWatch *syncTimer() { return mSyncTimer; } + StopWatch *truncateTimer() { return mTruncateTimer; } + + // Fork the children, run the test and wait for them to complete. + bool runTest(); + + void signalParentAndWait() { + if (!android::writePidAndWaitForReply(mIpc[WRITE_TO_PARENT], mIpc[READ_FROM_PARENT])) { + exit(1); + } + } + + void createTimers(); + bool setTypeFromName(const char *test_name); + Type type() const { return mType; } + pid_t pid() const { return mPid; } + const char *name() const { return mName; } + + // This is set to the function that will actually do the test when + // the command line arguments have been parsed. The function will + // be run in one or more child(ren) process(es). + bool (*mTestBody)(TestCase *); +private: + const char *mAppName; + size_t mDataSize; + size_t mChunkSize; + size_t mIter; + size_t mNproc; + pid_t mPid; + char mName[80]; + Type mType; + + bool mDump; // print the raw values instead of a human friendly report. + bool mCpuScaling; // true, do not turn off cpu scaling. + Sync mSync; + int mFadvice; + // When new files are created, truncate them to the final size. + bool mTruncateToSize; + + bool mNewFairSleepers; + bool mNormalizedSleepers; + + // IPC + // Parent Child(ren) + // --------------------------------------- + // 0: read from child closed + // 1: closed write to parent + // 2: closed read from parent + // 3: write to child closed + int mIpc[4]; + + StopWatch *mTestTimer; // Used to time the test overall. + StopWatch *mOpenTimer; // Used to time the open calls. + StopWatch *mReadTimer; // Used to time the read calls. + StopWatch *mWriteTimer; // Used to time the write calls. + StopWatch *mSyncTimer; // Used to time the sync/fsync calls. + StopWatch *mTruncateTimer; // Used to time the ftruncate calls. +}; + +} // namespace android_test + +#endif // SYSTEM_EXTRAS_TESTS_SDCARD_TESTCASE_H_