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 
84 extern char *optarg;
85 extern 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 
94 namespace  {
95 using android::kernelVersion;
96 using android::kMinKernelVersionBufferSize;
97 using android::schedFeatures;
98 using android::kMinSchedFeaturesBufferSize;
99 using android_test::StopWatch;
100 using android::writePidAndWaitForReply;
101 using android::waitForChildrenAndSignal;
102 using android::waitForChildrenOrExit;
103 using android_test::TestCase;
104 
105 const char *kAppName = "sdcard_perf_test";
106 const char *kTestDir = "/sdcard/perf";
107 const bool kVerbose = false;
108 
109 // Used by getopt to parse the command line.
110 struct 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 
usage()128 void 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.
printHeader(int argc,char ** argv,const TestCase & testCase)149 void 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.
cleanup()197 void 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.
parseCmdLine(int argc,char ** argv,TestCase * testCase)204 void 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 
readData(char * const chunk,const int idx,TestCase * testCase)290 bool 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 
testRead(TestCase * testCase)352 bool 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 
writeData(const char * const chunk,const int idx,TestCase * testCase)418 bool 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 
testWrite(TestCase * testCase)489 bool 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.
testReadWrite(TestCase * testCase)519 bool 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 
testOpenCreate(TestCase * testCase)531 bool 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 
writeTestFile(TestCase * testCase,const char * filename)559 bool 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;
595 fail:
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().
testTraverse(TestCase * testCase)608 bool 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 
main(int argc,char ** argv)682 int 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