os: add Block session tester component

Issue #2747.
This commit is contained in:
Josef Söntgen 2016-07-04 19:04:41 +02:00 committed by Christian Helmuth
parent d54f95d497
commit ce93e47e89
8 changed files with 1748 additions and 0 deletions

View File

@ -0,0 +1,181 @@
set use_linux [have_spec linux]
#
# Check used commands
#
set dd [check_installed dd]
#
# Build
#
set build_components {
core init
drivers/ahci
drivers/timer
server/ram_blk
server/lx_block
app/block_tester
}
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
build $build_components
#
# Build EXT2-file-system image
#
catch { exec $dd if=/dev/zero of=bin/block.raw bs=1M count=0 seek=32768 }
create_boot_directory
#
# Generate config
#
append config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<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>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>}
append_platform_drv_config
append_if [expr !$use_linux] config {
<start name="ahci_drv">
<resource name="RAM" quantum="10M" />
<provides><service name="Block" /></provides>
<config>
<!-- CAUTION setting writeable! -->
<policy label_prefix="block_tester" device="0" writeable="yes"/>
</config>
</start>}
append_if $use_linux config {
<start name="ahci_drv">
<binary name="lx_block"/>
<resource name="RAM" quantum="2G"/>
<provides><service name="Block"/></provides>
<config file="block.raw" block_size="512" writeable="yes"/>
</start>}
append config {
<start name="block_tester">
<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">
<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="2048" count="1016"/>
<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="2048" count="1016"/>
<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="2048" count="1016"/>
<request type="read" lba="4096" count="1"/>
<request type="read" lba="51881" count="1"/>
<request type="read" lba="51890" count="1"/>
<request type="read" lba="114184" count="14"/>
<request type="read" lba="114198" count="1"/>
<request type="read" lba="114033" count="127"/>
<request type="read" lba="114160" count="24"/>
<request type="write" lba="0" count="1"/>
<request type="read" lba="12288" count="2048"/>
<request type="write" lba="4096" count="2048"/>
<request type="write" lba="0" count="1"/>
<request type="write" lba="2048" count="1"/>
<request type="write" lba="5696" count="1"/>
<request type="write" lba="5696" count="1"/>
<request type="write" lba="5696" count="1"/>
<request type="read" lba="4096" count="1"/>
<request type="read" lba="61440" count="16"/>
<request type="read" lba="158777" count="127"/>
<request type="write" lba="40960" count="2048"/>
<request type="write" lba="0" count="1"/>
<request type="write" lba="2073" count="1"/>
<request type="read" lba="190483" count="64"/>
<request type="read" lba="190411" count="53"/>
<request type="read" lba="190464" count="11"/>
<request type="read" lba="106074" count="64"/>
<request type="read" lba="105954" count="56"/>
<request type="read" lba="122802" count="24"/>
<request type="read" lba="123594" count="64"/>
<request type="read" lba="123722" count="64"/>
</replay>
</tests>
</config>
<route>
<service name="Block"><child name="ahci_drv"/></service>
<any-service> <parent/> <any-child /> </any-service>
</route>
</start>
</config>}
install_config $config
#
# Boot modules
#
# generic modules
set boot_modules {
core init timer ahci_drv ram_blk block_tester
ld.lib.so
}
append_if $use_linux boot_modules {
block.raw lx_block
}
append_platform_drv_boot_modules
build_boot_image $boot_modules
#append qemu_args " -m 256 -nographic"
append qemu_args " -nographic -m 512 "
append qemu_args " -drive id=disk,file=bin/block.raw,format=raw,if=none -device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -boot d"
append qemu_args " -drive id=cd,file=[run_dir]/../block_replay.iso,if=none,media=cdrom -device ide-cd,drive=cd,bus=ahci.1"
#run_genode_until {.*child "block_tester" exited.*\n} 360
run_genode_until forever
exec rm -f bin/block.raw

