block_tester: update to new block-client API

- Added 'io_buffer' attribute, default is 4M
- Added 'batch' attribute, specifying the number of jobs used
  in parallel, default is 1 (sequential)
- Removed 'synchronous' attribute (use batch of 1 instead)
- Added 'copy' attribute (default "yes")
- Print number of signals ("triggered")

Issue #3283
This commit is contained in:
Norman Feske 2019-04-11 21:36:28 +02:00 committed by Christian Helmuth
parent 784206c85c
commit 17fda73ca1
8 changed files with 497 additions and 866 deletions

View File

@ -37,13 +37,18 @@ install_config {
<resource name="RAM" quantum="32M"/>
<config verbose="yes" report="no" log="yes" stop_on_error="no">
<tests>
<sequential length="1M" size="4K" synchronous="yes"/>
<sequential length="1M" size="8K" synchronous="yes"/>
<sequential length="1M" size="16K"/>
<sequential length="1M" size="64K"/>
<sequential length="1M" size="128K"/>
<sequential length="1M" size="4K" synchronous="yes" write="yes"/>
<sequential length="1M" size="64K" write="yes" synchronous="yes"/>
<sequential copy="no" length="16M" size="4K"/>
<sequential copy="no" length="16M" size="8K"/>
<sequential copy="no" length="3G" size="16K" batch="32"/>
<sequential copy="no" length="15" size="64K" batch="32"/>
<sequential copy="no" length="30G" size="128K" batch="32"/>
<sequential copy="no" length="16M" size="4K" write="yes"/>
<sequential copy="no" length="16M" size="64K" write="yes"/>
<!-- test client-side request splitting -->
<sequential copy="no" length="16M" size="512K" io_buffer="64K"/>
</tests>
</config>
<route>

View File

@ -81,21 +81,27 @@ append config {
<resource name="RAM" quantum="32M"/>
<config verbose="yes" report="no" log="yes" stop_on_error="no">
<tests>
<!-- synchronous="no" 4K/8K currently leads to deadlocking ahci_drv
<sequential length="1G" size="4K"/>
<sequential length="1G" size="8K"/>
-->
<sequential length="1G" size="4K" synchronous="yes"/>
<sequential length="1G" size="8K" synchronous="yes"/>
<sequential length="1G" size="16K"/>
<sequential length="1G" size="64K"/>
<sequential length="1G" size="128K"/>
<sequential length="1G" size="4K" synchronous="yes" write="yes"/>
<sequential length="1G" size="64K" write="yes" synchronous="yes"/>
<random length="1G" size="16K" seed="0xdeadbeef" synchronous="yes"/>
<random length="8G" size="512K" seed="0xc0ffee" synchronous="yes"/>
<ping_pong length="1G" size="16K"/>
<replay bulk="no">
<!-- synchronous="no" 4K/8K currently leads to deadlocking ahci_drv -->
<sequential copy="no" length="128M" size="4K"/>
<sequential copy="no" length="128M" size="4K" batch="32"/>
<sequential copy="no" length="128M" size="4K" batch="1000"/>
<sequential copy="no" length="128M" size="8K"/>
<sequential copy="no" length="128M" size="8K" batch="32"/>
<sequential copy="no" length="1G" size="16K" batch="32"/>
<sequential copy="no" length="1G" size="64K" batch="32"/>
<sequential copy="no" length="1G" size="128K" batch="32"/>
<sequential copy="no" length="128M" size="4K" write="yes"/>
<sequential copy="no" length="1G" size="64K" write="yes"/>
<random length="128M" size="16K" seed="0xdeadbeef" batch="32"/>
<random length="512M" size="512K" seed="0xc0ffee" />
<ping_pong length="128M" size="16K"/>
<replay batch="10">
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>

View File

@ -20,9 +20,6 @@ The following list contains all available tests:
* 'replay' executes a previously recorded Block session operation sequence.
- If the 'bulk' attribute is specified, the test will try to fill up
the packet stream with request until it is full.
Each request is specified by a 'request' node which has the following
attributes:
@ -50,15 +47,8 @@ The following list contains all available tests:
- The 'write' attribute specifies whether the tests writes or reads, if
it is missing it defaults to reading.
- The 'skip' attribute specifies how many bytes should be skipped between
each request.
- The 'synchronous' attribute specifies if each request is done in a
synchronous fashion or if the component tries to fill up the transfer
buffer as much as possible.
* 'ping_pong' reads or writes Blocks from the beginning and the end of the
specfied part of the Block session in an alternating fashion
specified part of the Block session in an alternating fashion
- The 'start' attribute specifies the logical block address where the test
begins, if it is missing the first block is used.
@ -90,6 +80,18 @@ which are supported by every test:
- If the 'verbose' attribute is specified, the test will print each
request to the LOG session before it will be executed.
- The 'copy' attribute specifies whether the content should be copied
in memory, which a typical client would do. The default is "yes".
If set to "no", the payload data remains untouched, exposing the raw
I/O and protocol overhead.
- The 'batch' attribute specifies how many block-operation jobs are
issued at once. The default value is 1, which corresponds to a
sequential mode of operation.
- The 'io_buffer' attribute defines the size of the I/O communication
buffer for the block session. The default value is "4M".
Note: all tests use a fixed sized scratch buffer of 1 (replay 4) MiB, plan the
quota and request size accordingly.
@ -116,9 +118,6 @@ The following config snippet illustrates the use of the component:
! <!-- write 1200MiB in 8KiB requests -->
! <sequential write="yes" length="1200M" size="8K"/>
!
! <!-- read first 32GiB in 128K chunks skipping 1MiB inbetween -->
! <sequential start="0" length="32G" size="128K" skip="1M"/>
!
! <!-- replay the beginning Ext2 mount operations -->
! <replay>
! <request type="read" block_number="2" count="1"/>
@ -166,13 +165,14 @@ value tuples:
* rx:<int> number of blocks read
* size:<int> size of one request in bytes
* test:<string> name of the test
* triggered<int> number of handled I/O signals
* tx:<int> number of blocks written
Since the LOG output is mainly intended for automated testing and analyzing all
size values are given in bytes. The following examplary output illustrates the
structure:
! test:sequential rx:1048576 tx:0 bytes:536870912 size:65536 bsize:512 duration:302 mibs:1695.364 iops:27125.828 result:0
! finished sequential rx:32768 tx:0 bytes:134217728 size:131072 bsize:4096 duration:27 mibs:4740.740 iops:37925.925 triggered:35 result:ok
Report

View File

@ -26,118 +26,285 @@ namespace Test {
using namespace Genode;
/*
* Result of a test
*/
struct Result
{
uint64_t duration { 0 };
uint64_t bytes { 0 };
uint64_t rx { 0 };
uint64_t tx { 0 };
uint64_t request_size { 0 };
uint64_t block_size { 0 };
bool success { false };
bool calculate { false };
float mibs { 0.0f };
float iops { 0.0f };
Result() { }
Result(bool success, uint64_t d, uint64_t b, uint64_t rx, uint64_t tx,
uint64_t rsize, uint64_t bsize)
:
duration(d), bytes(b), rx(rx), tx(tx),
request_size(rsize ? rsize : bsize), block_size(bsize),
success(success)
{
mibs = ((double)bytes / ((double)duration/1000)) / (1024 * 1024);
/* total ops / seconds w/o any latency inclusion */
iops = (double)((rx + tx) / (request_size / block_size))
/ ((double)duration / 1000);
}
void print(Genode::Output &out) const
{
Genode::print(out, "rx:", rx, " ",
"tx:", tx, " ",
"bytes:", bytes, " ",
"size:", request_size, " ",
"bsize:", block_size, " ",
"duration:", duration, " "
);
if (calculate) {
Genode::print(out, "mibs:", (unsigned)(mibs * (1<<20u)),
" ", "iops:", (unsigned)(iops + 0.5f));
}
Genode::print(out, " result:", success ? "ok" : "failed");
}
};
/*
* Base class used for test running list
*/
struct Test_base : private Genode::Fifo<Test_base>::Element
{
protected:
/*
* Must be called by every test when it has finished
*/
Genode::Signal_context_capability _finished_sig;
void _finish()
{
_finished = true;
if (_finished_sig.valid()) {
Genode::Signal_transmitter(_finished_sig).submit();
}
}
bool _verbose { false };
Block::Session::Info _info { };
size_t _length_in_blocks { 0 };
size_t _size_in_blocks { 0 };
uint64_t _start_time { 0 };
uint64_t _end_time { 0 };
size_t _bytes { 0 };
uint64_t _rx { 0 };
uint64_t _tx { 0 };
bool _stop_on_error { true };
bool _finished { false };
bool _success { false };
public:
friend class Genode::Fifo<Test_base>;
Test_base(Genode::Signal_context_capability finished_sig)
: _finished_sig(finished_sig) { }
virtual ~Test_base() { };
/********************
** Test interface **
********************/
virtual void start(bool stop_on_error) = 0;
virtual Result finish() = 0;
virtual char const *name() const = 0;
};
struct Test_failed : Genode::Exception { };
struct Constructing_test_failed : Genode::Exception { };
struct Result;
struct Test_base;
struct Main;
} /* Test */
struct Test_failed : Exception { };
struct Constructing_test_failed : Exception { };
}
struct Test::Result
{
uint64_t duration { 0 };
uint64_t bytes { 0 };
uint64_t rx { 0 };
uint64_t tx { 0 };
uint64_t request_size { 0 };
uint64_t block_size { 0 };
size_t triggered { 0 };
bool success { false };
bool calculate { false };
float mibs { 0.0f };
float iops { 0.0f };
Result() { }
Result(bool success, uint64_t d, uint64_t b, uint64_t rx, uint64_t tx,
uint64_t rsize, uint64_t bsize, size_t triggered)
:
duration(d), bytes(b), rx(rx), tx(tx),
request_size(rsize ? rsize : bsize), block_size(bsize),
triggered(triggered), success(success)
{
mibs = ((double)bytes / ((double)duration/1000)) / (1024 * 1024);
/* total ops / seconds w/o any latency inclusion */
iops = (double)((rx + tx) / (request_size / block_size))
/ ((double)duration / 1000);
}
void print(Genode::Output &out) const
{
Genode::print(out, "rx:", rx, " ",
"tx:", tx, " ",
"bytes:", bytes, " ",
"size:", request_size, " ",
"bsize:", block_size, " ",
"duration:", duration, " "
);
if (calculate) {
Genode::print(out, "mibs:", mibs, " iops:", iops);
}
Genode::print(out, " triggered:", triggered);
Genode::print(out, " result:", success ? "ok" : "failed");
}
};
/*
* Base class used for test running list
*/
struct Test::Test_base : private Genode::Fifo<Test_base>::Element
{
protected:
Env &_env;
Allocator &_alloc;
Xml_node const _node;
typedef Block::block_number_t block_number_t;
bool const _verbose;
size_t const _io_buffer;
uint64_t const _progress_interval;
bool const _copy;
size_t const _batch;
Constructible<Timer::Connection> _timer { };
Constructible<Timer::Periodic_timeout<Test_base>> _progress_timeout { };
Allocator_avl _block_alloc { &_alloc };
struct Job;
typedef Block::Connection<Job> Block_connection;
Constructible<Block_connection> _block { };
struct Job : Block_connection::Job
{
unsigned const id;
Job(Block_connection &connection, Block::Operation operation, unsigned id)
:
Block_connection::Job(connection, operation), id(id)
{ }
};
/*
* Must be called by every test when it has finished
*/
Genode::Signal_context_capability _finished_sig;
void finish()
{
_end_time = _timer->elapsed_ms();
_finished = true;
if (_finished_sig.valid()) {
Genode::Signal_transmitter(_finished_sig).submit();
}
_timer.destruct();
}
Block::Session::Info _info { };
size_t _length_in_blocks { 0 };
size_t _size_in_blocks { 0 };
uint64_t _start_time { 0 };
uint64_t _end_time { 0 };
size_t _bytes { 0 };
uint64_t _rx { 0 };
uint64_t _tx { 0 };
size_t _triggered { 0 }; /* number of I/O signals */
unsigned _job_cnt { 0 };
unsigned _completed { 0 };
bool _stop_on_error { true };
bool _finished { false };
bool _success { false };
char _scratch_buffer[1u<<20] { };
void _memcpy(char *dst, char const *src, size_t length)
{
if (length > sizeof(_scratch_buffer)) {
warning("scratch buffer too small for copying");
return;
}
Genode::memcpy(dst, src, length);
}
public:
/**
* Block::Connection::Update_jobs_policy
*/
void produce_write_content(Job &job, off_t offset, char *dst, size_t length)
{
_tx += length / _info.block_size;
_bytes += length;
if (_verbose)
log("job ", job.id, ": writing ", length, " bytes at ", offset);
if (_copy)
_memcpy(dst, _scratch_buffer, length);
}
/**
* Block::Connection::Update_jobs_policy
*/
void consume_read_result(Job &job, off_t offset,
char const *src, size_t length)
{
_rx += length / _info.block_size;
_bytes += length;
if (_verbose)
log("job ", job.id, ": got ", length, " bytes at ", offset);
if (_copy)
_memcpy(_scratch_buffer, src, length);
}
/**
* Block_connection::Update_jobs_policy
*/
void completed(Job &job, bool success)
{
_completed++;
if (_verbose)
log("job ", job.id, ": ", job.operation(), ", completed");
if (!success)
error("processing ", job.operation(), " failed");
destroy(_alloc, &job);
if (!success && _stop_on_error)
throw Test_failed();
/* replace completed job by new one */
_spawn_job();
bool const jobs_active = (_job_cnt != _completed);
_success = !jobs_active && success;
if (!jobs_active || !success)
finish();
}
protected:
void _handle_progress_timeout(Duration)
{
log("progress: rx:", _rx, " tx:", _tx);
}
void _handle_block_io()
{
_triggered++;
_block->update_jobs(*this);
}
Signal_handler<Test_base> _block_io_sigh {
_env.ep(), *this, &Test_base::_handle_block_io };
public:
friend class Genode::Fifo<Test_base>;
Test_base(Env &env, Allocator &alloc, Xml_node node,
Signal_context_capability finished_sig)
:
_env(env), _alloc(alloc), _node(node),
_verbose(node.attribute_value("verbose", false)),
_io_buffer(_node.attribute_value("io_buffer",
Number_of_bytes(4*1024*1024))),
_progress_interval(_node.attribute_value("progress", 0ul)),
_copy(_node.attribute_value("copy", true)),
_batch(_node.attribute_value("batch", 1u)),
_finished_sig(finished_sig)
{
if (_progress_interval)
_progress_timeout.construct(*_timer, *this,
&Test_base::_handle_progress_timeout,
Microseconds(_progress_interval*1000));
}
virtual ~Test_base() { };
void start(bool stop_on_error)
{
_stop_on_error = stop_on_error;
_block.construct(_env, &_block_alloc, _io_buffer);
_block->sigh(_block_io_sigh);
_info = _block->info();
_init();
for (unsigned i = 0; i < _batch; i++)
_spawn_job();
_timer.construct(_env);
_start_time = _timer->elapsed_ms();
_handle_block_io();
}
/********************
** Test interface **
********************/
virtual void _init() = 0;
virtual void _spawn_job() = 0;
virtual Result result() = 0;
virtual char const *name() const = 0;
virtual void print(Output &) const = 0;
};
/* tests */
#include <test_ping_pong.h>
@ -156,9 +323,6 @@ struct Test::Main
Genode::Attached_rom_dataspace _config_rom { _env, "config" };
bool const _verbose {
_config_rom.xml().attribute_value("verbose", false) };
bool const _log {
_config_rom.xml().attribute_value("log", false) };
@ -219,14 +383,14 @@ struct Test::Main
{
/* clean up current test */
if (_current) {
Result r = _current->finish();
Result r = _current->result();
if (!r.success) { _success = false; }
r.calculate = _calculate;
if (_log) {
Genode::log("test:", _current->name(), " ", r);
Genode::log("finished ", _current->name(), " ", r);
}
if (_report) {
@ -237,9 +401,6 @@ struct Test::Main
_generate_report();
}
if (_verbose) {
Genode::log("finished ", _current->name(), " ", r.success ? 0 : 1);
}
Genode::destroy(&_heap, _current);
_current = nullptr;
}
@ -247,13 +408,14 @@ struct Test::Main
/* execute next test */
if (!_current) {
_tests.dequeue([&] (Test_base &head) {
if (_verbose) { Genode::log("start ", head.name()); }
if (_log) { Genode::log("start ", head); }
try {
head.start(_stop_on_error);
_current = &head;
} catch (...) {
Genode::log("Could not start ", head.name());
Genode::log("Could not start ", head);
Genode::destroy(&_heap, &head);
throw;
}
});
}

View File

@ -14,9 +14,7 @@
#ifndef _TEST_PING_PONG_H_
#define _TEST_PING_PONG_H_
namespace Test {
struct Ping_pong;
}
namespace Test { struct Ping_pong; }
/*
@ -27,200 +25,77 @@ namespace Test {
*/
struct Test::Ping_pong : Test_base
{
Genode::Env &_env;
Genode::Allocator &_alloc;
bool _ping = true;
block_number_t _end = 0;
block_number_t _start = _node.attribute_value("start", 0u);
size_t const _size = _node.attribute_value("size", Number_of_bytes());
size_t const _length = _node.attribute_value("length", Number_of_bytes());
Block::Packet_descriptor::Opcode _op {
Block::Packet_descriptor::READ };
Block::Operation::Type const _op_type = _node.attribute_value("write", false)
? Block::Operation::Type::WRITE
: Block::Operation::Type::READ;
/* block session infos */
enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
Genode::Allocator_avl _block_alloc { &_alloc };
Genode::Constructible<Block::Connection<>> _block { };
using Test_base::Test_base;
Genode::Constructible<Timer::Connection> _timer { };
/* test data */
bool _ping { true };
Block::sector_t _start { 0 };
Block::sector_t _end { 0 };
size_t _length { 0 };
size_t _size { 0 };
size_t _blocks { 0 };
char _scratch_buffer[1u<<20] { };
Genode::Constructible<Timer::Periodic_timeout<Ping_pong>> _progress_timeout { };
void _handle_progress_timeout(Genode::Duration)
void _spawn_job() override
{
Genode::log("progress: rx:", _rx, " tx:", _tx);
}
void _handle_submit()
{
try {
while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit()) {
Block::Packet_descriptor tmp =
_block->alloc_packet(_size_in_blocks * _info.block_size);
Block::sector_t const lba = _ping ? _start + _blocks
: _end - _blocks;
_ping ^= true;
Block::Packet_descriptor p(tmp,
_op, lba, _size_in_blocks);
/* simulate write */
if (_op == Block::Packet_descriptor::WRITE) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(content, _scratch_buffer, p.size());
}
_block->tx()->submit_packet(p);
_blocks += _size_in_blocks;
}
} catch (...) { }
}
void _handle_ack()
{
if (_finished) { return; }
while (_block->tx()->ack_avail()) {
Block::Packet_descriptor p = _block->tx()->get_acked_packet();
if (!p.succeeded()) {
Genode::error("processing ", p.block_number(), " ",
p.block_count(), " failed");
if (_stop_on_error) { throw Test_failed(); }
else { _finish(); }
}
size_t const psize = p.size();
size_t const count = psize / _info.block_size;
Block::Packet_descriptor::Opcode const op = p.operation();
/* simulate read */
if (op == Block::Packet_descriptor::READ) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(_scratch_buffer, content, p.size());
}
_rx += (op == Block::Packet_descriptor::READ) * count;
_tx += (op == Block::Packet_descriptor::WRITE) * count;
_bytes += psize;
_block->tx()->release_packet(p);
}
if (_bytes >= _length) {
_success = true;
_finish();
if (_bytes >= _length)
return;
}
_handle_submit();
_job_cnt++;
block_number_t const lba = _ping ? _start : _end - _start;
_ping = !_ping;
Block::Operation const operation { .type = _op_type,
.block_number = lba,
.count = _size_in_blocks };
new (_alloc) Job(*_block, operation, _job_cnt);
_start += _size_in_blocks;
}
void _finish()
void _init() override
{
_end_time = _timer->elapsed_ms();
Test_base::_finish();
}
Genode::Signal_handler<Ping_pong> _ack_sigh {
_env.ep(), *this, &Ping_pong::_handle_ack };
Genode::Signal_handler<Ping_pong> _submit_sigh {
_env.ep(), *this, &Ping_pong::_handle_submit };
Genode::Xml_node _node;
/**
* Constructor
*
* \param block Block session reference
* \param node node containing the test configuration
*/
Ping_pong(Genode::Env &env, Genode::Allocator &alloc,
Genode::Xml_node node,
Genode::Signal_context_capability finished_sig)
: Test_base(finished_sig), _env(env), _alloc(alloc), _node(node)
{ }
/********************
** Test interface **
********************/
void start(bool stop_on_error) override
{
_stop_on_error = stop_on_error;
_block.construct(_env, &_block_alloc, TX_BUF_SIZE);
_block->tx_channel()->sigh_ack_avail(_ack_sigh);
_block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
_info = _block->info();
_start = _node.attribute_value("start", 0u);
_size = _node.attribute_value("size", Number_of_bytes());
_length = _node.attribute_value("length", Number_of_bytes());
if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size");
error("request size exceeds scratch buffer size");
throw Constructing_test_failed();
}
size_t const total_bytes = _info.block_count * _info.block_size;
if (_length > total_bytes - (_start * _info.block_size)) {
Genode::error("length too large invalid");
error("length too large invalid");
throw Constructing_test_failed();
}
if (_info.block_size > _size || (_size % _info.block_size) != 0) {
Genode::error("request size invalid");
error("request size invalid");
throw Constructing_test_failed();
}
if (_node.attribute_value("write", false)) {
_op = Block::Packet_descriptor::WRITE;
}
_size_in_blocks = _size / _info.block_size;
_length_in_blocks = _length / _info.block_size;
_end = _start + _length_in_blocks;
_timer.construct(_env);
uint64_t const progress_interval = _node.attribute_value("progress", 0ul);
if (progress_interval) {
_progress_timeout.construct(*_timer, *this,
&Ping_pong::_handle_progress_timeout,
Genode::Microseconds(progress_interval*1000));
}
_start_time = _timer->elapsed_ms();
_handle_submit();
}
Result finish() override
Result result() override
{
_timer.destruct();
_block.destruct();
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, _size, _info.block_size);
_bytes, _rx, _tx, _size, _info.block_size, _triggered);
}
char const *name() const override { return "ping_pong"; }
void print(Output &out) const override
{
Genode::print(out, name(), " ", Block::Operation::type_name(_op_type), " "
"start:", _start, " "
"size:", _size, " "
"length:", _length, " "
"copy:", _copy, " "
"batch:", _batch);
}
};
#endif /* _TEST_PING_PONG_H_ */

