/* read.c - hack for measuring disk timings */ /* Hal Murray, Wed Mar 19 00:14:43 PDT 2008 */ /* * Copyright (C) 2008 Hal Murray * Hal Murray wrote this file. As long as you retain this notice * you can do whatever you want with this stuff. If we meet some * day, and you think this stuff is worth it, you can buy me a * beer in return. * Thanks to Poul-Henning Kamp for the license template. */ /* Compile with: * cc -o read -O1 -Wall -Wstrict-prototypes -Wmissing-prototypes read.c * * Run with something like: * DISK_DRIVE=/dev/sda ./read [chunksize] * The default is /dev/sda and 64K * chunksize probably has to be a multiple of the sector size. * * It also works for partitions (/dev/sda1) and files. * It needs read permission. You probably have to be root. * This uses O_DIRECT to avoid trashing the buffer pool * and to force actual data transfers which is what I'm * trying to time. * * The original purpose was to time disk transfers. * (The outer cylinders transfer more bits/second than * inner ones.) * * It's also very handy for generating a lot of IO activity. * In particular, it has triggered lost interrupts on IDE disks * running with DMA disabled. * man hdparm for more info. * * The printout puts a # on header lines so you can feed it * directly to gnuplot. I use ./read | tee foo.log * * I mainly use it on Linux. * It at least compiles and runs on FreeBSD. */ /* To pick up O_DIRECT */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define MILLION 1000000 #define BUFSIZE 100 char *drive = "/dev/sda"; int chunk = 64*1024; int uSecPerLine = 2*MILLION; int pagesize; char *buffer; /* prototypes */ float xgetloadavg (void); #ifdef __linux__ float xgetloadavg () { FILE *cpuinfo = fopen("/proc/loadavg", "r"); int i; char buffer[BUFSIZE], temp; if (cpuinfo == NULL) return(0.0); for (i = 0; i < BUFSIZE; i++) { temp = getc(cpuinfo); if (temp == EOF) { printf("EOF while scanning /proc/loadavg.\n"); sleep(10); exit(1); } if (temp == ' ') break; buffer[i] = temp; } fclose(cpuinfo); buffer[i] = '\0'; return(atof(buffer)); } #endif #ifdef __FreeBSD__ float xgetloadavg () { double loadavg[3]; getloadavg(loadavg, sizeof(loadavg)); return(loadavg[0]); } #endif #ifdef __NetBSD__ Needs work. I think sysctl of vm.loadavg is the way to do it #endif int main (int argc, char *argv[]) { int pass, err, recent; int total = 0, total_frac = 0; struct timeval start, last, when, now; char *env; int micros; float sec; int disk; pagesize = sysconf(_SC_PAGESIZE); while (argc > 1) { argc--; argv++; chunk = atoi(argv[0]); if ((chunk < 1) || (chunk > 10000000)) { printf("Unreasonable size request: %d.\n", chunk); exit(1); } } env = getenv("DISK_DRIVE"); if (env != NULL) drive = env; #ifdef O_DIRECT disk = open(drive, O_RDONLY | O_DIRECT); #else printf("### No O_DIRECT, expect cache quirks!\n"); disk = open(drive, O_RDONLY); #endif if (disk == -1) { printf("Can't open %s: %s\n", drive, strerror(errno)); printf("Try something like: DISK_DRIVE=/dev/sda ./read\n"); exit(1); } /* raw devices require page alignment. */ buffer = malloc(chunk+pagesize); if (buffer == NULL) { printf("Can't allocate memory: %s\n", strerror(errno)); exit(1); } printf("# pagesize=%d, buffer=%p", pagesize, buffer); buffer = buffer+pagesize; buffer = (char*)(((int)buffer) & (~(pagesize-1))); printf("=>%p\n", buffer); printf("# Reading %s in chunks of %d bytes.\n", drive, chunk); printf("# total recent\n"); printf("# seconds M Bytes MB/S sec bytes MB/sec LDavg\n"); gettimeofday(&start, NULL); last = start; /* time of last printout */ when = start; /* when to print */ recent = 0; for (pass = 0; ; pass++) { err = read(disk, buffer, chunk); if (err == -1) { printf("Error on read. %s\n", strerror(errno)); exit(3); } if (err != chunk) { printf("# Partial read. Got %d bytes.\n", err); } recent += err; total_frac += err; while (total_frac > MILLION) { total_frac -= MILLION; total++; } gettimeofday(&now, NULL); micros = (now.tv_sec-when.tv_sec)*MILLION; micros += (now.tv_usec-when.tv_usec); if (micros > uSecPerLine) { sec = (now.tv_sec-start.tv_sec); sec += (now.tv_usec-start.tv_usec)/1E6; printf("%8.3f %8d", sec, total); printf(" %8.3f", total/sec); micros = (now.tv_sec-last.tv_sec)*MILLION; micros += (now.tv_usec-last.tv_usec); printf(" %8.3f %10d", micros/1E6, recent); printf(" %8.3f", (1E0*recent)/micros); printf(" %5.2f\n", xgetloadavg()); fflush(stdout); last = now; when.tv_usec += uSecPerLine; while (when.tv_usec > MILLION) { when.tv_usec -= MILLION; when.tv_sec++; } recent = 0; } if (err != chunk) break; } close(disk); return(0); }