| /* |
| * 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); |
| } |
| } |