View File

@ -14,9 +14,8 @@
#ifndef _TEST_RANDOM_H_
#define _TEST_RANDOM_H_
namespace Test {
struct Random;
}
namespace Test { struct Random; }
namespace Util {
@ -79,236 +78,101 @@ namespace Util {
*/
struct Test::Random : Test_base
{
Genode::Env &_env;
Genode::Allocator &_alloc;
Block::Packet_descriptor::Opcode _op {
Block::Packet_descriptor::READ };
bool _alternate_access { false };
/* block session infos */
enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
Genode::Allocator_avl _block_alloc { &_alloc };
Genode::Constructible<Block::Connection<>> _block { };
Genode::Constructible<Timer::Connection> _timer { };
Util::Xoroshiro _random;
size_t _size { 0 };
uint64_t _length { 0 };
char _scratch_buffer[1u<<20] { };
size_t const _size = _node.attribute_value("size", Number_of_bytes());
uint64_t const _length = _node.attribute_value("length", Number_of_bytes());
size_t _blocks { 0 };
Block::Operation::Type _op_type = Block::Operation::Type::READ;
/* _synchronous controls bulk */
bool _synchronous { false };
Genode::Constructible<Timer::Periodic_timeout<Random>> _progress_timeout { };
void _handle_progress_timeout(Genode::Duration)
{
Genode::log("progress: rx:", _rx, " tx:", _tx);
}
Block::sector_t _next_block()
block_number_t _next_block()
{
uint64_t r = 0;
block_number_t max = _info.block_count;
if (max >= _size_in_blocks + 1)
max -= _size_in_blocks + 1;
do {
r = _random.get() % _info.block_count;
r = _random.get() % max;
} while (r + _size_in_blocks > _info.block_count);
return r;
}
void _handle_submit()
{
try {
bool next = true;
while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit() && next) {
Block::Packet_descriptor tmp = _block->alloc_packet(_size);
Block::sector_t lba = _next_block();
Block::Packet_descriptor::Opcode op =
_alternate_access ? (lba & 0x1)
? Block::Packet_descriptor::WRITE
: Block::Packet_descriptor::READ
: _op;
Block::Packet_descriptor p(tmp, op, lba, _size_in_blocks);
bool const write = op == Block::Packet_descriptor::WRITE;
/* simulate write */
if (write) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(content, _scratch_buffer, p.size());
}
if (_verbose) {
Genode::log("submit: lba:", lba, " size:", _size,
" ", write ? "tx" : "rx");
}
_block->tx()->submit_packet(p);
_blocks += _size_in_blocks;
next = !_synchronous;
}
} catch (...) { }
}
void _handle_ack()
{
if (_finished) { return; }
while (_block->tx()->ack_avail()) {
Block::Packet_descriptor p = _block->tx()->get_acked_packet();
if (!p.succeeded()) {
Genode::error("processing ", p.block_number(), " ",
p.block_count(), " failed");
if (_stop_on_error) { throw Test_failed(); }
else { _finish(); break; }
}
size_t const psize = p.size();
size_t const count = psize / _info.block_size;
Block::Packet_descriptor::Opcode const op = p.operation();
bool const read = op == Block::Packet_descriptor::READ;
/* simulate read */
if (read) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(_scratch_buffer, content, p.size());
}
if (_verbose) {
Genode::log("ack: lba:", p.block_number(), " size:", p.size(),
" ", read ? "rx" : "tx");
}
_rx += (op == Block::Packet_descriptor::READ) * count;
_tx += (op == Block::Packet_descriptor::WRITE) * count;
_bytes += psize;
_block->tx()->release_packet(p);
}
if (_bytes >= _length) {
_success = true;
_finish();
return;
}
_handle_submit();
}
void _finish()
{
_end_time = _timer->elapsed_ms();
Test_base::_finish();
}
Genode::Signal_handler<Random> _ack_sigh {
_env.ep(), *this, &Random::_handle_ack };
Genode::Signal_handler<Random> _submit_sigh {
_env.ep(), *this, &Random::_handle_submit };
Genode::Xml_node _node;
/**
* Constructor
*
* \param block Block session reference
* \param node node containing the test configuration
*/
Random(Genode::Env &env, Genode::Allocator &alloc,
Genode::Xml_node node,
Genode::Signal_context_capability finished_sig)
template <typename... ARGS>
Random(ARGS &&...args)
:
Test_base(finished_sig), _env(env), _alloc(alloc),
_random(node.attribute_value("seed", 42u)),
_node(node)
Test_base(args...),
_random(_node.attribute_value("seed", 42UL))
{ }
void _init() override
{
_verbose = node.attribute_value("verbose", false);
}
/********************
** Test interface **
********************/
void start(bool stop_on_error) override
{
_stop_on_error = stop_on_error;
_block.construct(_env, &_block_alloc, TX_BUF_SIZE);
_block->tx_channel()->sigh_ack_avail(_ack_sigh);
_block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
_info = _block->info();
_size = _node.attribute_value("size", Number_of_bytes());
_length = _node.attribute_value("length", Number_of_bytes());
if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size");
error("request size exceeds scratch buffer size");
throw Constructing_test_failed();
}
if (!_size || !_length) {
Genode::error("request size or length invalid");
error("request size or length invalid");
throw Constructing_test_failed();
}
if (_info.block_size > _size || (_size % _info.block_size) != 0) {
Genode::error("request size invalid ", _info.block_size, " ", _size);
error("request size invalid ", _info.block_size, " ", _size);
throw Constructing_test_failed();
}
_synchronous = _node.attribute_value("synchronous", false);
bool const r = _node.attribute_value("read", false);
if (r) { _op_type = Block::Operation::Type::READ; }
bool r = _node.attribute_value("write", false);
if (r) { _op = Block::Packet_descriptor::WRITE; }
bool w = _node.attribute_value("read", false);
if (w) { _op = Block::Packet_descriptor::WRITE; }
bool const w = _node.attribute_value("write", false);
if (w) { _op_type = Block::Operation::Type::WRITE; }
_alternate_access = w && r;
_size_in_blocks = _size / _info.block_size;
_length_in_blocks = _length / _info.block_size;
_timer.construct(_env);
uint64_t const progress_interval = _node.attribute_value("progress", 0ul);
if (progress_interval) {
_progress_timeout.construct(*_timer, *this,
&Random::_handle_progress_timeout,
Genode::Microseconds(progress_interval*1000));
}
_start_time = _timer->elapsed_ms();
_handle_submit();
}
Result finish() override
void _spawn_job() override
{
_timer.destruct();
_block.destruct();
if (_bytes >= _length)
return;
_job_cnt++;
block_number_t const lba = _next_block();
Block::Operation::Type const op_type =
_alternate_access ? (lba & 0x1) ? Block::Operation::Type::WRITE
: Block::Operation::Type::READ
: _op_type;
Block::Operation const operation { .type = op_type,
.block_number = lba,
.count = _size_in_blocks };
new (_alloc) Job(*_block, operation, _job_cnt);
}
Result result() override
{
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, _size, _info.block_size);
_bytes, _rx, _tx, _size, _info.block_size, _triggered);
}
char const *name() const override { return "random"; }
void print(Output &out) const override
{
Genode::print(out, name(), " "
"size:", _size, " "
"length:", _length, " "
"copy:", _copy, " "
"batch:", _batch);
}
};
#endif /* _TEST_RANDOM_H_ */

