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