1 2/* 3 * COPYRIGHT NOTICE: 4 * Copyright (c) Tim Bray, 1990. 5 * Copyright (c) Russell Coker, 1999. I have updated the program, added 6 * support for >2G on 32bit machines, and tests for file creation. 7 * Licensed under the GPL version 2.0. 8 * DISCLAIMER: 9 * This program is provided AS IS with no warranty of any kind, and 10 * The author makes no representation with respect to the adequacy of this 11 * program for any particular purpose or with respect to its adequacy to 12 * produce any particular result, and 13 * The author shall not be liable for loss or damage arising out of 14 * the use of this program regardless of how sustained, and 15 * In no event shall the author be liable for special, direct, indirect 16 * or consequential damage, loss, costs or fees or expenses of any 17 * nature or kind. 18 */ 19 20#ifdef OS2 21#define INCL_DOSFILEMGR 22#define INCL_DOSMISC 23#define INCL_DOSQUEUES 24#define INCL_DOSPROCESS 25#include <os2.h> 26#else 27#include <sys/wait.h> 28#include <unistd.h> 29#endif 30#include <sys/time.h> 31#include <time.h> 32#include <stdlib.h> 33#include "bonnie.h" 34#include "bon_io.h" 35#include "bon_file.h" 36#include "bon_time.h" 37#include "semaphore.h" 38#include <pwd.h> 39#include <grp.h> 40#include <ctype.h> 41#include <string.h> 42#include <sys/utsname.h> 43#include <signal.h> 44 45#ifdef AIX_MEM_SIZE 46#include <cf.h> 47#include <sys/cfgodm.h> 48#include <sys/cfgdb.h> 49#endif 50 51void usage(); 52 53class CGlobalItems 54{ 55public: 56 bool quiet; 57 bool fast; 58 bool sync_bonnie; 59 BonTimer timer; 60 int ram; 61 Semaphore sem; 62 char *name; 63 bool bufSync; 64 int chunk_bits; 65 int chunk_size() const { return m_chunk_size; } 66 bool *doExit; 67 void set_chunk_size(int size) 68 { delete m_buf; m_buf = new char[size]; m_chunk_size = size; } 69 70 char *buf() { return m_buf; } 71 72 CGlobalItems(bool *exitFlag); 73 ~CGlobalItems() { delete name; delete m_buf; } 74 75 void decrement_and_wait(int nr_sem); 76 77 void SetName(CPCCHAR path) 78 { 79 delete name; 80 name = new char[strlen(path) + 15]; 81#ifdef OS2 82 ULONG myPid = 0; 83 DosQuerySysInfo(QSV_FOREGROUND_PROCESS, QSV_FOREGROUND_PROCESS 84 , &myPid, sizeof(myPid)); 85#else 86 pid_t myPid = getpid(); 87#endif 88 sprintf(name, "%s/Bonnie.%d", path, int(myPid)); 89 } 90private: 91 int m_chunk_size; 92 char *m_buf; 93 94 95 CGlobalItems(const CGlobalItems &f); 96 CGlobalItems & operator =(const CGlobalItems &f); 97}; 98 99CGlobalItems::CGlobalItems(bool *exitFlag) 100 : quiet(false) 101 , fast(false) 102 , sync_bonnie(false) 103 , timer() 104 , ram(0) 105 , sem(SemKey, TestCount) 106 , name(NULL) 107 , bufSync(false) 108 , chunk_bits(DefaultChunkBits) 109 , doExit(exitFlag) 110 , m_chunk_size(DefaultChunkSize) 111 , m_buf(new char[m_chunk_size]) 112{ 113 SetName("."); 114} 115 116void CGlobalItems::decrement_and_wait(int nr_sem) 117{ 118 if(sem.decrement_and_wait(nr_sem)) 119 exit(1); 120} 121 122int TestDirOps(int directory_size, int max_size, int min_size 123 , int num_directories, CGlobalItems &globals); 124int TestFileOps(int file_size, CGlobalItems &globals); 125 126static bool exitNow; 127static bool already_printed_error; 128 129#undef USE_SA_SIGACTION 130#ifdef USE_SA_SIGACTION 131#define SIGNAL_NUMBER siginf->si_signo 132#else 133#define SIGNAL_NUMBER sig 134#endif 135 136extern "C" 137{ 138 void ctrl_c_handler(int sig 139#ifdef USE_SA_SIGACTION 140 , siginfo_t *siginf, void *unused 141#endif 142 ) 143 { 144 if(SIGNAL_NUMBER == SIGXCPU) 145 fprintf(stderr, "Exceeded CPU usage.\n"); 146 else if(SIGNAL_NUMBER == SIGXFSZ) 147 fprintf(stderr, "exceeded file storage limits.\n"); 148 exitNow = true; 149 } 150} 151 152int main(int argc, char *argv[]) 153{ 154 int file_size = DefaultFileSize; 155 int directory_size = DefaultDirectorySize; 156 int directory_max_size = DefaultDirectoryMaxSize; 157 int directory_min_size = DefaultDirectoryMinSize; 158 int num_bonnie_procs = 0; 159 int num_directories = 1; 160 int count = -1; 161 const char * machine = NULL; 162 char *userName = NULL, *groupName = NULL; 163 CGlobalItems globals(&exitNow); 164 bool setSize = false; 165 166 exitNow = false; 167 already_printed_error = false; 168 169 struct sigaction sa; 170#ifdef USE_SA_SIGACTION 171 sa.sa_sigaction = &ctrl_c_handler; 172 sa.sa_flags = SA_RESETHAND | SA_SIGINFO; 173#else 174 sa.sa_handler = ctrl_c_handler; 175 sa.sa_flags = SA_RESETHAND; 176#endif 177 if(sigaction(SIGINT, &sa, NULL) 178 || sigaction(SIGXCPU, &sa, NULL) 179 || sigaction(SIGXFSZ, &sa, NULL)) 180 { 181 printf("Can't handle SIGINT.\n"); 182 return 1; 183 } 184#ifdef USE_SA_SIGACTION 185 sa.sa_sigaction = NULL; 186#endif 187 sa.sa_handler = SIG_IGN; 188 if(sigaction(SIGHUP, &sa, NULL)) 189 { 190 printf("Can't handle SIGHUP.\n"); 191 return 1; 192 } 193 194#ifdef _SC_PHYS_PAGES 195 int page_size = sysconf(_SC_PAGESIZE); 196 int num_pages = sysconf(_SC_PHYS_PAGES); 197 if(page_size != -1 && num_pages != -1) 198 { 199 globals.ram = page_size/1024 * (num_pages/1024); 200 } 201#else 202 203#ifdef AIX_MEM_SIZE 204 struct CuAt *odm_obj; 205 int how_many; 206 207 odm_set_path("/etc/objrepos"); 208 odm_obj = getattr("sys0", "realmem", 0, &how_many); 209 globals.ram = atoi(odm_obj->value) / 1024; 210 odm_terminate(); 211 printf("Memory = %d MiB\n", globals.ram); 212#endif 213 214#endif 215 216 int int_c; 217 while(-1 != (int_c = getopt(argc, argv, "bd:fg:m:n:p:qr:s:u:x:y")) ) 218 { 219 switch(char(int_c)) 220 { 221 case '?': 222 case ':': 223 usage(); 224 break; 225 case 'b': 226 globals.bufSync = true; 227 break; 228 case 'd': 229 if(chdir(optarg)) 230 { 231 fprintf(stderr, "Can't change to directory \"%s\".\n", optarg); 232 usage(); 233 } 234 break; 235 case 'f': 236 globals.fast = true; 237 break; 238 case 'm': 239 machine = optarg; 240 break; 241 case 'n': 242 sscanf(optarg, "%d:%d:%d:%d", &directory_size 243 , &directory_max_size, &directory_min_size 244 , &num_directories); 245 break; 246 case 'p': 247 num_bonnie_procs = atoi(optarg); 248 /* Set semaphore to # of bonnie++ procs 249 to synchronize */ 250 break; 251 case 'q': 252 globals.quiet = true; 253 break; 254 case 'r': 255 globals.ram = atoi(optarg); 256 break; 257 case 's': 258 { 259 char *sbuf = strdup(optarg); 260 char *size = strtok(sbuf, ":"); 261 file_size = atoi(size); 262 char c = size[strlen(size) - 1]; 263 if(c == 'g' || c == 'G') 264 file_size *= 1024; 265 size = strtok(NULL, ""); 266 if(size) 267 { 268 int tmp = atoi(size); 269 c = size[strlen(size) - 1]; 270 if(c == 'k' || c == 'K') 271 tmp *= 1024; 272 globals.set_chunk_size(tmp); 273 } 274 setSize = true; 275 } 276 break; 277 case 'g': 278 if(groupName) 279 usage(); 280 groupName = optarg; 281 break; 282 case 'u': 283 { 284 if(userName) 285 usage(); 286 userName = strdup(optarg); 287 int i; 288 for(i = 0; userName[i] && userName[i] != ':'; i++) 289 {} 290 if(userName[i] == ':') 291 { 292 if(groupName) 293 usage(); 294 userName[i] = '\0'; 295 groupName = &userName[i + 1]; 296 } 297 } 298 break; 299 case 'x': 300 count = atoi(optarg); 301 break; 302 case 'y': 303 /* tell procs to synchronize via previous 304 defined semaphore */ 305 globals.sync_bonnie = true; 306 break; 307 } 308 } 309 if(optind < argc) 310 usage(); 311 312 if(globals.ram && !setSize) 313 { 314 if(file_size < (globals.ram * 2)) 315 file_size = globals.ram * 2; 316 // round up to the nearest gig 317 if(file_size % 1024 > 512) 318 file_size = file_size + 1024 - (file_size % 1024); 319 } 320 321 if(machine == NULL) 322 { 323 struct utsname utsBuf; 324 if(uname(&utsBuf) != -1) 325 machine = utsBuf.nodename; 326 } 327 328 if(userName || groupName) 329 { 330 if(bon_setugid(userName, groupName, globals.quiet)) 331 return 1; 332 if(userName) 333 free(userName); 334 } 335 else if(geteuid() == 0) 336 { 337 fprintf(stderr, "You must use the \"-u\" switch when running as root.\n"); 338 usage(); 339 } 340 341 if(num_bonnie_procs && globals.sync_bonnie) 342 usage(); 343 344 if(num_bonnie_procs) 345 { 346 if(num_bonnie_procs == -1) 347 { 348 return globals.sem.clear_sem(); 349 } 350 else 351 { 352 return globals.sem.create(num_bonnie_procs); 353 } 354 } 355 356 if(globals.sync_bonnie) 357 { 358 if(globals.sem.get_semid()) 359 return 1; 360 } 361 362 if(file_size < 0 || directory_size < 0 || (!file_size && !directory_size) ) 363 usage(); 364 if(globals.chunk_size() < 256 || globals.chunk_size() > Unit) 365 usage(); 366 int i; 367 globals.chunk_bits = 0; 368 for(i = globals.chunk_size(); i > 1; i = i >> 1, globals.chunk_bits++) 369 {} 370 if(1 << globals.chunk_bits != globals.chunk_size()) 371 usage(); 372 373 if( (directory_max_size != -1 && directory_max_size != -2) 374 && (directory_max_size < directory_min_size || directory_max_size < 0 375 || directory_min_size < 0) ) 376 usage(); 377 /* If the storage size is too big for the maximum number of files (1000G) */ 378 if(file_size > IOFileSize * MaxIOFiles) 379 usage(); 380 /* If the file size is so large and the chunk size is so small that we have 381 * more than 2G of chunks */ 382 if(globals.chunk_bits < 20 && file_size > (1 << (31 - 20 + globals.chunk_bits)) ) 383 usage(); 384 // if doing more than one test run then we print a header before the 385 // csv format output. 386 if(count > 1) 387 { 388 globals.timer.SetType(BonTimer::csv); 389 globals.timer.PrintHeader(stdout); 390 } 391#ifdef OS2 392 ULONG myPid = 0; 393 DosQuerySysInfo(QSV_FOREGROUND_PROCESS, QSV_FOREGROUND_PROCESS 394 , &myPid, sizeof(myPid)); 395#else 396 pid_t myPid = getpid(); 397#endif 398 srand(myPid ^ time(NULL)); 399 for(; count > 0 || count == -1; count--) 400 { 401 globals.timer.Initialize(); 402 int rc; 403 rc = TestFileOps(file_size, globals); 404 if(rc) return rc; 405 rc = TestDirOps(directory_size, directory_max_size, directory_min_size 406 , num_directories, globals); 407 if(rc) return rc; 408 // if we are only doing one test run then print a plain-text version of 409 // the results before printing a csv version. 410 if(count == -1) 411 { 412 globals.timer.SetType(BonTimer::txt); 413 rc = globals.timer.DoReport(machine, file_size, directory_size 414 , directory_max_size, directory_min_size 415 , num_directories, globals.chunk_size() 416 , globals.quiet ? stderr : stdout); 417 } 418 // print a csv version in every case 419 globals.timer.SetType(BonTimer::csv); 420 rc = globals.timer.DoReport(machine, file_size, directory_size 421 , directory_max_size, directory_min_size 422 , num_directories, globals.chunk_size(), stdout); 423 if(rc) return rc; 424 } 425} 426 427int 428TestFileOps(int file_size, CGlobalItems &globals) 429{ 430 if(file_size) 431 { 432 CFileOp file(globals.timer, file_size, globals.chunk_bits, globals.bufSync); 433 int num_chunks; 434 int words; 435 char *buf = globals.buf(); 436 int bufindex; 437 int i; 438 439 if(globals.ram && file_size < globals.ram * 2) 440 { 441 fprintf(stderr 442 , "File size should be double RAM for good results, RAM is %dM.\n" 443 , globals.ram); 444 return 1; 445 } 446 // default is we have 1M / 8K * 200 chunks = 25600 447 num_chunks = Unit / globals.chunk_size() * file_size; 448 449 int rc; 450 rc = file.open(globals.name, true, true); 451 if(rc) 452 return rc; 453 if(exitNow) 454 return EXIT_CTRL_C; 455 globals.timer.timestamp(); 456 457 if(!globals.fast) 458 { 459 globals.decrement_and_wait(Putc); 460 // Fill up a file, writing it a char at a time with the stdio putc() call 461 if(!globals.quiet) fprintf(stderr, "Writing with putc()..."); 462 for(words = 0; words < num_chunks; words++) 463 { 464 if(file.write_block_putc() == -1) 465 return 1; 466 if(exitNow) 467 return EXIT_CTRL_C; 468 } 469 fflush(NULL); 470 /* 471 * note that we always close the file before measuring time, in an 472 * effort to force as much of the I/O out as we can 473 */ 474 file.close(); 475 globals.timer.get_delta_t(Putc); 476 if(!globals.quiet) fprintf(stderr, "done\n"); 477 } 478 /* Write the whole file from scratch, again, with block I/O */ 479 if(file.reopen(true)) 480 return 1; 481 globals.decrement_and_wait(FastWrite); 482 if(!globals.quiet) fprintf(stderr, "Writing intelligently..."); 483 memset(buf, 0, globals.chunk_size()); 484 globals.timer.timestamp(); 485 bufindex = 0; 486 // for the number of chunks of file data 487 for(i = 0; i < num_chunks; i++) 488 { 489 if(exitNow) 490 return EXIT_CTRL_C; 491 // for each chunk in the Unit 492 buf[bufindex]++; 493 bufindex = (bufindex + 1) % globals.chunk_size(); 494 if(file.write_block(PVOID(buf)) == -1) 495 return io_error("write(2)"); 496 } 497 file.close(); 498 globals.timer.get_delta_t(FastWrite); 499 if(!globals.quiet) fprintf(stderr, "done\n"); 500 501 502 /* Now read & rewrite it using block I/O. Dirty one word in each block */ 503 if(file.reopen(false)) 504 return 1; 505 if (file.seek(0, SEEK_SET) == -1) 506 { 507 if(!globals.quiet) fprintf(stderr, "error in lseek(2) before rewrite\n"); 508 return 1; 509 } 510 globals.decrement_and_wait(ReWrite); 511 if(!globals.quiet) fprintf(stderr, "Rewriting..."); 512 globals.timer.timestamp(); 513 bufindex = 0; 514 for(words = 0; words < num_chunks; words++) 515 { // for each chunk in the file 516 if (file.read_block(PVOID(buf)) == -1) 517 return 1; 518 bufindex = bufindex % globals.chunk_size(); 519 buf[bufindex]++; 520 bufindex++; 521 if (file.seek(-1, SEEK_CUR) == -1) 522 return 1; 523 if (file.write_block(PVOID(buf)) == -1) 524 return io_error("re write(2)"); 525 if(exitNow) 526 return EXIT_CTRL_C; 527 } 528 file.close(); 529 globals.timer.get_delta_t(ReWrite); 530 if(!globals.quiet) fprintf(stderr, "done\n"); 531 532 533 if(!globals.fast) 534 { 535 // read them all back with getc() 536 if(file.reopen(false, true)) 537 return 1; 538 globals.decrement_and_wait(Getc); 539 if(!globals.quiet) fprintf(stderr, "Reading with getc()..."); 540 globals.timer.timestamp(); 541 542 for(words = 0; words < num_chunks; words++) 543 { 544 if(file.read_block_getc(buf) == -1) 545 return 1; 546 if(exitNow) 547 return EXIT_CTRL_C; 548 } 549 550 file.close(); 551 globals.timer.get_delta_t(Getc); 552 if(!globals.quiet) fprintf(stderr, "done\n"); 553 } 554 555 /* Now suck it in, Chunk at a time, as fast as we can */ 556 if(file.reopen(false)) 557 return 1; 558 if (file.seek(0, SEEK_SET) == -1) 559 return io_error("lseek before read"); 560 globals.decrement_and_wait(FastRead); 561 if(!globals.quiet) fprintf(stderr, "Reading intelligently..."); 562 globals.timer.timestamp(); 563 for(i = 0; i < num_chunks; i++) 564 { /* per block */ 565 if ((words = file.read_block(PVOID(buf))) == -1) 566 return io_error("read(2)"); 567 if(exitNow) 568 return EXIT_CTRL_C; 569 } /* per block */ 570 file.close(); 571 globals.timer.get_delta_t(FastRead); 572 if(!globals.quiet) fprintf(stderr, "done\n"); 573 574 globals.timer.timestamp(); 575 if(file.seek_test(globals.quiet, globals.sem)) 576 return 1; 577 578 /* 579 * Now test random seeks; first, set up for communicating with children. 580 * The object of the game is to do "Seeks" lseek() calls as quickly 581 * as possible. So we'll farm them out among SeekProcCount processes. 582 * We'll control them by writing 1-byte tickets down a pipe which 583 * the children all read. We write "Seeks" bytes with val 1, whichever 584 * child happens to get them does it and the right number of seeks get 585 * done. 586 * The idea is that since the write() of the tickets is probably 587 * atomic, the parent process likely won't get scheduled while the 588 * children are seeking away. If you draw a picture of the likely 589 * timelines for three children, it seems likely that the seeks will 590 * overlap very nicely with the process scheduling with the effect 591 * that there will *always* be a seek() outstanding on the file. 592 * Question: should the file be opened *before* the fork, so that 593 * all the children are lseeking on the same underlying file object? 594 */ 595 } 596 return 0; 597} 598 599int 600TestDirOps(int directory_size, int max_size, int min_size 601 , int num_directories, CGlobalItems &globals) 602{ 603 COpenTest open_test(globals.chunk_size(), globals.bufSync, globals.doExit); 604 if(!directory_size) 605 { 606 return 0; 607 } 608 // if directory_size (in K) * data per file*2 > (ram << 10) (IE memory /1024) 609 // then the storage of file names will take more than half RAM and there 610 // won't be enough RAM to have Bonnie++ paged in and to have a reasonable 611 // meta-data cache. 612 if(globals.ram && directory_size * MaxDataPerFile * 2 > (globals.ram << 10)) 613 { 614 fprintf(stderr 615 , "When testing %dK of files in %d MiB of RAM the system is likely to\n" 616 "start paging Bonnie++ data and the test will give suspect\n" 617 "results, use less files or install more RAM for this test.\n" 618 , directory_size, globals.ram); 619 return 1; 620 } 621 // Can't use more than 1G of RAM 622 if(directory_size * MaxDataPerFile > (1 << 20)) 623 { 624 fprintf(stderr, "Not enough ram to test with %dK files.\n" 625 , directory_size); 626 return 1; 627 } 628 globals.decrement_and_wait(CreateSeq); 629 if(!globals.quiet) fprintf(stderr, "Create files in sequential order..."); 630 if(open_test.create(globals.name, globals.timer, directory_size 631 , max_size, min_size, num_directories, false)) 632 return 1; 633 globals.decrement_and_wait(StatSeq); 634 if(!globals.quiet) fprintf(stderr, "done.\nStat files in sequential order..."); 635 if(open_test.stat_sequential(globals.timer)) 636 return 1; 637 globals.decrement_and_wait(DelSeq); 638 if(!globals.quiet) fprintf(stderr, "done.\nDelete files in sequential order..."); 639 if(open_test.delete_sequential(globals.timer)) 640 return 1; 641 if(!globals.quiet) fprintf(stderr, "done.\n"); 642 643 globals.decrement_and_wait(CreateRand); 644 if(!globals.quiet) fprintf(stderr, "Create files in random order..."); 645 if(open_test.create(globals.name, globals.timer, directory_size 646 , max_size, min_size, num_directories, true)) 647 return 1; 648 globals.decrement_and_wait(StatRand); 649 if(!globals.quiet) fprintf(stderr, "done.\nStat files in random order..."); 650 if(open_test.stat_random(globals.timer)) 651 return 1; 652 globals.decrement_and_wait(DelRand); 653 if(!globals.quiet) fprintf(stderr, "done.\nDelete files in random order..."); 654 if(open_test.delete_random(globals.timer)) 655 return 1; 656 if(!globals.quiet) fprintf(stderr, "done.\n"); 657 return 0; 658} 659 660void 661usage() 662{ 663 fprintf(stderr, 664 "usage: bonnie++ [-d scratch-dir] [-s size(MiB)[:chunk-size(b)]]\n" 665 " [-n number-to-stat[:max-size[:min-size][:num-directories]]]\n" 666 " [-m machine-name]\n" 667 " [-r ram-size-in-MiB]\n" 668 " [-x number-of-tests] [-u uid-to-use:gid-to-use] [-g gid-to-use]\n" 669 " [-q] [-f] [-b] [-p processes | -y]\n" 670 "\nVersion: " BON_VERSION "\n"); 671 exit(1); 672} 673 674int 675io_error(CPCCHAR message, bool do_exit) 676{ 677 char buf[1024]; 678 679 if(!already_printed_error && !do_exit) 680 { 681 sprintf(buf, "Bonnie: drastic I/O error (%s)", message); 682 perror(buf); 683 already_printed_error = 1; 684 } 685 if(do_exit) 686 exit(1); 687 return(1); 688} 689 690