View File

@ -14,194 +14,68 @@
#ifndef _TEST_REPLAY_H_
#define _TEST_REPLAY_H_
namespace Test {
struct Replay;
}
namespace Test { struct Replay; }
/*
* Replay test
*
* This test replays a recorded sequence of Block session
* requests.
* This test replays a recorded sequence of Block session requests.
*/
struct Test::Replay : Test_base
{
Genode::Env &env;
Genode::Allocator &alloc;
using Test_base::Test_base;
struct Request : Genode::Fifo<Request>::Element
void _init() override
{
Block::Packet_descriptor::Opcode op;
Block::sector_t nr;
Genode::size_t count;
Request(Block::Packet_descriptor::Opcode op,
Block::sector_t nr, Genode::size_t count)
: op(op), nr(nr), count(count) { }
};
unsigned request_num { 0 };
Genode::Fifo<Request> requests { };
char _scratch_buffer[4u<<20] { };
bool _bulk { false };
Genode::Constructible<Timer::Connection> _timer { };
enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
Genode::Allocator_avl _block_alloc { &alloc };
Genode::Constructible<Block::Connection<>> _block { };
Genode::Signal_handler<Replay> _ack_sigh {
env.ep(), *this, &Replay::_handle_ack };
Genode::Signal_handler<Replay> _submit_sigh {
env.ep(), *this, &Replay::_handle_submit };
void _handle_submit()
{
bool more = true;
try {
while (_block->tx()->ready_to_submit() && more) {
/* peak at head ... */
more = false;
requests.dequeue([&] (Request &req) {
Block::Packet_descriptor p(
_block->alloc_packet(req.count * _info.block_size),
req.op, req.nr, req.count);
_node.for_each_sub_node("request", [&](Xml_node request) {
bool const write = req.op == Block::Packet_descriptor::WRITE;
auto op_type = [&] ()
{
typedef String<8> Type;
/* simulate write */
if (write) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(content, _scratch_buffer, p.size());
}
if (request.attribute_value("type", Type()) == "read")
return Block::Operation::Type::READ;
_block->tx()->submit_packet(p);
if (request.attribute_value("type", Type()) == "write")
return Block::Operation::Type::WRITE;
Genode::destroy(&alloc, &req);
more = _bulk;
});
}
} catch (...) { }
}
error("operation type not defined: ", request);
throw 1;
};
void _finish()
{
_end_time = _timer->elapsed_ms();
Block::Operation const operation
{
.type = op_type(),
.block_number = request.attribute_value("lba", (block_number_t)0),
.count = request.attribute_value("count", 0UL)
};
Test_base::_finish();
}
void _handle_ack()
{
if (_finished) { return; }
while (_block->tx()->ack_avail()) {
Block::Packet_descriptor p = _block->tx()->get_acked_packet();
if (!p.succeeded()) {
Genode::error("packet failed lba: ", p.block_number(),
" count: ", p.block_count());
if (_stop_on_error) { throw Test_failed(); }
else { _finish(); }
} else {
/* simulate read */
if (p.operation() == Block::Packet_descriptor::READ) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(_scratch_buffer, content, p.size());
}
size_t const psize = p.size();
size_t const count = psize / _info.block_size;
_rx += (p.operation() == Block::Packet_descriptor::READ) * count;
_tx += (p.operation() == Block::Packet_descriptor::WRITE) * count;
_bytes += psize;
if (--request_num == 0) {
_success = true;
_finish();
}
}
_block->tx()->release_packet(p);
}
_handle_submit();
}
Replay(Genode::Env &env, Genode::Allocator &alloc,
Genode::Xml_node config,
Genode::Signal_context_capability finished_sig)
: Test_base(finished_sig), env(env), alloc(alloc)
{
_verbose = config.attribute_value<bool>("verbose", false);
_bulk = config.attribute_value<bool>("bulk", false);
try {
config.for_each_sub_node("request", [&](Xml_node request) {
typedef Genode::String<8> Type;
Block::sector_t const nr = request.attribute_value("lba", (Block::sector_t)0u);
Genode::size_t const count = request.attribute_value("count", 0UL);
Type const type = request.attribute_value("type", Type());
Block::Packet_descriptor::Opcode op;
if (type == "read") { op = Block::Packet_descriptor::READ; }
else if (type == "write") { op = Block::Packet_descriptor::WRITE; }
else { throw -1; }
requests.enqueue(*(new (&alloc) Request(op, nr, count)));
++request_num;
_job_cnt++;
new (&_alloc) Job(*_block, operation, _job_cnt);
});
} catch (...) {
Genode::error("could not read request list");
error("could not read request list");
_block->dissolve_all_jobs([&] (Job &job) { destroy(_alloc, &job); });
return;
}
}
/********************
** Test interface **
********************/
void _spawn_job() override { }
void start(bool stop_on_error) override
Result result() override
{
_stop_on_error = stop_on_error;
_block.construct(env, &_block_alloc, TX_BUF_SIZE);
_block->tx_channel()->sigh_ack_avail(_ack_sigh);
_block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
_info = _block->info();
_timer.construct(env);
_start_time = _timer->elapsed_ms();
_handle_submit();
}
Result finish() override
{
_timer.destruct();
_block.destruct();
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, 0u, _info.block_size);
_bytes, _rx, _tx, 0u, _info.block_size, _triggered);
}
char const *name() const override { return "replay"; }
void print(Output &out) const override
{
Genode::print(out, name());
}
};
#endif /* _TEST_REPLAY_H_ */

