genode/repos/os/src/app/block_tester/main.cc

500 lines
11 KiB
C++

/*
* \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;
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<Test_base>::Element
{
protected:
Env &_env;
Allocator &_alloc;
Xml_node const _node;
typedef Block::block_number_t block_number_t;
bool const _verbose;
size_t const _io_buffer;
uint64_t const _progress_interval;
bool const _copy;
size_t const _batch;
Constructible<Timer::Connection> _timer { };
Constructible<Timer::Periodic_timeout<Test_base>> _progress_timeout { };
Allocator_avl _block_alloc { &_alloc };
struct Job;
typedef Block::Connection<Job> Block_connection;
Constructible<Block_connection> _block { };
struct Job : Block_connection::Job
{
unsigned const id;
Job(Block_connection &connection, Block::Operation operation, unsigned id)
:
Block_connection::Job(connection, operation), id(id)
{ }
};
/*
* Must be called by every test when it has finished
*/
Genode::Signal_context_capability _finished_sig;
void finish()
{
_end_time = _timer->elapsed_ms();
_finished = true;
if (_finished_sig.valid()) {
Genode::Signal_transmitter(_finished_sig).submit();
}
_timer.destruct();
}
Block::Session::Info _info { };
size_t _length_in_blocks { 0 };
size_t _size_in_blocks { 0 };
uint64_t _start_time { 0 };
uint64_t _end_time { 0 };
size_t _bytes { 0 };
uint64_t _rx { 0 };
uint64_t _tx { 0 };
size_t _triggered { 0 }; /* number of I/O signals */
unsigned _job_cnt { 0 };
unsigned _completed { 0 };
bool _stop_on_error { true };
bool _finished { false };
bool _success { false };
char _scratch_buffer[1u<<20] { };
void _memcpy(char *dst, char const *src, size_t length)
{
if (length > sizeof(_scratch_buffer)) {
warning("scratch buffer too small for copying");
return;
}
Genode::memcpy(dst, src, length);
}
public:
/**
* Block::Connection::Update_jobs_policy
*/
void produce_write_content(Job &job, off_t offset, char *dst, size_t length)
{
_tx += length / _info.block_size;
_bytes += length;
if (_verbose)
log("job ", job.id, ": writing ", length, " bytes at ", offset);
if (_copy)
_memcpy(dst, _scratch_buffer, length);
}
/**
* Block::Connection::Update_jobs_policy
*/
void consume_read_result(Job &job, off_t offset,
char const *src, size_t length)
{
_rx += length / _info.block_size;
_bytes += length;
if (_verbose)
log("job ", job.id, ": got ", length, " bytes at ", offset);
if (_copy)
_memcpy(_scratch_buffer, src, length);
}
/**
* Block_connection::Update_jobs_policy
*/
void completed(Job &job, bool success)
{
_completed++;
if (_verbose)
log("job ", job.id, ": ", job.operation(), ", completed");
if (!success)
error("processing ", job.operation(), " failed");
destroy(_alloc, &job);
if (!success && _stop_on_error)
throw Test_failed();
/* replace completed job by new one */
_spawn_job();
bool const jobs_active = (_job_cnt != _completed);
_success = !jobs_active && success;
if (!jobs_active || !success)
finish();
}
protected:
void _handle_progress_timeout(Duration)
{
log("progress: rx:", _rx, " tx:", _tx);
}
void _handle_block_io()
{
_triggered++;
_block->update_jobs(*this);
}
Signal_handler<Test_base> _block_io_sigh {
_env.ep(), *this, &Test_base::_handle_block_io };
public:
friend class Genode::Fifo<Test_base>;
Test_base(Env &env, Allocator &alloc, Xml_node node,
Signal_context_capability finished_sig)
:
_env(env), _alloc(alloc), _node(node),
_verbose(node.attribute_value("verbose", false)),
_io_buffer(_node.attribute_value("io_buffer",
Number_of_bytes(4*1024*1024))),
_progress_interval(_node.attribute_value("progress", (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<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, [&] () {
_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<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()
{
_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);
}