1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *  * Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 *  * Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in
12 *    the documentation and/or other materials provided with the
13 *    distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <cstdio>
30#include <cstdlib>
31#include <ctime>
32#include <errno.h>
33#include <fcntl.h>
34#include <getopt.h>
35#include <limits.h>
36#include <string.h>
37#include <sys/stat.h>
38#include <linux/fadvise.h>
39#include <unistd.h>
40#include <fts.h>
41
42#include "stopwatch.h"
43#include "sysutil.h"
44#include "testcase.h"
45
46// Stress test for the sdcard. Use this to generate some load on the
47// sdcard and collect performance statistics. The output is either a
48// human readable report or the raw timing samples that can be
49// processed using another tool.
50//
51// Needs debugfs:
52//   adb root;
53//   adb shell mount -t debugfs none /sys/kernel/debug
54//
55// The following tests are defined (value of the --test flag):
56//  write:       Open a file write some random data and close.
57//  read:        Open a file read it and close.
58//  read_write:  Combine readers and writers.
59//  open_create: Open|create an non existing file.
60//
61// For each run you can control how many processes will run the test in
62// parallel to simulate a real load (--procnb flag)
63//
64// For each process, the test selected will be executed many time to
65// get a meaningful average/min/max (--iterations flag)
66//
67// Use --dump to also get the raw data.
68//
69// For read/write tests, size is the number of Kbytes to use.
70//
71// To build: mmm system/extras/tests/sdcard/Android.mk SDCARD_TESTS=1
72//
73// Examples:
74// adb shell /system/bin/sdcard_perf_test --test=read --size=1000 --chunk-size=100 --procnb=1 --iterations=10 --dump > /tmp/data.txt
75// adb shell /system/bin/sdcard_perf_test --test=write --size=1000 --chunk-size=100 --procnb=1 --iterations=100 --dump > /tmp/data.txt
76//
77// To watch the memory: cat /proc/meminfo
78// If the phone crashes, look at /proc/last_kmsg on reboot.
79//
80// TODO: It would be cool if we could play with various fadvise()
81// strategies in here to see how tweaking read-ahead changes things.
82//
83
84extern char *optarg;
85extern int optind, opterr, optopt;
86
87// TODO: No clue where fadvise is. Disabled for now.
88#define FADVISE(fd, off, len, advice) (void)0
89
90#ifndef min
91#define min(a,b) (((a)>(b))?(b):(a))
92#endif
93
94namespace  {
95using android::kernelVersion;
96using android::kMinKernelVersionBufferSize;
97using android::schedFeatures;
98using android::kMinSchedFeaturesBufferSize;
99using android_test::StopWatch;
100using android::writePidAndWaitForReply;
101using android::waitForChildrenAndSignal;
102using android::waitForChildrenOrExit;
103using android_test::TestCase;
104
105const char *kAppName = "sdcard_perf_test";
106const char *kTestDir = "/sdcard/perf";
107const bool kVerbose = false;
108
109// Used by getopt to parse the command line.
110struct option long_options[] = {
111    {"size", required_argument, 0, 's'},
112    {"chunk-size", required_argument, 0, 'S'},
113    {"depth", required_argument, 0, 'D'},
114    {"iterations",  required_argument, 0, 'i'},
115    {"procnb",  required_argument, 0, 'p'},
116    {"test",  required_argument, 0, 't'},
117    {"dump",  no_argument, 0, 'd'},
118    {"cpu-scaling",  no_argument, 0, 'c'},
119    {"sync",  required_argument, 0, 'f'},
120    {"truncate", no_argument, 0, 'e'},
121    {"no-new-fair-sleepers", no_argument, 0, 'z'},
122    {"no-normalized-sleepers", no_argument, 0, 'Z'},
123    {"fadvise", required_argument, 0, 'a'},
124    {"help", no_argument, 0, 'h'},
125    {0, 0, 0, 0},
126};
127
128void usage()
129{
130    printf("sdcard_perf_test --test=write|read|read_write|open_create|traverse [options]\n\n"
131           "  -t --test:        Select the test.\n"
132           "  -s --size:        Size in kbytes of the data.\n"
133           "  -S --chunk-size:  Size of a chunk. Default to size ie 1 chunk.\n"
134           "                    Data will be written/read using that chunk size.\n"
135           "  -D --depth:       Depth of directory tree to create for traversal.\n",
136           "  -i --iterations:  Number of time a process should carry its task.\n"
137           "  -p --procnb:      Number of processes to use.\n"
138           "  -d --dump:        Print the raw timing on stdout.\n"
139           "  -c --cpu-scaling: Enable cpu scaling.\n"
140           "  -s --sync: fsync|sync Use fsync or sync in write test. Default: no sync call.\n"
141           "  -e --truncate:    Truncate to size the file on creation.\n"
142           "  -z --no-new-fair-sleepers: Turn them off. You need to mount debugfs.\n"
143           "  -Z --no-normalized-sleepers: Turn them off. You need to mount debugfs.\n"
144           "  -a --fadvise:     Specify an fadvise policy (not supported).\n"
145           );
146}
147
148// Print command line, pid, kernel version, OOM adj and scheduler.
149void printHeader(int argc, char **argv, const TestCase& testCase)
150{
151    printf("# Command: ");
152    for (int i = 0; i < argc; ++i)
153    {
154        printf("%s ", argv[i]);
155    }
156    printf("\n");
157
158    printf("# Pid: %d\n", getpid());
159
160    {
161        char buffer[kMinKernelVersionBufferSize] = {0, };
162        if (kernelVersion(buffer, sizeof(buffer)) > 0)
163        {
164            printf("# Kernel: %s", buffer);
165        }
166    }
167
168    // Earlier on, running this test was crashing the phone. It turned
169    // out that it was using too much memory but its oom_adj value was
170    // -17 which means disabled. -16 is the system_server and 0 is
171    // typically what applications run at. The issue is that adb runs
172    // at -17 and so is this test. We force oom_adj to 0 unless the
173    // oom_adj option has been used.
174    // TODO: We talked about adding an option to control oom_adj, not
175    // sure if we still need that.
176    int oomAdj = android::pidOutOfMemoryAdj();
177
178    printf("# Oom_adj: %d ", oomAdj);
179    if (oomAdj < 0)
180    {
181        android::setPidOutOfMemoryAdj(0);
182        printf("adjuted to %d", android::pidOutOfMemoryAdj());
183    }
184    printf("\n");
185
186    {
187        char buffer[kMinSchedFeaturesBufferSize] = {0, };
188        if (schedFeatures(buffer, sizeof(buffer)) > 0)
189        {
190            printf("# Sched features: %s", buffer);
191        }
192    }
193    printf("# Fadvise: %s\n", testCase.fadviseAsStr());
194}
195
196// Remove all the files under kTestDir and clear the caches.
197void cleanup() {
198    android::resetDirectory(kTestDir);
199    android::syncAndDropCaches();  // don't pollute runs.
200}
201
202// @param argc, argv have a wild guess.
203// @param[out] testCase Structure built from the cmd line args.
204void parseCmdLine(int argc, char **argv, TestCase *testCase)\
205{
206    int c;
207
208    while(true)
209    {
210        // getopt_long stores the option index here.
211        int option_index = 0;
212
213        c = getopt_long (argc, argv,
214                         "hS:s:D:i:p:t:dcf:ezZa:",
215                         long_options,
216                         &option_index);
217        // Detect the end of the options.
218        if (c == -1) break;
219
220        switch (c)
221        {
222            case 's':
223                testCase->setDataSize(atoi(optarg) * 1024);
224                break;
225            case 'S':
226                testCase->setChunkSize(atoi(optarg) * 1024);
227                break;
228            case 'D': // tree depth
229                testCase->setTreeDepth(atoi(optarg));
230                break;
231            case 'i':
232                testCase->setIter(atoi(optarg));
233                printf("# Iterations: %d\n", testCase->iter());
234                break;
235            case 'p':
236                testCase->setNproc(atoi(optarg));
237                printf("# Proc nb: %d\n", testCase->nproc());
238                break;
239            case 't':
240                testCase->setTypeFromName(optarg);
241                printf("# Test name %s\n", testCase->name());
242                break;
243            case 'd':
244                testCase->setDump();
245                break;
246            case 'c':
247                printf("# Cpu scaling is on\n");
248                testCase->setCpuScaling();
249                break;
250            case 'f':
251                if (strcmp("sync", optarg) == 0) {
252                    testCase->setSync(TestCase::SYNC);
253                } else if (strcmp("fsync", optarg) == 0) {
254                    testCase->setSync(TestCase::FSYNC);
255                }
256                break;
257            case 'e':  // e for empty
258                printf("# Will truncate to size\n");
259                testCase->setTruncateToSize();
260                break;
261            case 'z':  // no new fair sleepers
262                testCase->setNewFairSleepers(false);
263                break;
264            case 'Z':  // no normalized sleepers
265                testCase->setNormalizedSleepers(false);
266                break;
267            case 'a':  // fadvise
268                testCase->setFadvise(optarg);
269                break;
270            case 'h':
271                usage();
272                exit(0);
273            default:
274                fprintf(stderr, "Unknown option %s\n", optarg);
275                exit(EXIT_FAILURE);
276        }
277    }
278}
279
280// ----------------------------------------------------------------------
281// READ TEST
282
283// Read a file.  We use a new file each time to avoid any caching
284// effect that would happen if we were reading the same file each
285// time.
286// @param chunk buffer large enough where the chunk read are written.
287// @param idx iteration number.
288// @param testCase has all the timers and paramters to run the test.
289
290bool readData(char *const chunk, const int idx, TestCase *testCase)
291{
292    char filename[80] = {'\0',};
293
294    sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid());
295
296    testCase->openTimer()->start();
297    int fd = open(filename, O_RDONLY);
298    testCase->openTimer()->stop();
299
300    if (fd < 0)
301    {
302        fprintf(stderr, "Open read only failed.");
303        return false;
304    }
305    FADVISE(fd, 0, 0, testCase->fadvise());
306
307    size_t left = testCase->dataSize();
308    pid_t *pid = (pid_t*)chunk;
309    while (left > 0)
310    {
311        char *dest = chunk;
312        size_t chunk_size = testCase->chunkSize();
313
314        if (chunk_size > left)
315        {
316            chunk_size = left;
317            left = 0;
318        }
319        else
320        {
321            left -= chunk_size;
322        }
323
324        testCase->readTimer()->start();
325        while (chunk_size > 0)
326        {
327            ssize_t s = read(fd, dest, chunk_size);
328            if (s < 0)
329            {
330                fprintf(stderr, "Failed to read.\n");
331                close(fd);
332                return false;
333            }
334            chunk_size -= s;
335            dest += s;
336        }
337        testCase->readTimer()->stop();
338    }
339    close(fd);
340    if (testCase->pid() != *pid)
341    {
342        fprintf(stderr, "Wrong pid found @ read block %x != %x\n", testCase->pid(), *pid);
343        return false;
344    }
345    else
346    {
347        return true;
348    }
349}
350
351
352bool testRead(TestCase *testCase) {
353    // Setup the testcase by writting some dummy files.
354    const size_t size = testCase->dataSize();
355    size_t chunk_size = testCase->chunkSize();
356    char *const chunk = new char[chunk_size];
357
358    memset(chunk, 0xaa, chunk_size);
359    *((pid_t *)chunk) = testCase->pid(); // write our pid at the beginning of each chunk
360
361    size_t iter = testCase->iter();
362
363    // since readers are much faster we increase the number of
364    // iteration to last longer and have concurrent read/write
365    // thoughout the whole test.
366    if (testCase->type() == TestCase::READ_WRITE)
367    {
368        iter *= TestCase::kReadWriteFactor;
369    }
370
371    for (size_t i = 0; i < iter; ++i)
372    {
373        char filename[80] = {'\0',};
374
375        sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid());
376        int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
377
378        size_t left = size;
379        while (left > 0)
380        {
381            if (chunk_size > left)
382            {
383                chunk_size = left;
384            }
385            ssize_t written = write(fd, chunk, chunk_size);
386            if (written < 0)
387            {
388                fprintf(stderr, "Write failed %d %s.", errno, strerror(errno));
389                return false;
390            }
391            left -= written;
392        }
393        close(fd);
394    }
395    if (kVerbose) printf("Child %d all chunk written\n", testCase->pid());
396
397    android::syncAndDropCaches();
398    testCase->signalParentAndWait();
399
400    // Start the read test.
401    testCase->testTimer()->start();
402    for (size_t i = 0; i < iter; ++i)
403    {
404        if (!readData(chunk, i, testCase))
405        {
406            return false;
407        }
408    }
409    testCase->testTimer()->stop();
410
411    delete [] chunk;
412    return true;
413}
414
415// ----------------------------------------------------------------------
416// WRITE TEST
417
418bool writeData(const char *const chunk, const int idx, TestCase *testCase) {
419    char filename[80] = {'\0',};
420
421    sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid());
422    testCase->openTimer()->start();
423    int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);  // no O_TRUNC, see header comment
424    testCase->openTimer()->stop();
425
426    if (fd < 0)
427    {
428        fprintf(stderr, "Open write failed.");
429        return false;
430    }
431    FADVISE(fd, 0, 0, testCase->fadvise());
432
433    if (testCase->truncateToSize())
434    {
435        testCase->truncateTimer()->start();
436        ftruncate(fd, testCase->dataSize());
437        testCase->truncateTimer()->stop();
438    }
439
440    size_t left = testCase->dataSize();
441    while (left > 0)
442    {
443        const char *dest = chunk;
444        size_t chunk_size = testCase->chunkSize();
445
446        if (chunk_size > left)
447        {
448            chunk_size = left;
449            left = 0;
450        }
451        else
452        {
453            left -= chunk_size;
454        }
455
456
457        testCase->writeTimer()->start();
458        while (chunk_size > 0)
459        {
460            ssize_t s = write(fd, dest, chunk_size);
461            if (s < 0)
462            {
463                fprintf(stderr, "Failed to write.\n");
464                close(fd);
465                return false;
466            }
467            chunk_size -= s;
468            dest += s;
469        }
470        testCase->writeTimer()->stop();
471    }
472
473    if (TestCase::FSYNC == testCase->sync())
474    {
475        testCase->syncTimer()->start();
476        fsync(fd);
477        testCase->syncTimer()->stop();
478    }
479    else if (TestCase::SYNC == testCase->sync())
480    {
481        testCase->syncTimer()->start();
482        sync();
483        testCase->syncTimer()->stop();
484    }
485    close(fd);
486    return true;
487}
488
489bool testWrite(TestCase *testCase)
490{
491    const size_t size = testCase->dataSize();
492    size_t chunk_size = testCase->chunkSize();
493    char *data = new char[chunk_size];
494
495    memset(data, 0xaa, chunk_size);
496    // TODO: write the pid at the beginning like in the write
497    // test. Have a mode where we check the write was correct.
498    testCase->signalParentAndWait();
499
500    testCase->testTimer()->start();
501    for (size_t i = 0; i < testCase->iter(); ++i)
502    {
503        if (!writeData(data, i, testCase))
504        {
505            return false;
506        }
507    }
508    testCase->testTimer()->stop();
509    delete [] data;
510    return true;
511}
512
513
514// ----------------------------------------------------------------------
515// READ WRITE
516
517// Mix of read and write test. Even PID run the write test. Odd PID
518// the read test. Not fool proof but work most of the time.
519bool testReadWrite(TestCase *testCase)
520{
521    if (getpid() & 0x1) {
522        return testRead(testCase);
523    } else {
524        return testWrite(testCase);
525    }
526}
527
528// ----------------------------------------------------------------------
529// OPEN CREATE TEST
530
531bool testOpenCreate(TestCase *testCase)
532{
533    char filename[80] = {'\0',};
534
535    testCase->signalParentAndWait();
536    testCase->testTimer()->start();
537
538    for (size_t i = 0; i < testCase->iter(); ++i)
539    {
540        sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid());
541
542        int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
543        FADVISE(fd, 0, 0, testCase->fadvise());
544
545        if (testCase->truncateToSize())
546        {
547            ftruncate(fd, testCase->dataSize());
548        }
549        if (fd < 0)
550        {
551            return false;
552        }
553        close(fd);
554    }
555    testCase->testTimer()->stop();
556    return true;
557}
558
559bool writeTestFile(TestCase *testCase, const char* filename) {
560    int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
561    if (fd < 0) {
562        fprintf(stderr, "open() failed: %s\n", strerror(errno));
563        return false;
564    }
565
566    bool res = false;
567
568    char * const chunk = new char[testCase->chunkSize()];
569    memset(chunk, 0xaa, testCase->chunkSize());
570
571    size_t left = testCase->dataSize();
572    while (left > 0) {
573        char *dest = chunk;
574        size_t chunk_size = testCase->chunkSize();
575
576        if (chunk_size > left) {
577            chunk_size = left;
578            left = 0;
579        } else {
580            left -= chunk_size;
581        }
582
583        while (chunk_size > 0) {
584            ssize_t s = write(fd, dest, chunk_size);
585            if (s < 0) {
586                fprintf(stderr, "write() failed: %s\n", strerror(errno));
587                goto fail;
588            }
589            chunk_size -= s;
590            dest += s;
591        }
592    }
593
594    res = true;
595fail:
596    close(fd);
597    delete[] chunk;
598    return res;
599}
600
601// ----------------------------------------------------------------------
602// TRAVERSE
603
604#define MAX_PATH 512
605
606// Creates a directory tree that is both deep and wide, and times
607// traversal using fts_open().
608bool testTraverse(TestCase *testCase) {
609    char path[MAX_PATH];
610    char filepath[MAX_PATH];
611    strcpy(path, kTestDir);
612
613    // Generate a deep directory hierarchy
614    size_t depth = testCase->treeDepth();
615    for (size_t i = 0; i < depth; i++) {
616        // Go deeper by appending onto current path
617        snprintf(path + strlen(path), MAX_PATH - strlen(path), "/dir%d", i);
618        mkdir(path, S_IRWXU);
619
620        // Create some files at this depth
621        strcpy(filepath, path);
622        int pathlen = strlen(path);
623        char* nameStart = filepath + pathlen;
624        for (size_t j = 0; j < depth; j++) {
625            snprintf(nameStart, MAX_PATH - pathlen, "/file%d", j);
626            writeTestFile(testCase, filepath);
627        }
628    }
629
630    testCase->signalParentAndWait();
631    testCase->testTimer()->start();
632
633    // Now traverse structure
634    size_t iter = testCase->iter();
635    for (size_t i = 0; i < iter; i++) {
636        testCase->traverseTimer()->start();
637
638        FTS *ftsp;
639        if ((ftsp = fts_open((char **) &kTestDir, FTS_LOGICAL | FTS_XDEV, NULL)) == NULL) {
640            fprintf(stderr, "fts_open() failed: %s\n", strerror(errno));
641            return false;
642        }
643
644        // Count discovered files
645        int dirs = 0, files = 0;
646
647        FTSENT *curr;
648        while ((curr = fts_read(ftsp)) != NULL) {
649            switch (curr->fts_info) {
650            case FTS_D:
651                dirs++;
652                break;
653            case FTS_F:
654                files++;
655                break;
656            }
657        }
658
659        fts_close(ftsp);
660
661        testCase->traverseTimer()->stop();
662
663        int expectedDirs = depth + 1;
664        if (expectedDirs != dirs) {
665            fprintf(stderr, "expected %d dirs, but found %d\n", expectedDirs, dirs);
666            return false;
667        }
668
669        int expectedFiles = depth * depth;
670        if (expectedFiles != files) {
671            fprintf(stderr, "expected %d files, but found %d\n", expectedFiles, files);
672            return false;
673        }
674    }
675
676    testCase->testTimer()->stop();
677    return true;
678}
679
680}  // anonymous namespace
681
682int main(int argc, char **argv)
683{
684    android_test::TestCase testCase(kAppName);
685
686    cleanup();
687
688    parseCmdLine(argc, argv, &testCase);
689    printHeader(argc, argv, testCase);
690
691    printf("# File size %d kbytes\n", testCase.dataSize() / 1024);
692    printf("# Chunk size %d kbytes\n", testCase.chunkSize() / 1024);
693    printf("# Sync: %s\n", testCase.syncAsStr());
694
695    if (!testCase.cpuScaling())
696    {
697        android::disableCpuScaling();
698    }
699
700    switch(testCase.type()) {
701        case TestCase::WRITE:
702            testCase.mTestBody = testWrite;
703            break;
704        case TestCase::READ:
705            testCase.mTestBody = testRead;
706            break;
707        case TestCase::OPEN_CREATE:
708            testCase.mTestBody = testOpenCreate;
709            break;
710        case TestCase::READ_WRITE:
711            testCase.mTestBody = testReadWrite;
712            break;
713        case TestCase::TRAVERSE:
714            testCase.mTestBody = testTraverse;
715            break;
716        default:
717            fprintf(stderr, "Unknown test type %s", testCase.name());
718            exit(EXIT_FAILURE);
719    }
720
721    testCase.createTimers();
722
723    bool ok;
724
725    ok = testCase.runTest();
726
727    cleanup();
728    if (!ok)
729    {
730        printf("error %d %s", errno, strerror(errno));
731        exit(EXIT_FAILURE);
732    }
733    else
734    {
735        exit(EXIT_SUCCESS);
736    }
737}
738