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"/> <resource name="RAM" quantum="32M"/>
<config verbose="yes" report="no" log="yes" stop_on_error="no"> <config verbose="yes" report="no" log="yes" stop_on_error="no">
<tests> <tests>
<sequential length="1M" size="4K" synchronous="yes"/> <sequential copy="no" length="16M" size="4K"/>
<sequential length="1M" size="8K" synchronous="yes"/> <sequential copy="no" length="16M" size="8K"/>
<sequential length="1M" size="16K"/>
<sequential length="1M" size="64K"/> <sequential copy="no" length="3G" size="16K" batch="32"/>
<sequential length="1M" size="128K"/> <sequential copy="no" length="15" size="64K" batch="32"/>
<sequential length="1M" size="4K" synchronous="yes" write="yes"/> <sequential copy="no" length="30G" size="128K" batch="32"/>
<sequential length="1M" size="64K" write="yes" synchronous="yes"/>
<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> </tests>
</config> </config>
<route> <route>

View File

@ -81,21 +81,27 @@ append config {
<resource name="RAM" quantum="32M"/> <resource name="RAM" quantum="32M"/>
<config verbose="yes" report="no" log="yes" stop_on_error="no"> <config verbose="yes" report="no" log="yes" stop_on_error="no">
<tests> <tests>
<!-- synchronous="no" 4K/8K currently leads to deadlocking ahci_drv <!-- synchronous="no" 4K/8K currently leads to deadlocking ahci_drv -->
<sequential length="1G" size="4K"/>
<sequential length="1G" size="8K"/> <sequential copy="no" length="128M" size="4K"/>
--> <sequential copy="no" length="128M" size="4K" batch="32"/>
<sequential length="1G" size="4K" synchronous="yes"/> <sequential copy="no" length="128M" size="4K" batch="1000"/>
<sequential length="1G" size="8K" synchronous="yes"/>
<sequential length="1G" size="16K"/> <sequential copy="no" length="128M" size="8K"/>
<sequential length="1G" size="64K"/> <sequential copy="no" length="128M" size="8K" batch="32"/>
<sequential length="1G" size="128K"/> <sequential copy="no" length="1G" size="16K" batch="32"/>
<sequential length="1G" size="4K" synchronous="yes" write="yes"/> <sequential copy="no" length="1G" size="64K" batch="32"/>
<sequential length="1G" size="64K" write="yes" synchronous="yes"/> <sequential copy="no" length="1G" size="128K" batch="32"/>
<random length="1G" size="16K" seed="0xdeadbeef" synchronous="yes"/>
<random length="8G" size="512K" seed="0xc0ffee" synchronous="yes"/> <sequential copy="no" length="128M" size="4K" write="yes"/>
<ping_pong length="1G" size="16K"/> <sequential copy="no" length="1G" size="64K" write="yes"/>
<replay bulk="no">
<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"/> <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. * '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 Each request is specified by a 'request' node which has the following
attributes: attributes:
@ -50,15 +47,8 @@ The following list contains all available tests:
- The 'write' attribute specifies whether the tests writes or reads, if - The 'write' attribute specifies whether the tests writes or reads, if
it is missing it defaults to reading. 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 * '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 - The 'start' attribute specifies the logical block address where the test
begins, if it is missing the first block is used. 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 - If the 'verbose' attribute is specified, the test will print each
request to the LOG session before it will be executed. 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 Note: all tests use a fixed sized scratch buffer of 1 (replay 4) MiB, plan the
quota and request size accordingly. quota and request size accordingly.
@ -116,9 +118,6 @@ The following config snippet illustrates the use of the component:
! <!-- write 1200MiB in 8KiB requests --> ! <!-- write 1200MiB in 8KiB requests -->
! <sequential write="yes" length="1200M" size="8K"/> ! <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 the beginning Ext2 mount operations -->
! <replay> ! <replay>
! <request type="read" block_number="2" count="1"/> ! <request type="read" block_number="2" count="1"/>
@ -166,13 +165,14 @@ value tuples:
* rx:<int> number of blocks read * rx:<int> number of blocks read
* size:<int> size of one request in bytes * size:<int> size of one request in bytes
* test:<string> name of the test * test:<string> name of the test
* triggered<int> number of handled I/O signals
* tx:<int> number of blocks written * tx:<int> number of blocks written
Since the LOG output is mainly intended for automated testing and analyzing all 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 size values are given in bytes. The following examplary output illustrates the
structure: 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 Report

View File

@ -26,118 +26,285 @@ namespace Test {
using namespace Genode; using namespace Genode;
/* struct Result;
* Result of a test struct Test_base;
*/
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 Main; 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 */ /* tests */
#include <test_ping_pong.h> #include <test_ping_pong.h>
@ -156,9 +323,6 @@ struct Test::Main
Genode::Attached_rom_dataspace _config_rom { _env, "config" }; Genode::Attached_rom_dataspace _config_rom { _env, "config" };
bool const _verbose {
_config_rom.xml().attribute_value("verbose", false) };
bool const _log { bool const _log {
_config_rom.xml().attribute_value("log", false) }; _config_rom.xml().attribute_value("log", false) };
@ -219,14 +383,14 @@ struct Test::Main
{ {
/* clean up current test */ /* clean up current test */
if (_current) { if (_current) {
Result r = _current->finish(); Result r = _current->result();
if (!r.success) { _success = false; } if (!r.success) { _success = false; }
r.calculate = _calculate; r.calculate = _calculate;
if (_log) { if (_log) {
Genode::log("test:", _current->name(), " ", r); Genode::log("finished ", _current->name(), " ", r);
} }
if (_report) { if (_report) {
@ -237,9 +401,6 @@ struct Test::Main
_generate_report(); _generate_report();
} }
if (_verbose) {
Genode::log("finished ", _current->name(), " ", r.success ? 0 : 1);
}
Genode::destroy(&_heap, _current); Genode::destroy(&_heap, _current);
_current = nullptr; _current = nullptr;
} }
@ -247,13 +408,14 @@ struct Test::Main
/* execute next test */ /* execute next test */
if (!_current) { if (!_current) {
_tests.dequeue([&] (Test_base &head) { _tests.dequeue([&] (Test_base &head) {
if (_verbose) { Genode::log("start ", head.name()); } if (_log) { Genode::log("start ", head); }
try { try {
head.start(_stop_on_error); head.start(_stop_on_error);
_current = &head; _current = &head;
} catch (...) { } catch (...) {
Genode::log("Could not start ", head.name()); Genode::log("Could not start ", head);
Genode::destroy(&_heap, &head); Genode::destroy(&_heap, &head);
throw;
} }
}); });
} }

View File

@ -14,9 +14,7 @@
#ifndef _TEST_PING_PONG_H_ #ifndef _TEST_PING_PONG_H_
#define _TEST_PING_PONG_H_ #define _TEST_PING_PONG_H_
namespace Test { namespace Test { struct Ping_pong; }
struct Ping_pong;
}
/* /*
@ -27,200 +25,77 @@ namespace Test {
*/ */
struct Test::Ping_pong : Test_base struct Test::Ping_pong : Test_base
{ {
Genode::Env &_env; bool _ping = true;
Genode::Allocator &_alloc; 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::Operation::Type const _op_type = _node.attribute_value("write", false)
Block::Packet_descriptor::READ }; ? Block::Operation::Type::WRITE
: Block::Operation::Type::READ;
/* block session infos */ using Test_base::Test_base;
enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
Genode::Allocator_avl _block_alloc { &_alloc };
Genode::Constructible<Block::Connection<>> _block { };
Genode::Constructible<Timer::Connection> _timer { }; void _spawn_job() override
/* 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)
{ {
Genode::log("progress: rx:", _rx, " tx:", _tx); if (_bytes >= _length)
}
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();
return; 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)) { if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size"); error("request size exceeds scratch buffer size");
throw Constructing_test_failed(); throw Constructing_test_failed();
} }
size_t const total_bytes = _info.block_count * _info.block_size; size_t const total_bytes = _info.block_count * _info.block_size;
if (_length > total_bytes - (_start * _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(); throw Constructing_test_failed();
} }
if (_info.block_size > _size || (_size % _info.block_size) != 0) { if (_info.block_size > _size || (_size % _info.block_size) != 0) {
Genode::error("request size invalid"); error("request size invalid");
throw Constructing_test_failed(); throw Constructing_test_failed();
} }
if (_node.attribute_value("write", false)) {
_op = Block::Packet_descriptor::WRITE;
}
_size_in_blocks = _size / _info.block_size; _size_in_blocks = _size / _info.block_size;
_length_in_blocks = _length / _info.block_size; _length_in_blocks = _length / _info.block_size;
_end = _start + _length_in_blocks; _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, 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"; } 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_ */ #endif /* _TEST_PING_PONG_H_ */

View File

@ -14,9 +14,8 @@
#ifndef _TEST_RANDOM_H_ #ifndef _TEST_RANDOM_H_
#define _TEST_RANDOM_H_ #define _TEST_RANDOM_H_
namespace Test { namespace Test { struct Random; }
struct Random;
}
namespace Util { namespace Util {
@ -79,236 +78,101 @@ namespace Util {
*/ */
struct Test::Random : Test_base struct Test::Random : Test_base
{ {
Genode::Env &_env;
Genode::Allocator &_alloc;
Block::Packet_descriptor::Opcode _op {
Block::Packet_descriptor::READ };
bool _alternate_access { false }; 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; Util::Xoroshiro _random;
size_t _size { 0 }; size_t const _size = _node.attribute_value("size", Number_of_bytes());
uint64_t _length { 0 }; uint64_t const _length = _node.attribute_value("length", Number_of_bytes());
char _scratch_buffer[1u<<20] { };
size_t _blocks { 0 }; Block::Operation::Type _op_type = Block::Operation::Type::READ;
/* _synchronous controls bulk */ block_number_t _next_block()
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()
{ {
uint64_t r = 0; uint64_t r = 0;
block_number_t max = _info.block_count;
if (max >= _size_in_blocks + 1)
max -= _size_in_blocks + 1;
do { do {
r = _random.get() % _info.block_count; r = _random.get() % max;
} while (r + _size_in_blocks > _info.block_count); } while (r + _size_in_blocks > _info.block_count);
return r; return r;
} }
void _handle_submit() template <typename... ARGS>
{ Random(ARGS &&...args)
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)
: :
Test_base(finished_sig), _env(env), _alloc(alloc), Test_base(args...),
_random(node.attribute_value("seed", 42u)), _random(_node.attribute_value("seed", 42UL))
_node(node) { }
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)) { if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size"); error("request size exceeds scratch buffer size");
throw Constructing_test_failed(); throw Constructing_test_failed();
} }
if (!_size || !_length) { if (!_size || !_length) {
Genode::error("request size or length invalid"); error("request size or length invalid");
throw Constructing_test_failed(); throw Constructing_test_failed();
} }
if (_info.block_size > _size || (_size % _info.block_size) != 0) { 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(); 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); bool const w = _node.attribute_value("write", false);
if (r) { _op = Block::Packet_descriptor::WRITE; } if (w) { _op_type = Block::Operation::Type::WRITE; }
bool w = _node.attribute_value("read", false);
if (w) { _op = Block::Packet_descriptor::WRITE; }
_alternate_access = w && r; _alternate_access = w && r;
_size_in_blocks = _size / _info.block_size; _size_in_blocks = _size / _info.block_size;
_length_in_blocks = _length / _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(); if (_bytes >= _length)
_block.destruct(); 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, 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"; } 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_ */ #endif /* _TEST_RANDOM_H_ */

View File

@ -14,194 +14,68 @@
#ifndef _TEST_REPLAY_H_ #ifndef _TEST_REPLAY_H_
#define _TEST_REPLAY_H_ #define _TEST_REPLAY_H_
namespace Test { namespace Test { struct Replay; }
struct Replay;
}
/* /*
* Replay test * Replay test
* *
* This test replays a recorded sequence of Block session * This test replays a recorded sequence of Block session requests.
* requests.
*/ */
struct Test::Replay : Test_base struct Test::Replay : Test_base
{ {
Genode::Env &env; using Test_base::Test_base;
Genode::Allocator &alloc;
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 { try {
while (_block->tx()->ready_to_submit() && more) { _node.for_each_sub_node("request", [&](Xml_node request) {
/* 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);
bool const write = req.op == Block::Packet_descriptor::WRITE; auto op_type = [&] ()
{
typedef String<8> Type;
/* simulate write */ if (request.attribute_value("type", Type()) == "read")
if (write) { return Block::Operation::Type::READ;
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(content, _scratch_buffer, p.size());
}
_block->tx()->submit_packet(p); if (request.attribute_value("type", Type()) == "write")
return Block::Operation::Type::WRITE;
Genode::destroy(&alloc, &req); error("operation type not defined: ", request);
more = _bulk; throw 1;
}); };
}
} catch (...) { }
}
void _finish() Block::Operation const operation
{ {
_end_time = _timer->elapsed_ms(); .type = op_type(),
.block_number = request.attribute_value("lba", (block_number_t)0),
.count = request.attribute_value("count", 0UL)
};
Test_base::_finish(); _job_cnt++;
} new (&_alloc) Job(*_block, operation, _job_cnt);
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;
}); });
} catch (...) { } catch (...) {
Genode::error("could not read request list"); error("could not read request list");
_block->dissolve_all_jobs([&] (Job &job) { destroy(_alloc, &job); });
return; return;
} }
} }
/******************** void _spawn_job() override { }
** Test interface **
********************/
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, 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"; } char const *name() const override { return "replay"; }
void print(Output &out) const override
{
Genode::print(out, name());
}
}; };
#endif /* _TEST_REPLAY_H_ */ #endif /* _TEST_REPLAY_H_ */

