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
83 extern char *optarg;
84 extern 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
93 namespace {
94 using android::kernelVersion;
95 using android::kMinKernelVersionBufferSize;
96 using android::schedFeatures;
97 using android::kMinSchedFeaturesBufferSize;
98 using android_test::StopWatch;
99 using android::writePidAndWaitForReply;
100 using android::waitForChildrenAndSignal;
101 using android::waitForChildrenOrExit;
102 using android_test::TestCase;
103
104 const char *kAppName = "sdcard_perf_test";
105 const char *kTestDir = "/sdcard/perf";
106 const bool kVerbose = false;
107
108 // Used by getopt to parse the command line.
109 struct 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
usage()126 void 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.
printHeader(int argc,char ** argv,const TestCase & testCase)146 void 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.
cleanup()194 void 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.
parseCmdLine(int argc,char ** argv,TestCase * testCase)201 void 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
readData(char * const chunk,const int idx,TestCase * testCase)284 bool 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
testRead(TestCase * testCase)346 bool 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
writeData(const char * const chunk,const int idx,TestCase * testCase)412 bool 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
testWrite(TestCase * testCase)483 bool 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.
testReadWrite(TestCase * testCase)513 bool 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
testOpenCreate(TestCase * testCase)525 bool 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
main(int argc,char ** argv)555 int 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