Whamcloud - gitweb
LU-8726 osd-ldiskfs: bypass read for benchmarking
[fs/lustre-release.git] / lustre / utils / llverdev.c
1 /*
2  * GPL HEADER START
3  *
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 only,
8  * as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License version 2 for more details (a copy is included
14  * in the LICENSE file that accompanied this code).
15  *
16  * You should have received a copy of the GNU General Public License
17  * version 2 along with this program; If not, see
18  * http://www.gnu.org/licenses/gpl-2.0.html
19  *
20  * GPL HEADER END
21  */
22 /*
23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Use is subject to license terms.
25  *
26  * Copyright (c) 2011, Intel Corporation.
27  *
28  */
29 /*
30  * This file is part of Lustre, http://www.lustre.org/
31  * Lustre is a trademark of Sun Microsystems, Inc.
32  *
33  * lustre/utils/llverdev.c
34  *
35  * Large Block Device Verification Tool.
36  * This program is used to test whether the block device is correctly
37  * handling IO beyond 2TB boundary.
38  * This tool have two working modes
39  * 1. full mode
40  * 2. partial mode
41  *
42  * In full mode, the program writes a test pattern on the entire disk.
43  * The test pattern (device offset and timestamp) is written at the
44  * beginning of each 4kB block. When the whole device is full the read
45  * operation is performed to verify that the test pattern is correct.
46  *
47  * In partial mode, the program writes data at the critical locations
48  * of the device such as start of the device, before and after multiple of 1GB
49  * offset and at the end.
50  *
51  * A chunk buffer with default size of 1MB is used to write and read test
52  * pattern in bulk.
53  */
54
55 #ifndef _GNU_SOURCE
56 #define _GNU_SOURCE
57 #endif
58 #ifndef LUSTRE_UTILS
59 #define LUSTRE_UTILS
60 #endif
61 #ifndef _LARGEFILE64_SOURCE
62 #define _LARGEFILE64_SOURCE
63 #endif
64 #ifndef _FILE_OFFSET_BITS
65 #define _FILE_OFFSET_BITS 64
66 #endif
67
68 #include <features.h>
69 #include <stdlib.h>
70 #include <stdio.h>
71 #include <string.h>
72 #include <ctype.h>
73 #include <fcntl.h>
74 #include <unistd.h>
75 #include <limits.h>
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <getopt.h>
79 #include <time.h>
80 #include <sys/types.h>
81 #include <sys/stat.h>
82 #include <sys/ioctl.h>
83 #include <sys/mount.h>
84 #include <sys/time.h>
85 #include <gnu/stubs.h>
86
87 #ifdef HAVE_EXT2FS_EXT2FS_H
88 #  include <ext2fs/ext2fs.h>
89 #endif
90
91 #define ONE_MB (1024 * 1024)
92 #define ONE_GB (1024 * 1024 * 1024)
93 #define HALF_MB (ONE_MB / 2)
94 #define ONE_KB 1024
95 #define HALF_KB (ONE_KB / 2)
96 #define BLOCKSIZE 4096
97
98 /* Structure for writting test pattern */
99 struct block_data {
100         unsigned long long bd_offset;
101         unsigned long long bd_time;
102         unsigned long long bd_inode;
103 };
104 static char *progname;          /* name by which this program was run. */
105 static unsigned verbose = 1;    /* prints offset in kB, operation rate */
106 static int readoption;          /* run test in read-only (verify) mode */
107 static int writeoption;         /* run test in write_only mode */
108 const char *devname;            /* name of device to be tested. */
109 static unsigned full = 1;       /* flag to full check */
110 static int error_count;         /* number of IO errors hit during run */
111 static int isatty_flag;
112
113 static struct option const longopts[] =
114 {
115         { "chunksize", required_argument, 0, 'c' },
116         { "force", no_argument, 0, 'f' },
117         { "help", no_argument, 0, 'h' },
118         { "offset", required_argument, 0, 'o' },
119         { "partial", required_argument, 0, 'p' },
120         { "quiet", required_argument, 0, 'q' },
121         { "read", no_argument, 0, 'r' },
122         { "timestamp", required_argument, 0, 't' },
123         { "verbose", no_argument, 0, 'v' },
124         { "write", no_argument, 0, 'w' },
125         { "long", no_argument, 0, 'l' },
126         { "full", no_argument, 0, 'l' },
127         { 0, 0, 0, 0}
128 };
129
130 /*
131  * Usage: displays help information, whenever user supply --help option in
132  * command or enters incorrect command line.
133  */
134 void usage(int status)
135 {
136         if (status != 0) {
137                 printf("\nUsage: %s [OPTION]... <device-name> ...\n",
138                        progname);
139                 printf("Block device verification tool.\n"
140                        "\t-t {seconds}, --timestamp, "
141                        "set test time  (default=current time())\n"
142                        "\t-o {offset}, --offset, "
143                        "offset in kB of start of test, default=0\n"
144                        "\t-r, --read, run in verify mode\n"
145                        "\t-w, --write, run in test-pattern mode, default=rw\n"
146                        "\t-v, --verbose\n"
147                        "\t-q, --quiet\n"
148                        "\t-l, --long, --full check of device\n"
149                        "\t-p, --partial, for partial check (1GB steps)\n"
150                        "\t-c {bytes}, --chunksize, IO size, default=1048576\n"
151                        "\t-f, --force, force test to run without confirmation\n"
152                        "\t-h, --help, display this help and exit\n");
153         }
154         exit(status);
155 }
156
157 /*
158  * Open_dev: Opens device in specified mode and returns fd.
159  */
160 static int open_dev(const char *devname, int mode)
161 {
162         int     fd;
163 #ifdef HAVE_EXT2FS_EXT2FS_H
164         int     mount_flags;
165         char    mountpt[80] = "";
166
167         if (ext2fs_check_mount_point(devname, &mount_flags, mountpt,
168                                      sizeof(mountpt))) {
169                 fprintf(stderr, "%s: ext2fs_check_mount_point failed:%s",
170                         progname, strerror(errno));
171                 exit(1);
172         }
173         if (mount_flags & EXT2_MF_MOUNTED){
174                 fprintf(stderr, "%s: %s is already mounted\n", progname,
175                         devname);
176                 exit(1);
177         }
178 #endif
179         fd = open(devname, mode | O_EXCL | O_LARGEFILE);
180         if (fd < 0) {
181                 fprintf(stderr, "%s: Open failed: %s",progname,strerror(errno));
182                 exit(3);
183         }
184         return fd;
185 }
186
187 #ifdef HAVE_BLKID_BLKID_H
188 #include <blkid/blkid.h>
189 #endif
190 /*
191  * sizeof_dev: Returns size of device in bytes
192  */
193 static loff_t sizeof_dev(int fd)
194 {
195         loff_t numbytes;
196
197 #ifdef HAVE_BLKID_BLKID_H
198         numbytes = blkid_get_dev_size(fd);
199         if (numbytes <= 0) {
200                 fprintf(stderr, "%s: blkid_get_dev_size(%s) failed",
201                         progname, devname);
202                 return 1;
203         }
204         goto out;
205 #else
206 # if defined BLKGETSIZE64       /* in sys/mount.h */
207         if (ioctl(fd, BLKGETSIZE64, &numbytes) >= 0)
208                 goto out;
209 # endif
210 # if defined BLKGETSIZE         /* in sys/mount.h */
211         {
212                 unsigned long sectors;
213
214                 if (ioctl(fd, BLKGETSIZE, &sectors) >= 0) {
215                         numbytes = (loff_t)sectors << 9;
216                         goto out;
217                 }
218         }
219 # endif
220         {
221                 struct stat statbuf;
222
223                 if (fstat(fd, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
224                         numbytes = statbuf.st_size;
225                         goto out;
226                 }
227         }
228         fprintf(stderr, "%s: unable to determine size of %s\n",
229                 progname, devname);
230         return 0;
231 #endif
232
233 out:
234         if (verbose)
235                 printf("%s: %s is %llu bytes (%g GB) in size\n",
236                        progname, devname,
237                        (unsigned long long)numbytes, (double)numbytes / ONE_GB);
238
239         return numbytes;
240 }
241
242 /*
243  * Verify_chunk: Verifies test pattern in each 4kB (BLOCKSIZE) is correct.
244  * Returns 0 if test offset and timestamp is correct otherwise 1.
245  */
246 int verify_chunk(char *chunk_buf, const size_t chunksize,
247                  unsigned long long chunk_off, const unsigned long long time_st,
248                  const unsigned long long inode_st, const char *file)
249 {
250         struct block_data *bd;
251         char *chunk_end;
252
253         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
254              (char *)chunk_buf < chunk_end;
255              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
256                 bd = (struct block_data *)chunk_buf;
257                 if ((bd->bd_offset == chunk_off) && (bd->bd_time == time_st) &&
258                     (bd->bd_inode == inode_st))
259                         continue;
260
261                 fprintf(stderr, "\n%s: verify %s failed offset/timestamp/inode "
262                         "%llu/%llu/%llu: found %llu/%llu/%llu instead\n",
263                         progname, file, chunk_off, time_st, inode_st,
264                         bd->bd_offset, bd->bd_time, bd->bd_inode);
265                 error_count++;
266                 return 1;
267         }
268         return 0;
269 }
270
271 /*
272  * fill_chunk: Fills the chunk with current or user specified timestamp
273  * and offset. The test pattern is filled at the beginning of
274  * each 4kB(BLOCKSIZE) blocks in chunk_buf.
275  */
276 void fill_chunk(char *chunk_buf, size_t chunksize, loff_t chunk_off,
277                 const time_t time_st, const ino_t inode_st)
278 {
279         struct block_data *bd;
280         char *chunk_end;
281
282         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
283              (char *)chunk_buf < chunk_end;
284              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
285                 bd = (struct block_data *)chunk_buf;
286                 bd->bd_offset = chunk_off;
287                 bd->bd_time = time_st;
288                 bd->bd_inode = inode_st;
289         }
290 }
291
292 void show_rate(char *op, unsigned long long offset, unsigned long long *count)
293 {
294         static time_t last;
295         time_t now;
296         double diff;
297
298         now = time(NULL);
299         diff = now - last;
300
301         if (diff > 4) {
302                 if (last != 0) {
303                         if (isatty_flag)
304                                 printf("\r");
305                         printf("%s offset: %14llukB %5g MB/s            ", op,
306                                offset / ONE_KB, (double)(*count) /ONE_MB /diff);
307                         if (isatty_flag)
308                                 fflush(stdout);
309                         else
310                                 printf("\n");
311
312                         *count = 0;
313                 }
314                 last = now;
315         }
316 }
317
318 /*
319  * Write a chunk to disk, handling errors, interrupted writes, etc.
320  *
321  * If there is an IO error hit during the write, it is possible that
322  * this will just show up as a short write, and a subsequent write
323  * will return the actual error.  We want to continue in the face of
324  * minor media errors so that we can validate the whole device if
325  * possible, but if there are many errors we don't want to loop forever.
326  *
327  * The error count will be returned upon exit to ensure that the
328  * media errors are detected even if nobody is looking at the output.
329  *
330  * Returns 0 on success, or -ve errno on failure.
331  */
332 size_t write_retry(int fd, const char *chunk_buf, size_t nrequested,
333                    unsigned long long offset, const char *file)
334 {
335         long nwritten;
336
337 retry:
338         nwritten = write(fd, chunk_buf, nrequested);
339         if (nwritten < 0) {
340                 if (errno != ENOSPC) {
341                         fprintf(stderr, "\n%s: write %s@%llu+%zi failed: %s\n",
342                                 progname, file, offset, nrequested,
343                                 strerror(errno));
344                         if (error_count++ < 100)
345                                 return 0;
346                 }
347                 return -errno;
348         }
349         if (nwritten < nrequested) {
350                 fprintf(stderr, "\n%s: write %s@%llu+%zi short: %ld written\n",
351                         progname, file, offset, nrequested, nwritten);
352                 offset += nwritten;
353                 chunk_buf += nwritten;
354                 nrequested -= nwritten;
355                 goto retry;
356         }
357
358         return 0;
359 }
360
361 /*
362  * write_chunks: write the chunk_buf on the device. The number of write
363  * operations are based on the parameters write_end, offset, and chunksize.
364  *
365  * Returns 0 on success, or -ve error number on failure.
366  */
367 int write_chunks(int fd, unsigned long long offset,unsigned long long write_end,
368                  char *chunk_buf, size_t chunksize, const time_t time_st,
369                  const ino_t inode_st, const char *file)
370 {
371         unsigned long long stride, count = 0;
372
373         stride = full ? chunksize : (ONE_GB - chunksize);
374         for (offset = offset & ~(chunksize - 1); offset < write_end;
375              offset += stride) {
376                 int ret;
377
378                 if (lseek64(fd, offset, SEEK_SET) == -1) {
379                         fprintf(stderr, "\n%s: lseek64(%s+%llu) failed: %s\n",
380                                 progname, file, offset, strerror(errno));
381                         return -errno;
382                 }
383                 if (offset + chunksize > write_end)
384                         chunksize = write_end - offset;
385                 if (!full && offset > chunksize) {
386                         fill_chunk(chunk_buf, chunksize, offset, time_st,
387                                    inode_st);
388                         ret = write_retry(fd, chunk_buf, chunksize,
389                                           offset, file);
390                         if (ret < 0)
391                                 return ret;
392                         offset += chunksize;
393                         count += chunksize;
394                         if (offset + chunksize > write_end)
395                                 chunksize = write_end - offset;
396                 }
397                 fill_chunk(chunk_buf, chunksize, offset, time_st, inode_st);
398                 ret = write_retry(fd, chunk_buf, chunksize, offset, file);
399                 if (ret < 0)
400                         return ret;
401
402                 count += chunksize;
403                 if (verbose > 1)
404                         show_rate("write", offset, &count);
405         }
406
407         if (verbose > 1) {
408                 show_rate("write", offset, &count);
409                 printf("\nwrite complete\n");
410         }
411         if (fsync(fd) == -1) {
412                 fprintf(stderr, "%s: fsync failed: %s\n", progname,
413                         strerror(errno));
414                 return -errno;
415         }
416         return 0;
417 }
418
419 /*
420  * read_chunk: reads the chunk_buf from the device. The number of read
421  * operations are based on the parameters read_end, offset, and chunksize.
422  */
423 int read_chunks(int fd, unsigned long long offset, unsigned long long read_end,
424                 char *chunk_buf, size_t chunksize, const time_t time_st,
425                 const ino_t inode_st, const char *file)
426 {
427         unsigned long long stride, count = 0;
428
429         if (ioctl(fd, BLKFLSBUF, 0) < 0 && verbose)
430                 fprintf(stderr, "%s: ioctl BLKFLSBUF failed: %s (ignoring)\n",
431                         progname, strerror(errno));
432
433         stride = full ? chunksize : (ONE_GB - chunksize);
434         for (offset = offset & ~(chunksize - 1); offset < read_end;
435              offset += stride) {
436                 ssize_t nread;
437
438                 if (lseek64(fd, offset, SEEK_SET) == -1) {
439                         fprintf(stderr, "\n%s: lseek64(%llu) failed: %s\n",
440                                 progname, offset, strerror(errno));
441                         return 1;
442                 }
443                 if (offset + chunksize > read_end)
444                         chunksize = read_end - offset;
445
446                 if (!full && offset > chunksize) {
447                         nread = read(fd, chunk_buf, chunksize);
448                         if (nread < 0) {
449                                 fprintf(stderr,"\n%s: read %s@%llu+%zi failed: "
450                                         "%s\n", progname, file, offset,
451                                         chunksize, strerror(errno));
452                                 error_count++;
453                                 return 1;
454                         }
455                         if (nread < chunksize) {
456                                 fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
457                                         "%zi read\n", progname, file, offset,
458                                         chunksize, nread);
459                                 error_count++;
460                         }
461                         if (verify_chunk(chunk_buf, nread, offset, time_st,
462                                          inode_st, file) != 0)
463                                 return 1;
464                         offset += chunksize;
465                         count += chunksize;
466
467                         /* Need to reset position after read error */
468                         if (nread < chunksize &&
469                             lseek64(fd, offset, SEEK_SET) == -1) {
470                                 fprintf(stderr,
471                                         "\n%s: lseek64(%s@%llu) failed: %s\n",
472                                         progname, file, offset,strerror(errno));
473                                 return 1;
474                         }
475                         if (offset + chunksize >= read_end)
476                                 chunksize = read_end - offset;
477                 }
478
479                 nread = read(fd, chunk_buf, chunksize);
480                 if (nread < 0) {
481                         fprintf(stderr, "\n%s: read failed: %s\n", progname,
482                                 strerror(errno));
483                         error_count++;
484                         return 1;
485                 }
486                 if (nread < chunksize) {
487                         fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
488                                 "%zi read\n", progname, file, offset,
489                                 chunksize, nread);
490                         error_count++;
491                 }
492
493                 if (verify_chunk(chunk_buf, nread, offset, time_st,
494                                  inode_st, file) != 0)
495                         return 1;
496
497                 count += chunksize;
498                 if (verbose > 1)
499                         show_rate("read", offset, &count);
500         }
501         if (verbose > 1) {
502                 show_rate("read", offset, &count);
503                 printf("\nread complete\n");
504         }
505         return 0;
506 }
507
508 int main(int argc, char **argv)
509 {
510         time_t time_st = 0;             /* Default timestamp */
511         long long offset = 0, offset_orig; /* offset in kB */
512         size_t chunksize = ONE_MB;      /* IO chunk size */
513         char *chunk_buf = NULL;
514         unsigned int force = 0;         /* run test run without confirmation*/
515         unsigned long long dev_size = 0;
516         char yesno[4];
517         int mode = O_RDWR;              /* mode which device should be opened */
518         int fd;
519         int error = 0, c;
520
521         progname = strrchr(argv[0], '/') == NULL ?
522                 argv[0] : strrchr(argv[0], '/') + 1;
523         while ((c = getopt_long(argc, argv, "c:fhlo:pqrt:vw", longopts,
524                                 NULL)) != -1) {
525                 switch (c) {
526                 case 'c':
527                         chunksize = (strtoul(optarg, NULL, 0) * ONE_MB);
528                         if (!chunksize) {
529                                 fprintf(stderr, "%s: chunk size value should be"
530                                         "nonzero and multiple of 1MB\n",
531                                         progname);
532                                 return -1;
533                         }
534                         break;
535                 case 'f':
536                         force = 1;
537                         break;
538                 case 'l':
539                         full = 1;
540                         break;
541                 case 'o':
542                         offset = strtoull(optarg, NULL, 0) * ONE_KB;
543                         break;
544                 case 'p':
545                         full = 0;
546                         break;
547                 case 'q':
548                         verbose = 0;
549                         break;
550                 case 'r':
551                         readoption = 1;
552                         mode = O_RDONLY;
553                         break;
554                 case 't':
555                         time_st = (time_t)strtoul(optarg, NULL, 0);
556                         break;
557                 case 'v':
558                         verbose++;
559                         break;
560                 case 'w':
561                         writeoption = 1;
562                         mode = O_WRONLY;
563                         break;
564                 case 'h':
565                 default:
566                         usage (1);
567                         return 0;
568                 }
569         }
570         offset_orig = offset;
571         devname = argv[optind];
572         if (!devname) {
573                 fprintf(stderr, "%s: device name not given\n", progname);
574                 usage (1);
575                 return -1;
576         }
577
578         if (readoption && writeoption)
579                 mode = O_RDWR;
580         if (!readoption && !writeoption) {
581                 readoption = 1;
582                 writeoption = 1;
583         }
584
585         if (!force && writeoption) {
586                 printf("%s: permanently overwrite all data on %s (yes/no)? ",
587                        progname, devname);
588                 if (scanf("%3s", yesno) == EOF && ferror(stdin)) {
589                         perror("reading from stdin");
590                         return -1;
591                 }
592                 if (!(strcasecmp("yes", yesno) || strcasecmp("y", yesno))) {
593                         printf("Not continuing due to '%s' response", yesno);
594                         return 0;
595                 }
596         }
597
598         if (!writeoption && time_st == 0) {
599                 fprintf(stderr, "%s: must give timestamp for read-only test\n",
600                         progname);
601                 usage(1);
602         }
603
604         fd = open_dev(devname, mode);
605         dev_size = sizeof_dev(fd);
606         if (!dev_size) {
607                 fprintf(stderr, "%s: cannot test on device size < 1MB\n",
608                         progname);
609                 error = 7;
610                 goto close_dev;
611         }
612
613         if (dev_size < (offset * 2)) {
614                 fprintf(stderr, "%s: device size %llu < offset %llu\n",
615                         progname, dev_size, offset);
616                 error = 6;
617                 goto close_dev;
618         }
619         if (!time_st)
620                 (void)time(&time_st);
621
622         isatty_flag = isatty(STDOUT_FILENO);
623
624         if (verbose)
625                 printf("Timestamp: %lu\n", time_st);
626
627         chunk_buf = (char *)calloc(chunksize, 1);
628         if (chunk_buf == NULL) {
629                 fprintf(stderr, "%s: memory allocation failed for chunk_buf\n",
630                         progname);
631                 error = 4;
632                 goto close_dev;
633         }
634         if (writeoption) {
635                 c = write_chunks(fd, offset, dev_size, chunk_buf, chunksize,
636                                  time_st, 0, devname);
637                 if (c < 0 && c != -ENOSPC) {
638                         error = 3;
639                         goto chunk_buf;
640                 }
641                 if (!full) { /* end of device aligned to a block */
642                         offset = ((dev_size - chunksize + BLOCKSIZE - 1) &
643                                   ~(BLOCKSIZE - 1));
644                         c = write_chunks(fd, offset, dev_size, chunk_buf,
645                                          chunksize, time_st, 0, devname);
646                         if (c < 0 && c != -ENOSPC) {
647                                 error = 3;
648                                 goto chunk_buf;
649                         }
650                 }
651                 offset = offset_orig;
652         }
653         if (readoption) {
654                 if (read_chunks(fd, offset, dev_size, chunk_buf, chunksize,
655                                 time_st, 0, devname)) {
656                         error = 2;
657                         goto chunk_buf;
658                 }
659                 if (!full) { /* end of device aligned to a block */
660                         offset = ((dev_size - chunksize + BLOCKSIZE - 1) &
661                                   ~(BLOCKSIZE - 1));
662                         if (read_chunks(fd, offset, dev_size, chunk_buf,
663                                         chunksize, time_st, 0, devname)) {
664                                 error = 2;
665                                 goto chunk_buf;
666                         }
667                 }
668                 if (verbose)
669                         printf("\n%s: data verified successfully\n", progname);
670         }
671         error = error_count;
672 chunk_buf:
673         free(chunk_buf);
674 close_dev:
675         close(fd);
676         return error;
677 }