/* * \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; struct Result; struct Test_base; struct Main; 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::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 { }; Constructible> _progress_timeout { }; Allocator_avl _block_alloc { &_alloc }; struct Job; typedef Block::Connection Block_connection; Constructible _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 _block_io_sigh { _env.ep(), *this, &Test_base::_handle_block_io }; public: friend class Genode::Fifo; 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", (uint64_t)0)), _copy(_node.attribute_value("copy", true)), _batch(_node.attribute_value("batch", 1u)), _finished_sig(finished_sig) { if (_progress_interval) _progress_timeout.construct(*_timer, *this, &Test_base::_handle_progress_timeout, Microseconds(_progress_interval*1000)); } virtual ~Test_base() { }; void start(bool stop_on_error) { _stop_on_error = stop_on_error; _block.construct(_env, &_block_alloc, _io_buffer); _block->sigh(_block_io_sigh); _info = _block->info(); _init(); for (unsigned i = 0; i < _batch; i++) _spawn_job(); _timer.construct(_env); _start_time = _timer->elapsed_ms(); _handle_block_io(); } /******************** ** Test interface ** ********************/ virtual void _init() = 0; virtual void _spawn_job() = 0; virtual Result result() = 0; virtual char const *name() const = 0; virtual void print(Output &) const = 0; }; /* tests */ #include "test_ping_pong.h" #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 _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, [&] () { _results.for_each([&] (Test_result &tr) { 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->result(); if (!r.success) { _success = false; } r.calculate = _calculate; if (_log) { Genode::log("finished ", _current->name(), " ", r); } if (_report) { Test_result *tr = new (&_heap) Test_result(_current->name()); tr->result = r; _results.enqueue(*tr); _generate_report(); } Genode::destroy(&_heap, _current); _current = nullptr; } /* execute next test */ if (!_current) { _tests.dequeue([&] (Test_base &head) { if (_log) { Genode::log("start ", head); } try { head.start(_stop_on_error); _current = &head; } catch (...) { Genode::log("Could not start ", head); Genode::destroy(&_heap, &head); throw; } }); } if (!_current) { /* execution is finished */ Genode::log("--- all tests finished ---"); _env.parent().exit(_success ? 0 : 1); } } 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() { _results.dequeue_all([&] (Test_result &tr) { 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); }