View File

@ -14,9 +14,7 @@
#ifndef _TEST_SEQUENTIAL_H_ #ifndef _TEST_SEQUENTIAL_H_
#define _TEST_SEQUENTIAL_H_ #define _TEST_SEQUENTIAL_H_
namespace Test { namespace Test { struct Sequential; }
struct Sequential;
}
/* /*
@ -27,218 +25,65 @@ namespace Test {
*/ */
struct Test::Sequential : Test_base struct Test::Sequential : Test_base
{ {
Genode::Env &_env; block_number_t _start = _node.attribute_value("start", 0u);
Genode::Allocator &_alloc; 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::Operation::Type const _op_type = _node.attribute_value("write", false)
Block::sector_t _start { 0 }; ? Block::Operation::Type::WRITE
size_t _length { 0 }; : Block::Operation::Type::READ;
size_t _size { 0 };
/* _synchronous controls bulk */ using Test_base::Test_base;
bool _synchronous { false };
Block::Packet_descriptor::Opcode _op { void _init() override
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)
{ {
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)) { if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size"); error("request size exceeds scratch buffer size");
throw Constructing_test_failed(); throw Constructing_test_failed();
} }
if (_info.block_size > _size || (_size % _info.block_size) != 0) { if (_info.block_size > _size || (_size % _info.block_size) != 0) {
Genode::error("request size invalid"); error("request size invalid");
throw Constructing_test_failed(); throw Constructing_test_failed();
} }
if (_node.attribute_value("write", false)) {
_op = Block::Packet_descriptor::WRITE;
}
_size_in_blocks = _size / _info.block_size; _size_in_blocks = _size / _info.block_size;
_length_in_blocks = _length / _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(); if (_bytes >= _length)
_block.destruct(); 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, 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"; } 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_ */ #endif /* _TEST_SEQUENTIAL_H_ */