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