PostgreSQL Source Code  git master
pg_test_fsync.c
Go to the documentation of this file.
1 /*
2  * pg_test_fsync.c
3  * tests all supported fsync() methods
4  */
5 
6 #include "postgres_fe.h"
7 
8 #include <limits.h>
9 #include <sys/stat.h>
10 #include <sys/time.h>
11 #include <fcntl.h>
12 #include <time.h>
13 #include <unistd.h>
14 #include <signal.h>
15 
16 #include "access/xlogdefs.h"
17 #include "common/logging.h"
18 #include "getopt_long.h"
19 
20 /*
21  * put the temp files in the local directory
22  * unless the user specifies otherwise
23  */
24 #define FSYNC_FILENAME "./pg_test_fsync.out"
25 
26 #define XLOG_BLCKSZ_K (XLOG_BLCKSZ / 1024)
27 
28 #define LABEL_FORMAT " %-30s"
29 #define NA_FORMAT "%21s\n"
30 /* translator: maintain alignment with NA_FORMAT */
31 #define OPS_FORMAT gettext_noop("%13.3f ops/sec %6.0f usecs/op\n")
32 #define USECS_SEC 1000000
33 
34 /* These are macros to avoid timing the function call overhead. */
35 #ifndef WIN32
36 #define START_TIMER \
37 do { \
38  alarm_triggered = false; \
39  alarm(secs_per_test); \
40  gettimeofday(&start_t, NULL); \
41 } while (0)
42 #else
43 /* WIN32 doesn't support alarm, so we create a thread and sleep there */
44 #define START_TIMER \
45 do { \
46  alarm_triggered = false; \
47  if (CreateThread(NULL, 0, process_alarm, NULL, 0, NULL) == \
48  INVALID_HANDLE_VALUE) \
49  { \
50  pg_log_error("could not create thread for alarm"); \
51  exit(1); \
52  } \
53  gettimeofday(&start_t, NULL); \
54 } while (0)
55 #endif
56 
57 #define STOP_TIMER \
58 do { \
59  gettimeofday(&stop_t, NULL); \
60  print_elapse(start_t, stop_t, ops); \
61 } while (0)
62 
63 
64 static const char *progname;
65 
66 static unsigned int secs_per_test = 5;
67 static int needs_unlink = 0;
69  *buf,
71 static struct timeval start_t,
72  stop_t;
73 static bool alarm_triggered = false;
74 
75 
76 static void handle_args(int argc, char *argv[]);
77 static void prepare_buf(void);
78 static void test_open(void);
79 static void test_non_sync(void);
80 static void test_sync(int writes_per_op);
81 static void test_open_syncs(void);
82 static void test_open_sync(const char *msg, int writes_size);
83 static void test_file_descriptor_sync(void);
84 
85 #ifndef WIN32
86 static void process_alarm(int sig);
87 #else
88 static DWORD WINAPI process_alarm(LPVOID param);
89 #endif
90 static void signal_cleanup(int sig);
91 
92 #ifdef HAVE_FSYNC_WRITETHROUGH
93 static int pg_fsync_writethrough(int fd);
94 #endif
95 static void print_elapse(struct timeval start_t, struct timeval stop_t, int ops);
96 
97 #define die(msg) do { pg_log_error("%s: %m", _(msg)); exit(1); } while(0)
98 
99 
100 int
101 main(int argc, char *argv[])
102 {
103  pg_logging_init(argv[0]);
104  set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_test_fsync"));
105  progname = get_progname(argv[0]);
106 
107  handle_args(argc, argv);
108 
109  /* Prevent leaving behind the test file */
110  pqsignal(SIGINT, signal_cleanup);
111  pqsignal(SIGTERM, signal_cleanup);
112 #ifndef WIN32
114 #endif
115 #ifdef SIGHUP
116  /* Not defined on win32 */
118 #endif
119 
120  prepare_buf();
121 
122  test_open();
123 
124  /* Test using 1 XLOG_BLCKSZ write */
125  test_sync(1);
126 
127  /* Test using 2 XLOG_BLCKSZ writes */
128  test_sync(2);
129 
130  test_open_syncs();
131 
133 
134  test_non_sync();
135 
136  unlink(filename);
137 
138  return 0;
139 }
140 
141 static void
142 handle_args(int argc, char *argv[])
143 {
144  static struct option long_options[] = {
145  {"filename", required_argument, NULL, 'f'},
146  {"secs-per-test", required_argument, NULL, 's'},
147  {NULL, 0, NULL, 0}
148  };
149 
150  int option; /* Command line option */
151  int optindex = 0; /* used by getopt_long */
152  unsigned long optval; /* used for option parsing */
153  char *endptr;
154 
155  if (argc > 1)
156  {
157  if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
158  {
159  printf(_("Usage: %s [-f FILENAME] [-s SECS-PER-TEST]\n"), progname);
160  exit(0);
161  }
162  if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
163  {
164  puts("pg_test_fsync (PostgreSQL) " PG_VERSION);
165  exit(0);
166  }
167  }
168 
169  while ((option = getopt_long(argc, argv, "f:s:",
170  long_options, &optindex)) != -1)
171  {
172  switch (option)
173  {
174  case 'f':
176  break;
177 
178  case 's':
179  errno = 0;
180  optval = strtoul(optarg, &endptr, 10);
181 
182  if (endptr == optarg || *endptr != '\0' ||
183  errno != 0 || optval != (unsigned int) optval)
184  {
185  pg_log_error("invalid argument for option %s", "--secs-per-test");
186  fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
187  exit(1);
188  }
189 
190  secs_per_test = (unsigned int) optval;
191  if (secs_per_test == 0)
192  {
193  pg_log_error("%s must be in range %u..%u",
194  "--secs-per-test", 1, UINT_MAX);
195  exit(1);
196  }
197  break;
198 
199  default:
200  fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
201  progname);
202  exit(1);
203  break;
204  }
205  }
206 
207  if (argc > optind)
208  {
209  pg_log_error("too many command-line arguments (first is \"%s\")",
210  argv[optind]);
211  fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
212  progname);
213  exit(1);
214  }
215 
216  printf(ngettext("%u second per test\n",
217  "%u seconds per test\n",
218  secs_per_test),
219  secs_per_test);
220 #if PG_O_DIRECT != 0
221  printf(_("O_DIRECT supported on this platform for open_datasync and open_sync.\n"));
222 #else
223  printf(_("Direct I/O is not supported on this platform.\n"));
224 #endif
225 }
226 
227 static void
229 {
230  int ops;
231 
232  /* write random data into buffer */
233  for (ops = 0; ops < DEFAULT_XLOG_SEG_SIZE; ops++)
234  full_buf[ops] = random();
235 
236  buf = (char *) TYPEALIGN(XLOG_BLCKSZ, full_buf);
237 }
238 
239 static void
241 {
242  int tmpfile;
243 
244  /*
245  * test if we can open the target file
246  */
247  if ((tmpfile = open(filename, O_RDWR | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR)) == -1)
248  die("could not open output file");
249  needs_unlink = 1;
250  if (write(tmpfile, full_buf, DEFAULT_XLOG_SEG_SIZE) !=
252  die("write failed");
253 
254  /* fsync now so that dirty buffers don't skew later tests */
255  if (fsync(tmpfile) != 0)
256  die("fsync failed");
257 
258  close(tmpfile);
259 }
260 
261 static void
262 test_sync(int writes_per_op)
263 {
264  int tmpfile,
265  ops,
266  writes;
267  bool fs_warning = false;
268 
269  if (writes_per_op == 1)
270  printf(_("\nCompare file sync methods using one %dkB write:\n"), XLOG_BLCKSZ_K);
271  else
272  printf(_("\nCompare file sync methods using two %dkB writes:\n"), XLOG_BLCKSZ_K);
273  printf(_("(in wal_sync_method preference order, except fdatasync is Linux's default)\n"));
274 
275  /*
276  * Test open_datasync if available
277  */
278  printf(LABEL_FORMAT, "open_datasync");
279  fflush(stdout);
280 
281 #ifdef OPEN_DATASYNC_FLAG
282  if ((tmpfile = open(filename, O_RDWR | O_DSYNC | PG_O_DIRECT | PG_BINARY, 0)) == -1)
283  {
284  printf(NA_FORMAT, _("n/a*"));
285  fs_warning = true;
286  }
287  else
288  {
289  START_TIMER;
290  for (ops = 0; alarm_triggered == false; ops++)
291  {
292  for (writes = 0; writes < writes_per_op; writes++)
293  if (pg_pwrite(tmpfile,
294  buf,
295  XLOG_BLCKSZ,
296  writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
297  die("write failed");
298  }
299  STOP_TIMER;
300  close(tmpfile);
301  }
302 #else
303  printf(NA_FORMAT, _("n/a"));
304 #endif
305 
306 /*
307  * Test fdatasync if available
308  */
309  printf(LABEL_FORMAT, "fdatasync");
310  fflush(stdout);
311 
312 #ifdef HAVE_FDATASYNC
313  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
314  die("could not open output file");
315  START_TIMER;
316  for (ops = 0; alarm_triggered == false; ops++)
317  {
318  for (writes = 0; writes < writes_per_op; writes++)
319  if (pg_pwrite(tmpfile,
320  buf,
321  XLOG_BLCKSZ,
322  writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
323  die("write failed");
324  fdatasync(tmpfile);
325  }
326  STOP_TIMER;
327  close(tmpfile);
328 #else
329  printf(NA_FORMAT, _("n/a"));
330 #endif
331 
332 /*
333  * Test fsync
334  */
335  printf(LABEL_FORMAT, "fsync");
336  fflush(stdout);
337 
338  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
339  die("could not open output file");
340  START_TIMER;
341  for (ops = 0; alarm_triggered == false; ops++)
342  {
343  for (writes = 0; writes < writes_per_op; writes++)
344  if (pg_pwrite(tmpfile,
345  buf,
346  XLOG_BLCKSZ,
347  writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
348  die("write failed");
349  if (fsync(tmpfile) != 0)
350  die("fsync failed");
351  }
352  STOP_TIMER;
353  close(tmpfile);
354 
355 /*
356  * If fsync_writethrough is available, test as well
357  */
358  printf(LABEL_FORMAT, "fsync_writethrough");
359  fflush(stdout);
360 
361 #ifdef HAVE_FSYNC_WRITETHROUGH
362  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
363  die("could not open output file");
364  START_TIMER;
365  for (ops = 0; alarm_triggered == false; ops++)
366  {
367  for (writes = 0; writes < writes_per_op; writes++)
368  if (pg_pwrite(tmpfile,
369  buf,
370  XLOG_BLCKSZ,
371  writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
372  die("write failed");
373  if (pg_fsync_writethrough(tmpfile) != 0)
374  die("fsync failed");
375  }
376  STOP_TIMER;
377  close(tmpfile);
378 #else
379  printf(NA_FORMAT, _("n/a"));
380 #endif
381 
382 /*
383  * Test open_sync if available
384  */
385  printf(LABEL_FORMAT, "open_sync");
386  fflush(stdout);
387 
388 #ifdef OPEN_SYNC_FLAG
389  if ((tmpfile = open(filename, O_RDWR | OPEN_SYNC_FLAG | PG_O_DIRECT | PG_BINARY, 0)) == -1)
390  {
391  printf(NA_FORMAT, _("n/a*"));
392  fs_warning = true;
393  }
394  else
395  {
396  START_TIMER;
397  for (ops = 0; alarm_triggered == false; ops++)
398  {
399  for (writes = 0; writes < writes_per_op; writes++)
400  if (pg_pwrite(tmpfile,
401  buf,
402  XLOG_BLCKSZ,
403  writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
404  /*
405  * This can generate write failures if the filesystem has
406  * a large block size, e.g. 4k, and there is no support
407  * for O_DIRECT writes smaller than the file system block
408  * size, e.g. XFS.
409  */
410  die("write failed");
411  }
412  STOP_TIMER;
413  close(tmpfile);
414  }
415 #else
416  printf(NA_FORMAT, _("n/a"));
417 #endif
418 
419  if (fs_warning)
420  {
421  printf(_("* This file system and its mount options do not support direct\n"
422  " I/O, e.g. ext4 in journaled mode.\n"));
423  }
424 }
425 
426 static void
428 {
429  printf(_("\nCompare open_sync with different write sizes:\n"));
430  printf(_("(This is designed to compare the cost of writing 16kB in different write\n"
431  "open_sync sizes.)\n"));
432 
433  test_open_sync(_(" 1 * 16kB open_sync write"), 16);
434  test_open_sync(_(" 2 * 8kB open_sync writes"), 8);
435  test_open_sync(_(" 4 * 4kB open_sync writes"), 4);
436  test_open_sync(_(" 8 * 2kB open_sync writes"), 2);
437  test_open_sync(_("16 * 1kB open_sync writes"), 1);
438 }
439 
440 /*
441  * Test open_sync with different size files
442  */
443 static void
444 test_open_sync(const char *msg, int writes_size)
445 {
446 #ifdef OPEN_SYNC_FLAG
447  int tmpfile,
448  ops,
449  writes;
450 #endif
451 
452  printf(LABEL_FORMAT, msg);
453  fflush(stdout);
454 
455 #ifdef OPEN_SYNC_FLAG
456  if ((tmpfile = open(filename, O_RDWR | OPEN_SYNC_FLAG | PG_O_DIRECT | PG_BINARY, 0)) == -1)
457  printf(NA_FORMAT, _("n/a*"));
458  else
459  {
460  START_TIMER;
461  for (ops = 0; alarm_triggered == false; ops++)
462  {
463  for (writes = 0; writes < 16 / writes_size; writes++)
464  if (pg_pwrite(tmpfile,
465  buf,
466  writes_size * 1024,
467  writes * writes_size * 1024) !=
468  writes_size * 1024)
469  die("write failed");
470  }
471  STOP_TIMER;
472  close(tmpfile);
473  }
474 #else
475  printf(NA_FORMAT, _("n/a"));
476 #endif
477 }
478 
479 static void
481 {
482  int tmpfile,
483  ops;
484 
485  /*
486  * Test whether fsync can sync data written on a different descriptor for
487  * the same file. This checks the efficiency of multi-process fsyncs
488  * against the same file. Possibly this should be done with writethrough
489  * on platforms which support it.
490  */
491  printf(_("\nTest if fsync on non-write file descriptor is honored:\n"));
492  printf(_("(If the times are similar, fsync() can sync data written on a different\n"
493  "descriptor.)\n"));
494 
495  /*
496  * first write, fsync and close, which is the normal behavior without
497  * multiple descriptors
498  */
499  printf(LABEL_FORMAT, "write, fsync, close");
500  fflush(stdout);
501 
502  START_TIMER;
503  for (ops = 0; alarm_triggered == false; ops++)
504  {
505  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
506  die("could not open output file");
507  if (write(tmpfile, buf, XLOG_BLCKSZ) != XLOG_BLCKSZ)
508  die("write failed");
509  if (fsync(tmpfile) != 0)
510  die("fsync failed");
511  close(tmpfile);
512 
513  /*
514  * open and close the file again to be consistent with the following
515  * test
516  */
517  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
518  die("could not open output file");
519  close(tmpfile);
520  }
521  STOP_TIMER;
522 
523  /*
524  * Now open, write, close, open again and fsync This simulates processes
525  * fsyncing each other's writes.
526  */
527  printf(LABEL_FORMAT, "write, close, fsync");
528  fflush(stdout);
529 
530  START_TIMER;
531  for (ops = 0; alarm_triggered == false; ops++)
532  {
533  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
534  die("could not open output file");
535  if (write(tmpfile, buf, XLOG_BLCKSZ) != XLOG_BLCKSZ)
536  die("write failed");
537  close(tmpfile);
538  /* reopen file */
539  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
540  die("could not open output file");
541  if (fsync(tmpfile) != 0)
542  die("fsync failed");
543  close(tmpfile);
544  }
545  STOP_TIMER;
546 }
547 
548 static void
550 {
551  int tmpfile,
552  ops;
553 
554  /*
555  * Test a simple write without fsync
556  */
557  printf(_("\nNon-sync'ed %dkB writes:\n"), XLOG_BLCKSZ_K);
558  printf(LABEL_FORMAT, "write");
559  fflush(stdout);
560 
561  if ((tmpfile = open(filename, O_RDWR | PG_BINARY, 0)) == -1)
562  die("could not open output file");
563  START_TIMER;
564  for (ops = 0; alarm_triggered == false; ops++)
565  {
566  if (pg_pwrite(tmpfile, buf, XLOG_BLCKSZ, 0) != XLOG_BLCKSZ)
567  die("write failed");
568  }
569  STOP_TIMER;
570  close(tmpfile);
571 }
572 
573 static void
574 signal_cleanup(int signum)
575 {
576  /* Delete the file if it exists. Ignore errors */
577  if (needs_unlink)
578  unlink(filename);
579  /* Finish incomplete line on stdout */
580  puts("");
581  exit(signum);
582 }
583 
584 #ifdef HAVE_FSYNC_WRITETHROUGH
585 
586 static int
588 {
589 #ifdef WIN32
590  return _commit(fd);
591 #elif defined(F_FULLFSYNC)
592  return (fcntl(fd, F_FULLFSYNC, 0) == -1) ? -1 : 0;
593 #else
594  errno = ENOSYS;
595  return -1;
596 #endif
597 }
598 #endif
599 
600 /*
601  * print out the writes per second for tests
602  */
603 static void
604 print_elapse(struct timeval start_t, struct timeval stop_t, int ops)
605 {
606  double total_time = (stop_t.tv_sec - start_t.tv_sec) +
607  (stop_t.tv_usec - start_t.tv_usec) * 0.000001;
608  double per_second = ops / total_time;
609  double avg_op_time_us = (total_time / ops) * USECS_SEC;
610 
611  printf(_(OPS_FORMAT), per_second, avg_op_time_us);
612 }
613 
614 #ifndef WIN32
615 static void
617 {
618  alarm_triggered = true;
619 }
620 #else
621 static DWORD WINAPI
622 process_alarm(LPVOID param)
623 {
624  /* WIN32 doesn't support alarm, so we create a thread and sleep here */
625  Sleep(secs_per_test * 1000);
626  alarm_triggered = true;
627  ExitThread(0);
628 }
629 #endif
#define PG_O_DIRECT
Definition: xlogdefs.h:79
static void test_sync(int writes_per_op)
static char * filename
Definition: pg_test_fsync.c:70
#define write(a, b, c)
Definition: win32.h:14
const char * get_progname(const char *argv0)
Definition: path.c:453
#define pg_log_error(...)
Definition: logging.h:80
int getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex)
Definition: getopt_long.c:57
long random(void)
Definition: random.c:22
int pg_fsync_writethrough(int fd)
Definition: fd.c:419
void pg_logging_init(const char *argv0)
Definition: logging.c:81
#define USECS_SEC
Definition: pg_test_fsync.c:32
#define DEFAULT_XLOG_SEG_SIZE
#define printf(...)
Definition: port.h:222
#define fprintf
Definition: port.h:220
static int fd(const char *x, int i)
Definition: preproc-init.c:105
#define PG_BINARY
Definition: c.h:1271
ssize_t pg_pwrite(int fd, const void *buf, size_t nbyte, off_t offset)
Definition: pwrite.c:27
static char full_buf[DEFAULT_XLOG_SEG_SIZE]
Definition: pg_test_fsync.c:68
static void test_open(void)
static bool alarm_triggered
Definition: pg_test_fsync.c:73
static void prepare_buf(void)
#define required_argument
Definition: getopt_long.h:25
#define fsync(fd)
Definition: win32_port.h:68
int optind
Definition: getopt.c:50
#define S_IWUSR
Definition: win32_port.h:283
static const char * progname
Definition: pg_test_fsync.c:64
static char * buf
Definition: pg_test_fsync.c:68
#define SIGHUP
Definition: win32_port.h:159
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
#define NA_FORMAT
Definition: pg_test_fsync.c:29
#define XLOG_BLCKSZ_K
Definition: pg_test_fsync.c:26
#define STOP_TIMER
Definition: pg_test_fsync.c:57
#define ngettext(s, p, n)
Definition: c.h:1182
#define PG_TEXTDOMAIN(domain)
Definition: c.h:1215
#define O_DSYNC
Definition: win32_port.h:328
#define LABEL_FORMAT
Definition: pg_test_fsync.c:28
static void print_elapse(struct timeval start_t, struct timeval stop_t, int ops)
static void test_open_syncs(void)
static void test_non_sync(void)
static unsigned int secs_per_test
Definition: pg_test_fsync.c:66
static int sig
Definition: pg_ctl.c:84
static struct timeval start_t stop_t
Definition: pg_test_fsync.c:71
#define TYPEALIGN(ALIGNVAL, LEN)
Definition: c.h:750
static void process_alarm(int sig)
pqsigfunc pqsignal(int signum, pqsigfunc handler)
Definition: signal.c:170
static void signal_cleanup(int sig)
int main(int argc, char *argv[])
#define START_TIMER
Definition: pg_test_fsync.c:36
#define OPS_FORMAT
Definition: pg_test_fsync.c:31
#define SIGALRM
Definition: win32_port.h:165
#define S_IRUSR
Definition: win32_port.h:280
static int needs_unlink
Definition: pg_test_fsync.c:67
void set_pglocale_pgservice(const char *argv0, const char *app)
Definition: exec.c:433
#define FSYNC_FILENAME
Definition: pg_test_fsync.c:24
char * optarg
Definition: getopt.c:52
static void test_file_descriptor_sync(void)
#define close(a)
Definition: win32.h:12
#define _(x)
Definition: elog.c:89
#define die(msg)
Definition: pg_test_fsync.c:97
static void test_open_sync(const char *msg, int writes_size)
static void handle_args(int argc, char *argv[])