View File

@ -0,0 +1,199 @@
Brief
=====
This component implements various Block session tests. All block number values
are specified in terms of the underlying Block session, i.e., the start block
1024 will be at byte offset 512KiB on a 512B sector session whereas it will be
at byte offset 4MiB on a 4K sector session. All size values must be given in
sector size granularity and the request size must be at least as larger as the
sector size. The tests can be executed consecutively and a new Block connection
will be used for every test. After each test has finished the result will be
printed to the LOG session and a report will be generated that contains the
duration, the number of operations and the amount of bytes processed if the
'log' and 'report' attribute are set to 'yes'. If the 'stop_on_error' attribute
is set, the execution stops whenever a tests failes. In addition, if the
'calculate' attribute is set, the component will calculate MiB/s and I/O
operations per second for each test (this data is only meaningful in conjunction
with the other information like block size and so on).
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:
- The 'lba' attribute specifies the logical block address where
the request will start and is mandatory
- The 'count' attribute specifies the number of blocks that are processed
in the request and is mandatory.
- The 'type' attribute specifies the kind of the request, valid values
are 'read' and 'write' and is mandatory.
* 'sequential' reads or writes a given amount of bytes sequentially.
- The 'start' attribute specifies the logical block address where the test
begins, if it is missing the first block is used.
- The 'length' attribute specifies how many bytes are processed,
if it is missing the whole underlying Block session starting on the given
start block is used.
- The 'size' attribute specifies the size of a request, if it is missing
the block size of the underlying Block session is used.
- 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
- The 'start' attribute specifies the logical block address where the test
begins, if it is missing the first block is used.
- The 'length' attribute specifies how many bytes are processed,
if it is missing the whole underlying Block session starting on the given
start block is used.
- The 'write' attribute specifies whether the tests writes or reads, if
it is missing it defaults to reading.
* 'random' reads or writes random Blocks in a deterministic order that depends
on the seed value of the PRNG (currently xoroshiro128+ is used).
- The 'length' attribute specifies how much bytes are processed by the
random test, if is missing the total length of the underlying block
session are used.
- The 'size' attribute specifies the size of a request, if it is missing
the block size of the underlying Block session is used.
- The 'write' or 'read' attribute denote how the access is done. If both
attributes are specified the type of operation also depends on the PRNG.
If the lowest bit is set it will be a 'write' and otherwise a 'read' access.
In addition to the test specific attributes, there are generic attributes,
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.
Note: all tests use a fixed sized scratch buffer of 1 (replay 4) MiB, plan the
quota and request size accordingly.
The 'random' test might generate overlapping request, which might trigger
unstable operation with components that do not do sanity checking.
Configuration
=============
The following config snippet illustrates the use of the component:
! <config stop_on_error="yes" calculate="yes" log="yes" "report="no">
! <tests>
! <!-- read first 1GiB with request size = block size -->
! <sequential length="1G"/>
!
! <!-- read 512MiB starting at block 1024 with request size = 64KiB -->
! <sequential start="1024" length="512M" size="64K"/>
!
! <!-- read the last 16KiB in 512B chunks on a 512b sector session -->
! <sequential start="-131072" size="512"/>
!
! <!-- 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"/>
! <request type="write" block_number="2" count="1"/>
! </replay>
!
! <!-- read the whole session in 64KiB chunks jumping back and forth -->
! <ping_pong size="64K"/>
!
! <!-- write 16GiB in 16KiB chunks starting at byte offset 4GiB on a 4K session -->
! <ping_pong start="1048576" length="16G" size="16K"/>
!
! <!-- read 32768 random 16KiB chunks -->
! <random count="32768" size="16K" seed="0xdeadbeef"/>
!
! <!-- write 6144 random chunks in underlying block size -->
! <random write="yes" count="6144" seed="0xc0ffee"/>
!
! <!-- read/write 123456 random 4KiB chunks -->
! <random read="yes" write="yes" count="6144" size="4K" seed="42"/>
! </tests>
! </config>
Result output
=============
There are two ways of presenting the test results, printing to a LOG
session and generating a Report.
LOG
---
The LOG output provides one line for every test that is composed of key and
value tuples:
* bcount:<int> total count of blocks
* bsize:<int> block size in bytes
* bytes:<int> total amount of bytes of all operations
* duration:<int> total duration time in milliseconds
* iops:<float> total number of I/O operatins
* mibs:<float> total throughput of the test in MiB/s
* result:<number> result of the test, either 0 (ok) or 1 (failed)
* rx:<int> number of blocks read
* size:<int> size of one request in bytes
* test:<string> name of the test
* 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
Report
------
The Report output contains a node for every test and is updated continuosly
during execution, i.e., new results are appended to the Report. The structure
of the report mirrors the LOG output and is as follows:
! <results>
! <result test="sequential" rx="1048576" tx="0" bytes="536870912" size="65536" duration="302" mibs="1695.364" iops="27125.828" result="0"/>
! <result test="random" rx="0" tx="3167616" bytes="1621819392" size="65536" bsize="512" duration="11921" mibs="129.744" iops="2075.916" result="1"/>
! <results>
TODO
====
- move boilerplate code to Test_base (_block etc.)
- check all range/overlap checks (_start, _end etc.)
- fix report=yes (add Report support)
- add req min/max/avg time
- make daemon like, i.e., react upon config changes and execute tests
dynamically

