* All files now get unique, and verifiable contents.

* Added argument -c|--check-interval to determine how often the file contents
  are checked.
* Added some statistics.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30325 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2009-04-22 13:01:54 +00:00
parent 7615c5eee5
commit 97f89e0360
@@ -17,7 +17,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <SupportDefs.h>
#include <OS.h>
enum file_action {
@@ -33,7 +33,19 @@ enum file_action {
kNumActions
};
typedef std::vector<std::string> EntryVector;
struct block_identifier {
off_t offset;
uint32 identifier;
uint16 data[0];
};
struct entry {
std::string name;
uint32 identifier;
off_t size;
};
typedef std::vector<entry> EntryVector;
const uint32 kDefaultDirCount = 1;
@@ -55,9 +67,14 @@ static bool sVerbose = false;
static off_t sMaxFileSize = kDefaultMaxFileSize;
static uint32 sCount = 0;
static off_t sWriteTotal = 0;
static off_t sReadTotal = 0;
static bigtime_t sWriteTime = 0;
static bigtime_t sReadTime = 0;
static off_t
to_size(const char* valueWithUnit)
string_to_size(const char* valueWithUnit)
{
char* unit;
off_t size = strtoull(valueWithUnit, &unit, 10);
@@ -73,26 +90,113 @@ to_size(const char* valueWithUnit)
}
static std::string
size_to_string(off_t size)
{
char buffer[256];
if (size > 10LL * 1024 * 1024 * 1024)
snprintf(buffer, sizeof(buffer), "%g GB", size / (1024.0 * 1024 * 1024));
else if (size > 10 * 1024 * 1024)
snprintf(buffer, sizeof(buffer), "%g MB", size / (1024.0 * 1024));
else if (size > 10 * 1024)
snprintf(buffer, sizeof(buffer), "%g KB", size / (1024.0));
else
snprintf(buffer, sizeof(buffer), "%lld B", size);
return buffer;
}
static std::string
time_to_string(bigtime_t usecs)
{
static const bigtime_t kSecond = 1000000ULL;
static const bigtime_t kHour = 3600 * kSecond;
static const bigtime_t kMinute = 60 * kSecond;
uint32 hours = usecs / kHour;
uint32 minutes = usecs / kMinute;
uint32 seconds = usecs / kSecond;
char buffer[256];
if (usecs >= kHour) {
minutes %= 60;
seconds %= 60;
snprintf(buffer, sizeof(buffer), "%luh %02lum %02lus", hours, minutes,
seconds);
} else if (usecs > 100 * kSecond) {
seconds %= 60;
snprintf(buffer, sizeof(buffer), "%lum %02lus", minutes, seconds);
} else
snprintf(buffer, sizeof(buffer), "%gs", 1.0 * usecs / kSecond);
return buffer;
}
static void
usage(int status)
{
fprintf(stderr,
"Usage: %s [options]\n"
"Performs some random file actions.\n"
"Performs some random file actions for file system testing.\n"
"\n"
" -r, --runs=<count>\tThe number of actions to perform. Defaults to 100.\n"
" -s, --seed=<seed>\tThe base seed to use for the random numbers.\n"
" -r, --runs=<count>\t\tThe number of actions to perform.\n"
"\t\t\t\tDefaults to %lu.\n"
" -s, --seed=<seed>\t\tThe base seed to use for the random numbers.\n"
" -f, --file-count=<count>\tThe maximal number of files to create.\n"
"\t\t\t\tDefaults to %lu.\n"
" -d, --dir-count=<count>\tThe maximal number of directories to create.\n"
"\t\t\t\tDefaults to %lu.\n"
" -m, --max-file-size=<size>\tThe maximal file size of the files.\n"
" -n, --no-cache\t\tDisables the file cache when doing I/O on a file.\n"
" -v, --verbose\t\tShow the actions as being performed\n",
kProgramName);
"\t\t\t\tDefaults to %lld.\n"
" -c, --check-interval=<count>\tCheck after every <count> runs. "
"Defaults to 0,\n"
"\t\t\t\tmeaning only check once at the end.\n"
" -n, --no-cache\t\tDisables the file cache when doing I/O on\n"
"\t\t\t\ta file.\n"
" -k, --keep-dirty\t\tDo not remove the working files on quit.\n"
" -v, --verbose\t\t\tShow the actions as being performed\n",
kProgramName, kDefaultRunCount, kDefaultFileCount, kDefaultDirCount,
kDefaultMaxFileSize);
exit(status);
}
static void
error(const char* format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "%s: ", kProgramName);
vfprintf(stderr, format, args);
fputc('\n', stderr);
va_end(args);
fflush(stderr);
exit(1);
}
static void
warning(const char* format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "%s: ", kProgramName);
vfprintf(stderr, format, args);
fputc('\n', stderr);
va_end(args);
fflush(stderr);
}
static void
verbose(const char* format, ...)
{
@@ -106,6 +210,7 @@ verbose(const char* format, ...)
putchar('\n');
va_end(args);
fflush(stdout);
}
@@ -126,7 +231,7 @@ choose_index(const EntryVector& entries)
static inline const std::string&
choose_parent(const EntryVector& entries)
{
return entries[choose_index(entries)];
return entries[choose_index(entries)].name;
}
@@ -145,19 +250,28 @@ create_name(const std::string& parent, const char* prefix)
static int
open_file(const std::string& name, int mode)
{
return open(name.c_str(), mode | (sDisableFileCache ? O_DIRECT : 0));
return open(name.c_str(), mode | (sDisableFileCache ? O_DIRECT : 0), 0666);
}
static void
generate_block(char* block, const std::string& name, off_t offset)
generate_block(char* buffer, const struct entry& entry, off_t offset)
{
// TODO: generate unique and verifiable file contents.
block_identifier* block = (block_identifier*)buffer;
block->offset = offset;
block->identifier = entry.identifier;
uint32 count = (kBlockSize - offsetof(block_identifier, data)) / 2;
offset += offsetof(block_identifier, data);
for (uint32 i = 0; i < count; i++) {
block->data[i] = offset + i * 2;
}
}
static void
write_blocks(int fd, const std::string& name, bool append = false)
write_blocks(int fd, struct entry& entry, bool append = false)
{
off_t size = min_c(rand() % sMaxFileSize, sMaxFileSize);
off_t offset = 0;
@@ -165,11 +279,8 @@ write_blocks(int fd, const std::string& name, bool append = false)
if (append) {
// in the append case, we need to check the file size
struct stat stat;
if (fstat(fd, &stat) != 0) {
fprintf(stderr, "%s: stat file failed: %s\n", kProgramName,
strerror(errno));
exit(1);
}
if (fstat(fd, &stat) != 0)
error("stat file failed: %s\n", strerror(errno));
if (size + stat.st_size > sMaxFileSize)
size = sMaxFileSize - stat.st_size;
@@ -179,20 +290,132 @@ write_blocks(int fd, const std::string& name, bool append = false)
verbose(" write %lu bytes", size);
entry.size += size;
uint32 blockOffset = offset % kBlockSize;
sWriteTotal += size;
bigtime_t start = system_time();
while (size > 0) {
char block[kBlockSize];
generate_block(block, name, offset);
generate_block(block, entry, offset - blockOffset);
ssize_t toWrite = min_c(size, kBlockSize);
ssize_t bytesWritten = write(fd, block, toWrite);
if (bytesWritten != toWrite) {
fprintf(stderr, "%s: writing failed: %s\n", kProgramName,
strerror(errno));
exit(1);
}
ssize_t toWrite = min_c(size, kBlockSize - blockOffset);
ssize_t bytesWritten = write(fd, block + blockOffset, toWrite);
if (bytesWritten != toWrite)
error("writing failed: %s", strerror(errno));
offset += toWrite;
size -= toWrite;
blockOffset = 0;
}
sWriteTime += system_time() - start;
}
static void
dump_block(const char* buffer, int size, const char* prefix)
{
const int DUMPED_BLOCK_SIZE = 16;
int i;
for (i = 0; i < size;) {
int start = i;
printf("%s%04x ", prefix, i);
for (; i < start + DUMPED_BLOCK_SIZE; i++) {
if (!(i % 4))
printf(" ");
if (i >= size)
printf(" ");
else
printf("%02x", *(unsigned char*)(buffer + i));
}
printf(" ");
for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) {
if (i < size) {
char c = buffer[i];
if (c < 30)
printf(".");
else
printf("%c", c);
} else
break;
}
printf("\n");
}
}
static void
check_files(EntryVector& files)
{
verbose("check all files...");
for (EntryVector::iterator i = files.begin(); i != files.end(); i++) {
const struct entry& file = *i;
int fd = open_file(file.name, O_RDONLY);
if (fd < 0) {
error("opening file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
// first check if size matches
struct stat stat;
if (fstat(fd, &stat) != 0) {
error("stat file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
if (file.size != stat.st_size) {
warning("size does not match for \"%s\"! Expected %lld "
"reported %lld", file.name.c_str(), file.size, stat.st_size);
close(fd);
continue;
}
// check contents
off_t size = file.size;
off_t offset = 0;
sReadTotal += size;
bigtime_t start = system_time();
while (size > 0) {
// read block
char block[kBlockSize];
ssize_t toRead = min_c(size, kBlockSize);
ssize_t bytesRead = read(fd, block, toRead);
if (bytesRead != toRead) {
error("reading \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
// compare with generated block
char generatedBlock[kBlockSize];
generate_block(generatedBlock, file, offset);
if (memcmp(generatedBlock, block, bytesRead) != 0) {
dump_block(generatedBlock, bytesRead, "generated: ");
dump_block(block, bytesRead, "read: ");
error("block at %lld differ in \"%s\"!", offset,
file.name.c_str());
}
offset += toRead;
size -= toRead;
}
sReadTime += system_time() - start;
close(fd);
}
}
@@ -208,13 +431,15 @@ create_dir(EntryVector& dirs)
verbose("create dir %s", name.c_str());
if (mkdir(name.c_str(), 0777) != 0) {
fprintf(stderr, "%s: creating dir \"%s\" failed: %s\n", kProgramName,
name.c_str(), strerror(errno));
exit(1);
}
if (mkdir(name.c_str(), 0777) != 0)
error("creating dir \"%s\" failed: %s", name.c_str(), strerror(errno));
dirs.push_back(name);
struct entry dir;
dir.name = name;
dir.identifier = sCount;
dir.size = 0;
dirs.push_back(dir);
}
@@ -225,19 +450,18 @@ remove_dir(EntryVector& dirs)
return;
int index = choose_index(dirs);
const std::string& name = dirs[index].name;
if (rmdir(dirs[index].c_str()) != 0) {
if (rmdir(name.c_str()) != 0) {
if (errno == ENOTEMPTY || errno == EEXIST) {
// TODO: in rare cases, we could remove all files
return;
}
fprintf(stderr, "%s: removing dir \"%s\" failed: %s\n", kProgramName,
dirs[index].c_str(), strerror(errno));
exit(1);
error("removing dir \"%s\" failed: %s", name.c_str(), strerror(errno));
}
verbose("removed dir %s", dirs[index].c_str());
verbose("removed dir %s", name.c_str());
EntryVector::iterator iterator = dirs.begin();
dirs.erase(iterator + index);
@@ -253,15 +477,18 @@ create_file(const EntryVector& dirs, EntryVector& files)
verbose("create file %s", name.c_str());
int fd = open_file(name, O_RDWR | O_CREAT | O_TRUNC);
if (fd < 0) {
fprintf(stderr, "%s: creating file \"%s\" failed: %s\n", kProgramName,
name.c_str(), strerror(errno));
exit(1);
}
if (fd < 0)
error("creating file \"%s\" failed: %s", name.c_str(), strerror(errno));
struct entry file;
file.name = name;
file.identifier = sCount;
file.size = 0;
write_blocks(fd, file);
files.push_back(file);
write_blocks(fd, name);
close(fd);
files.push_back(name);
}
@@ -272,14 +499,12 @@ remove_file(EntryVector& files)
return;
int index = choose_index(files);
const std::string& name = files[index].name;
if (remove(files[index].c_str()) != 0) {
fprintf(stderr, "%s: removing file \"%s\" failed: %s\n", kProgramName,
files[index].c_str(), strerror(errno));
exit(1);
}
if (remove(name.c_str()) != 0)
error("removing file \"%s\" failed: %s", name.c_str(), strerror(errno));
verbose("removed file %s", files[index].c_str());
verbose("removed file %s", name.c_str());
EntryVector::iterator iterator = files.begin();
files.erase(iterator + index);
@@ -293,83 +518,84 @@ rename_file(const EntryVector& dirs, EntryVector& files)
return;
std::string parent = choose_parent(dirs);
std::string name = create_name(parent, "renamed-file");
std::string newName = create_name(parent, "renamed-file");
int index = choose_index(files);
const std::string& oldName = files[index].name;
verbose("rename file \"%s\" to \"%s\"", files[index].c_str(), name.c_str());
verbose("rename file \"%s\" to \"%s\"", oldName.c_str(), newName.c_str());
if (rename(files[index].c_str(), name.c_str()) != 0) {
fprintf(stderr, "%s: renaming file \"%s\" to \"%s\" failed: %s\n",
kProgramName, files[index].c_str(), name.c_str(), strerror(errno));
exit(1);
if (rename(oldName.c_str(), newName.c_str()) != 0) {
error("renaming file \"%s\" to \"%s\" failed: %s", oldName.c_str(),
newName.c_str(), strerror(errno));
}
files[index] = name;
files[index].name = newName;
}
static void
append_file(const EntryVector& files)
append_file(EntryVector& files)
{
if (files.empty())
return;
const std::string& name = files[choose_index(files)];
struct entry& file = files[choose_index(files)];
verbose("append to \"%s\"", name.c_str());
verbose("append to \"%s\"", file.name.c_str());
int fd = open_file(name, O_WRONLY | O_APPEND);
int fd = open_file(file.name, O_WRONLY | O_APPEND);
if (fd < 0) {
fprintf(stderr, "%s: appending to file \"%s\" failed: %s\n",
kProgramName, name.c_str(), strerror(errno));
exit(1);
error("appending to file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
write_blocks(fd, name, true);
write_blocks(fd, file, true);
close(fd);
}
static void
replace_file(const EntryVector& files)
replace_file(EntryVector& files)
{
if (files.empty())
return;
const std::string& name = files[choose_index(files)];
struct entry& file = files[choose_index(files)];
verbose("replace \"%s\"", name.c_str());
verbose("replace \"%s\"", file.name.c_str());
int fd = open_file(name, O_CREAT | O_WRONLY | O_TRUNC);
int fd = open_file(file.name, O_CREAT | O_WRONLY | O_TRUNC);
if (fd < 0) {
fprintf(stderr, "%s: replacing file \"%s\" failed: %s\n",
kProgramName, name.c_str(), strerror(errno));
exit(1);
error("replacing file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
write_blocks(fd, name);
file.size = 0;
write_blocks(fd, file);
close(fd);
}
static void
truncate_file(const EntryVector& files)
truncate_file(EntryVector& files)
{
if (files.empty())
return;
const std::string& name = files[choose_index(files)];
struct entry& file = files[choose_index(files)];
verbose("truncate \"%s\"", name.c_str());
verbose("truncate \"%s\"", file.name.c_str());
int fd = open_file(name, O_WRONLY | O_TRUNC);
int fd = open_file(file.name, O_WRONLY | O_TRUNC);
if (fd < 0) {
fprintf(stderr, "%s: truncating file \"%s\" failed: %s\n",
kProgramName, name.c_str(), strerror(errno));
exit(1);
error("truncating file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
file.size = 0;
close(fd);
}
@@ -387,8 +613,10 @@ main(int argc, char** argv)
{"seed", required_argument, 0, 's'},
{"file-count", required_argument, 0, 'f'},
{"dir-count", required_argument, 0, 'd'},
{"check-interval", required_argument, 0, 'c'},
{"max-file-size", required_argument, 0, 'm'},
{"no-cache", no_argument, 0, 'n'},
{"keep-dirty", no_argument, 0, 'k'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{NULL}
@@ -397,9 +625,11 @@ main(int argc, char** argv)
uint32 maxFileCount = kDefaultFileCount;
uint32 maxDirCount = kDefaultDirCount;
uint32 runs = kDefaultRunCount;
uint32 checkInterval = 0;
bool keepDirty = false;
int c;
while ((c = getopt_long(argc, argv, "r:s:f:d:m:nvh", kOptions,
while ((c = getopt_long(argc, argv, "r:s:f:d:c:m:nkvh", kOptions,
NULL)) != -1) {
switch (c) {
case 0:
@@ -429,15 +659,24 @@ main(int argc, char** argv)
else if (maxDirCount > kMaxDirCount)
maxDirCount = kMaxDirCount;
break;
case 'c':
// check interval
checkInterval = strtoul(optarg, NULL, 0);
if (checkInterval < 0)
checkInterval = 0;
break;
case 'm':
{
// max file size
sMaxFileSize = to_size(optarg);
sMaxFileSize = string_to_size(optarg);
break;
}
case 'n':
sDisableFileCache = true;
break;
case 'k':
keepDirty = true;
break;
case 'v':
sVerbose = true;
break;
@@ -453,18 +692,24 @@ main(int argc, char** argv)
EntryVector dirs;
EntryVector files;
std::string baseName = "./random_file_temp";
if (mkdir(baseName.c_str(), 0777) != 0 && errno != EEXIST) {
struct entry base;
base.name = "./random_file_temp";
base.identifier = 0;
if (mkdir(base.name.c_str(), 0777) != 0 && errno != EEXIST) {
fprintf(stderr, "%s: cannot create base directory: %s\n",
kProgramName, strerror(errno));
return 1;
}
dirs.push_back(baseName);
dirs.push_back(base);
for (uint32 run = 0; run < runs; run++) {
file_action action = choose_action();
if ((run % 100) == 0)
verbose("run %lu", run + 1);
switch (action) {
case kCreateFile:
if (files.size() > maxFileCount / 2) {
@@ -514,14 +759,31 @@ main(int argc, char** argv)
default:
break;
}
if (checkInterval != 0 && run > 0 && (run % checkInterval) == 0
&& run + 1 < runs)
check_files(files);
}
for (int i = files.size(); i-- > 0;) {
remove_file(files);
}
for (int i = dirs.size(); i-- > 0;) {
remove_dir(dirs);
check_files(files);
if (!keepDirty) {
for (int i = files.size(); i-- > 0;) {
remove_file(files);
}
for (int i = dirs.size(); i-- > 0;) {
remove_dir(dirs);
}
}
printf("%s written in %s, %s/s\n", size_to_string(sWriteTotal).c_str(),
time_to_string(sWriteTime).c_str(),
size_to_string(int64(0.5 + sWriteTotal
/ (sWriteTime / 1000000.0))).c_str());
printf("%s read in %s, %s/s\n", size_to_string(sReadTotal).c_str(),
time_to_string(sReadTime).c_str(),
size_to_string(int64(0.5 + sReadTotal
/ (sReadTime / 1000000.0))).c_str());
return 0;
}