From ce93e47e89404a6d45b21651d0bceac4294b0155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20S=C3=B6ntgen?= Date: Mon, 4 Jul 2016 19:04:41 +0200 Subject: [PATCH] os: add Block session tester component Issue #2747. --- repos/os/run/block_tester.run | 181 +++++++++ repos/os/src/app/block_tester/README | 199 ++++++++++ repos/os/src/app/block_tester/main.cc | 345 ++++++++++++++++++ repos/os/src/app/block_tester/target.mk | 4 + .../os/src/app/block_tester/test_ping_pong.h | 233 ++++++++++++ repos/os/src/app/block_tester/test_random.h | 322 ++++++++++++++++ repos/os/src/app/block_tester/test_replay.h | 213 +++++++++++ .../os/src/app/block_tester/test_sequential.h | 251 +++++++++++++ 8 files changed, 1748 insertions(+) create mode 100644 repos/os/run/block_tester.run create mode 100644 repos/os/src/app/block_tester/README create mode 100644 repos/os/src/app/block_tester/main.cc create mode 100644 repos/os/src/app/block_tester/target.mk create mode 100644 repos/os/src/app/block_tester/test_ping_pong.h create mode 100644 repos/os/src/app/block_tester/test_random.h create mode 100644 repos/os/src/app/block_tester/test_replay.h create mode 100644 repos/os/src/app/block_tester/test_sequential.h diff --git a/repos/os/run/block_tester.run b/repos/os/run/block_tester.run new file mode 100644 index 0000000000..c653535b93 --- /dev/null +++ b/repos/os/run/block_tester.run @@ -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 { + + + + + + + + + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append_if [expr !$use_linux] config { + + + + + + + + } + +append_if $use_linux config { + + + + + + } + +append 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 diff --git a/repos/os/src/app/block_tester/README b/repos/os/src/app/block_tester/README new file mode 100644 index 0000000000..7e5b5e78b4 --- /dev/null +++ b/repos/os/src/app/block_tester/README @@ -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: + +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! + + +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: total count of blocks + * bsize: block size in bytes + * bytes: total amount of bytes of all operations + * duration: total duration time in milliseconds + * iops: total number of I/O operatins + * mibs: total throughput of the test in MiB/s + * result: result of the test, either 0 (ok) or 1 (failed) + * rx: number of blocks read + * size: size of one request in bytes + * test: name of the test + * tx: 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: + +! +! +! +! + + +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 diff --git a/repos/os/src/app/block_tester/main.cc b/repos/os/src/app/block_tester/main.cc new file mode 100644 index 0000000000..d48289e308 --- /dev/null +++ b/repos/os/src/app/block_tester/main.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include + + +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::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(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 +#include +#include +#include + + +/* + * 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 _tests { }; + + struct Test_result : Genode::Fifo::Element + { + Genode::String<32> name { }; + Test::Result result { }; + + Test_result(char const *name) : name(name) { }; + }; + Genode::Fifo _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
_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); +} diff --git a/repos/os/src/app/block_tester/target.mk b/repos/os/src/app/block_tester/target.mk new file mode 100644 index 0000000000..b9a689631b --- /dev/null +++ b/repos/os/src/app/block_tester/target.mk @@ -0,0 +1,4 @@ +TARGET := block_tester +SRC_CC := main.cc +LIBS := base +INC_DIR += $(PRG_DIR) diff --git a/repos/os/src/app/block_tester/test_ping_pong.h b/repos/os/src/app/block_tester/test_ping_pong.h new file mode 100644 index 0000000000..8c096f2a0a --- /dev/null +++ b/repos/os/src/app/block_tester/test_ping_pong.h @@ -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 { }; + + Genode::Constructible _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> _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 _ack_sigh { + _env.ep(), *this, &Ping_pong::_handle_ack }; + + Genode::Signal_handler _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_ */ diff --git a/repos/os/src/app/block_tester/test_random.h b/repos/os/src/app/block_tester/test_random.h new file mode 100644 index 0000000000..03c27745b8 --- /dev/null +++ b/repos/os/src/app/block_tester/test_random.h @@ -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 { }; + + Genode::Constructible _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> _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 _ack_sigh { + _env.ep(), *this, &Random::_handle_ack }; + + Genode::Signal_handler _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_ */ diff --git a/repos/os/src/app/block_tester/test_replay.h b/repos/os/src/app/block_tester/test_replay.h new file mode 100644 index 0000000000..d0b4887465 --- /dev/null +++ b/repos/os/src/app/block_tester/test_replay.h @@ -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::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 requests { }; + + char _scratch_buffer[4u<<20] { }; + + bool _bulk { false }; + + Genode::Constructible _timer { }; + + enum { TX_BUF_SIZE = 4 * 1024 * 1024, }; + Genode::Allocator_avl _block_alloc { &alloc }; + Genode::Constructible _block { }; + + Genode::Signal_handler _ack_sigh { + env.ep(), *this, &Replay::_handle_ack }; + + Genode::Signal_handler _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("verbose", false); + _bulk = config.attribute_value("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_ */ diff --git a/repos/os/src/app/block_tester/test_sequential.h b/repos/os/src/app/block_tester/test_sequential.h new file mode 100644 index 0000000000..e05bae2b6b --- /dev/null +++ b/repos/os/src/app/block_tester/test_sequential.h @@ -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 { }; + + Genode::Constructible _timer { }; + + /* test data */ + size_t _blocks { 0 }; + size_t _ack_blocks { 0 }; + char _scratch_buffer[1u<<20] { }; + + Genode::Constructible> _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 _ack_sigh { + _env.ep(), *this, &Sequential::_handle_ack }; + + Genode::Signal_handler _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_ */