View File

@ -0,0 +1,345 @@
/*
* \brief Block session testing
* \author Josef Soentgen
* \date 2016-07-04
*/
/*
* Copyright (C) 2016-2018 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.
*/
/* Genode includes */
#include <base/allocator_avl.h>
#include <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/log.h>
#include <block_session/connection.h>
#include <os/reporter.h>
#include <timer_session/connection.h>
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::Operations _block_ops { };
Block::sector_t _block_count { 0 };
size_t _block_size { 0 };
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;
} /* Test */
/* tests */
#include <test_ping_pong.h>
#include <test_random.h>
#include <test_replay.h>
#include <test_sequential.h>
/*
* Main
*/
struct Test::Main
{
Genode::Env &_env;
Genode::Heap _heap { _env.ram(), _env.rm() };
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) };
bool const _report {
_config_rom.xml().attribute_value("report", false) };
bool const _calculate {
_config_rom.xml().attribute_value("calculate", true) };
bool const _stop_on_error {
_config_rom.xml().attribute_value("stop_on_error", true) };
Genode::Fifo<Test_base> _tests { };
struct Test_result : Genode::Fifo<Test_result>::Element
{
Genode::String<32> name { };
Test::Result result { };
Test_result(char const *name) : name(name) { };
};
Genode::Fifo<Test_result> _results { };
Genode::Reporter _result_reporter { _env, "results" };
void _generate_report()
{
try {
Genode::Reporter::Xml_generator xml(_result_reporter, [&] () {
for (Test_result *tr = _results.head(); tr;
tr = tr->next()) {
xml.node("result", [&] () {
xml.attribute("test", tr->name);
xml.attribute("rx", tr->result.rx);
xml.attribute("tx", tr->result.tx);
xml.attribute("bytes", tr->result.bytes);
xml.attribute("size", tr->result.request_size);
xml.attribute("bsize", tr->result.block_size);
xml.attribute("duration", tr->result.duration);
if (_calculate) {
/* XXX */
xml.attribute("mibs", (unsigned)(tr->result.mibs * (1<<20u)));
xml.attribute("iops", (unsigned)(tr->result.iops + 0.5f));
}
xml.attribute("result", tr->result.success ? 0 : 1);
});
}
});
} catch (...) { Genode::warning("could generate results report"); }
}
Test_base *_current { nullptr };
bool _success { true };
void _handle_finished()
{
/* clean up current test */
if (_current) {
Result r = _current->finish();
if (!r.success) { _success = false; }
r.calculate = _calculate;
if (_log) {
Genode::log("test:", _current->name(), " ", r);
}
if (_report) {
Test_result *tr = new (&_heap) Test_result(_current->name());
tr->result = r;
_results.enqueue(tr);
_generate_report();
}
if (_verbose) {
Genode::log("finished ", _current->name(), " ", r.success ? 0 : 1);
}
Genode::destroy(&_heap, _current);
_current = nullptr;
}
/* execute next test */
if (!_current) {
while (true) {
_current = _tests.dequeue();
if (_current) {
if (_verbose) { Genode::log("start ", _current->name()); }
try {
_current->start(_stop_on_error);
break;
} catch (...) {
Genode::log("Could not start ", _current->name());
Genode::destroy(&_heap, _current);
}
} else {
/* execution is finished */
Genode::log("--- all tests finished ---");
_env.parent().exit(_success ? 0 : 1);
break;
}
}
}
}
Genode::Signal_handler<Main> _finished_sigh {
_env.ep(), *this, &Main::_handle_finished };
void _construct_tests(Genode::Xml_node config)
{
try {
Genode::Xml_node tests = config.sub_node("tests");
tests.for_each_sub_node([&] (Genode::Xml_node node) {
if (node.has_type("ping_pong")) {
Test_base *t = new (&_heap)
Ping_pong(_env, _heap, node, _finished_sigh);
_tests.enqueue(t);
} else
if (node.has_type("random")) {
Test_base *t = new (&_heap)
Random(_env, _heap, node, _finished_sigh);
_tests.enqueue(t);
} else
if (node.has_type("replay")) {
Test_base *t = new (&_heap)
Replay(_env, _heap, node, _finished_sigh);
_tests.enqueue(t);
} else
if (node.has_type("sequential")) {
Test_base *t = new (&_heap)
Sequential(_env, _heap, node, _finished_sigh);
_tests.enqueue(t);
}
});
} catch (...) { Genode::error("invalid tests"); }
}
/**
* Constructor
*/
Main(Genode::Env &env) : _env(env)
{
_result_reporter.enabled(_report);
try {
_construct_tests(_config_rom.xml());
} catch (...) { throw; }
Genode::log("--- start tests ---");
/* initial kick-off */
_handle_finished();
}
~Main()
{
Test_result * tr = nullptr;
while ((tr = _results.dequeue())) {
Genode::destroy(&_heap, tr);
}
}
private:
Main(const Main&) = delete;
Main& operator=(const Main&) = delete;
};
void Component::construct(Genode::Env &env)
{
static Test::Main main(env);
}

