sdcard_perf_test.cpp revision 39c016f875b793296a121f41de5775b88f6fa1c9
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
41#include "stopwatch.h"
42#include "sysutil.h"
43#include "testcase.h"
44
45// Stress test for the sdcard. Use this to generate some load on the
46// sdcard and collect performance statistics. The ouput is either a
47// human readable report or the raw timing samples that can be
48// processed using another tool.
49//
50// Needs debugfs:
51//   adb root;
52//   adb shell mount -t debugfs none /sys/kernel/debug
53//
54// The following tests are defined (value of the --test flag):
55//  write:       Open a file write some random data and close.
56//  read:        Open a file read it and close.
57//  read_write:  Combine readers and writers.
58//  open_create: Open|create an non existing file.
59//
60// For each run you can control how many processes will run the test in
61// parallel to simulate a real load (--procnb flag)
62//
63// For each process, the test selected will be executed many time to
64// get a meaningful average/min/max (--iterations flag)
65//
66// Use --dump to also get the raw data.
67//
68// For read/write tests, size is the number of Kbytes to use.
69//
70// To build: mmm system/extras/tests/sdcard/Android.mk SDCARD_TESTS=1
71//
72// Examples:
73// adb shell /system/bin/sdcard_perf_test --test=read --size=1000 --chunk-size=100 --procnb=1 --iterations=10 --dump > /tmp/data.txt
74// adb shell /system/bin/sdcard_perf_test --test=write --size=1000 --chunk-size=100 --procnb=1 --iterations=100 --dump > /tmp/data.txt
75//
76// To watch the memory: cat /proc/meminfo
77// If the phone crashes, look at /proc/last_kmsg on reboot.
78//
79// TODO: It would be cool if we could play with various fadvise()
80// strategies in here to see how tweaking read-ahead changes things.
81//
82
83extern char *optarg;
84extern int optind, opterr, optopt;
85
86// TODO: No clue where fadvise is. Disabled for now.
87#define FADVISE(fd, off, len, advice) (void)0
88
89#ifndef min
90#define min(a,b) (((a)>(b))?(b):(a))
91#endif
92
93namespace  {
94using android::kernelVersion;
95using android::kMinKernelVersionBufferSize;
96using android::schedFeatures;
97using android::kMinSchedFeaturesBufferSize;
98using android_test::StopWatch;
99using android::writePidAndWaitForReply;
100using android::waitForChildrenAndSignal;
101using android::waitForChildrenOrExit;
102using android_test::TestCase;
103
104const char *kAppName = "sdcard_perf_test";
105const char *kTestDir = "/sdcard/perf";
106const bool kVerbose = false;
107
108// Used by getopt to parse the command line.
109struct option long_options[] = {
110    {"size", required_argument, 0, 's'},
111    {"chunk-size", required_argument, 0, 'S'},
112    {"iterations",  required_argument, 0, 'i'},
113    {"procnb",  required_argument, 0, 'p'},
114    {"test",  required_argument, 0, 't'},
115    {"dump",  no_argument, 0, 'd'},
116    {"cpu-scaling",  no_argument, 0, 'c'},
117    {"sync",  required_argument, 0, 'f'},
118    {"truncate", no_argument, 0, 'e'},
119    {"no-new-fair-sleepers", no_argument, 0, 'z'},
120    {"no-normalized-sleepers", no_argument, 0, 'Z'},
121    {"fadvise", required_argument, 0, 'a'},
122    {"help", no_argument, 0, 'h'},
123    {0, 0, 0, 0},
124};
125
126void usage()
127{
128    printf("sdcard_perf_test --test=write|read|read_write|open_create [options]\n\n"
129           "  -t --test:        Select the test.\n"
130           "  -s --size:        Size in kbytes of the data.\n"
131           "  -S --chunk-size:  Size of a chunk. Default to size ie 1 chunk.\n"
132           "                    Data will be written/read using that chunk size.\n"
133           "  -i --iterations:  Number of time a process should carry its task.\n"
134           "  -p --procnb:      Number of processes to use.\n"
135           "  -d --dump:        Print the raw timing on stdout.\n"
136           "  -c --cpu-scaling: Enable cpu scaling.\n"
137           "  -s --sync: fsync|sync Use fsync or sync in write test. Default: no sync call.\n"
138           "  -e --truncate:    Truncate to size the file on creation.\n"
139           "  -z --no-new-fair-sleepers: Turn them off. You need to mount debugfs.\n"
140           "  -Z --no-normalized-sleepers: Turn them off. You need to mount debugfs.\n"
141           "  -a --fadvise:     Specify an fadvise policy (not supported).\n"
142           );
143}
144
145// Print command line, pid, kernel version, OOM adj and scheduler.
146void printHeader(int argc, char **argv, const TestCase& testCase)
147{
148    printf("# Command: ");
149    for (int i = 0; i < argc; ++i)
150    {
151        printf("%s ", argv[i]);
152    }
153    printf("\n");
154
155    printf("# Pid: %d\n", getpid());
156
157    {
158        char buffer[kMinKernelVersionBufferSize] = {0, };
159        if (kernelVersion(buffer, sizeof(buffer)) > 0)
160        {
161            printf("# Kernel: %s", buffer);
162        }
163    }
164
165    // Earlier on, running this test was crashing the phone. It turned
166    // out that it was using too much memory but its oom_adj value was
167    // -17 which means disabled. -16 is the system_server and 0 is
168    // typically what applications run at. The issue is that adb runs
169    // at -17 and so is this test. We force oom_adj to 0 unless the
170    // oom_adj option has been used.
171    // TODO: We talked about adding an option to control oom_adj, not
172    // sure if we still need that.
173    int oomAdj = android::pidOutOfMemoryAdj();
174
175    printf("# Oom_adj: %d ", oomAdj);
176    if (oomAdj < 0)
177    {
178        android::setPidOutOfMemoryAdj(0);
179        printf("adjuted to %d", android::pidOutOfMemoryAdj());
180    }
181    printf("\n");
182
183    {
184        char buffer[kMinSchedFeaturesBufferSize] = {0, };
185        if (schedFeatures(buffer, sizeof(buffer)) > 0)
186        {
187            printf("# Sched features: %s", buffer);
188        }
189    }
190    printf("# Fadvise: %s\n", testCase.fadviseAsStr());
191}
192
193// Remove all the files under kTestDir and clear the caches.
194void cleanup() {
195    android::resetDirectory(kTestDir);
196    android::syncAndDropCaches();  // don't pollute runs.
197}
198
199// @param argc, argv have a wild guess.
200// @param[out] testCase Structure built from the cmd line args.
201void parseCmdLine(int argc, char **argv, TestCase *testCase)\
202{
203    int c;
204
205    while(true)
206    {
207        // getopt_long stores the option index here.
208        int option_index = 0;
209
210        c = getopt_long (argc, argv,
211                         "hS:s:i:p:t:dcf:ezZa:",
212                         long_options,
213                         &option_index);
214        // Detect the end of the options.
215        if (c == -1) break;
216
217        switch (c)
218        {
219            case 's':
220                testCase->setDataSize(atoi(optarg) * 1024);
221                break;
222            case 'S':
223                testCase->setChunkSize(atoi(optarg) * 1024);
224                break;
225            case 'i':
226                testCase->setIter(atoi(optarg));
227                printf("# Iterations: %d\n", testCase->iter());
228                break;
229            case 'p':
230                testCase->setNproc(atoi(optarg));
231                printf("# Proc nb: %d\n", testCase->nproc());
232                break;
233            case 't':
234                testCase->setTypeFromName(optarg);
235                printf("# Test name %s\n", testCase->name());
236                break;
237            case 'd':
238                testCase->setDump();
239                break;
240            case 'c':
241                printf("# Cpu scaling is on\n");
242                testCase->setCpuScaling();
243                break;
244            case 'f':
245                if (strcmp("sync", optarg) == 0) {
246                    testCase->setSync(TestCase::SYNC);
247                } else if (strcmp("fsync", optarg) == 0) {
248                    testCase->setSync(TestCase::FSYNC);
249                }
250                break;
251            case 'e':  // e for empty
252                printf("# Will truncate to size\n");
253                testCase->setTruncateToSize();
254                break;
255            case 'z':  // no new fair sleepers
256                testCase->setNewFairSleepers(false);
257                break;
258            case 'Z':  // no normalized sleepers
259                testCase->setNormalizedSleepers(false);
260                break;
261            case 'a':  // fadvise
262                testCase->setFadvise(optarg);
263                break;
264            case 'h':
265                usage();
266                exit(0);
267            default:
268                fprintf(stderr, "Unknown option %s\n", optarg);
269                exit(EXIT_FAILURE);
270        }
271    }
272}
273
274// ----------------------------------------------------------------------
275// READ TEST
276
277// Read a file.  We use a new file each time to avoid any caching
278// effect that would happen if we were reading the same file each
279// time.
280// @param chunk buffer large enough where the chunk read are written.
281// @param idx iteration number.
282// @param testCase has all the timers and paramters to run the test.
283
284bool readData(char *const chunk, const int idx, TestCase *testCase)
285{
286    char filename[80] = {'\0',};
287
288    sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid());
289
290    testCase->openTimer()->start();
291    int fd = open(filename, O_RDONLY);
292    testCase->openTimer()->stop();
293
294    if (fd < 0)
295    {
296        fprintf(stderr, "Open read only failed.");
297        return false;
298    }
299    FADVISE(fd, 0, 0, testCase->fadvise());
300
301    size_t left = testCase->dataSize();
302    pid_t *pid = (pid_t*)chunk;
303    while (left > 0)
304    {
305        char *dest = chunk;
306        size_t chunk_size = testCase->chunkSize();
307
308        if (chunk_size > left)
309        {
310            chunk_size = left;
311            left = 0;
312        }
313        else
314        {
315            left -= chunk_size;
316        }
317
318        testCase->readTimer()->start();
319        while (chunk_size > 0)
320        {
321            ssize_t s = read(fd, dest, chunk_size);
322            if (s < 0)
323            {
324                fprintf(stderr, "Failed to read.\n");
325                close(fd);
326                return false;
327            }
328            chunk_size -= s;
329            dest += s;
330        }
331        testCase->readTimer()->stop();
332    }
333    close(fd);
334    if (testCase->pid() != *pid)
335    {
336        fprintf(stderr, "Wrong pid found @ read block %x != %x\n", testCase->pid(), *pid);
337        return false;
338    }
339    else
340    {
341        return true;
342    }
343}
344
345
346bool testRead(TestCase *testCase) {
347    // Setup the testcase by writting some dummy files.
348    const size_t size = testCase->dataSize();
349    size_t chunk_size = testCase->chunkSize();
350    char *const chunk = new char[chunk_size];
351
352    memset(chunk, 0xaa, chunk_size);
353    *((pid_t *)chunk) = testCase->pid(); // write our pid at the beginning of each chunk
354
355    size_t iter = testCase->iter();
356
357    // since readers are much faster we increase the number of
358    // iteration to last longer and have concurrent read/write
359    // thoughout the whole test.
360    if (testCase->type() == TestCase::READ_WRITE)
361    {
362        iter *= TestCase::kReadWriteFactor;
363    }
364
365    for (size_t i = 0; i < iter; ++i)
366    {
367        char filename[80] = {'\0',};
368
369        sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid());
370        int fd = open(filename, O_RDWR | O_CREAT);
371
372        size_t left = size;
373        while (left > 0)
374        {
375            if (chunk_size > left)
376            {
377                chunk_size = left;
378            }
379            ssize_t written = write(fd, chunk, chunk_size);
380            if (written < 0)
381            {
382                fprintf(stderr, "Write failed %d %s.", errno, strerror(errno));
383                return false;
384            }
385            left -= written;
386        }
387        close(fd);
388    }
389    if (kVerbose) printf("Child %d all chunk written\n", testCase->pid());
390
391    android::syncAndDropCaches();
392    testCase->signalParentAndWait();
393
394    // Start the read test.
395    testCase->testTimer()->start();
396    for (size_t i = 0; i < iter; ++i)
397    {
398        if (!readData(chunk, i, testCase))
399        {
400            return false;
401        }
402    }
403    testCase->testTimer()->stop();
404
405    delete [] chunk;
406    return true;
407}
408
409// ----------------------------------------------------------------------
410// WRITE TEST
411
412bool writeData(const char *const chunk, const int idx, TestCase *testCase) {
413    char filename[80] = {'\0',};
414
415    sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid());
416    testCase->openTimer()->start();
417    int fd = open(filename, O_RDWR | O_CREAT);  // no O_TRUNC, see header comment
418    testCase->openTimer()->stop();
419
420    if (fd < 0)
421    {
422        fprintf(stderr, "Open write failed.");
423        return false;
424    }
425    FADVISE(fd, 0, 0, testCase->fadvise());
426
427    if (testCase->truncateToSize())
428    {
429        testCase->truncateTimer()->start();
430        ftruncate(fd, testCase->dataSize());
431        testCase->truncateTimer()->stop();
432    }
433
434    size_t left = testCase->dataSize();
435    while (left > 0)
436    {
437        const char *dest = chunk;
438        size_t chunk_size = testCase->chunkSize();
439
440        if (chunk_size > left)
441        {
442            chunk_size = left;
443            left = 0;
444        }
445        else
446        {
447            left -= chunk_size;
448        }
449
450
451        testCase->writeTimer()->start();
452        while (chunk_size > 0)
453        {
454            ssize_t s = write(fd, dest, chunk_size);
455            if (s < 0)
456            {
457                fprintf(stderr, "Failed to write.\n");
458                close(fd);
459                return false;
460            }
461            chunk_size -= s;
462            dest += s;
463        }
464        testCase->writeTimer()->stop();
465    }
466
467    if (TestCase::FSYNC == testCase->sync())
468    {
469        testCase->syncTimer()->start();
470        fsync(fd);
471        testCase->syncTimer()->stop();
472    }
473    else if (TestCase::SYNC == testCase->sync())
474    {
475        testCase->syncTimer()->start();
476        sync();
477        testCase->syncTimer()->stop();
478    }
479    close(fd);
480    return true;
481}
482
483bool testWrite(TestCase *testCase)
484{
485    const size_t size = testCase->dataSize();
486    size_t chunk_size = testCase->chunkSize();
487    char *data = new char[chunk_size];
488
489    memset(data, 0xaa, chunk_size);
490    // TODO: write the pid at the beginning like in the write
491    // test. Have a mode where we check the write was correct.
492    testCase->signalParentAndWait();
493
494    testCase->testTimer()->start();
495    for (size_t i = 0; i < testCase->iter(); ++i)
496    {
497        if (!writeData(data, i, testCase))
498        {
499            return false;
500        }
501    }
502    testCase->testTimer()->stop();
503    delete [] data;
504    return true;
505}
506
507
508// ----------------------------------------------------------------------
509// READ WRITE
510
511// Mix of read and write test. Even PID run the write test. Odd PID
512// the read test. Not fool proof but work most of the time.
513bool testReadWrite(TestCase *testCase)
514{
515    if (getpid() & 0x1) {
516        return testRead(testCase);
517    } else {
518        return testWrite(testCase);
519    }
520}
521
522// ----------------------------------------------------------------------
523// OPEN CREATE TEST
524
525bool testOpenCreate(TestCase *testCase)
526{
527    char filename[80] = {'\0',};
528
529    testCase->signalParentAndWait();
530    testCase->testTimer()->start();
531
532    for (size_t i = 0; i < testCase->iter(); ++i)
533    {
534        sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid());
535
536        int fd = open(filename, O_RDWR | O_CREAT);
537        FADVISE(fd, 0, 0, testCase->fadvise());
538
539        if (testCase->truncateToSize())
540        {
541            ftruncate(fd, testCase->dataSize());
542        }
543        if (fd < 0)
544        {
545            return false;
546        }
547        close(fd);
548    }
549    testCase->testTimer()->stop();
550    return true;
551}
552
553}  // anonymous namespace
554
555int main(int argc, char **argv)
556{
557    android_test::TestCase testCase(kAppName);
558
559    cleanup();
560
561    parseCmdLine(argc, argv, &testCase);
562    printHeader(argc, argv, testCase);
563
564    printf("# File size %d kbytes\n", testCase.dataSize() / 1024);
565    printf("# Chunk size %d kbytes\n", testCase.chunkSize() / 1024);
566    printf("# Sync: %s\n", testCase.syncAsStr());
567
568    if (!testCase.cpuScaling())
569    {
570        android::disableCpuScaling();
571    }
572
573    switch(testCase.type()) {
574        case TestCase::WRITE:
575            testCase.mTestBody = testWrite;
576            break;
577        case TestCase::READ:
578            testCase.mTestBody = testRead;
579            break;
580        case TestCase::OPEN_CREATE:
581            testCase.mTestBody = testOpenCreate;
582            break;
583        case TestCase::READ_WRITE:
584            testCase.mTestBody = testReadWrite;
585            break;
586        default:
587            fprintf(stderr, "Unknown test type %s", testCase.name());
588            exit(EXIT_FAILURE);
589    }
590
591    testCase.createTimers();
592
593    bool ok;
594
595    ok = testCase.runTest();
596
597    cleanup();
598    if (!ok)
599    {
600        printf("error %d %s", errno, strerror(errno));
601        exit(EXIT_FAILURE);
602    }
603    else
604    {
605        exit(EXIT_SUCCESS);
606    }
607}
608