Dittosuite is a work in progress collection of tools that aims at providing a high-level language called Dittolang that defines operations.
The defined Dittolang operations can be interpreted by Dittosim for simulation to provide a simulated performance measurement and quickly identify the goodness of a solution.
Specularly, Dittobench interprets the Dittolang operations and executes them on a real device, tracking the behavior and measuring the performance.
$ ./dittobench [options] [.ditto file]
To run a benchmark, a well formed .ditto file must be provided, see section How to write .ditto files In addition, these options can be set:
--results-output=<int | string>
(default: report). Select the results output format.
Options: report, csv with 0, 1 respectively.--log-stream=<int | string>
(default: stdout). Select the output stream for the log messages.
Options: stdout, logcat with 0, 1 respectively.--log-level=<int | string>
(default: INFO). Select to output messages which are at or below
the set level. Options: VERBOSE, DEBUG, INFO, WARNING, ERROR, FATAL with 0, 1, 2, 3, 4 and 5
respectively.--parameters=string
. If the benchmark is parametric, all the parameters (separated by commas)
can be given through this option.Every .ditto file should begin with this skeleton:
main: {
...
},
global {
...
}
Optionally, it can contain init
and clean_up
sections:
init: {
...
},
main: {
...
},
clean_up: {
...
},
global {
...
}
global
Global section should contain general benchmark configuration. Currently available options:
string absolute_path
(default = ""
). Specifies the absolute path for the files.init
init
is optional and can be used to initialize the benchmarking environment. It executes
instructions similar to main
, but the results are not collected in the end.
main
main
is the entry point for the benchmark. It can contain a single instruction
or
instruction_set
(also with nested instruction_set
).
clean_up
clean_up
is optional and can be used to reset the benchmarking environment to the initial state,
e.g, delete benchmark files. Similar to init
, it executes instructions like main
, but results
are not collected in the end.
instruction
{
<name of the instruction>: {
<first argument>,
<second argument>,
...
},
<general instruction options>
}
Currently available options:
int repeat
(default = 1
). Specifies how many times the instruction should be
repeated.instruction_set
{
instruction_set: {
instructions: {
{
<name of the first instruction>: {
<first argument>,
<second argument>,
...
},
<general instruction options>
},
{
<name of the second instruction>: {
<first argument>,
<second argument>,
...
},
<general instruction options>
},
...
},
iterate_options: {...}
},
<general instruction options>
}
Instruction set is an Instruction container that executes the contained instructions sequentially.
Instruction set can optionally iterate over a list and execute the provided set of instructions on
each item from the list. To use it, iterate_options
should be set with these options:
string list_name
- Shared variable name pointing to a list of values.string item_name
- Shared variable name to which a selected value should be stored.Order order
(default = SEQUENTIAL
) - Specifies if
the elements of the list should be accessed sequentially or randomly. Options:
SEQUENTIAL
, RANDOM
.Reseeding reseeding
(default = ONCE
) - Specifies how often the random number
generator should be reseeded with the same provided (or generated) seed. Options: ONCE
,
EACH_ROUND_OF_CYCLES
, EACH_CYCLE
.uint32 seed
- Seed for the random number generator. If the seed is not provided,
current system time is used as the seed.multithreading
and threads
multithreading: {
threads: [
{
instruction: {...},
spawn: <number of threads to spawn with the provided instruction>
},
...
]
}
Multithreading is another instruction container that executes the specified instructions
(or instruction sets) in different threads. If the optional spawn
option for a specific
instruction (or instruction set) is provided, then the provided number of threads will be created
for it.
main: {
instruction_set: {
instructions: [
{
open_file: {
path_name: "newfile2.txt",
output_fd: "test_file"
}
},
{
close_file: {
input_fd: "test_file"
}
}
]
},
repeat: 10
},
global {
absolute_path: "/data/local/tmp/";
}
See more examples in example/
.
open_file
Opens the file with a file path or a shared variable name pointing to a file path. If neither of those are provided, a random name consisting of 9 random digits is generated. Optionally saves the file descriptor which can then be used by subsequent instructions. Also, can optionally create the file if it does not already exist.
string path_name
- Specifies the file path.string input
- Shared variable name pointing to a file path.string output_fd
- Shared variable name to which output file descriptor
should be saved.bool create
(default = true
) - Specifies if the file should be created if it does
not already exist. If the file exists, nothing happens.delete_file
Deletes the file with a file path or a shared variable name pointing to a file path.
Uses unlink(2)
.
string path_name
- Specifies the file path.string input
- Shared variable name pointing to a file path.close_file
Closes the file with the provided file descriptor.
Uses close(2)
.
string input_fd
- Shared variable name pointing to a file descriptor.resize_file
Resizes the file with the provided file descriptor and new size. If the provided size is greater
than the current file size, fallocate(2)
is used, while ftruncate(2)
is used if the provided
size is not greater than the current file size.
string input_fd
- Shared variable name pointing to a file descriptor.int64 size
- New file size (in bytes).resize_file_random
Resizes the file with the provided file descriptor and a range for new size. New file size is
randomly generated in the provided range and if the generated size is greater
than the current file size, fallocate(2)
is used, while ftruncate(2)
is used if the generated
size is not greater than the current file size.
string input_fd
- Shared variable name pointing to a file descriptor.int64 min
- Minimum value (in bytes)int64 max
- Maximum value (in bytes)uint32 seed
- Seed for the random number generator. If the seed is not provided,
current system time is used as the seed.Reseeding reseeding
(default = ONCE
). How often the random number
generator should be reseeded with the provided (or generated) seed. Options: ONCE
,
EACH_ROUND_OF_CYCLES
, EACH_CYCLE
.write_file
Writes to file with the provided file descriptor. For SEQUENTIAL
access, the blocks of data will be written sequentially and if the end of
the file is reached, new blocks will start from the beginning of the file. For
RANDOM
access, the block offset, to which data should be written, will
be randomly chosen with uniform distribution. 10101010
byte is used for the
write operation to fill the memory with alternating ones and zeroes. Uses
pwrite64(2)
.
string input_fd
- Shared variable name pointing to a file descriptor.int64 size
(default = -1
) - How much data (in bytes) should be written in total.
If it is set to -1
, then file size is used.int64 block_size
(default = 4096
) - How much data (in bytes) should be written at
once. If it is set to -1
, then file size is used.int64 starting_offset
(default = 0
) - If access_order
is
set to SEQUENTIAL
, then the blocks, to which the data should be written,
will start from this starting offset (in bytes).Order access_order
(default = SEQUENTIAL
) - Order of the
write. Options: SEQUENTIAL
and RANDOM
.uint32 seed
- Seed for the random number generator. If the seed is not provided,
current system time is used as the seed.bool fsync
(default = false
) - If set, fsync(2)
will be called after the
execution of all write operations.Reseeding reseeding
(default = ONCE
) - How often the random number
generator should be reseeded with the provided (or generated) seed. Options: ONCE
,
EACH_ROUND_OF_CYCLES
, EACH_CYCLE
.read_file
Reads from file with the provided file descriptor. For SEQUENTIAL
access, the blocks of data will be read sequentially and if the end of
the file is reached, new blocks will start from the beginning of the file. For
RANDOM
access, the block offset, from which data should be read, will
be randomly chosen with uniform distribution. Calls posix_fadvise(2)
before
the read operations. Uses pread64(2)
.
string input_fd
- Shared variable name pointing to a file descriptor.int64 size
(default = -1
) - How much data (in bytes) should be read in total.
If it is set to -1
, then file size is used.int64 block_size
(default = 4096
) - How much data (in bytes) should be read at
once. If it is set to -1
, then file size is used.int64 starting_offset
(default = 0
) - If access_order
is
set to SEQUENTIAL
, then the blocks, from which the data should be read,
will start from this starting offset (in bytes).Order access_order
(default = SEQUENTIAL
) - Order of the
read. Options: SEQUENTIAL
and RANDOM
.uint32 seed
- Seed for the random number generator. If the seed is not provided,
current system time is used as the seed.ReadFAdvise fadvise
(default = AUTOMATIC
) - Sets the argument
for the posix_fadvise(2)
operation. Options: AUTOMATIC
, NORMAL
,
SEQUENTIAL
and RANDOM
. If AUTOMATIC
is set, then
POSIX_FADV_SEQUENTIAL
or POSIX_FADV_RANDOM
will be used for SEQUENTIAL
and RANDOM
access order respectively.Reseeding reseeding
(default = ONCE
) - How often the random number
generator should be reseeded with the provided (or generated) seed. Options: ONCE
,
EACH_ROUND_OF_CYCLES
, EACH_CYCLE
.read_directory
Reads file names from a directory and stores them as a list in a shared variable. Uses readdir(3)
.
string directory_name
- Name of the directorystring output
- Shared variable name to which files names should be saved.invalidate_cache
Drops kernel clean caches, including, dentry, inode and page caches by calling sync() first and
then writing 3
to /proc/sys/vm/drop_caches
. No arguments.
The project is currently being developed as part of the Android Open Source Project (AOSP) and is supposed to run out-of-the-box.
The following utilities are required to build the project on Linux:
sudo apt install cmake protobuf-compiler
A suite of unit tests is provided in the test/ directory. In Linux these tests can be run with the following commands:
mkdir build
cd build
make
cd test
ctest
Bandwidth and measurement when dealing with few huge files or many small files. Operations are combinations of sequential/random-offset read/write.
Latency in creating/deleting files/folders.
These operations should be able to be triggered in a multiprogrammed fashion.
A graph of processes that are communicating with each others in a pipeline of operations that are parallely contributing to the generation of display frames.
Spawning tasks (period, duration, deadline) and verifying their scheduling latency and deadline misses count.
In the following scenario, two threads are running.
T1 runs the following operations: Read, Write, Read, sends a request to T2 and waits for the reply, then Write, Read. T2 waits for a request, then Read, Write, then sends the reply to the requester.
Operations are encoded as primitives expressed with ProtoBuffers. The definition of dependencies among threads can be represented as graphs.
The first graph traversal is performed at initialization time, when all the ProtoBuffer configuration files are distributed among all the binaries so that they can perform all the heavy initialization duties.
After the initialization phase completes the graph can be traversed again to put all the workloads in execution.
A final graph traversal can be performed to fetch all the measurements that each entity internally stored.
All the measurements must be ordered and later processed to provide useful information to the user.
T1: INIT : [ RD WR RD ] SND RCV [ WR RD ] : END T2: INIT : RCV [ RD WR ] SND : END
critical path [ READ WRITE READ ] [ READ WRITE ] [ WRITE READ ] --------------------> > < Thread1 III-XXXXXX|X-SSSSSS-XX-TTTT Thread2 III-XXXX|XXX-TTTT ^
> XXXXXXX XX<
XXXX
READ WRITE READ
--->
vector<instr*> {read(), write(), read()}; -> start()
RECEIVE READ WRITE READ SEND ---> vector<instr*> {receive(), read(), write(), read(), send()}; start()
lock on receive()