View File

@ -0,0 +1,4 @@
TARGET := block_tester
SRC_CC := main.cc
LIBS := base
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,233 @@
/*
* \brief Block session testing - ping pong test
* \author Josef Soentgen
* \date 2016-07-04
*/
/*
* Copyright (C) 2016-2018 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.
*/
#ifndef _TEST_PING_PONG_H_
#define _TEST_PING_PONG_H_
namespace Test {
struct Ping_pong;
}
/*
* Ping_pong operation test
*
* This test reads or writes the given number of blocks from the
* specified start block sequentially in sized requests.
*/
struct Test::Ping_pong : Test_base
{
Genode::Env &_env;
Genode::Allocator &_alloc;
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 */
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);
}
void _handle_submit()
{
try {
while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit()) {
Block::Packet_descriptor tmp =
_block->tx()->alloc_packet(_size_in_blocks * _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 / _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;
}
_handle_submit();
}
void _finish()
{
_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);
_block->info(&_block_count, &_block_size, &_block_ops);
_start = _node.attribute_value("start", 0u);
try {
Genode::Number_of_bytes tmp;
_node.attribute("size").value(&tmp);
_size = tmp;
_node.attribute("length").value(&tmp);
_length = tmp;
} catch (...) { }
if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size");
throw Constructing_test_failed();
}
size_t const total_bytes = _block_count * _block_size;
if (_length > total_bytes - (_start * _block_size)) {
Genode::error("length too large invalid");
throw Constructing_test_failed();
}
if (_block_size > _size || (_size % _block_size) != 0) {
Genode::error("request size invalid");
throw Constructing_test_failed();
}
if (_node.attribute_value("write", false)) {
_op = Block::Packet_descriptor::WRITE;
}
_size_in_blocks = _size / _block_size;
_length_in_blocks = _length / _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
{
_timer.destruct();
_block.destruct();
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, _size, _block_size);
}
char const *name() const { return "ping_pong"; }
};
#endif /* _TEST_PING_PONG_H_ */

