OSDN Git Service

am 3d44de40: Merge change 1918 into donut
authorAndroid (Google) Code Review <android-gerrit@google.com>
Tue, 2 Jun 2009 17:54:57 +0000 (10:54 -0700)
committerThe Android Open Source Project <initial-contribution@android.com>
Tue, 2 Jun 2009 17:54:57 +0000 (10:54 -0700)
Merge commit '3d44de40d56dd4da750c453814ab2bf6fc46273d'

* commit '3d44de40d56dd4da750c453814ab2bf6fc46273d':
  Load test for the sdcard.

tests/sdcard/Android.mk [new file with mode: 0644]
tests/sdcard/plot_sdcard.py [new file with mode: 0755]
tests/sdcard/sdcard_perf_test.cpp [new file with mode: 0644]
tests/sdcard/stopwatch.cpp [new file with mode: 0644]
tests/sdcard/stopwatch.h [new file with mode: 0644]
tests/sdcard/sysutil.cpp [new file with mode: 0644]
tests/sdcard/sysutil.h [new file with mode: 0644]
tests/sdcard/testcase.cpp [new file with mode: 0644]
tests/sdcard/testcase.h [new file with mode: 0644]

diff --git a/tests/sdcard/Android.mk b/tests/sdcard/Android.mk
new file mode 100644 (file)
index 0000000..d1e06f2
--- /dev/null
@@ -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 (executable)
index 0000000..10ee00b
--- /dev/null
@@ -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 (file)
index 0000000..28069b9
--- /dev/null
@@ -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 (file)
index 0000000..12fe8f1
--- /dev/null
@@ -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 (file)
index 0000000..4d1a794
--- /dev/null
@@ -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 (file)
index 0000000..0182590
--- /dev/null
@@ -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 (file)
index 0000000..3df79c1
--- /dev/null
@@ -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 (file)
index 0000000..0de436f
--- /dev/null
@@ -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 (file)
index 0000000..66af9d6
--- /dev/null
@@ -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_