[PATCH] Add drivescan tool
authorJens Axboe <axboe@suse.de>
Tue, 18 Oct 2005 12:15:22 +0000 (14:15 +0200)
committerJens Axboe <axboe@suse.de>
Tue, 18 Oct 2005 12:15:22 +0000 (14:15 +0200)
Makefile
drivescan.c [new file with mode: 0644]

index 2a4be12c2841f92f6af1d28e3cf5d15518a065d4..bb2537011388e9d8fcebe673a01099e264c3e196 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 CC     = gcc
 CFLAGS = -Wall -O2 -g -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
-PROGS  = dops fio sgioread
+PROGS  = dops fio sgioread drivescan
 
 all: $(PROGS)
 
@@ -13,6 +13,9 @@ fio: fio.o
 sgioread: sgioread.o
        $(CC) $(CFLAGS) -o $@ $(filter %.o,$^)
 
+drivescan: drivescan.o
+       $(CC) $(CFLAGS) -o $@ $(filter %.o,$^)
+
 clean:
        -rm -f *.o $(PROGS)
 
diff --git a/drivescan.c b/drivescan.c
new file mode 100644 (file)
index 0000000..e7d9738
--- /dev/null
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2004 Jens Axboe <axboe@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+enum {
+       arch_x86_64,
+       arch_i386,
+       arch_ppc,
+       arch_ia64,
+};
+
+#if defined(__x86_64)
+#define BITS_PER_LONG 64
+static int arch = arch_x86_64;
+inline unsigned long ffz(unsigned long bitmask)
+{
+       __asm__("bsfq %1,%0" :"=r" (bitmask) :"r" (~bitmask));
+       return bitmask;
+}
+#elif defined (__i386__)
+#define BITS_PER_LONG 32
+static int arch = arch_i386;
+inline unsigned long ffz(unsigned long bitmask)
+{
+       __asm__("bsfl %1,%0" :"=r" (bitmask) :"r" (~bitmask));
+       return bitmask;
+}
+#elif defined (__powerpc__)
+#define BITS_PER_LONG 32
+static int arch = arch_ppc;
+inline int __ilog2(unsigned long bitmask)
+{
+       int lz;
+
+       asm ("cntlzw %0,%1" : "=r" (lz) : "r" (bitmask));
+       return 31 - lz;
+}
+
+static inline int ffz(unsigned long bitmask)
+{
+       if ((bitmask = ~bitmask) == 0)
+               return 32;
+       return  __ilog2(bitmask & -bitmask);
+}
+#elif defined(__ia64__)
+#define BITS_PER_LONG 64
+static int arch = arch_ia64;
+#define ia64_popcnt(x)                                                 \
+({                                                                     \
+       unsigned long ia64_intri_res;                                   \
+       asm ("popcnt %0=%1" : "=r" (ia64_intri_res) : "r" (x));         \
+       ia64_intri_res;                                                 \
+})
+inline unsigned long ffz(unsigned long bitmask)
+{
+       return ia64_popcnt(bitmask & (~bitmask - 1));
+}
+#else
+#error "Unsupported architecture"
+#endif
+
+#if BITS_PER_LONG == 32
+typedef unsigned long long u64;
+typedef unsigned long u32;
+#elif BITS_PER_LONG == 64
+typedef unsigned long u64;
+typedef unsigned int u32;
+#endif
+
+#ifndef BLKGETSIZE64
+#define BLKGETSIZE64 _IOR(0x12,114,size_t)
+#endif
+
+#define BS     (4096)
+#define MASK   (4095)
+
+#define ALIGN(buf)     (((unsigned long) (buf) + MASK) & ~(MASK))
+
+#define BITMAP_BLOCKS(blocks)  (((blocks) / BITS_PER_LONG) + 1)
+#define BITMAP_SIZE(blocks)    (BITMAP_BLOCKS(blocks) * sizeof(unsigned long))
+
+#define RANDOM_COVERAGE                (90)
+
+static char device[256];
+static char state_file[256];
+static int progress_interval;
+static int block_size;
+static int restore_state = 1;
+static struct timeval start;
+static int sequential;
+static int disk_util = 100;
+static int state_file_fd = -1;
+static int state_restored;
+
+static unsigned long *read_bitmap;
+static unsigned long find_next_bit_index;
+static unsigned long blocks_read, blocks_read_now;
+static u64 dev_size;
+
+static struct drand48_data random_state;
+
+struct our_time {
+       unsigned long hour;
+       unsigned long min;
+       unsigned long sec;
+};
+
+#define DRIVESCAN_MAGIC                0xabc0ffee
+#define DRIVESCAN_VERSION      2
+
+struct progress_state {
+       u32 magic;
+       u32 arch;
+       u32 version;
+       u32 bs;
+       u64 blocks;
+       u32 disk_util;
+       struct drand48_data random_state;
+};
+
+inline int test_block_read(unsigned long block)
+{
+       unsigned long index, bitnr;
+
+       index = block / BITS_PER_LONG;
+       bitnr = block & (BITS_PER_LONG - 1);
+       return (read_bitmap[index] & (1UL << bitnr)) != 0;
+}
+       
+inline int mark_block_read(unsigned long block)
+{
+        unsigned long index, bitnr, retval;
+
+       index = block / BITS_PER_LONG;
+       bitnr = block & (BITS_PER_LONG - 1);
+
+       retval = (read_bitmap[index] & (1UL << bitnr)) != 0;
+       read_bitmap[index] |= 1UL << bitnr;
+       return retval;
+}
+
+inline unsigned long find_next_unread_block(void)
+{
+       unsigned int i, bitnr;
+
+       for (i = find_next_bit_index; i < BITMAP_BLOCKS(dev_size); i++) {
+               if (read_bitmap[i] == ~0UL)
+                       continue;
+
+               goto gotit;
+       }
+
+       return -1;
+gotit:
+       find_next_bit_index = i;
+       bitnr = ffz(read_bitmap[i]);
+       read_bitmap[i] |= 1UL << bitnr;
+       return bitnr + BITS_PER_LONG * i;
+}
+
+int load_progress_state(void)
+{
+       struct progress_state state;
+       unsigned long i;
+       int ret;
+
+       if (state_file_fd == -1)
+               return 1;
+
+       if (lseek(state_file_fd, 0, SEEK_SET) == -1) {
+               perror("state file seek");
+               return 1;
+       }
+
+       ret = read(state_file_fd, &state, sizeof(state));
+       if (ret == -1) {
+               perror("load progress file");
+               return 1;
+       } else if (!ret)
+               return 0;
+
+       if (state.magic != DRIVESCAN_MAGIC) {
+               printf("Bad magic %lx\n", (unsigned long) state.magic);
+               return 1;
+       }
+       if (state.version != DRIVESCAN_VERSION) {
+               printf("Unsupported file version (%d)\n", (int) state.version);
+               return 1;
+       }
+       if (state.arch != arch) {
+               printf("State file from different arch\n");
+               return 1;
+       }
+
+       read_bitmap = malloc(BITMAP_SIZE(state.blocks));
+       if (!read_bitmap) {
+               printf("Unable to allocate bitmap (blocks=%Lu)\n", (unsigned long long) state.blocks);
+               return 1;
+       }
+       memset(read_bitmap, 0, BITMAP_SIZE(dev_size));
+
+       if (read(state_file_fd, (char *) read_bitmap, BITMAP_SIZE(state.blocks)) < BITMAP_SIZE(state.blocks)) {
+               printf("Unable to read bitmap\n");
+               return 1;
+       }
+
+       blocks_read = 0;
+       for (i = 0; i < state.blocks; i++)
+               if (test_block_read(i))
+                       blocks_read++;
+
+       if (blocks_read == dev_size)
+               return 0;
+
+       memcpy(&random_state, &state.random_state, sizeof(random_state));
+
+       dev_size = state.blocks;
+       block_size = state.bs;
+       disk_util = state.disk_util;
+       printf("Restored state: %lu blocks\n", blocks_read);
+       state_restored = 1;
+       return 0;
+}
+
+void save_progress_state(void)
+{
+       struct progress_state state;
+       int ret;
+
+       if (ftruncate(state_file_fd, 0) == -1) {
+               perror("truncate state file");
+               return;
+       }
+       if (lseek(state_file_fd, 0, SEEK_SET) == -1) {
+               perror("lseek state file");
+               return;
+       }
+
+       memset(&state, 0, sizeof(state));
+       state.magic = DRIVESCAN_MAGIC;
+       state.arch = arch;
+       state.version = DRIVESCAN_VERSION;
+       state.bs = block_size;
+       state.blocks = dev_size;
+       state.disk_util = disk_util;
+       memcpy(&state.random_state, &random_state, sizeof(random_state));
+
+       ret = write(state_file_fd, &state, sizeof(state));
+       if (ret == -1) {
+               perror("write state file");
+               return;
+       }
+
+       ret = write(state_file_fd, (char *) read_bitmap, BITMAP_SIZE(dev_size));
+       if (ret == -1) {
+               perror("write state bitmap");
+               return;
+       }
+
+       fsync(state_file_fd);
+       printf("Saved state: %lu blocks\n", blocks_read);
+}
+
+unsigned long utime_since(struct timeval *s)
+{
+       double sec, usec;
+       struct timeval now;
+
+       gettimeofday(&now, NULL);
+       sec = now.tv_sec - s->tv_sec;
+       usec = now.tv_usec - s->tv_usec;
+       if (sec > 0 && usec < 0) {
+               sec--;
+               usec += 1000000;
+       }
+       return usec + (sec * (double) 1000000);
+}
+
+unsigned long time_since_now(void)
+{
+       double sec, usec, ret;
+       struct timeval now;
+
+       gettimeofday(&now, NULL);
+       sec = now.tv_sec - start.tv_sec;
+       usec = now.tv_usec - start.tv_usec;
+       if (sec > 0 && usec < 0) {
+               sec--;
+               usec += 1000000;
+       }
+       ret = sec + usec / (double) 1000000;
+       if (ret < 0)
+               ret = 0;
+
+       return (unsigned long) ret;
+}
+
+void normalize_time(struct our_time *ot, unsigned long seconds)
+{
+       ot->hour = seconds / 3600;
+       seconds -= ot->hour * 3600;
+       ot->min = seconds / 60;
+       seconds -= ot->min * 60;
+       ot->sec = seconds;
+}
+
+void show_progress_state(void)
+{
+       unsigned long rate, now;
+       struct our_time comp, left;
+
+       now = time_since_now();
+
+       normalize_time(&comp, now);
+
+       if (now == 0)
+               now = 1;
+
+       rate = blocks_read_now / now;
+
+       memset(&left, 0, sizeof(left));
+       if (blocks_read_now)
+               normalize_time(&left, (dev_size * now / blocks_read_now) - now);
+       
+       printf("Status:\n");
+       printf("\tRun: [%luh%lum%lusec, %luh%lum%lusec], %lu blocks/sec, %lu KiB/sec\n", comp.hour, comp.min, comp.sec, left.hour, left.min, left.sec, rate, rate * block_size / 1024);
+       printf("\tRead:[%lu, %lu]: %lu%% coverage\n", blocks_read, (unsigned long) dev_size, (unsigned long) (blocks_read * 100 / dev_size));
+}
+
+void sig_handler(int sig)
+{
+       show_progress_state();
+
+       switch (sig) {
+               case SIGINT:
+                       if (state_file[0])
+                               save_progress_state();
+                       exit(0);
+               case SIGALRM:
+                       if (progress_interval)
+                               alarm(progress_interval);
+                       break;
+       }
+}
+
+unsigned long get_next_offset(unsigned long blocks)
+{
+       unsigned long b, retries = dev_size;
+
+       if (!sequential) {
+               retries = dev_size;
+               do {
+                       long r;
+                       lrand48_r(&random_state, &r);
+                       b = (1+(double) (blocks-1) * r / (RAND_MAX+1.0));
+
+                       if (!mark_block_read(b))
+                               break;
+                       
+               } while (--retries);
+
+               if (!retries) {
+                       printf("Gave up finding new block\n");
+                       sequential = 1;
+                       goto seq;
+               }
+       } else {
+seq:
+               b = find_next_unread_block();
+       }
+
+       blocks_read++;
+       blocks_read_now++;
+       return b;
+}
+
+void idle_drive(struct timeval *s)
+{
+       unsigned long msleep, sleep_time;
+       double ratio;
+
+       if (disk_util == 100)
+               return;
+
+       msleep = utime_since(s);
+       ratio = (double) 100 / disk_util;
+       sleep_time = msleep * ratio - msleep;
+       usleep(sleep_time);
+}
+
+void read_error_report(int fd, char *buffer, unsigned long long offset)
+{
+       unsigned long long bad_block = offset >> 9;
+       int blocks = block_size / 512;
+       int i, ret;
+
+       if (lseek(fd, offset, SEEK_SET) == -1) {
+               perror("lseek");
+               return;
+       }
+
+       for (i = 0; i < blocks; i++) {
+               ret = read(fd, buffer, 512);
+
+               if (ret == 512)
+                       continue;
+               else if (ret < 512 && ret >= 0) {
+                       printf("Short read at sector %Lu\n", bad_block + i);
+                       continue;
+               }
+               if (errno == EINVAL) {
+                       printf("512-b O_DIRECT does not work\n");
+                       break;
+               }
+               if (errno == EIO)
+                       printf("Sector %Lu bad\n", bad_block + i);
+       }
+}
+
+void do_random_reads(int fd, unsigned long blocks)
+{
+       struct timeval s;
+       char *ptr, *buffer;
+       int ret;
+
+       ptr = malloc(block_size + MASK + 1);
+       buffer = (char *) ALIGN(ptr);
+
+       do {
+               off_t offset = get_next_offset(blocks);
+               int coverage;
+
+               offset = get_next_offset(blocks);
+               if (offset == -1UL)
+                       break;
+
+               offset = offset * block_size;
+
+               if (lseek(fd, offset, SEEK_SET) == -1) {
+                       perror("lseek");
+                       break;
+               }
+
+               gettimeofday(&s, NULL);
+               ret = read(fd, buffer, block_size);
+
+               if (ret == -1) {
+                       if (errno == EINVAL) {
+                               printf("EINVAL on read, check if O_DIRECT works\n");
+                               break;
+                       } else if (errno == EIO) {
+                               read_error_report(fd, buffer, offset);
+                       } else {
+                               perror("read");
+                               break;
+                       }
+               }
+               if (ret < block_size)
+                       read_error_report(fd, buffer, offset);
+
+               coverage = blocks_read * 100 / dev_size;
+               if (!sequential && coverage >= RANDOM_COVERAGE) {
+                       printf("Switching to scan for rest of blocks\n");
+                       sequential = 1;
+               }
+
+               if (blocks_read == dev_size)
+                       break;
+
+               idle_drive(&s);
+
+       } while (1);
+
+       show_progress_state();
+
+       free(ptr);
+}
+
+int seed_randomizer(void)
+{
+       unsigned long seed;
+       int fd;
+
+       fd = open("/dev/random", O_RDONLY);
+       if (fd == -1) {
+               perror("open rand");
+               return 1;
+       }
+
+       if (read(fd, &seed, sizeof(seed)) < (int) sizeof(seed)) {
+               perror("read rand");
+               return 1;
+       }
+
+       close(fd);
+       
+       printf("Randomizer seed: %lx\n", seed);
+       srand48_r(seed, &random_state);
+       return 0;
+}
+
+void device_to_state_file(void)
+{
+       unsigned int i, offset;
+
+       offset = sprintf(state_file, "scanstate");
+
+       for (i = 0; i < strlen(device); i++) {
+               if (device[i] == '/')
+                       state_file[offset+i] = '_';
+               else
+                       state_file[offset+i] = device[i];
+       }
+}
+
+void init_state_file(void)
+{
+       if (!state_file[0])
+               device_to_state_file();
+
+       state_file_fd = open(state_file, O_RDWR | O_CREAT, 0644);
+
+       if (state_file_fd == -1) {
+               perror("open state file");
+               return;
+       }
+}
+
+int get_options(int argc, char *argv[])
+{
+       static struct option longoptions[] = {
+               { "device",     1,      NULL,   'd' },
+               { "block size", 1,      NULL,   'b' },
+               { "progress",   1,      NULL,   'p' },
+               { "file",       1,      NULL,   'f' },
+               { "restore",    1,      NULL,   'r' },
+               { "utilization",1,      NULL,   'i' },
+       };
+       int c, res = 0;
+
+       while ((c = getopt_long(argc, argv, "d:b:p:f:ru:", longoptions, &res)) != -1) {
+               switch (c) {
+                       case 'd':
+                               strncpy(device, optarg, sizeof(device) - 1);
+                               break;
+                       case 'b':
+                               block_size = atoi(optarg);
+                               if (block_size < 1 || block_size > 1024) {
+                                       printf("bad block size %d\n", block_size);
+                                       block_size = BS;
+                               }
+                               block_size <<= 10;
+                               break;
+                       case 'p':
+                               progress_interval = atoi(optarg);
+                               break;
+                       case 'f':
+                               strncpy(state_file, optarg, sizeof(state_file)-1);
+                               break;
+                       case 'r':
+                               restore_state = !!atoi(optarg);
+                               break;
+                       case 'u':
+                               disk_util = atoi(optarg);
+                               if (disk_util < 0)
+                                       disk_util = 1;
+                               if (disk_util > 100)
+                                       disk_util = 100;
+                               break;
+               }
+       }
+
+       if (!device[0]) {
+               printf("Must give device with -d\n");
+               return 1;
+       }
+       if (!block_size)
+               block_size = BS;
+
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       u64 blocks, size;
+       struct stat statbuf;
+       int fd;
+
+       if (get_options(argc, argv))
+               return 1;
+
+       seed_randomizer();
+
+       init_state_file();
+
+       if (restore_state)
+               load_progress_state();
+
+       if (stat(device, &statbuf) == -1) {
+               perror("stat");
+               return 1;
+       }
+
+       if (!S_ISBLK(statbuf.st_mode)) {
+               printf("%s does not appear to be a block device\n", device);
+               return 1;
+       }
+
+       fd = open(device, O_RDONLY | O_DIRECT | O_LARGEFILE);
+       if (fd == -1) {
+               perror("open");
+               return 2;
+       }
+
+       if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
+               perror("BLKGETSIZE64");
+               return 3;
+       }
+
+       blocks = size / block_size;
+
+       if (state_restored && dev_size != blocks) {
+               printf("Device size seems different %lu != %lu\n", (unsigned long) dev_size, (unsigned long) blocks);
+               return 1;
+       }
+
+       dev_size = blocks;
+
+       if (!read_bitmap) {
+               read_bitmap = malloc(BITMAP_SIZE(dev_size));
+               memset(read_bitmap, 0, BITMAP_SIZE(dev_size));
+       }
+
+       signal(SIGUSR1, sig_handler);
+       signal(SIGINT, sig_handler);
+
+       if (progress_interval) {
+               signal(SIGALRM, sig_handler);
+               alarm(progress_interval);
+       }
+
+       gettimeofday(&start, NULL);
+       do_random_reads(fd, blocks);
+
+       save_progress_state();
+
+       free((void *) read_bitmap);
+       close(fd);
+       if (state_file_fd != -1)
+               close(state_file_fd);
+
+       return 0;
+}