View File

@ -0,0 +1,322 @@
/*
* \brief Block session testing - random test
* \author Josef Soentgen
* \date 2016-07-04
*/
/*
* Copyright (C) 2016-2018 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.
*/
#ifndef _TEST_RANDOM_H_
#define _TEST_RANDOM_H_
namespace Test {
struct Random;
}
namespace Util {
using namespace Genode;
/*
* Xoroshiro128+ written in 2014-2016 by Sebastiano Vigna (vigna@acm.org)
*
* (see http://xoroshiro.di.unimi.it/xorshift128plus.c and
* http://xoroshiro.di.unimi.it/splitmix64.c)
*/
struct Xoroshiro
{
uint64_t seed;
uint64_t splitmix64()
{
uint64_t z = (seed += __UINT64_C(0x9E3779B97F4A7C15));
z = (z ^ (z >> 30)) * __UINT64_C(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)) * __UINT64_C(0x94D049BB133111EB);
return z ^ (z >> 31);
}
Xoroshiro(uint64_t seed) : seed(seed)
{
s[0] = splitmix64();
s[1] = splitmix64();
}
uint64_t s[2];
static uint64_t rotl(uint64_t const x, int k) {
return (x << k) | (x >> (64 - k));
}
uint64_t get()
{
uint64_t const s0 = s[0];
uint64_t s1 = s[1];
uint64_t const result = s0 + s1;
s1 ^= s0;
s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
s[1] = rotl(s1, 36);
return result;
}
};
}
/*
* Random test
*
* This test reads or writes the given number of bytes in a
* deterministic order that depends and the seed value of a
* PRNG in particular sized requests.
*/
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 _blocks { 0 };
/* _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()
{
uint64_t r = 0;
do {
r = _random.get() % _block_count;
} while (r + _size_in_blocks > _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->tx()->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 / _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),
_random(node.attribute_value("seed", 42u)),
_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);
_block->info(&_block_count, &_block_size, &_block_ops);
try {
Genode::Number_of_bytes tmp;
_node.attribute("size").value(&tmp);
_size = tmp;
_node.attribute("length").value(&tmp);
_length = tmp;
} catch (...) { }
if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size");
throw Constructing_test_failed();
}
if (!_size || !_length) {
Genode::error("request size or length invalid");
throw Constructing_test_failed();
}
if (_block_size > _size || (_size % _block_size) != 0) {
Genode::error("request size invalid ", _block_size, " ", _size);
throw Constructing_test_failed();
}
_synchronous = _node.attribute_value("synchronous", false);
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; }
_alternate_access = w && r;
_size_in_blocks = _size / _block_size;
_length_in_blocks = _length / _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
{
_timer.destruct();
_block.destruct();
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, _size, _block_size);
}
char const *name() const { return "random"; }
};
#endif /* _TEST_RANDOM_H_ */

View File

