/* * COPYRIGHT NOTICE: * Copyright (c) Tim Bray, 1990. * Copyright (c) Russell Coker, 1999. I have updated the program, added * support for >2G on 32bit machines, and tests for file creation. * Licensed under the GPL version 2.0. * DISCLAIMER: * This program is provided AS IS with no warranty of any kind, and * The author makes no representation with respect to the adequacy of this * program for any particular purpose or with respect to its adequacy to * produce any particular result, and * The author shall not be liable for loss or damage arising out of * the use of this program regardless of how sustained, and * In no event shall the author be liable for special, direct, indirect * or consequential damage, loss, costs or fees or expenses of any * nature or kind. */ #include "bonnie.h" #include #ifdef OS2 #define INCL_DOSMISC #endif #ifdef NON_UNIX typedef char Sync; #include "getopt.h" #endif #ifdef OS2 #define INCL_DOSFILEMGR #define INCL_DOSMISC #define INCL_DOSQUEUES #define INCL_DOSPROCESS #include #endif #ifdef WIN32 #include #include #endif #ifndef NON_UNIX #include "conf.h" #ifdef HAVE_ALGORITHM #include #else #ifdef HAVE_ALGO #include #else #include #endif #endif #include #include #include #include #include #include #include "sync.h" #endif #include #include "bon_io.h" #include "bon_file.h" #include "bon_time.h" #include "rand.h" #include #include #include void usage(); class CGlobalItems { public: bool quiet; int byte_io_size; bool sync_bonnie; BonTimer timer; int ram; Sync *syn; char *name; bool bufSync; int io_chunk_bits; int file_chunk_bits; int io_chunk_size() const { return m_io_chunk_size; } int file_chunk_size() const { return m_file_chunk_size; } bool *doExit; void set_io_chunk_size(int size) { delete m_buf; m_buf = new char[__max(size, m_file_chunk_size)]; m_io_chunk_size = size; } void set_file_chunk_size(int size) { delete m_buf; m_buf = new char[__max(size, m_io_chunk_size)]; m_file_chunk_size = size; } char *buf() { return m_buf; } CGlobalItems(bool *exitFlag); ~CGlobalItems() { delete name; delete m_buf; #ifndef NON_UNIX delete syn; #endif } void decrement_and_wait(int nr_sem); void SetName(CPCCHAR path) { delete name; name = new char[strlen(path) + 15]; #ifdef OS2 ULONG myPid = 0; DosQuerySysInfo(QSV_FOREGROUND_PROCESS, QSV_FOREGROUND_PROCESS , &myPid, sizeof(myPid)); #else pid_t myPid = sys_getpid(); #endif sprintf(name, "%s/Bonnie.%d", path, int(myPid)); } #ifndef NON_UNIX void setSync(SYNC_TYPE type, int semKey = 0, int num_tests = 0) { syn = new Sync(type, semKey, num_tests); } #endif private: int m_io_chunk_size; int m_file_chunk_size; char *m_buf; CGlobalItems(const CGlobalItems &f); CGlobalItems & operator =(const CGlobalItems &f); }; CGlobalItems::CGlobalItems(bool *exitFlag) : quiet(false) , byte_io_size(DefaultByteIO) , sync_bonnie(false) , timer() , ram(0) , syn(NULL) , name(NULL) , bufSync(false) , io_chunk_bits(DefaultChunkBits) , file_chunk_bits(DefaultChunkBits) , doExit(exitFlag) , m_io_chunk_size(DefaultChunkSize) , m_file_chunk_size(DefaultChunkSize) , m_buf(new char[__max(m_io_chunk_size, m_file_chunk_size)]) { SetName("."); } void CGlobalItems::decrement_and_wait(int nr_sem) { #ifndef NON_UNIX if(syn->decrement_and_wait(nr_sem)) exit(1); #endif } int TestDirOps(int directory_size, int max_size, int min_size , int num_directories, CGlobalItems &globals); int TestFileOps(int file_size, CGlobalItems &globals); static bool exitNow; static bool already_printed_error; #ifndef NON_UNIX extern "C" { void ctrl_c_handler(int sig, siginfo_t *siginf, void *unused) { if(siginf->si_signo == SIGXCPU) fprintf(stderr, "Exceeded CPU usage.\n"); else if(siginf->si_signo == SIGXFSZ) fprintf(stderr, "exceeded file storage limits.\n"); exitNow = true; } } #endif int main(int argc, char *argv[]) { int file_size = DefaultFileSize; int directory_size = DefaultDirectorySize; int directory_max_size = DefaultDirectoryMaxSize; int directory_min_size = DefaultDirectoryMinSize; int num_bonnie_procs = 0; int num_directories = 1; int test_count = -1; const char * machine = NULL; #ifndef NON_UNIX char *userName = NULL, *groupName = NULL; #endif CGlobalItems globals(&exitNow); bool setSize = false; exitNow = false; already_printed_error = false; #ifndef NON_UNIX struct sigaction sa; sa.sa_sigaction = &ctrl_c_handler; sa.sa_flags = SA_RESETHAND | SA_SIGINFO; if(sigaction(SIGINT, &sa, NULL) || sigaction(SIGXCPU, &sa, NULL) || sigaction(SIGXFSZ, &sa, NULL)) { printf("Can't handle SIGINT.\n"); return 1; } sa.sa_handler = SIG_IGN; if(sigaction(SIGHUP, &sa, NULL)) { printf("Can't handle SIGHUP.\n"); return 1; } #endif #ifdef _SC_PHYS_PAGES int page_size = sysconf(_SC_PAGESIZE); int num_pages = sysconf(_SC_PHYS_PAGES); if(page_size != -1 && num_pages != -1) { globals.ram = page_size/1024 * (num_pages/1024); } #endif #ifdef WIN32 MEMORYSTATUS ms; GlobalMemoryStatus(&ms); globals.ram = ms.dwTotalPhys / 1024 / 1024; #endif pid_t myPid = 0; #ifdef OS2 DosQuerySysInfo(QSV_FOREGROUND_PROCESS, QSV_FOREGROUND_PROCESS , &myPid, sizeof(myPid)); #else myPid = sys_getpid(); #endif globals.timer.random_source.seedNum(myPid ^ time(NULL)); int concurrency = 1; int int_c; while(-1 != (int_c = getopt(argc, argv, "bc:d:f::g:l:m:n:p:qr:s:u:x:y:z:Z:")) ) { switch(char(int_c)) { case '?': case ':': usage(); break; case 'b': globals.bufSync = true; break; case 'c': concurrency = atoi(optarg); break; case 'd': if(sys_chdir(optarg)) { fprintf(stderr, "Can't change to directory \"%s\".\n", optarg); usage(); } break; case 'f': if(optarg) globals.byte_io_size = atoi(optarg); else globals.byte_io_size = 0; break; case 'm': machine = optarg; break; case 'n': { char *sbuf = _strdup(optarg); char *size = strtok(sbuf, ":"); directory_size = size_from_str(size, "m"); size = strtok(NULL, ":"); if(size) { directory_max_size = size_from_str(size, "kmg"); size = strtok(NULL, ":"); if(size) { directory_min_size = size_from_str(size, "kmg"); size = strtok(NULL, ":"); if(size) { num_directories = size_from_str(size, "k"); size = strtok(NULL, ""); if(size) { int tmp = size_from_str(size, "kmg"); globals.set_file_chunk_size(tmp); } } } } free(sbuf); } break; case 'p': num_bonnie_procs = atoi(optarg); /* Set semaphore to # of bonnie++ procs to synchronize */ break; case 'q': globals.quiet = true; break; case 'r': globals.ram = atoi(optarg); break; case 's': { char *sbuf = _strdup(optarg); char *size = strtok(sbuf, ":"); file_size = size_from_str(size, "gt"); size = strtok(NULL, ""); if(size) { int tmp = size_from_str(size, "k"); globals.set_io_chunk_size(tmp); } setSize = true; free(sbuf); } break; #ifndef NON_UNIX case 'g': if(groupName) usage(); groupName = optarg; break; case 'u': { if(userName) usage(); userName = _strdup(optarg); int i; for(i = 0; userName[i] && userName[i] != ':'; i++); if(userName[i] == ':') { if(groupName) usage(); userName[i] = '\0'; groupName = &userName[i + 1]; } } break; #endif case 'x': test_count = atoi(optarg); break; #ifndef NON_UNIX case 'y': /* tell procs to synchronize via previous defined semaphore */ switch(tolower(optarg[0])) { case 's': globals.setSync(eSem, SemKey, TestCount); break; case 'p': globals.setSync(ePrompt); break; } globals.sync_bonnie = true; break; #endif case 'z': { UINT tmp; if(sscanf(optarg, "%u", &tmp) == 1) globals.timer.random_source.seedNum(tmp); } break; case 'Z': { if(globals.timer.random_source.seedFile(optarg)) return eParam; } break; } } if(concurrency < 1 || concurrency > 200) usage(); #ifndef NON_UNIX if(!globals.syn) globals.setSync(eNone); #endif if(optind < argc) usage(); if(globals.ram && !setSize) { if(file_size < (globals.ram * 2)) file_size = globals.ram * 2; // round up to the nearest gig if(file_size % 1024 > 512) file_size = file_size + 1024 - (file_size % 1024); } globals.byte_io_size = __min(file_size, globals.byte_io_size); globals.byte_io_size = __max(0, globals.byte_io_size); if(machine == NULL) { #ifdef WIN32 char utsBuf[MAX_COMPUTERNAME_LENGTH + 2]; DWORD size = MAX_COMPUTERNAME_LENGTH + 1; if(GetComputerName(utsBuf, &size)) machine = _strdup(utsBuf); #else #ifdef OS2 machine = "OS/2"; #else struct utsname utsBuf; if(uname(&utsBuf) != -1) machine = utsBuf.nodename; #endif #endif } globals.timer.setMachineName(machine); globals.timer.setConcurrency(concurrency); #ifndef NON_UNIX if(userName || groupName) { if(bon_setugid(userName, groupName, globals.quiet)) return 1; if(userName) free(userName); } else if(geteuid() == 0) { fprintf(stderr, "You must use the \"-u\" switch when running as root.\n"); usage(); } #endif if(num_bonnie_procs && globals.sync_bonnie) usage(); #ifndef NON_UNIX if(num_bonnie_procs) { globals.setSync(eSem, SemKey, TestCount); if(num_bonnie_procs == -1) { return globals.syn->clear_sem(); } else { return globals.syn->create(num_bonnie_procs); } } if(globals.sync_bonnie) { if(globals.syn->get_semid()) return 1; } #endif if(file_size < 0 || directory_size < 0 || (!file_size && !directory_size) ) usage(); if(globals.io_chunk_size() < 256 || globals.io_chunk_size() > Unit) usage(); if(globals.file_chunk_size() < 256 || globals.file_chunk_size() > Unit) usage(); int i; globals.io_chunk_bits = 0; globals.file_chunk_bits = 0; for(i = globals.io_chunk_size(); i > 1; i = i >> 1, globals.io_chunk_bits++); if(1 << globals.io_chunk_bits != globals.io_chunk_size()) usage(); for(i = globals.file_chunk_size(); i > 1; i = i >> 1, globals.file_chunk_bits++); if(1 << globals.file_chunk_bits != globals.file_chunk_size()) usage(); if( (directory_max_size != -1 && directory_max_size != -2) && (directory_max_size < directory_min_size || directory_max_size < 0 || directory_min_size < 0) ) usage(); if(file_size && globals.ram && (file_size * concurrency) < (globals.ram * 2) ) { fprintf(stderr , "File size should be double RAM for good results, RAM is %dM.\n" , globals.ram); usage(); } // if doing more than one test run then we print a header before the // csv format output. if(test_count > 1) { globals.timer.SetType(BonTimer::csv); globals.timer.PrintHeader(stdout); } for(; test_count > 0 || test_count == -1; test_count--) { globals.timer.Initialize(); int rc; rc = TestFileOps(file_size, globals); if(rc) return rc; rc = TestDirOps(directory_size, directory_max_size, directory_min_size , num_directories, globals); if(rc) return rc; // if we are only doing one test run then print a plain-text version of // the results before printing a csv version. if(test_count == -1) { globals.timer.SetType(BonTimer::txt); rc = globals.timer.DoReportIO(file_size, globals.byte_io_size , globals.io_chunk_size(), globals.quiet ? stderr : stdout); rc |= globals.timer.DoReportFile(directory_size , directory_max_size, directory_min_size, num_directories , globals.file_chunk_size() , globals.quiet ? stderr : stdout); } // print a csv version in every case globals.timer.SetType(BonTimer::csv); rc = globals.timer.DoReportIO(file_size, globals.byte_io_size , globals.io_chunk_size(), stdout); rc |= globals.timer.DoReportFile(directory_size , directory_max_size, directory_min_size, num_directories , globals.file_chunk_size(), stdout); if(rc) return rc; } return eNoErr; } int TestFileOps(int file_size, CGlobalItems &globals) { if(file_size) { CFileOp file(globals.timer, file_size, globals.io_chunk_bits, globals.bufSync); int num_chunks; int words; char *buf = globals.buf(); int bufindex; int i; // default is we have 1M / 8K * 300 chunks = 38400 num_chunks = Unit / globals.io_chunk_size() * file_size; int char_io_chunks = Unit / globals.io_chunk_size() * globals.byte_io_size; int rc; rc = file.Open(globals.name, true); if(rc) return rc; if(exitNow) return eCtrl_C; Duration dur; globals.timer.start(); if(char_io_chunks) { dur.reset(); globals.decrement_and_wait(ByteWrite); // Fill up a file, writing it a char at a time if(!globals.quiet) fprintf(stderr, "Writing a byte at a time..."); for(words = 0; words < char_io_chunks; words++) { dur.start(); if(file.write_block_byte() == -1) return 1; dur.stop(); if(exitNow) return eCtrl_C; } fflush(NULL); /* * note that we always close the file before measuring time, in an * effort to force as much of the I/O out as we can */ file.Close(); globals.timer.stop_and_record(ByteWrite); globals.timer.add_latency(ByteWrite, dur.getMax()); if(!globals.quiet) fprintf(stderr, "done\n"); } /* Write the whole file from scratch, again, with block I/O */ if(file.reopen(true)) return 1; dur.reset(); globals.decrement_and_wait(FastWrite); if(!globals.quiet) fprintf(stderr, "Writing intelligently..."); memset(buf, 0, globals.io_chunk_size()); globals.timer.start(); bufindex = 0; // for the number of chunks of file data for(i = 0; i < num_chunks; i++) { if(exitNow) return eCtrl_C; // for each chunk in the Unit buf[bufindex]++; bufindex = (bufindex + 1) % globals.io_chunk_size(); dur.start(); if(file.write_block(PVOID(buf)) == -1) { fprintf(stderr, "Can't write block %d.\n", i); return 1; } dur.stop(); } file.Close(); globals.timer.stop_and_record(FastWrite); globals.timer.add_latency(FastWrite, dur.getMax()); if(!globals.quiet) fprintf(stderr, "done\n"); /* Now read & rewrite it using block I/O. Dirty one word in each block */ if(file.reopen(false)) return 1; if (file.seek(0, SEEK_SET) == -1) { if(!globals.quiet) fprintf(stderr, "error in lseek(2) before rewrite\n"); return 1; } dur.reset(); globals.decrement_and_wait(ReWrite); if(!globals.quiet) fprintf(stderr, "Rewriting..."); globals.timer.start(); bufindex = 0; for(words = 0; words < num_chunks; words++) { // for each chunk in the file dur.start(); if (file.read_block(PVOID(buf)) == -1) return 1; bufindex = bufindex % globals.io_chunk_size(); buf[bufindex]++; bufindex++; if (file.seek(-1, SEEK_CUR) == -1) return 1; if (file.write_block(PVOID(buf)) == -1) return io_error("re write(2)"); dur.stop(); if(exitNow) return eCtrl_C; } file.Close(); globals.timer.stop_and_record(ReWrite); globals.timer.add_latency(ReWrite, dur.getMax()); if(!globals.quiet) fprintf(stderr, "done\n"); if(char_io_chunks) { // read them all back a byte at a time if(file.reopen(false)) return 1; dur.reset(); globals.decrement_and_wait(ByteRead); if(!globals.quiet) fprintf(stderr, "Reading a byte at a time..."); globals.timer.start(); for(words = 0; words < char_io_chunks; words++) { dur.start(); if(file.read_block_byte(buf) == -1) return 1; dur.stop(); if(exitNow) return eCtrl_C; } file.Close(); globals.timer.stop_and_record(ByteRead); globals.timer.add_latency(ByteRead, dur.getMax()); if(!globals.quiet) fprintf(stderr, "done\n"); } /* Now suck it in, Chunk at a time, as fast as we can */ if(file.reopen(false)) return 1; if (file.seek(0, SEEK_SET) == -1) return io_error("lseek before read"); dur.reset(); globals.decrement_and_wait(FastRead); if(!globals.quiet) fprintf(stderr, "Reading intelligently..."); globals.timer.start(); for(i = 0; i < num_chunks; i++) { /* per block */ dur.start(); if ((words = file.read_block(PVOID(buf))) == -1) return io_error("read(2)"); dur.stop(); if(exitNow) return eCtrl_C; } /* per block */ file.Close(); globals.timer.stop_and_record(FastRead); globals.timer.add_latency(FastRead, dur.getMax()); if(!globals.quiet) fprintf(stderr, "done\n"); globals.timer.start(); if(file.seek_test(globals.timer.random_source, globals.quiet, *globals.syn)) return 1; /* * Now test random seeks; first, set up for communicating with children. * The object of the game is to do "Seeks" lseek() calls as quickly * as possible. So we'll farm them out among SeekProcCount processes. * We'll control them by writing 1-byte tickets down a pipe which * the children all read. We write "Seeks" bytes with val 1, whichever * child happens to get them does it and the right number of seeks get * done. * The idea is that since the write() of the tickets is probably * atomic, the parent process likely won't get scheduled while the * children are seeking away. If you draw a picture of the likely * timelines for three children, it seems likely that the seeks will * overlap very nicely with the process scheduling with the effect * that there will *always* be a seek() outstanding on the file. * Question: should the file be opened *before* the fork, so that * all the children are lseeking on the same underlying file object? */ } return eNoErr; } int TestDirOps(int directory_size, int max_size, int min_size , int num_directories, CGlobalItems &globals) { COpenTest open_test(globals.file_chunk_size(), globals.bufSync, globals.doExit); if(!directory_size) { return 0; } // if directory_size (in K) * data per file*2 > (ram << 10) (IE memory /1024) // then the storage of file names will take more than half RAM and there // won't be enough RAM to have Bonnie++ paged in and to have a reasonable // meta-data cache. if(globals.ram && directory_size * MaxDataPerFile * 2 > (globals.ram << 10)) { fprintf(stderr , "When testing %dK of files in %d MB of RAM the system is likely to\n" "start paging Bonnie++ data and the test will give suspect\n" "results, use less files or install more RAM for this test.\n" , directory_size, globals.ram); return eParam; } // Can't use more than 1G of RAM if(directory_size * MaxDataPerFile > (1 << 20)) { fprintf(stderr, "Not enough ram to test with %dK files.\n" , directory_size); return eParam; } globals.decrement_and_wait(CreateSeq); if(!globals.quiet) fprintf(stderr, "Create files in sequential order..."); if(open_test.create(globals.name, globals.timer, directory_size , max_size, min_size, num_directories, false)) return 1; globals.decrement_and_wait(StatSeq); if(!globals.quiet) fprintf(stderr, "done.\nStat files in sequential order..."); if(open_test.stat_sequential(globals.timer)) return 1; globals.decrement_and_wait(DelSeq); if(!globals.quiet) fprintf(stderr, "done.\nDelete files in sequential order..."); if(open_test.delete_sequential(globals.timer)) return 1; if(!globals.quiet) fprintf(stderr, "done.\n"); globals.decrement_and_wait(CreateRand); if(!globals.quiet) fprintf(stderr, "Create files in random order..."); if(open_test.create(globals.name, globals.timer, directory_size , max_size, min_size, num_directories, true)) return 1; globals.decrement_and_wait(StatRand); if(!globals.quiet) fprintf(stderr, "done.\nStat files in random order..."); if(open_test.stat_random(globals.timer)) return 1; globals.decrement_and_wait(DelRand); if(!globals.quiet) fprintf(stderr, "done.\nDelete files in random order..."); if(open_test.delete_random(globals.timer)) return 1; if(!globals.quiet) fprintf(stderr, "done.\n"); return eNoErr; } void usage() { fprintf(stderr, "usage:\n" "bonnie++ [-d scratch-dir] [-c concurrency] [-s size(Mb)[:chunk-size(b)]]\n" " [-n number-to-stat[:max-size[:min-size][:num-directories[:chunk-size]]]]\n" " [-m machine-name] [-r ram-size-in-Mb]\n" " [-x number-of-tests] [-u uid-to-use:gid-to-use] [-g gid-to-use]\n" " [-q] [-f] [-b] [-p processes | -y] [-z seed | -Z random-file]\n" "\nVersion: " BON_VERSION "\n"); exit(eParam); } int io_error(CPCCHAR message, bool do_exit) { char buf[1024]; if(!already_printed_error && !do_exit) { sprintf(buf, "Bonnie: drastic I/O error (%s)", message); perror(buf); already_printed_error = 1; } if(do_exit) exit(1); return(1); }