summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Catania <niko@google.com>2009-05-18 16:08:12 -0700
committerNicolas Catania <niko@google.com>2009-05-28 08:39:34 -0700
commit39c016f875b793296a121f41de5775b88f6fa1c9 (patch)
tree72b834990f561599692d547405e9d77c67816d02
parentfb96abdcfc53dc0c4ff0450c0afd192d6ba570c1 (diff)
downloadextras-39c016f875b793296a121f41de5775b88f6fa1c9.tar.gz
Load test for the sdcard.
Simulate loads on the sdcard for write, read and open operations using one or more process. The number of processes to run concurently can be specified on the command line. The size of the file and the number of iterations (for averaging result) are also a command line flag. The user can have summary of the performance plus the raw data for processing with a separate tool. There is a basic python script to help plotting the result.
-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_