fsx: Add mechanism to replay failed operations

Create a $name.fsxops file next to $test.fsxlog.  When a test fails,
dump the operations in the log into that file in a simple, parseable
format like:

  fallocate 0x2e0f2 0xf04a 0x0 keep_size
  truncate 0x0 0x11e00 0x0 *
  write 0x73400 0x6c00 0x11e00
  skip punch_hole 0x71539913 0xdf76 0x7a000 close_open
  mapread 0x56000 0x16d08 0x7a000

Here, each operation is on a separate line. When the first word is
"skip", the operation will be skipped.  The next parameters are offset,
length, and the current file size, followed by optional flags like
keep_size and clode_open.  A trailing asterisk indicates that the
operation overlaps with the operation that has failed.

Add a --replay-ops option that allows to replay the operations recorded
in such a $name.fsxops file.  (The log can be modified to easily narrow
down which operations are causing the failure.)

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
This commit is contained in:
Andreas Gruenbacher
2016-01-11 14:45:26 +11:00
committed by Dave Chinner
parent ed49ff0eba
commit 2a78c1d1ba
+213 -33
View File
@@ -166,7 +166,10 @@ int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset);
#define fsxwrite(a,b,c,d) write(a,b,c) #define fsxwrite(a,b,c,d) write(a,b,c)
#endif #endif
const char *replayops = NULL;
FILE * fsxlogf = NULL; FILE * fsxlogf = NULL;
FILE * replayopsf = NULL;
char opsfile[1024];
int badoff = -1; int badoff = -1;
int closeopen = 0; int closeopen = 0;
@@ -200,7 +203,7 @@ warn(const char * fmt, ...) {
#define BUF_SIZE 1024 #define BUF_SIZE 1024
void void
prt(char *fmt, ...) prt(const char *fmt, ...)
{ {
va_list args; va_list args;
char buffer[BUF_SIZE]; char buffer[BUF_SIZE];
@@ -214,12 +217,43 @@ prt(char *fmt, ...)
} }
void void
prterr(char *prefix) prterr(const char *prefix)
{ {
prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno)); prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
} }
static const char *op_names[] = {
[OP_READ] = "read",
[OP_WRITE] = "write",
[OP_MAPREAD] = "mapread",
[OP_MAPWRITE] = "mapwrite",
[OP_TRUNCATE] = "truncate",
[OP_FALLOCATE] = "fallocate",
[OP_PUNCH_HOLE] = "punch_hole",
[OP_ZERO_RANGE] = "zero_range",
[OP_COLLAPSE_RANGE] = "collapse_range",
[OP_INSERT_RANGE] = "insert_range",
};
static const char *op_name(int operation)
{
if (operation >= 0 &&
operation < sizeof(op_names) / sizeof(op_names[0]))
return op_names[operation];
return NULL;
}
static int op_code(const char *name)
{
int i;
for (i = 0; i < sizeof(op_names) / sizeof(op_names[0]); i++)
if (op_names[i] && strcmp(name, op_names[i]) == 0)
return i;
return -1;
}
void void
log4(int operation, int arg0, int arg1, enum opflags flags) log4(int operation, int arg0, int arg1, enum opflags flags)
{ {
@@ -243,10 +277,16 @@ log4(int operation, int arg0, int arg1, enum opflags flags)
void void
logdump(void) logdump(void)
{ {
FILE *logopsf;
int i, count, down; int i, count, down;
struct log_entry *lp; struct log_entry *lp;
prt("LOG DUMP (%d total operations):\n", logcount); prt("LOG DUMP (%d total operations):\n", logcount);
logopsf = fopen(opsfile, "w");
if (!logopsf)
prterr(opsfile);
if (logcount < LOGSIZE) { if (logcount < LOGSIZE) {
i = 0; i = 0;
count = logcount; count = logcount;
@@ -255,12 +295,16 @@ logdump(void)
count = LOGSIZE; count = LOGSIZE;
} }
for ( ; count > 0; count--) { for ( ; count > 0; count--) {
bool overlap;
int opnum; int opnum;
opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE; opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE;
prt("%d(%3d mod 256): ", opnum, opnum%256); prt("%d(%3d mod 256): ", opnum, opnum%256);
lp = &oplog[i]; lp = &oplog[i];
overlap = badoff >= lp->args[0] &&
badoff < lp->args[0] + lp->args[1];
if (lp->flags & FL_SKIPPED) { if (lp->flags & FL_SKIPPED) {
prt("SKIPPED (no operation)"); prt("SKIPPED (no operation)");
goto skipped; goto skipped;
@@ -271,24 +315,21 @@ logdump(void)
prt("MAPREAD 0x%x thru 0x%x\t(0x%x bytes)", prt("MAPREAD 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && badoff < if (overlap)
lp->args[0] + lp->args[1])
prt("\t***RRRR***"); prt("\t***RRRR***");
break; break;
case OP_MAPWRITE: case OP_MAPWRITE:
prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)", prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && badoff < if (overlap)
lp->args[0] + lp->args[1])
prt("\t******WWWW"); prt("\t******WWWW");
break; break;
case OP_READ: case OP_READ:
prt("READ 0x%x thru 0x%x\t(0x%x bytes)", prt("READ 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && if (overlap)
badoff < lp->args[0] + lp->args[1])
prt("\t***RRRR***"); prt("\t***RRRR***");
break; break;
case OP_WRITE: case OP_WRITE:
@@ -299,16 +340,19 @@ logdump(void)
prt(" HOLE"); prt(" HOLE");
else if (lp->args[0] + lp->args[1] > lp->args[2]) else if (lp->args[0] + lp->args[1] > lp->args[2])
prt(" EXTEND"); prt(" EXTEND");
if ((badoff >= lp->args[0] || badoff >=lp->args[2]) && overlap = (badoff >= lp->args[0] ||
badoff < lp->args[0] + lp->args[1]) badoff >=lp->args[2]) &&
badoff < lp->args[0] + lp->args[1];
if (overlap)
prt("\t***WWWW"); prt("\t***WWWW");
break; break;
case OP_TRUNCATE: case OP_TRUNCATE:
down = lp->args[1] < lp->args[2]; down = lp->args[1] < lp->args[2];
prt("TRUNCATE %s\tfrom 0x%x to 0x%x", prt("TRUNCATE %s\tfrom 0x%x to 0x%x",
down ? "DOWN" : "UP", lp->args[2], lp->args[1]); down ? "DOWN" : "UP", lp->args[2], lp->args[1]);
if (badoff >= lp->args[1 + !down] && overlap = badoff >= lp->args[1 + !down] &&
badoff < lp->args[1 + !!down]) badoff < lp->args[1 + !!down];
if (overlap)
prt("\t******WWWW"); prt("\t******WWWW");
break; break;
case OP_FALLOCATE: case OP_FALLOCATE:
@@ -322,46 +366,43 @@ logdump(void)
prt("PAST_EOF"); prt("PAST_EOF");
else else
prt("EXTENDING"); prt("EXTENDING");
if (badoff >= lp->args[0] && if (overlap)
badoff < lp->args[0] + lp->args[1])
prt("\t******FFFF"); prt("\t******FFFF");
break; break;
case OP_PUNCH_HOLE: case OP_PUNCH_HOLE:
prt("PUNCH 0x%x thru 0x%x\t(0x%x bytes)", prt("PUNCH 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && badoff < if (overlap)
lp->args[0] + lp->args[1])
prt("\t******PPPP"); prt("\t******PPPP");
break; break;
case OP_ZERO_RANGE: case OP_ZERO_RANGE:
prt("ZERO 0x%x thru 0x%x\t(0x%x bytes)", prt("ZERO 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && badoff < if (overlap)
lp->args[0] + lp->args[1])
prt("\t******ZZZZ"); prt("\t******ZZZZ");
break; break;
case OP_COLLAPSE_RANGE: case OP_COLLAPSE_RANGE:
prt("COLLAPSE 0x%x thru 0x%x\t(0x%x bytes)", prt("COLLAPSE 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && badoff < if (overlap)
lp->args[0] + lp->args[1])
prt("\t******CCCC"); prt("\t******CCCC");
break; break;
case OP_INSERT_RANGE: case OP_INSERT_RANGE:
prt("INSERT 0x%x thru 0x%x\t(0x%x bytes)", prt("INSERT 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1, lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]); lp->args[1]);
if (badoff >= lp->args[0] && badoff < if (overlap)
lp->args[0] + lp->args[1])
prt("\t******IIII"); prt("\t******IIII");
break; break;
default: default:
prt("BOGUS LOG ENTRY (operation code = %d)!", prt("BOGUS LOG ENTRY (operation code = %d)!",
lp->operation); lp->operation);
continue;
} }
skipped: skipped:
if (lp->flags & FL_CLOSE_OPEN) if (lp->flags & FL_CLOSE_OPEN)
prt("\n\t\tCLOSE/OPEN"); prt("\n\t\tCLOSE/OPEN");
@@ -369,6 +410,30 @@ logdump(void)
i++; i++;
if (i == LOGSIZE) if (i == LOGSIZE)
i = 0; i = 0;
if (logopsf) {
if (lp->flags & FL_SKIPPED)
fprintf(logopsf, "skip ");
fprintf(logopsf, "%s 0x%x 0x%x 0x%x",
op_name(lp->operation),
lp->args[0], lp->args[1], lp->args[2]);
if (lp->flags & FL_KEEP_SIZE)
fprintf(logopsf, " keep_size");
if (lp->flags & FL_CLOSE_OPEN)
fprintf(logopsf, " close_open");
if (overlap)
fprintf(logopsf, " *");
fprintf(logopsf, "\n");
}
}
if (logopsf) {
if (fclose(logopsf) != 0)
prterr(opsfile);
else
prt("Log of operations saved to \"%s\"; "
"replay with --replay-ops\n",
opsfile);
} }
} }
@@ -1206,12 +1271,78 @@ do { \
TRIM_LEN(off, len, size); \ TRIM_LEN(off, len, size); \
} while (0) } while (0)
void void cleanup();
static int
read_op(struct log_entry *log_entry)
{
char line[256];
memset(log_entry, 0, sizeof(*log_entry));
log_entry->operation = -1;
while (log_entry->operation == -1) {
char *str;
int i;
do {
if (!fgets(line, sizeof(line), replayopsf)) {
if (feof(replayopsf)) {
replayopsf = NULL;
return 0;
}
goto fail;
}
str = strtok(line, " \t\n");
} while (!str);
if (strcmp(str, "skip") == 0) {
log_entry->flags |= FL_SKIPPED;
str = strtok(NULL, " \t\n");
if (!str)
goto fail;
}
log_entry->operation = op_code(str);
if (log_entry->operation == -1)
goto fail;
for (i = 0; i < 3; i++) {
char *end;
str = strtok(NULL, " \t\n");
if (!str)
goto fail;
log_entry->args[i] = strtoul(str, &end, 0);
if (*end)
goto fail;
}
while ((str = strtok(NULL, " \t\n"))) {
if (strcmp(str, "keep_size") == 0)
log_entry->flags |= FL_KEEP_SIZE;
else if (strcmp(str, "close_open") == 0)
log_entry->flags |= FL_CLOSE_OPEN;
else if (strcmp(str, "*") == 0)
; /* overlap marker; ignore */
else
goto fail;
}
}
return 1;
fail:
fprintf(stderr, "%s: parse error\n", replayops);
fclose(replayopsf);
replayopsf = NULL;
cleanup(100); /* doesn't return */
return 0;
}
int
test(void) test(void)
{ {
unsigned long offset; unsigned long offset;
unsigned long size = maxoplen; unsigned long size;
unsigned long rv = random(); unsigned long rv;
unsigned long op; unsigned long op;
int keep_size = 0; int keep_size = 0;
@@ -1220,16 +1351,39 @@ test(void)
testcalls++; testcalls++;
if (closeprob)
closeopen = (rv >> 3) < (1 << 28) / closeprob;
if (debugstart > 0 && testcalls >= debugstart) if (debugstart > 0 && testcalls >= debugstart)
debug = 1; debug = 1;
if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0) if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0)
prt("%lu...\n", testcalls); prt("%lu...\n", testcalls);
if (replayopsf) {
struct log_entry log_entry;
while (read_op(&log_entry)) {
if (log_entry.flags & FL_SKIPPED) {
log4(log_entry.operation,
log_entry.args[0], log_entry.args[1],
log_entry.flags);
continue;
}
op = log_entry.operation;
offset = log_entry.args[0];
size = log_entry.args[1];
closeopen = !!(log_entry.flags & FL_CLOSE_OPEN);
keep_size = !!(log_entry.flags & FL_KEEP_SIZE);
goto have_op;
}
return 0;
}
rv = random();
if (closeprob)
closeopen = (rv >> 3) < (1 << 28) / closeprob;
offset = random(); offset = random();
size = maxoplen;
if (randomoplen) if (randomoplen)
size = random() % (maxoplen + 1); size = random() % (maxoplen + 1);
@@ -1240,6 +1394,10 @@ test(void)
op = rv % OP_MAX_FULL; op = rv % OP_MAX_FULL;
switch(op) { switch(op) {
case OP_TRUNCATE:
if (!style)
size = random() % maxfilelen;
break;
case OP_FALLOCATE: case OP_FALLOCATE:
if (fallocate_calls && size && keep_size_calls) if (fallocate_calls && size && keep_size_calls)
keep_size = random() % 2; keep_size = random() % 2;
@@ -1250,6 +1408,8 @@ test(void)
break; break;
} }
have_op:
switch (op) { switch (op) {
case OP_MAPREAD: case OP_MAPREAD:
if (!mapped_reads) if (!mapped_reads)
@@ -1313,8 +1473,6 @@ test(void)
break; break;
case OP_TRUNCATE: case OP_TRUNCATE:
if (!style)
size = random() % maxfilelen;
dotruncate(size); dotruncate(size);
break; break;
@@ -1368,6 +1526,7 @@ out:
check_size(); check_size();
if (closeopen) if (closeopen)
docloseopen(); docloseopen();
return 1;
} }
@@ -1594,6 +1753,11 @@ __test_fallocate(int mode, const char *mode_str)
#endif #endif
} }
static struct option longopts[] = {
{"replay-ops", required_argument, 0, 256},
{ }
};
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
@@ -1613,8 +1777,9 @@ main(int argc, char **argv)
setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */ setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
while ((ch = getopt(argc, argv, "b:c:dfl:m:no:p:qr:s:t:w:xyAD:FKHzCILN:OP:RS:WZ")) while ((ch = getopt_long(argc, argv,
!= EOF) "b:c:dfl:m:no:p:qr:s:t:w:xyAD:FKHzCILN:OP:RS:WZ",
longopts, NULL)) != EOF)
switch (ch) { switch (ch) {
case 'b': case 'b':
simulatedopcount = getnum(optarg, &endp); simulatedopcount = getnum(optarg, &endp);
@@ -1762,6 +1927,9 @@ main(int argc, char **argv)
case 'Z': case 'Z':
o_direct = O_DIRECT; o_direct = O_DIRECT;
break; break;
case 256: /* --replay-ops */
replayops = optarg;
break;
default: default:
usage(); usage();
/* NOTREACHED */ /* NOTREACHED */
@@ -1827,6 +1995,17 @@ main(int argc, char **argv)
prterr(logfile); prterr(logfile);
exit(93); exit(93);
} }
strncat(opsfile, fname, 256);
strcat(opsfile, ".fsxops");
unlink(opsfile);
if (replayops) {
replayopsf = fopen(replayops, "r");
if (!replayopsf) {
prterr(replayops);
exit(93);
}
}
#ifdef AIO #ifdef AIO
if (aio) if (aio)
@@ -1889,7 +2068,8 @@ main(int argc, char **argv)
insert_range_calls = test_fallocate(FALLOC_FL_INSERT_RANGE); insert_range_calls = test_fallocate(FALLOC_FL_INSERT_RANGE);
while (numops == -1 || numops--) while (numops == -1 || numops--)
test(); if (!test())
break;
if (close(fd)) { if (close(fd)) {
prterr("close"); prterr("close");