Statistics
| Branch: | Revision:

root / synthbench / bonnie++ / .svn / text-base / bonnie++.cpp.svn-base @ 0:839f52ef7657

History | View | Annotate | Download (18.9 kB)

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

    
51
void usage();
52

    
53
class CGlobalItems
54
{
55
public:
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
  }
90
private:
91
  int m_chunk_size;
92
  char *m_buf;
93

    
94

    
95
  CGlobalItems(const CGlobalItems &f);
96
  CGlobalItems & operator =(const CGlobalItems &f);
97
};
98

    
99
CGlobalItems::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

    
116
void CGlobalItems::decrement_and_wait(int nr_sem)
117
{
118
  if(sem.decrement_and_wait(nr_sem))
119
    exit(1);
120
}
121

    
122
int TestDirOps(int directory_size, int max_size, int min_size
123
             , int num_directories, CGlobalItems &globals);
124
int TestFileOps(int file_size, CGlobalItems &globals);
125

    
126
static bool exitNow;
127
static bool already_printed_error;
128

    
129
#ifdef USE_SA_SIGACTION
130
#define SIGNAL_NUMBER siginf->si_signo
131
#else
132
#define SIGNAL_NUMBER sig
133
#endif
134

    
135
extern "C"
136
{
137
  void ctrl_c_handler(int sig
138
#ifdef USE_SA_SIGACTION
139
		    , siginfo_t *siginf, void *unused
140
#endif
141
		     )
142
  {
143
    if(SIGNAL_NUMBER == SIGXCPU)
144
      fprintf(stderr, "Exceeded CPU usage.\n");
145
    else if(SIGNAL_NUMBER == SIGXFSZ)
146
      fprintf(stderr, "exceeded file storage limits.\n");
147
    exitNow = true;
148
  }
149
}
150

    
151
int main(int argc, char *argv[])
152
{
153
  int    file_size = DefaultFileSize;
154
  int    directory_size = DefaultDirectorySize;
155
  int    directory_max_size = DefaultDirectoryMaxSize;
156
  int    directory_min_size = DefaultDirectoryMinSize;
157
  int    num_bonnie_procs = 0;
158
  int    num_directories = 1;
159
  int    count = -1;
160
  const char * machine = NULL;
161
  char *userName = NULL, *groupName = NULL;
162
  CGlobalItems globals(&exitNow);
163
  bool setSize = false;
164

    
165
  exitNow = false;
166
  already_printed_error = false;
167

    
168
  struct sigaction sa;
169
#ifdef USE_SA_SIGACTION
170
  sa.sa_sigaction = &ctrl_c_handler;
171
  sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
172
#else
173
  sa.sa_handler = ctrl_c_handler;
174
  sa.sa_flags = SA_RESETHAND;
175
#endif
176
  if(sigaction(SIGINT, &sa, NULL)
177
   || sigaction(SIGXCPU, &sa, NULL)
178
   || sigaction(SIGXFSZ, &sa, NULL))
179
  {
180
    printf("Can't handle SIGINT.\n");
181
    return 1;
182
  }
183
#ifdef USE_SA_SIGACTION
184
  sa.sa_sigaction = NULL;
185
#endif
186
  sa.sa_handler = SIG_IGN;
187
  if(sigaction(SIGHUP, &sa, NULL))
188
  {
189
    printf("Can't handle SIGHUP.\n");
190
    return 1;
191
  }
192

    
193
#ifdef _SC_PHYS_PAGES
194
  int page_size = sysconf(_SC_PAGESIZE);
195
  int num_pages = sysconf(_SC_PHYS_PAGES);
196
  if(page_size != -1 && num_pages != -1)
197
  {
198
    globals.ram = page_size/1024 * (num_pages/1024);
199
  }
200
#else
201

    
202
#ifdef AIX_MEM_SIZE
203
  struct CuAt *odm_obj;
204
  int how_many;
205

    
206
  odm_set_path("/etc/objrepos");
207
  odm_obj = getattr("sys0", "realmem", 0, &how_many);
208
  globals.ram = atoi(odm_obj->value) / 1024;
209
  odm_terminate();
210
  printf("Memory = %d MiB\n", globals.ram);
211
#endif
212

    
213
#endif
214

    
215
  int int_c;
216
  while(-1 != (int_c = getopt(argc, argv, "bd:fg:m:n:p:qr:s:u:x:y")) )
217
  {
218
    switch(char(int_c))
219
    {
220
      case '?':
221
      case ':':
222
        usage();
223
      break;
224
      case 'b':
225
        globals.bufSync = true;
226
      break;
227
      case 'd':
228
        if(chdir(optarg))
229
        {
230
          fprintf(stderr, "Can't change to directory \"%s\".\n", optarg);
231
          usage();
232
        }
233
      break;
234
      case 'f':
235
        globals.fast = true;
236
      break;
237
      case 'm':
238
        machine = optarg;
239
      break;
240
      case 'n':
241
        sscanf(optarg, "%d:%d:%d:%d", &directory_size
242
                     , &directory_max_size, &directory_min_size
243
                     , &num_directories);
244
      break;
245
      case 'p':
246
        num_bonnie_procs = atoi(optarg);
247
                        /* Set semaphore to # of bonnie++ procs 
248
                           to synchronize */
249
      break;
250
      case 'q':
251
        globals.quiet = true;
252
      break;
253
      case 'r':
254
        globals.ram = atoi(optarg);
255
      break;
256
      case 's':
257
      {
258
        char *sbuf = strdup(optarg);
259
        char *size = strtok(sbuf, ":");
260
        file_size = atoi(size);
261
        char c = size[strlen(size) - 1];
262
        if(c == 'g' || c == 'G')
263
          file_size *= 1024;
264
        size = strtok(NULL, "");
265
        if(size)
266
        {
267
          int tmp = atoi(size);
268
          c = size[strlen(size) - 1];
269
          if(c == 'k' || c == 'K')
270
            tmp *= 1024;
271
          globals.set_chunk_size(tmp);
272
        }
273
        setSize = true;
274
      }
275
      break;
276
      case 'g':
277
        if(groupName)
278
          usage();
279
        groupName = optarg;
280
      break;
281
      case 'u':
282
      {
283
        if(userName)
284
          usage();
285
        userName = strdup(optarg);
286
        int i;
287
        for(i = 0; userName[i] && userName[i] != ':'; i++)
288
        {}
289
        if(userName[i] == ':')
290
        {
291
          if(groupName)
292
            usage();
293
          userName[i] = '\0';
294
          groupName = &userName[i + 1];
295
        }
296
      }
297
      break;
298
      case 'x':
299
        count = atoi(optarg);
300
      break;
301
      case 'y':
302
                        /* tell procs to synchronize via previous
303
                           defined semaphore */
304
        globals.sync_bonnie = true;
305
      break;
306
    }
307
  }
308
  if(optind < argc)
309
    usage();
310

    
311
  if(globals.ram && !setSize)
312
  {
313
    if(file_size < (globals.ram * 2))
314
      file_size = globals.ram * 2;
315
    // round up to the nearest gig
316
    if(file_size % 1024 > 512)
317
      file_size = file_size + 1024 - (file_size % 1024);
318
  }
319

    
320
  if(machine == NULL)
321
  {
322
    struct utsname utsBuf;
323
    if(uname(&utsBuf) != -1)
324
      machine = utsBuf.nodename;
325
  }
326

    
327
  if(userName || groupName)
328
  {
329
    if(bon_setugid(userName, groupName, globals.quiet))
330
      return 1;
331
    if(userName)
332
      free(userName);
333
  }
334
  else if(geteuid() == 0)
335
  {
336
    fprintf(stderr, "You must use the \"-u\" switch when running as root.\n");
337
    usage();
338
  }
339

    
340
  if(num_bonnie_procs && globals.sync_bonnie)
341
    usage();
342

    
343
  if(num_bonnie_procs)
344
  {
345
    if(num_bonnie_procs == -1)
346
    {
347
      return globals.sem.clear_sem();
348
    }
349
    else
350
    {
351
      return globals.sem.create(num_bonnie_procs);
352
    }
353
  }
354

    
355
  if(globals.sync_bonnie)
356
  {
357
    if(globals.sem.get_semid())
358
      return 1;
359
  }
360

    
361
  if(file_size < 0 || directory_size < 0 || (!file_size && !directory_size) )
362
    usage();
363
  if(globals.chunk_size() < 256 || globals.chunk_size() > Unit)
364
    usage();
365
  int i;
366
  globals.chunk_bits = 0;
367
  for(i = globals.chunk_size(); i > 1; i = i >> 1, globals.chunk_bits++)
368
  {}
369
  if(1 << globals.chunk_bits != globals.chunk_size())
370
    usage();
371

    
372
  if( (directory_max_size != -1 && directory_max_size != -2)
373
     && (directory_max_size < directory_min_size || directory_max_size < 0
374
     || directory_min_size < 0) )
375
    usage();
376
  /* If the storage size is too big for the maximum number of files (1000G) */
377
  if(file_size > IOFileSize * MaxIOFiles)
378
    usage();
379
  /* If the file size is so large and the chunk size is so small that we have
380
   * more than 2G of chunks */
381
  if(globals.chunk_bits < 20 && file_size > (1 << (31 - 20 + globals.chunk_bits)) )
382
    usage();
383
  // if doing more than one test run then we print a header before the
384
  // csv format output.
385
  if(count > 1)
386
  {
387
    globals.timer.SetType(BonTimer::csv);
388
    globals.timer.PrintHeader(stdout);
389
  }
390
#ifdef OS2
391
    ULONG myPid = 0;
392
    DosQuerySysInfo(QSV_FOREGROUND_PROCESS, QSV_FOREGROUND_PROCESS
393
                  , &myPid, sizeof(myPid));
394
#else
395
  pid_t myPid = getpid();
396
#endif
397
  srand(myPid ^ time(NULL));
398
  for(; count > 0 || count == -1; count--)
399
  {
400
    globals.timer.Initialize();
401
    int rc;
402
    rc = TestFileOps(file_size, globals);
403
    if(rc) return rc;
404
    rc = TestDirOps(directory_size, directory_max_size, directory_min_size
405
                  , num_directories, globals);
406
    if(rc) return rc;
407
    // if we are only doing one test run then print a plain-text version of
408
    // the results before printing a csv version.
409
    if(count == -1)
410
    {
411
      globals.timer.SetType(BonTimer::txt);
412
      rc = globals.timer.DoReport(machine, file_size, directory_size
413
                                , directory_max_size, directory_min_size
414
                                , num_directories, globals.chunk_size()
415
                                , globals.quiet ? stderr : stdout);
416
    }
417
    // print a csv version in every case
418
    globals.timer.SetType(BonTimer::csv);
419
    rc = globals.timer.DoReport(machine, file_size, directory_size
420
                              , directory_max_size, directory_min_size
421
                              , num_directories, globals.chunk_size(), stdout);
422
    if(rc) return rc;
423
  }
424
}
425

    
426
int
427
TestFileOps(int file_size, CGlobalItems &globals)
428
{
429
  if(file_size)
430
  {
431
    CFileOp file(globals.timer, file_size, globals.chunk_bits, globals.bufSync);
432
    int    num_chunks;
433
    int    words;
434
    char  *buf = globals.buf();
435
    int    bufindex;
436
    int    i;
437

    
438
    if(globals.ram && file_size < globals.ram * 2)
439
    {
440
      fprintf(stderr
441
            , "File size should be double RAM for good results, RAM is %dM.\n"
442
            , globals.ram);
443
      return 1;
444
    }
445
    // default is we have 1M / 8K * 200 chunks = 25600
446
    num_chunks = Unit / globals.chunk_size() * file_size;
447

    
448
    int rc;
449
    rc = file.open(globals.name, true, true);
450
    if(rc)
451
      return rc;
452
    if(exitNow)
453
      return EXIT_CTRL_C;
454
    globals.timer.timestamp();
455

    
456
    if(!globals.fast)
457
    {
458
      globals.decrement_and_wait(Putc);
459
      // Fill up a file, writing it a char at a time with the stdio putc() call
460
      if(!globals.quiet) fprintf(stderr, "Writing with putc()...");
461
      for(words = 0; words < num_chunks; words++)
462
      {
463
        if(file.write_block_putc() == -1)
464
          return 1;
465
        if(exitNow)
466
          return EXIT_CTRL_C;
467
      }
468
      fflush(NULL);
469
      /*
470
       * note that we always close the file before measuring time, in an
471
       *  effort to force as much of the I/O out as we can
472
       */
473
      file.close();
474
      globals.timer.get_delta_t(Putc);
475
      if(!globals.quiet) fprintf(stderr, "done\n");
476
    }
477
    /* Write the whole file from scratch, again, with block I/O */
478
    if(file.reopen(true))
479
      return 1;
480
    globals.decrement_and_wait(FastWrite);
481
    if(!globals.quiet) fprintf(stderr, "Writing intelligently...");
482
    memset(buf, 0, globals.chunk_size());
483
    globals.timer.timestamp();
484
    bufindex = 0;
485
    // for the number of chunks of file data
486
    for(i = 0; i < num_chunks; i++)
487
    {
488
      if(exitNow)
489
        return EXIT_CTRL_C;
490
      // for each chunk in the Unit
491
      buf[bufindex]++;
492
      bufindex = (bufindex + 1) % globals.chunk_size();
493
      if(file.write_block(PVOID(buf)) == -1)
494
        return io_error("write(2)");
495
    }
496
    file.close();
497
    globals.timer.get_delta_t(FastWrite);
498
    if(!globals.quiet) fprintf(stderr, "done\n");
499

    
500

    
501
    /* Now read & rewrite it using block I/O.  Dirty one word in each block */
502
    if(file.reopen(false))
503
      return 1;
504
    if (file.seek(0, SEEK_SET) == -1)
505
    {
506
      if(!globals.quiet) fprintf(stderr, "error in lseek(2) before rewrite\n");
507
      return 1;
508
    }
509
    globals.decrement_and_wait(ReWrite);
510
    if(!globals.quiet) fprintf(stderr, "Rewriting...");
511
    globals.timer.timestamp();
512
    bufindex = 0;
513
    for(words = 0; words < num_chunks; words++)
514
    { // for each chunk in the file
515
      if (file.read_block(PVOID(buf)) == -1)
516
        return 1;
517
      bufindex = bufindex % globals.chunk_size();
518
      buf[bufindex]++;
519
      bufindex++;
520
      if (file.seek(-1, SEEK_CUR) == -1)
521
        return 1;
522
      if (file.write_block(PVOID(buf)) == -1)
523
        return io_error("re write(2)");
524
      if(exitNow)
525
        return EXIT_CTRL_C;
526
    }
527
    file.close();
528
    globals.timer.get_delta_t(ReWrite);
529
    if(!globals.quiet) fprintf(stderr, "done\n");
530

    
531

    
532
    if(!globals.fast)
533
    {
534
      // read them all back with getc()
535
      if(file.reopen(false, true))
536
        return 1;
537
      globals.decrement_and_wait(Getc);
538
      if(!globals.quiet) fprintf(stderr, "Reading with getc()...");
539
      globals.timer.timestamp();
540

    
541
      for(words = 0; words < num_chunks; words++)
542
      {
543
        if(file.read_block_getc(buf) == -1)
544
          return 1;
545
        if(exitNow)
546
          return EXIT_CTRL_C;
547
      }
548

    
549
      file.close();
550
      globals.timer.get_delta_t(Getc);
551
      if(!globals.quiet) fprintf(stderr, "done\n");
552
    }
553

    
554
    /* Now suck it in, Chunk at a time, as fast as we can */
555
    if(file.reopen(false))
556
      return 1;
557
    if (file.seek(0, SEEK_SET) == -1)
558
      return io_error("lseek before read");
559
    globals.decrement_and_wait(FastRead);
560
    if(!globals.quiet) fprintf(stderr, "Reading intelligently...");
561
    globals.timer.timestamp();
562
    for(i = 0; i < num_chunks; i++)
563
    { /* per block */
564
      if ((words = file.read_block(PVOID(buf))) == -1)
565
        return io_error("read(2)");
566
      if(exitNow)
567
        return EXIT_CTRL_C;
568
    } /* per block */
569
    file.close();
570
    globals.timer.get_delta_t(FastRead);
571
    if(!globals.quiet) fprintf(stderr, "done\n");
572

    
573
    globals.timer.timestamp();
574
    if(file.seek_test(globals.quiet, globals.sem))
575
      return 1;
576

    
577
    /*
578
     * Now test random seeks; first, set up for communicating with children.
579
     * The object of the game is to do "Seeks" lseek() calls as quickly
580
     *  as possible.  So we'll farm them out among SeekProcCount processes.
581
     *  We'll control them by writing 1-byte tickets down a pipe which
582
     *  the children all read.  We write "Seeks" bytes with val 1, whichever
583
     *  child happens to get them does it and the right number of seeks get
584
     *  done.
585
     * The idea is that since the write() of the tickets is probably
586
     *  atomic, the parent process likely won't get scheduled while the
587
     *  children are seeking away.  If you draw a picture of the likely
588
     *  timelines for three children, it seems likely that the seeks will
589
     *  overlap very nicely with the process scheduling with the effect
590
     *  that there will *always* be a seek() outstanding on the file.
591
     * Question: should the file be opened *before* the fork, so that
592
     *  all the children are lseeking on the same underlying file object?
593
     */
594
  }
595
  return 0;
596
}
597

    
598
int
599
TestDirOps(int directory_size, int max_size, int min_size
600
         , int num_directories, CGlobalItems &globals)
