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