VFS stress test

Issue #1648
This commit is contained in:
Emery Hemingway 2015-10-21 16:15:42 +02:00 committed by Christian Helmuth
parent cdb44850d3
commit 1b4f894e2d
5 changed files with 760 additions and 0 deletions

View File

@ -0,0 +1,49 @@
#
# \brief VFS stress test
# \author Emery Hemingway
# \date 2015-08-30
#
build "core init drivers/timer server/ram_fs test/vfs_stress"
create_boot_directory
install_config {
<config>
<affinity-space width="3" height="2" />
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="vfs_stress">
<resource name="RAM" quantum="8M"/>
<config depth="16"> <vfs> <fs/> </vfs> </config>
</start>
<start name="ram_fs">
<resource name="RAM" quantum="1G"/>
<provides><service name="File_system"/></provides>
<config>
<policy label="" root="/" writeable="yes"/>
</config>
</start>
</config>
}
build_boot_image "core init ld.lib.so timer ram_fs vfs_stress"
append qemu_args "-nographic -smp cpus=6"
run_genode_until ".*child \"vfs_stress\" exited with exit value 0.*" 180

View File

@ -0,0 +1,42 @@
#
# \brief VFS stress test
# \author Emery Hemingway
# \date 2015-08-30
#
build "core init drivers/timer test/vfs_stress"
create_boot_directory
install_config {
<config>
<affinity-space width="3" height="2" />
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="vfs_stress">
<resource name="RAM" quantum="1G"/>
<config depth="16"> <vfs> <ram/> </vfs> </config>
</start>
</config>
}
build_boot_image "core init ld.lib.so timer vfs_stress"
append qemu_args "-nographic -smp cpus=6"
run_genode_until ".*child \"vfs_stress\" exited with exit value 0.*" 600

View File

@ -0,0 +1,7 @@
Vfs_stress populates, writes, and reads a file system using multiple threads.
The following attributes on the <config> node control test behaviour:
* depth - maximum tree depth, defaults to sixteen
* threads - number of threads to start, defaults to six
* write - perform write test
* read - perform read test
* unlink - unlink all generated files

View File