601
{
602
  COpenTest open_test(globals.chunk_size(), globals.bufSync, globals.doExit);
603
  if(!directory_size)
604
  {
605
    return 0;
606
  }
607
  // if directory_size (in K) * data per file*2 > (ram << 10) (IE memory /1024)
608
  // then the storage of file names will take more than half RAM and there
609
  // won't be enough RAM to have Bonnie++ paged in and to have a reasonable
610
  // meta-data cache.
611
  if(globals.ram && directory_size * MaxDataPerFile * 2 > (globals.ram << 10))
612
  {
613
    fprintf(stderr
614
        , "When testing %dK of files in %d MiB of RAM the system is likely to\n"
615
           "start paging Bonnie++ data and the test will give suspect\n"
616
           "results, use less files or install more RAM for this test.\n"
617
          , directory_size, globals.ram);
618
    return 1;
619
  }
620
  // Can't use more than 1G of RAM
621
  if(directory_size * MaxDataPerFile > (1 << 20))
622
  {
623
    fprintf(stderr, "Not enough ram to test with %dK files.\n"
624
                  , directory_size);
625
    return 1;
626
  }
627
  globals.decrement_and_wait(CreateSeq);
628
  if(!globals.quiet) fprintf(stderr, "Create files in sequential order...");
629
  if(open_test.create(globals.name, globals.timer, directory_size
630
                    , max_size, min_size, num_directories, false))
631
    return 1;
632
  globals.decrement_and_wait(StatSeq);
633
  if(!globals.quiet) fprintf(stderr, "done.\nStat files in sequential order...");
634
  if(open_test.stat_sequential(globals.timer))
635
    return 1;
636
  globals.decrement_and_wait(DelSeq);
637
  if(!globals.quiet) fprintf(stderr, "done.\nDelete files in sequential order...");
638
  if(open_test.delete_sequential(globals.timer))
639
    return 1;
640
  if(!globals.quiet) fprintf(stderr, "done.\n");
641

    
642
  globals.decrement_and_wait(CreateRand);
643
  if(!globals.quiet) fprintf(stderr, "Create files in random order...");
644
  if(open_test.create(globals.name, globals.timer, directory_size
645
                    , max_size, min_size, num_directories, true))
646
    return 1;
647
  globals.decrement_and_wait(StatRand);
648
  if(!globals.quiet) fprintf(stderr, "done.\nStat files in random order...");
649
  if(open_test.stat_random(globals.timer))
650
    return 1;
651
  globals.decrement_and_wait(DelRand);
652
  if(!globals.quiet) fprintf(stderr, "done.\nDelete files in random order...");
653
  if(open_test.delete_random(globals.timer))
654
    return 1;
655
  if(!globals.quiet) fprintf(stderr, "done.\n");
656
  return 0;
657
}
658

    
659
void
660
usage()
661
{
662
  fprintf(stderr,
663
    "usage: bonnie++ [-d scratch-dir] [-s size(MiB)[:chunk-size(b)]]\n"
664
    "                [-n number-to-stat[:max-size[:min-size][:num-directories]]]\n"
665
    "                [-m machine-name]\n"
666
    "                [-r ram-size-in-MiB]\n"
667
    "                [-x number-of-tests] [-u uid-to-use:gid-to-use] [-g gid-to-use]\n"
668
    "                [-q] [-f] [-b] [-p processes | -y]\n"
669
    "\nVersion: " BON_VERSION "\n");
670
  exit(1);
671
}
672

    
673
int
674
io_error(CPCCHAR message, bool do_exit)
675
{
676
  char buf[1024];
677

    
678
  if(!already_printed_error && !do_exit)
679
  {
680
    sprintf(buf, "Bonnie: drastic I/O error (%s)", message);
681
    perror(buf);
682
    already_printed_error = 1;
683
  }
684
  if(do_exit)
685
    exit(1);
686
  return(1);
687
}
688