mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 17:52:52 +00:00
parent
d54f95d497
commit
ce93e47e89
181
repos/os/run/block_tester.run
Normal file
181
repos/os/run/block_tester.run
Normal 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
|
199
repos/os/src/app/block_tester/README
Normal file
199
repos/os/src/app/block_tester/README
Normal 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
|
345
repos/os/src/app/block_tester/main.cc
Normal file
345
repos/os/src/app/block_tester/main.cc
Normal 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);
|
||||
}
|
4
repos/os/src/app/block_tester/target.mk
Normal file
4
repos/os/src/app/block_tester/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET := block_tester
|
||||
SRC_CC := main.cc
|
||||
LIBS := base
|
||||
INC_DIR += $(PRG_DIR)
|
233
repos/os/src/app/block_tester/test_ping_pong.h
Normal file
233
repos/os/src/app/block_tester/test_ping_pong.h
Normal 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_ */
|
322
repos/os/src/app/block_tester/test_random.h
Normal file
322
repos/os/src/app/block_tester/test_random.h
Normal 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_ */
|
213
repos/os/src/app/block_tester/test_replay.h
Normal file
213
repos/os/src/app/block_tester/test_replay.h
Normal 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_ */
|
251
repos/os/src/app/block_tester/test_sequential.h
Normal file
251
repos/os/src/app/block_tester/test_sequential.h
Normal 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_ */
|
Loading…
x
Reference in New Issue
Block a user