@ -0,0 +1,659 @@
/*
* \brief File system stress tester
* \author Emery Hemingway
* \date 2015-08-29
*/
/*
* Copyright (C) 2015 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/*
* This test populates the VFS as follows:
*
* A directory is created at root for each thread with
* sequential names. For each of these directories, a
* subtree is generated until the maximum path depth
* is reached at each branch. The subtree is layed out
* like this:
*
* a
* |\
* a b
* |\ \
* a b b
* |\ \ \
* a b b b
* |\ \ \ \
* . . . . .
*
*/
/* Genode includes */
#include <vfs/file_system_factory.h>
#include <vfs/dir_file_system.h>
#include <timer_session/connection.h>
#include <base/process.h>
#include <os/config.h>
#include <base/printf.h>
#include <base/snprintf.h>
#include <base/exception.h>
using namespace Genode;
inline void assert_mkdir(Vfs::Directory_service::Mkdir_result r)
{
typedef Vfs::Directory_service::Mkdir_result Result;
switch (r) {
case Result::MKDIR_OK: return;
case Result::MKDIR_ERR_EXISTS:
PERR("MKDIR_ERR_EXISTS"); break;
case Result::MKDIR_ERR_NO_ENTRY:
PERR("MKDIR_ERR_NO_ENTRY"); break;
case Result::MKDIR_ERR_NO_SPACE:
PERR("MKDIR_ERR_NO_SPACE"); break;
case Result::MKDIR_ERR_NO_PERM:
PERR("MKDIR_ERR_NO_PERM"); break;
case Result::MKDIR_ERR_NAME_TOO_LONG:
PERR("MKDIR_ERR_NAME_TOO_LONG"); break;
}
throw Exception();
}
inline void assert_open(Vfs::Directory_service::Open_result r)
{
typedef Vfs::Directory_service::Open_result Result;
switch (r) {
case Result::OPEN_OK: return;
case Result::OPEN_ERR_NAME_TOO_LONG:
PERR("OPEN_ERR_NAME_TOO_LONG"); break;
case Result::OPEN_ERR_UNACCESSIBLE:
PERR("OPEN_ERR_UNACCESSIBLE"); break;
case Result::OPEN_ERR_NO_SPACE:
PERR("OPEN_ERR_NO_SPACE"); break;
case Result::OPEN_ERR_NO_PERM:
PERR("OPEN_ERR_NO_PERM"); break;
case Result::OPEN_ERR_EXISTS:
PERR("OPEN_ERR_EXISTS"); break;
}
throw Exception();
}
inline void assert_write(Vfs::File_io_service::Write_result r)
{
typedef Vfs::File_io_service::Write_result Result;
switch (r) {
case Result::WRITE_OK: return;
case Result::WRITE_ERR_AGAIN:
PERR("WRITE_ERR_AGAIN"); break;
case Result::WRITE_ERR_WOULD_BLOCK:
PERR("WRITE_ERR_WOULD_BLOCK"); break;
case Result::WRITE_ERR_INVALID:
PERR("WRITE_ERR_INVALID"); break;
case Result::WRITE_ERR_IO:
PERR("WRITE_ERR_IO"); break;
case Result::WRITE_ERR_INTERRUPT:
PERR("WRITE_ERR_INTERRUPT"); break;
}
throw Exception();
}
inline void assert_read(Vfs::File_io_service::Read_result r)
{
typedef Vfs::File_io_service::Read_result Result;
switch (r) {
case Result::READ_OK: return;
case Result::READ_ERR_AGAIN:
PERR("READ_ERR_AGAIN"); break;
case Result::READ_ERR_WOULD_BLOCK:
PERR("READ_ERR_WOULD_BLOCK"); break;
case Result::READ_ERR_INVALID:
PERR("READ_ERR_INVALID"); break;
case Result::READ_ERR_IO:
PERR("READ_ERR_IO"); break;
case Result::READ_ERR_INTERRUPT:
PERR("READ_ERR_INTERRUPT"); break;
}
throw Exception();
}
inline void assert_unlink(Vfs::Directory_service::Unlink_result r)
{
typedef Vfs::Directory_service::Unlink_result Result;
switch (r) {
case Result::UNLINK_OK: return;
case Result::UNLINK_ERR_NO_ENTRY:
PERR("UNLINK_ERR_NO_ENTRY"); break;
case Result::UNLINK_ERR_NO_PERM:
PERR("UNLINK_ERR_NO_PERM"); break;
}
throw Exception();
}
static int MAX_DEPTH;
typedef Genode::Path<Vfs::MAX_PATH_LEN> Path;
struct Stress_thread : public Genode::Thread<4*1024*sizeof(Genode::addr_t)>
{
::Path path;
Vfs::file_size count;
Vfs::File_system &vfs;
Stress_thread(Vfs::File_system &vfs, char const *parent)
: Thread(parent), path(parent), count(0), vfs(vfs) { }
};
struct Mkdir_thread : public Stress_thread
{
Mkdir_thread(Vfs::File_system &vfs, char const *parent)
: Stress_thread(vfs, parent) { start(); }
void mkdir_b(int depth)
{
if (++depth > MAX_DEPTH) return;
path.append("/b");
assert_mkdir(vfs.mkdir(path.base(), 0));
++count;
mkdir_b(depth);
}
void mkdir_a(int depth)
{
if (++depth > MAX_DEPTH) return;
size_t path_len = strlen(path.base());
path.append("/b");
assert_mkdir(vfs.mkdir(path.base(), 0));
++count;
mkdir_b(depth);
path.base()[path_len] = '\0';
path.append("/a");
assert_mkdir(vfs.mkdir(path.base(), 0));
++count;
mkdir_a(depth);
}
void entry()
{
try { mkdir_a(1); } catch (...) {
PERR("failed at %s after %llu directories", path.base(), count);
}
}
int wait()
{
join();
return count;
}
};
struct Populate_thread : public Stress_thread
{
Populate_thread(Vfs::File_system &vfs, char const *parent)
: Stress_thread(vfs, parent) { start(); }
void populate(int depth)
{
if (++depth > MAX_DEPTH) return;
using namespace Vfs;
size_t path_len = 1+strlen(path.base());
char dir_type = *(path.base()+(path_len-2));
path.append("/c");
{
Vfs_handle *handle = nullptr;
assert_open(vfs.open(
path.base(), Directory_service::OPEN_MODE_CREATE, &handle));
Vfs_handle::Guard guard(handle);
++count;
}
switch (dir_type) {
case 'a':
path.base()[path_len] = '\0';
path.append("a");
populate(depth);
case 'b':
path.base()[path_len] = '\0';
path.append("b");
populate(depth);
return;
default:
PERR("bad directory %c at the end of %s", dir_type, path.base());
throw Exception();
}
}
void entry()
{
::Path start_path(path.base());
try {
path.append("/a");
populate(1);
path.import(start_path.base());
path.append("/b");
populate(1);
} catch (...) {
PERR("failed at %s after %llu files", path.base(), count);
}
}
int wait()
{
join();
return count;
}
};
struct Write_thread : public Stress_thread
{
Write_thread(Vfs::File_system &vfs, char const *parent)
: Stress_thread(vfs, parent) { start(); }
void write(int depth)
{
if (++depth > MAX_DEPTH) return;
size_t path_len = 1+strlen(path.base());
char dir_type = *(path.base()+(path_len-2));
using namespace Vfs;
path.append("/c");
{
Vfs_handle *handle = nullptr;
assert_open(vfs.open(
path.base(), Directory_service::OPEN_MODE_WRONLY, &handle));
Vfs_handle::Guard guard(handle);
file_size n;
assert_write(handle->fs().write(
handle, path.base(), path_len, n));
count += n;
}
switch (dir_type) {
case 'a':
path.base()[path_len] = '\0';
path.append("a");
write(depth);
case 'b':
path.base()[path_len] = '\0';
path.append("b");
write(depth);
return;
default:
PERR("bad directory %c at the end of %s", dir_type, path.base());
throw Exception();
}
}
void entry()
{
size_t path_len = strlen(path.base());
try {
path.append("/a");
write(1);
path.base()[path_len] = '\0';
path.append("/b");
write(1);
} catch (...) {
PERR("failed at %s after writing %llu bytes", path.base(), count);
}
}
Vfs::file_size wait()
{
join();
return count;
}
};
struct Read_thread : public Stress_thread
{
Read_thread(Vfs::File_system &vfs, char const *parent)
: Stress_thread(vfs, parent) { start(); }
void read(int depth)
{
if (++depth > MAX_DEPTH) return;
size_t path_len = 1+strlen(path.base());
char dir_type = *(path.base()+(path_len-2));
using namespace Vfs;
path.append("/c");
{
Vfs_handle *handle = nullptr;
assert_open(vfs.open(
path.base(), Directory_service::OPEN_MODE_RDONLY, &handle));
Vfs_handle::Guard guard(handle);
char tmp[MAX_PATH_LEN];
file_size n;
assert_read(handle->fs().read(handle, tmp, sizeof(tmp), n));
if (strcmp(path.base(), tmp, n))
PERR("read returned bad data");
count += n;
}
switch (dir_type) {
case 'a':
path.base()[path_len] = '\0';
path.append("a");
read(depth);
case 'b':
path.base()[path_len] = '\0';
path.append("/b");
read(depth);
return;
default:
PERR("bad directory %c at the end of %s", dir_type, path.base());
throw Exception();
}
}
void entry()
{
size_t path_len = strlen(path.base());
try {
path.append("/a");
read(1);
path.base()[path_len] = '\0';
path.append("/b");
read(1);
} catch (...) {
PERR("failed at %s after reading %llu bytes", path.base(), count);
}
}
Vfs::file_size wait()
{
join();
return count;
}
};
struct Unlink_thread : public Stress_thread
{
Unlink_thread(Vfs::File_system &vfs, char const *parent)
: Stress_thread(vfs, parent) { start(); }
void empty_dir(char const *path)
{
::Path subpath(path);
subpath.append("/");
Vfs::Directory_service::Dirent dirent;
for (Vfs::file_size i = vfs.num_dirent(path); i;) {
vfs.dirent(path, --i, dirent);
subpath.append(dirent.name);
switch (dirent.type) {
case Vfs::Directory_service::DIRENT_TYPE_END:
PERR("reached the end prematurely");
throw Exception();
case Vfs::Directory_service::DIRENT_TYPE_DIRECTORY:
empty_dir(subpath.base());
default:
try {
assert_unlink(vfs.unlink(subpath.base()));
} catch (...) {
PERR("unlink %s failed", subpath.base());
throw;
}
subpath.strip_last_element();
}
}
}
void entry()
{
try {
empty_dir(path.base());
vfs.unlink(path.base());
} catch (...) { }
}
};
int main()
{
/* look for dynamic linker */
try {
static Genode::Rom_connection rom("ld.lib.so");
Genode::Process::dynamic_linker(rom.dataspace());
} catch (...) { }
static Vfs::Dir_file_system vfs_root(config()->xml_node().sub_node("vfs"),
Vfs::global_file_system_factory());
static char path[Vfs::MAX_PATH_LEN];
MAX_DEPTH = config()->xml_node().attribute_value("depth", 16U);
unsigned thread_count =
config()->xml_node().attribute_value("threads", 6U);
unsigned long elapsed_ms;
Timer::Connection timer;
/* populate the directory file system at / */
vfs_root.num_dirent("/");
size_t initial_consumption = env()->ram_session()->used();
/**************************
** Generate directories **
**************************/
{
int count = 0;
Mkdir_thread *threads[thread_count];
PLOG("generating directory surface...");
elapsed_ms = timer.elapsed_ms();
for (size_t i = 0; i < thread_count; ++i) {
snprintf(path, 3, "/%lu", i);
vfs_root.mkdir(path, 0);
threads[i] = new (Genode::env()->heap())
Mkdir_thread(vfs_root, path);
}
for (size_t i = 0; i < thread_count; ++i) {
count += threads[i]->wait();
destroy(Genode::env()->heap(), threads[i]);
}
elapsed_ms = timer.elapsed_ms() - elapsed_ms;
vfs_root.sync("/");
PINF("created %d empty directories, %luμs/op , %luKB consumed",
count, (elapsed_ms*1000)/count, env()->ram_session()->used()/1024);
}
/********************
** Generate files **
********************/
{
int count = 0;
Populate_thread *threads[thread_count];
PLOG("generating files...");
elapsed_ms = timer.elapsed_ms();
for (size_t i = 0; i < thread_count; ++i) {
snprintf(path, 3, "/%lu", i);
vfs_root.mkdir(path, 0);
threads[i] = new (Genode::env()->heap())
Populate_thread(vfs_root, path);
}
for (size_t i = 0; i < thread_count; ++i) {
count += threads[i]->wait();
destroy(Genode::env()->heap(), threads[i]);
}
elapsed_ms = timer.elapsed_ms() - elapsed_ms;
vfs_root.sync("/");
PINF("created %d empty files, %luμs/op, %luKB consumed",
count, (elapsed_ms*1000)/count, env()->ram_session()->used()/1024);
}
/*****************
** Write files **
*****************/
if (!config()->xml_node().attribute_value("write", true)) {
elapsed_ms = timer.elapsed_ms();
PINF("total: %lums, %luK consumed",
elapsed_ms, env()->ram_session()->used()/1024);
return 0;
}
{
Vfs::file_size count = 0;
Write_thread *threads[thread_count];
PLOG("writing files...");
elapsed_ms = timer.elapsed_ms();
for (size_t i = 0; i < thread_count; ++i) {
snprintf(path, 3, "/%lu", i);
vfs_root.mkdir(path, 0);
threads[i] = new (Genode::env()->heap())
Write_thread(vfs_root, path);
}
for (size_t i = 0; i < thread_count; ++i) {
count += threads[i]->wait();
destroy(Genode::env()->heap(), threads[i]);
}
elapsed_ms = timer.elapsed_ms() - elapsed_ms;
vfs_root.sync("/");
PINF("wrote %llu bytes %llukB/s, %luKB consumed",
count, count/elapsed_ms, env()->ram_session()->used()/1024);
}
/*****************
** Read files **
*****************/
if (!config()->xml_node().attribute_value("read", true)) {
elapsed_ms = timer.elapsed_ms();
PINF("total: %lums, %luKB consumed",
elapsed_ms, env()->ram_session()->used()/1024);
return 0;
}
{
Vfs::file_size count = 0;
Read_thread *threads[thread_count];
PLOG("reading files...");
elapsed_ms = timer.elapsed_ms();
for (size_t i = 0; i < thread_count; ++i) {
snprintf(path, 3, "/%lu", i);
vfs_root.mkdir(path, 0);
threads[i] = new (Genode::env()->heap())
Read_thread(vfs_root, path);
}
for (size_t i = 0; i < thread_count; ++i) {
count += threads[i]->wait();
destroy(Genode::env()->heap(), threads[i]);
}
elapsed_ms = timer.elapsed_ms() - elapsed_ms;
vfs_root.sync("/");
PINF("read %llu bytes, %llukB/s, %luKB consumed",
count, count/elapsed_ms, env()->ram_session()->used()/1024);
}
/******************
** Unlink files **
******************/
if (!config()->xml_node().attribute_value("unlink", true)) {
elapsed_ms = timer.elapsed_ms();
PINF("total: %lums, %luKB consumed",
elapsed_ms, env()->ram_session()->used()/1024);
return 0;
}
{
Unlink_thread *threads[thread_count];
PLOG("unlink files...");
elapsed_ms = timer.elapsed_ms();
for (size_t i = 0; i < thread_count; ++i) {
snprintf(path, 3, "/%lu", i);
vfs_root.mkdir(path, 0);
threads[i] = new (Genode::env()->heap())
Unlink_thread(vfs_root, path);
}
for (size_t i = 0; i < thread_count; ++i) {
threads[i]->join();
destroy(Genode::env()->heap(), threads[i]);
}
elapsed_ms = timer.elapsed_ms() - elapsed_ms;
vfs_root.sync("/");
PINF("unlink in %lums, %luKB consumed",
elapsed_ms, env()->ram_session()->used()/1024);
}
PINF("total: %lums, %luKB consumed",
timer.elapsed_ms(), env()->ram_session()->used()/1024);
size_t outstanding = env()->ram_session()->used() - initial_consumption;
if (outstanding) {
if (outstanding < 1024)
PERR("%luB not freed after unlink and sync!", outstanding);
else
PERR("%luKB not freed after unlink and sync!", outstanding/1024);
}
return 0;
}

View File

@ -0,0 +1,3 @@
TARGET = vfs_stress
SRC_CC = main.cc
LIBS = base vfs