View File

@ -14,9 +14,7 @@
#ifndef _TEST_SEQUENTIAL_H_
#define _TEST_SEQUENTIAL_H_
namespace Test {
struct Sequential;
}
namespace Test { struct Sequential; }
/*
@ -27,218 +25,65 @@ namespace Test {
*/
struct Test::Sequential : Test_base
{
Genode::Env &_env;
Genode::Allocator &_alloc;
block_number_t _start = _node.attribute_value("start", 0u);
size_t const _size = _node.attribute_value("size", Number_of_bytes());
size_t const _length = _node.attribute_value("length", Number_of_bytes());
/* test infos */
Block::sector_t _start { 0 };
size_t _length { 0 };
size_t _size { 0 };
Block::Operation::Type const _op_type = _node.attribute_value("write", false)
? Block::Operation::Type::WRITE
: Block::Operation::Type::READ;
/* _synchronous controls bulk */
bool _synchronous { false };
using Test_base::Test_base;
Block::Packet_descriptor::Opcode _op {
Block::Packet_descriptor::READ };
/* block session infos */
enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
Genode::Allocator_avl _block_alloc { &_alloc };
Genode::Constructible<Block::Connection<>> _block { };
Genode::Constructible<Timer::Connection> _timer { };
/* test data */
size_t _blocks { 0 };
size_t _ack_blocks { 0 };
char _scratch_buffer[1u<<20] { };
Genode::Constructible<Timer::Periodic_timeout<Sequential>> _progress_timeout { };
void _handle_progress_timeout(Genode::Duration)
void _init() override
{
Genode::log("progress: rx:", _rx, " tx:", _tx);
}
void _handle_submit()
{
try {
bool next = true;
while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit() && next) {
Block::Packet_descriptor tmp = _block->alloc_packet(_size);
Block::Packet_descriptor p(tmp,
_op, _start, _size_in_blocks);
bool const write = _op == Block::Packet_descriptor::WRITE;
/* simulate write */
if (write) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(content, _scratch_buffer, p.size());
}
try { _block->tx()->submit_packet(p); }
catch (...) { _block->tx()->release_packet(p); }
if (_verbose) {
Genode::log("submit: lba:", _start, " size:", _size,
" ", write ? "tx" : "rx");
}
_start += _size_in_blocks;
_blocks += _size_in_blocks;
/* wrap if needed */
if (_start >= _info.block_count) { _start = 0; }
next = !_synchronous;
}
} catch (...) { }
}
void _handle_ack()
{
if (_finished) { return; }
while (_block->tx()->ack_avail()) {
Block::Packet_descriptor p = _block->tx()->get_acked_packet();
if (!p.succeeded()) {
Genode::error("processing ", p.block_number(), " ",
p.block_count(), " failed");
if (_stop_on_error) { throw Test_failed(); }
else { _finish(); break; }
}
bool const read = _op == Block::Packet_descriptor::READ;
/* simulate read */
if (read) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(_scratch_buffer, content, p.size());
}
size_t const psize = p.size();
size_t const count = psize / _info.block_size;
Block::Packet_descriptor::Opcode const op = p.operation();
_rx += (op == Block::Packet_descriptor::READ) * count;
_tx += (op == Block::Packet_descriptor::WRITE) * count;
_bytes += psize;
_ack_blocks += count;
if (_verbose) {
Genode::log("ack: lba:", p.block_number(), " size:", p.size(),
" ", read ? "rx" : "tx");
}
_block->tx()->release_packet(p);
}
if (_bytes >= _length || _ack_blocks == _length_in_blocks) {
_success = true;
_finish();
return;
}
_handle_submit();
}
void _finish()
{
_end_time = _timer->elapsed_ms();
Test_base::_finish();
}
Genode::Signal_handler<Sequential> _ack_sigh {
_env.ep(), *this, &Sequential::_handle_ack };
Genode::Signal_handler<Sequential> _submit_sigh {
_env.ep(), *this, &Sequential::_handle_submit };
Genode::Xml_node _node;
/**
* Constructor
*
* \param block Block session reference
* \param node node containing the test configuration
*/
Sequential(Genode::Env &env, Genode::Allocator &alloc,
Genode::Xml_node node,
Genode::Signal_context_capability finished_sig)
: Test_base(finished_sig), _env(env), _alloc(alloc), _node(node)
{
_verbose = node.attribute_value("verbose", false);
}
/********************
** Test interface **
********************/
void start(bool stop_on_error) override
{
_stop_on_error = stop_on_error;
_block.construct(_env, &_block_alloc, TX_BUF_SIZE);
_block->tx_channel()->sigh_ack_avail(_ack_sigh);
_block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
_info = _block->info();
_synchronous = _node.attribute_value("synchronous", false);
_start = _node.attribute_value("start", 0u);
_size = _node.attribute_value("size", Genode::Number_of_bytes());
_length = _node.attribute_value("length", Genode::Number_of_bytes());
if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size");
error("request size exceeds scratch buffer size");
throw Constructing_test_failed();
}
if (_info.block_size > _size || (_size % _info.block_size) != 0) {
Genode::error("request size invalid");
error("request size invalid");
throw Constructing_test_failed();
}
if (_node.attribute_value("write", false)) {
_op = Block::Packet_descriptor::WRITE;
}
_size_in_blocks = _size / _info.block_size;
_length_in_blocks = _length / _info.block_size;
_timer.construct(_env);
uint64_t const progress_interval = _node.attribute_value("progress", 0ul);
if (progress_interval) {
_progress_timeout.construct(*_timer, *this,
&Sequential::_handle_progress_timeout,
Genode::Microseconds(progress_interval*1000));
}
_start_time = _timer->elapsed_ms();
_handle_submit();
}
Result finish() override
void _spawn_job() override
{
_timer.destruct();
_block.destruct();
if (_bytes >= _length)
return;
_job_cnt++;
Block::Operation const operation { .type = _op_type,
.block_number = _start,
.count = _size_in_blocks };
new (_alloc) Job(*_block, operation, _job_cnt);
_start += _size_in_blocks;
}
Result result() override
{
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, _size, _info.block_size);
_bytes, _rx, _tx, _size, _info.block_size, _triggered);
}
char const *name() const override { return "sequential"; }
void print(Genode::Output &out) const override
{
Genode::print(out, name(), " ", Block::Operation::type_name(_op_type), " "
"start:", _start, " "
"size:", _size, " "
"length:", _length, " "
"copy:", _copy, " "
"batch:", _batch);
}
};
#endif /* _TEST_SEQUENTIAL_H_ */