summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid (Google) Code Review <android-gerrit@google.com>2009-06-02 10:54:57 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-06-02 10:54:57 -0700
commit3ab8f4e41cef2ef2bed51963fd5f8534ad8861ff (patch)
treeb2151407ec8a0d48c08fcfc5c08f2155a4a03213
parent1ded028acecd41f7fd10fc63fb859ecdad0e1ff4 (diff)
parent3d44de40d56dd4da750c453814ab2bf6fc46273d (diff)
downloadextras-3ab8f4e41cef2ef2bed51963fd5f8534ad8861ff.tar.gz
am 3d44de40: Merge change 1918 into donut
Merge commit '3d44de40d56dd4da750c453814ab2bf6fc46273d' * commit '3d44de40d56dd4da750c453814ab2bf6fc46273d': Load test for the sdcard.
-rw-r--r--tests/sdcard/Android.mk37
-rwxr-xr-xtests/sdcard/plot_sdcard.py200
-rw-r--r--tests/sdcard/sdcard_perf_test.cpp607
-rw-r--r--tests/sdcard/stopwatch.cpp226
-rw-r--r--tests/sdcard/stopwatch.h155
-rw-r--r--tests/sdcard/sysutil.cpp604
-rw-r--r--tests/sdcard/sysutil.h146
-rw-r--r--tests/sdcard/testcase.cpp228
-rw-r--r--tests/sdcard/testcase.h163
9 files changed, 2366 insertions, 0 deletions
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 <cstdio>
+#include <cstdlib>
+#include <ctime>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <linux/fadvise.h>
+#include <unistd.h>
+
+#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 <malloc.h>
+#include <stdio.h>
+#include <time.h>
+#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<int>(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 <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#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<char *>(&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<char *>(&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 <stdlib.h>
+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/<pid>/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 <hardware_legacy/power.h> // wake lock
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/fadvise.h>
+
+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 <stdlib.h>
+#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_