@ -0,0 +1,213 @@
/*
* \brief Block session testing
* \author Josef Soentgen
* \date 2016-07-04
*/
/*
* Copyright (C) 2016-2018 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.
*/
#ifndef _TEST_REPLAY_H_
#define _TEST_REPLAY_H_
namespace Test {
struct Replay;
}
/*
* Replay test
*
* This test replays a recorded sequence of Block session
* requests.
*/
struct Test::Replay : Test_base
{
Genode::Env &env;
Genode::Allocator &alloc;
struct Request : Genode::Fifo<Request>::Element
{
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 ... */
Request *req = requests.head();
if (!req) { return; }
Block::Packet_descriptor p(
_block->tx()->alloc_packet(req->count * _block_size),
req->op, req->nr, req->count);
bool const write = req->op == Block::Packet_descriptor::WRITE;
/* simulate write */
if (write) {
char * const content = _block->tx()->packet_content(p);
Genode::memcpy(content, _scratch_buffer, p.size());
}
_block->tx()->submit_packet(p);
/* ... and only remove it if we could submit it */
req = requests.dequeue();
Genode::destroy(&alloc, req);
more = _bulk;
}
} catch (...) { }
}
void _finish()
{
_end_time = _timer->elapsed_ms();
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 / _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) {
Block::Packet_descriptor::Opcode op;
Block::sector_t nr { 0 };
Genode::size_t count { 0 };
try {
request.attribute("lba").value(&nr);
request.attribute("count").value(&count);
Genode::String<8> tmp;
request.attribute("type").value(&tmp);
if (tmp == "read") { op = Block::Packet_descriptor::READ; }
else if (tmp == "write") { op = Block::Packet_descriptor::WRITE; }
else { throw -1; }
Request *req = new (&alloc) Request(op, nr, count);
requests.enqueue(req);
++request_num;
} catch (...) { return; }
});
} catch (...) {
Genode::error("could not read request list");
return;
}
}
/********************
** 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);
_block->info(&_block_count, &_block_size, &_block_ops);
_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, _block_size);
}
char const *name() const { return "replay"; }
};
#endif /* _TEST_REPLAY_H_ */

View File

@ -0,0 +1,251 @@
/*
* \brief Block session testing - ping pong test
* \author Josef Soentgen
* \date 2016-07-04
*/
/*
* Copyright (C) 2016-2018 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.
*/
#ifndef _TEST_SEQUENTIAL_H_
#define _TEST_SEQUENTIAL_H_
namespace Test {
struct Sequential;
}
/*
* Sequential operation test
*
* This test reads or writes the given number of blocks from the
* specified start block sequentially in sized requests.
*/
struct Test::Sequential : Test_base
{
Genode::Env &_env;
Genode::Allocator &_alloc;
/* test infos */
Block::sector_t _start { 0 };
size_t _length { 0 };
size_t _size { 0 };
/* _synchronous controls bulk */
bool _synchronous { false };
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)
{
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->tx()->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 >= _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 / _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);
_block->info(&_block_count, &_block_size, &_block_ops);
_synchronous = _node.attribute_value("synchronous", false);
_start = _node.attribute_value("start", 0u);
try {
Genode::Number_of_bytes tmp;
_node.attribute("size").value(&tmp);
_size = tmp;
_node.attribute("length").value(&tmp);
_length = tmp;
} catch (...) { }
if (_size > sizeof(_scratch_buffer)) {
Genode::error("request size exceeds scratch buffer size");
throw Constructing_test_failed();
}
if (_block_size > _size || (_size % _block_size) != 0) {
Genode::error("request size invalid");
throw Constructing_test_failed();
}
if (_node.attribute_value("write", false)) {
_op = Block::Packet_descriptor::WRITE;
}
_size_in_blocks = _size / _block_size;
_length_in_blocks = _length / _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
{
_timer.destruct();
_block.destruct();
return Result(_success, _end_time - _start_time,
_bytes, _rx, _tx, _size, _block_size);
}
char const *name() const { return "sequential"; }
};
#endif /* _TEST_SEQUENTIAL_H_ */