738 lines
21 KiB
C++
738 lines
21 KiB
C++
|
/*
|
||
|
* 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 <fts.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 output 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'},
|
||
|
{"depth", required_argument, 0, 'D'},
|
||
|
{"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|traverse [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"
|
||
|
" -D --depth: Depth of directory tree to create for traversal.\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:D: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 'D': // tree depth
|
||
|
testCase->setTreeDepth(atoi(optarg));
|
||
|
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, S_IRWXU);
|
||
|
|
||
|
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, S_IRWXU); // 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, S_IRWXU);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
bool writeTestFile(TestCase *testCase, const char* filename) {
|
||
|
int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
|
||
|
if (fd < 0) {
|
||
|
fprintf(stderr, "open() failed: %s\n", strerror(errno));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool res = false;
|
||
|
|
||
|
char * const chunk = new char[testCase->chunkSize()];
|
||
|
memset(chunk, 0xaa, testCase->chunkSize());
|
||
|
|
||
|
size_t left = testCase->dataSize();
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
while (chunk_size > 0) {
|
||
|
ssize_t s = write(fd, dest, chunk_size);
|
||
|
if (s < 0) {
|
||
|
fprintf(stderr, "write() failed: %s\n", strerror(errno));
|
||
|
goto fail;
|
||
|
}
|
||
|
chunk_size -= s;
|
||
|
dest += s;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
res = true;
|
||
|
fail:
|
||
|
close(fd);
|
||
|
delete[] chunk;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------------------------------
|
||
|
// TRAVERSE
|
||
|
|
||
|
#define MAX_PATH 512
|
||
|
|
||
|
// Creates a directory tree that is both deep and wide, and times
|
||
|
// traversal using fts_open().
|
||
|
bool testTraverse(TestCase *testCase) {
|
||
|
char path[MAX_PATH];
|
||
|
char filepath[MAX_PATH];
|
||
|
strcpy(path, kTestDir);
|
||
|
|
||
|
// Generate a deep directory hierarchy
|
||
|
size_t depth = testCase->treeDepth();
|
||
|
for (size_t i = 0; i < depth; i++) {
|
||
|
// Go deeper by appending onto current path
|
||
|
snprintf(path + strlen(path), MAX_PATH - strlen(path), "/dir%d", i);
|
||
|
mkdir(path, S_IRWXU);
|
||
|
|
||
|
// Create some files at this depth
|
||
|
strcpy(filepath, path);
|
||
|
int pathlen = strlen(path);
|
||
|
char* nameStart = filepath + pathlen;
|
||
|
for (size_t j = 0; j < depth; j++) {
|
||
|
snprintf(nameStart, MAX_PATH - pathlen, "/file%d", j);
|
||
|
writeTestFile(testCase, filepath);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
testCase->signalParentAndWait();
|
||
|
testCase->testTimer()->start();
|
||
|
|
||
|
// Now traverse structure
|
||
|
size_t iter = testCase->iter();
|
||
|
for (size_t i = 0; i < iter; i++) {
|
||
|
testCase->traverseTimer()->start();
|
||
|
|
||
|
FTS *ftsp;
|
||
|
if ((ftsp = fts_open((char **) &kTestDir, FTS_LOGICAL | FTS_XDEV, NULL)) == NULL) {
|
||
|
fprintf(stderr, "fts_open() failed: %s\n", strerror(errno));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Count discovered files
|
||
|
int dirs = 0, files = 0;
|
||
|
|
||
|
FTSENT *curr;
|
||
|
while ((curr = fts_read(ftsp)) != NULL) {
|
||
|
switch (curr->fts_info) {
|
||
|
case FTS_D:
|
||
|
dirs++;
|
||
|
break;
|
||
|
case FTS_F:
|
||
|
files++;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fts_close(ftsp);
|
||
|
|
||
|
testCase->traverseTimer()->stop();
|
||
|
|
||
|
int expectedDirs = depth + 1;
|
||
|
if (expectedDirs != dirs) {
|
||
|
fprintf(stderr, "expected %d dirs, but found %d\n", expectedDirs, dirs);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int expectedFiles = depth * depth;
|
||
|
if (expectedFiles != files) {
|
||
|
fprintf(stderr, "expected %d files, but found %d\n", expectedFiles, files);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
case TestCase::TRAVERSE:
|
||
|
testCase.mTestBody = testTraverse;
|
||
|
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);
|
||
|
}
|
||
|
}
|