trace_recorder: implementation

genodelabs/genode#4352
This commit is contained in:
Johannes Schlatow 2022-05-20 14:33:07 +02:00 committed by Christian Helmuth
parent 53f576e50e
commit ef742001e8
42 changed files with 2690 additions and 4 deletions

View File

@ -0,0 +1 @@
Trace recorder for collecting continuous traces in file system.

View File

@ -0,0 +1,4 @@
_/src/trace_recorder_policy
_/src/trace_recorder
_/raw/trace_recorder
_/src/vfs

View File

@ -0,0 +1 @@
2022-05-11 b0ff8e7876af3b8bd5fcf7d4055290d029e29198

View File

@ -0,0 +1,28 @@
<runtime ram="48M" caps="300" binary="trace_recorder">
<requires>
<file_system label="target"/>
<trace/>
<rtc/>
<rom label="platform_info"/>
</requires>
<content>
<rom label="trace_recorder"/>
<rom label="ld.lib.so"/>
<rom label="vfs.lib.so"/>
<rom label="ctf0"/>
<rom label="pcapng"/>
<rom label="metadata"/>
</content>
<!-- example, must be refined and enabled -->
<config period_ms="5000" enable="no">
<vfs> <fs label="target"/> </vfs>
<default-policy policy="ctf0">
<ctf/>
</default-policy>
</config>
</runtime>

View File

@ -0,0 +1,4 @@
content: metadata
metadata:
cp $(REP_DIR)/src/app/trace_recorder/ctf/$@ $@

View File

@ -0,0 +1 @@
2022-05-11 5e78bddea021b50315ea907a1cd003162439da22

View File

@ -0,0 +1,9 @@
MIRROR_FROM_REP_DIR := src/app/trace_recorder
content: $(MIRROR_FROM_REP_DIR) LICENSE
$(MIRROR_FROM_REP_DIR):
$(mirror_from_rep_dir)
LICENSE:
cp $(GENODE_DIR)/LICENSE $@

View File

@ -0,0 +1 @@
2022-05-11 2cc9c248476c5b323740496cc1da0bf4fdbae1f9

View File

@ -0,0 +1,8 @@
base
os
so
vfs
ctf
rtc_session
trace
trace_recorder_policy

View File

@ -0,0 +1,2 @@
SRC_DIR = src/lib/trace_recorder/policy
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -0,0 +1 @@
2022-05-17 2c7fca33754a0cb26bc494a276cb285cdb6650d7

View File

@ -0,0 +1,5 @@
base
os
ctf
trace
trace_recorder_policy

View File

@ -0,0 +1,175 @@
assert_spec linux
# check that babeltrace2 is present
set babeltrace_missing [catch {
spawn babeltrace2 -V
expect {
{Babeltrace 2.*} { }
eof { return }
timeout { return }
}
}]
if {$babeltrace_missing} {
puts "\nPlease install babeltrace2 on your host system."
exit 1;
}
build { server/lx_fs }
create_boot_directory
import_from_depot \
[depot_user]/src/[base_src] \
[depot_user]/src/init \
[depot_user]/src/libc \
[depot_user]/src/rom_logger \
[depot_user]/src/report_rom \
[depot_user]/src/vfs \
[depot_user]/src/dummy_rtc_drv \
[depot_user]/src/trace_recorder \
[depot_user]/raw/trace_recorder \
[depot_user]/src/trace_recorder_policy \
[depot_user]/src/dynamic_rom
install_config {
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="LOG"/>
<service name="RM"/>
<service name="CPU"/>
<service name="PD"/>
<service name="IRQ"/>
<service name="TRACE"/>
</parent-provides>
<default-route>
<service name="File_system"> <child name="vfs"/> </service>
<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>
<start name="dynamic_rom">
<resource name="RAM" quantum="1M"/>
<provides><service name="ROM"/></provides>
<config>
<rom name="test">
<inline>
<foo/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<bar/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<baz/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<foo/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<bar/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<foo/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<bar/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<foo/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<bar/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<end/>
</inline>
<sleep milliseconds="1000"/>
</rom>
</config>
</start>
<start name="rom_logger">
<resource name="RAM" quantum="1M"/>
<config rom="test"/>
<route>
<service name="ROM" label="test"> <child name="dynamic_rom"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="dummy_rtc_drv">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Rtc"/> </provides>
</start>
<start name="lx_fs" ld="no">
<resource name="RAM" quantum="4M"/>
<provides> <service name="File_system"/> </provides>
<config>
<default-policy root="/fs" writeable="yes"/>
</config>
</start>
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides>
<service name="Report"/>
<service name="ROM"/>
</provides>
<config verbose="yes">
</config>
</start>
<start name="trace_recorder" caps="200">
<resource name="RAM" quantum="16M"/>
<config period_ms="5000" enable="yes">
<vfs> <fs/> </vfs>
<policy label_suffix="dynamic_rom" thread="dynamic_rom" policy="ctf0">
<ctf/>
</policy>
<policy label_suffix="rom_logger" policy="ctf0">
<ctf/>
</policy>
</config>
<route>
<service name="File_system"> <child name="lx_fs"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>
}
exec rm -rf bin/fs
exec mkdir -p bin/fs
build_boot_image { lx_fs
fs }
append qemu_args " -nographic "
run_genode_until {Enabled ctf writer for init -> rom_logger -> ep} 20
set spawn_id [output_spawn_id]
run_genode_until {<end/>} 60 $spawn_id
# trace file has non-zero size
exec test -s bin/fs/0-0-0\ 0\:0\:0/init/rom_logger/ep
# check generated trace by reading CTF trace as fast as possible using a dummy output
exec babeltrace2 bin/fs/0-0-0\ 0\:0\:0/init/rom_logger --output-format=dummy

View File

@ -0,0 +1,163 @@
assert_spec linux
# check that python-pcapng is present
set python_pcapng_missing [catch {
spawn -noecho sh -c "echo \"import pcapng\" | python"
expect {
{No module} { return }
{not found} { return }
eof { }
}
}]
if {$python_pcapng_missing} {
puts "\nPlease install python-pcapng on your host system."
exit 1;
}
build { server/lx_fs app/ping }
create_boot_directory
import_from_depot \
[depot_user]/src/[base_src] \
[depot_user]/src/init \
[depot_user]/src/libc \
[depot_user]/src/nic_router \
[depot_user]/src/vfs \
[depot_user]/src/linux_rtc_drv \
[depot_user]/src/trace_recorder \
[depot_user]/raw/trace_recorder \
[depot_user]/src/trace_recorder_policy \
[depot_user]/src/dynamic_rom
install_config {
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="LOG"/>
<service name="RM"/>
<service name="CPU"/>
<service name="PD"/>
<service name="IRQ"/>
<service name="TRACE"/>
</parent-provides>
<default-route>
<service name="File_system"> <child name="vfs"/> </service>
<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>
<start name="linux_rtc_drv" ld="no">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Rtc"/> </provides>
</start>
<start name="lx_fs" ld="no">
<resource name="RAM" quantum="4M"/>
<provides> <service name="File_system"/> </provides>
<config>
<default-policy root="/fs" writeable="yes"/>
</config>
</start>
<start name="nic_router">
<resource name="RAM" quantum="1M"/>
<provides>
<service name="Nic"/>
<service name="Uplink"/>
</provides>
<config icmp_echo_server="yes" trace_packets="yes">
<default-policy domain="default"/>
<domain name="default" interface="10.0.4.1/24">
<dhcp-server ip_first="10.0.4.2" ip_last="10.0.4.10"/>
</domain>
</config>
</start>
<start name="ping">
<resource name="RAM" quantum="10M"/>
<config dst_ip="10.0.4.1" period_sec="1" count="10" verbose="yes"/>
</start>
<!-- using dynamic_rom to delay enabling of trace_recorder -->
<start name="dynamic_rom">
<resource name="RAM" quantum="1M"/>
<provides><service name="ROM"/></provides>
<config>
<rom name="config">
<inline>
<config/>
</inline>
<sleep milliseconds="1000"/>
<inline>
<config period_ms="3000" enable="yes">
<vfs> <fs/> </vfs>
<policy label_suffix="nic_router" thread="ep" policy="pcapng">
<pcapng/>
</policy>
</config>
</inline>
<sleep milliseconds="5000"/>
<inline>
<config/>
</inline>
<sleep milliseconds="10000"/>
</rom>
</config>
</start>
<start name="trace_recorder" caps="200">
<resource name="RAM" quantum="16M"/>
<route>
<service name="File_system"> <child name="lx_fs"/> </service>
<service name="ROM" label="config"> <child name="dynamic_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>
}
exec rm -rf bin/fs
exec mkdir -p bin/fs/
build_boot_image { lx_fs
fs
ping }
append qemu_args " -nographic "
run_genode_until {Enabled pcapng writer for init -> nic_router -> ep} 15
set spawn_id [output_spawn_id]
run_genode_until {.*child "ping" exited with exit value 0.*} 60 $spawn_id
set pcap_file [exec find bin/fs -name nic_router.pcapng]
# trace file has non-zero size?
exec test -s $pcap_file
# create python script for pcapng parsing
set fd [open [run_dir]/genode/check_pcapng.py "w"]
puts $fd {
import sys
import pcapng
with open(sys.argv[1], "rb") as fp:
scanner = pcapng.FileScanner(fp)
for block in pcapng.FileScanner(fp):
pass
}
close $fd
# check generated trace by python script
exec python [run_dir]/genode/check_pcapng.py $pcap_file

View File

@ -0,0 +1,56 @@
The trace recorder uses Genode's trace session to insert trace policies and
periodically process the trace buffers to record continuous traces in a file
system. The trace recorder comprises multiple backends that produce different
output formats.
An examplary configuration is shown below:
! <config period_ms="5000" target_root="/" enable="true">
! <vfs> <fs/> </vfs>
! <policy label_suffix="dynamic_rom" policy="ctf0">
! <ctf/>
! </policy>
! </config>
The mandatory argument 'period_ms' specifies the trace-buffer sampling period
in milliseconds. The 'enable' attribute activates trace recording.
Whenever the 'enable' attribute is toggled from "no" to "yes", a new directory
is created (using the real-time clock) to record a new set of traces.
The '<config>' node can contain an arbitray number of '<policy>' nodes by which
the plugin determines what components and threads are traced.
The specified 'label_suffix', 'label_prefix' and/or 'label' attributes are
matched against the component labels. By default, all threads of the matching
component(s) will be traced. The mandatory 'policy' attribute specifies the name
of the trace policy to be applied to the matching threads.
Every '<policy>' node must contain at least one sub-node specifying what
backend(s) shall be used for trace output. Currently, the following backends are
available:
:'ctf':
Produces CTF (common trace format) traces of component interactions
and checkpoints. These traces can be processed with babeltrace or
visualised with TraceCompass. Note that the ctf backend opens a ROM
session "metadata" that is used as a blueprint for the
[https://diamon.org/ctf/ - CTF metadata file] created with the trace
files. Currently, only the frequency of the specified clock is adapted.
The metadata file is required for deserialisation of trace data.
:'pcapng':
Captures packets (e.g. Ethernet packets) in a pcapng file that can be read
by wireshark, for instance.
The '<config>' node may take the following optional attributes:
:'enable': Enables/starts tracing (default: 'no')
:'target_root': Sets the target root directory for trace output.
Furthermore, the '<policy>' nodes may take the following optional attributes:
:'thread': Restricts the tracing to a certain thread of the matching component(s).
:'buffer': Sets the size of the trace buffer (default: '64K').

View File

@ -0,0 +1,57 @@
/*
* \brief Factory base for creating writers
* \author Johannes Schlatow
* \date 2022-05-11
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _BACKEND_H_
#define _BACKEND_H_
/* local includes */
#include <named_registry.h>
#include <writer.h>
namespace Trace_recorder {
class Backend_base;
using Backends = Named_registry<Backend_base>;
}
class Trace_recorder::Backend_base : Backends::Element
{
protected:
friend class Backends::Element;
friend class Avl_node<Backend_base>;
friend class Avl_tree<Backend_base>;
public:
using Name = Backends::Element::Name;
using Backends::Element::name;
using Backends::Element::Element;
Backend_base(Backends & registry, Name const &name)
: Backends::Element(registry, name)
{ }
virtual ~Backend_base() { }
/***************
** Interface **
***************/
virtual Writer_base &create_writer(Genode::Allocator &,
Genode::Registry<Writer_base> &,
Directory &,
Directory::Path const &) = 0;
};
#endif /* _BACKEND_H_ */

View File

@ -0,0 +1,59 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="base_types.xsd"/>
<xs:include schemaLocation="timeout_types.xsd"/>
<xs:simpleType name="Trace_policy_name">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="40"/>
</xs:restriction>
</xs:simpleType><!-- Trace_policy_name -->
<xs:simpleType name="Path">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="256"/>
</xs:restriction>
</xs:simpleType><!-- Path -->
<xs:element name="config">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="vfs"/>
<xs:element name="default-policy">
<xs:complexType>
<xs:attribute name="thread" type="Thread_name" />
<xs:attribute name="buffer" type="Number_of_bytes" />
<xs:attribute name="policy" type="Trace_policy_name" use="required"/>
</xs:complexType>
</xs:element><!-- default-policy -->
<xs:element name="policy">
<xs:complexType>
<xs:complexContent>
<xs:extension base="Session_policy">
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="ctf"/>
<xs:element name="log"/>
<xs:element name="pcapng"/>
</xs:choice>
<xs:attribute name="thread" type="Thread_name" />
<xs:attribute name="buffer" type="Number_of_bytes" />
<xs:attribute name="policy" type="Trace_policy_name" use="required"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element><!-- policy -->
</xs:choice>
<xs:attribute name="period_ms" type="Seconds" use="required"/>
<xs:attribute name="target_root" type="Path"/>
<xs:attribute name="enable" type="Boolean" />
</xs:complexType>
</xs:element><!-- config -->
</xs:schema>

View File

@ -0,0 +1,59 @@
/*
* \brief CTF backend
* \author Johannes Schlatow
* \date 2022-05-11
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include <ctf/backend.h>
using namespace Ctf;
void Writer::start_iteration(Directory &root,
Directory::Path const &path,
::Subject_info const &info)
{
_file_path = Directory::join(path, info.thread_name());
try {
_dst_file.construct(root, _file_path, true);
/* initialise packet header */
_packet_buffer.init_header(info);
}
catch (New_file::Create_failed) {
error("Could not create file."); }
}
void Writer::process_event(Trace_recorder::Trace_event_base const &trace_event, size_t length)
{
if (!_dst_file.constructed()) return;
if (trace_event.type() != Trace_recorder::Event_type::CTF) return;
try {
/* write to file if buffer is full */
if (_packet_buffer.bytes_remaining() < length)
_packet_buffer.write_to_file(*_dst_file, _file_path);
_packet_buffer.add_event(trace_event.event<Trace_recorder::Ctf_event>(), length - sizeof(Trace_event_base));
}
catch (Buffer::Buffer_too_small) {
error("Packet buffer overflow. (Trace buffer wrapped during read?)"); }
}
void Writer::end_iteration()
{
/* write buffer to file */
_packet_buffer.write_to_file(*_dst_file, _file_path);
_dst_file.destruct();
}

View File

@ -0,0 +1,96 @@
/*
* \brief CTF backend
* \author Johannes Schlatow
* \date 2021-08-02
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _CTF__BACKEND_H_
#define _CTF__BACKEND_H_
/* local includes */
#include <ctf/metadata.h>
#include <ctf/write_buffer.h>
#include <backend.h>
#include <timestamp_calibrator.h>
#include <os/vfs.h>
#include <base/attached_rom_dataspace.h>
namespace Ctf {
using namespace Trace_recorder;
using Genode::Directory;
using Genode::New_file;
using Buffer = Write_buffer<32*1024>;
class Backend;
class Writer;
}
class Ctf::Writer : public Trace_recorder::Writer_base
{
private:
Buffer &_packet_buffer;
Constructible<New_file> _dst_file { };
Directory::Path _file_path { };
public:
Writer(Genode::Registry<Writer_base> &registry, Buffer &packet_buffer)
: Writer_base(registry),
_packet_buffer(packet_buffer)
{ }
virtual void start_iteration(Directory &,
Directory::Path const &,
::Subject_info const &) override;
virtual void process_event(Trace_recorder::Trace_event_base const &, Genode::size_t) override;
virtual void end_iteration() override;
};
class Ctf::Backend : Trace_recorder::Backend_base
{
private:
Attached_rom_dataspace _metadata_rom;
Metadata _metadata;
Buffer _packet_buf { };
public:
Backend(Env &env, Timestamp_calibrator const &ts_calibrator, Backends &backends)
: Backend_base(backends, "ctf"),
_metadata_rom(env, "metadata"),
_metadata(_metadata_rom, ts_calibrator.ticks_per_second())
{ }
Writer_base &create_writer(Genode::Allocator &alloc,
Genode::Registry<Writer_base> &registry,
Directory &root,
Directory::Path const &path) override
{
/* copy metadata file while adapting clock declaration */
Directory::Path metadata_path { Directory::join(path, "metadata") };
if (!root.file_exists(metadata_path)) {
New_file metadata_file { root, metadata_path };
_metadata.write_file(metadata_file);
}
return *new (alloc) Writer(registry, _packet_buf);
}
};
#endif /* _CTF__BACKEND_H_ */

View File

@ -0,0 +1,121 @@
/* CTF 1.8 */
typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
typealias uint8_t := char;
typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
typealias integer { size = 4; align = 1; signed = false; } := uint4_t;
trace {
major = 1;
minor = 8;
uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
byte_order = le;
packet.header := struct {
uint32_t magic;
uint32_t stream_id;
};
};
clock {
name = "monotonic";
freq = 1000000000; /* Frequency, in Hz (DO NOT REMOVE) */
};
typealias integer {
size = 64; align = 8; signed = false;
map = clock.monotonic.value;
} := uint64_tsc_t;
struct packet_context {
uint64_tsc_t timestamp_begin;
uint64_tsc_t timestamp_end;
uint32_t packet_size;
uint16_t _hdrsz;
uint4_t xpos;
uint4_t ypos;
uint4_t width;
uint4_t height;
uint8_t priority;
string session_label;
string thread_name;
};
struct event_header {
uint8_t id;
uint64_tsc_t timestamp;
} align(8);
stream {
id = 0;
event.header := struct event_header;
packet.context := struct packet_context;
};
event {
name = "Rpc_call";
id = 1;
stream_id = 0;
fields := struct {
string _name;
};
};
event {
name = "Rpc_returned";
id = 2;
stream_id = 0;
fields := struct {
string _name;
};
};
event {
name = "Rpc_dispatch";
id = 3;
stream_id = 0;
fields := struct {
string _name;
};
};
event {
name = "Rpc_reply";
id = 4;
stream_id = 0;
fields := struct {
string _name;
};
};
event {
name = "Signal_submit";
id = 5;
stream_id = 0;
fields := struct {
uint32_t _number;
};
};
event {
name = "Signal_receive";
id = 6;
stream_id = 0;
fields := struct {
uint32_t _number;
uint64_t _context;
};
};
event {
name = "Checkpoint";
id = 7;
stream_id = 0;
fields := struct {
uint32_t _data;
uint64_t _addr;
uint8_t _type;
string _name;
};
};

View File

@ -0,0 +1,106 @@
/*
* \brief Metadata file writer
* \author Johannes Schlatow
* \date 2021-12-02
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _CTF__METADATA_H_
#define _CTF__METADATA_H_
#include <os/vfs.h>
#include <base/attached_rom_dataspace.h>
#include <base/buffered_output.h>
namespace Ctf {
using namespace Genode;
class Metadata;
}
class Ctf::Metadata
{
private:
Attached_rom_dataspace &_metadata_rom;
uint64_t _timestamp_freq;
template <typename PROLOGUE, typename EPILOGUE>
bool _with_metadata_rom(PROLOGUE && prologue,
EPILOGUE && epilogue) const
{
using Token = Genode::Token<Genode::Scanner_policy_identifier_with_underline>;
char const * rom_start = _metadata_rom.local_addr<char>();
size_t const rom_size = _metadata_rom.size();
Token token { rom_start, rom_size };
for (; token; token = token.next()) {
if (token.type() == Token::IDENT) {
if (token.matches("freq") && token.len() == 4) {
prologue(rom_start, token.next().start() - rom_start);
token = token.next_after("\n");
/* find null termination */
char const * rom_end = token.start();
for (; rom_end < rom_start+rom_size && rom_end && *rom_end; rom_end++);
epilogue(token.start(), rom_end - token.start());
return true;
}
}
}
error("Error parsing metadata ROM. Could not find 'freq' definition.");
return false;
}
public:
Metadata(Attached_rom_dataspace & metadata_rom, uint64_t freq)
: _metadata_rom(metadata_rom),
_timestamp_freq(freq)
{ }
void write_file(Genode::New_file & dst) const
{
using namespace Genode;
bool write_error = false;
auto write = [&] (char const *str)
{
if (dst.append(str, strlen(str)) != New_file::Append_result::OK)
write_error = true;
};
Buffered_output<32, decltype(write)> output(write);
_with_metadata_rom(
/* prologue, up to 'freq' */
[&] (char const * start, size_t len) {
if (dst.append(start, len) != New_file::Append_result::OK)
write_error = true;
print(output, " = ", _timestamp_freq, ";\n");
},
/* epilogue, everything after '\n' */
[&] (char const * start, size_t len) {
if (dst.append(start, len) != New_file::Append_result::OK)
write_error = true;
});
if (write_error)
error("Write to 'metadata' failed");
}
};
#endif /* _CTF__METADATA_H_ */

View File

@ -1,5 +1,5 @@
/*
* \brief Packet header and context types
* \brief Packet header
* \author Johannes Schlatow
* \date 2021-08-04
*/
@ -11,8 +11,8 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _CTF__PACKET_TYPES_H_
#define _CTF__PACKET_TYPES_H_
#ifndef _CTF__PACKET_HEADER_H_
#define _CTF__PACKET_HEADER_H_
#include <base/trace/types.h>
#include <ctf/timestamp.h>
@ -130,4 +130,4 @@ struct Ctf::Packet_header
} __attribute__((packed));
#endif /* _CTF__PACKET_TYPES_H_ */
#endif /* _CTF__PACKET_HEADER_H_ */

View File

@ -0,0 +1,82 @@
/*
* \brief Convenience helper for creating a CTF packet
* \author Johannes Schlatow
* \date 2021-08-06
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _CTF__WRITE_BUFFER_H_
#define _CTF__WRITE_BUFFER_H_
/* local includes */
#include <subject_info.h>
#include <ctf/packet_header.h>
/* Genode includes */
#include <os/vfs.h>
#include <trace_recorder_policy/ctf.h>
namespace Ctf {
template <unsigned>
class Write_buffer;
}
template <unsigned BUFSIZE>
class Ctf::Write_buffer
{
public:
struct Buffer_too_small : Genode::Exception { };
private:
char _buffer[BUFSIZE] { };
Packet_header &_header() { return *(Packet_header*)_buffer; }
public:
void init_header(::Subject_info const &info)
{
construct_at<Packet_header>(_buffer,
info.session_label(),
info.thread_name(),
info.affinity(),
info.priority(),
BUFSIZE);
}
void add_event(Ctf::Event_header_base const &event, Genode::size_t length)
{
_header().append_event(_buffer, BUFSIZE,
event.timestamp(), length,
[&] (char * ptr, Timestamp_base ts)
{
/* copy event into buffer */
memcpy(ptr, &event, length);
/* update timestamp */
((Event_header_base *)ptr)->timestamp(ts);
});
}
void write_to_file(Genode::New_file &dst, Genode::Directory::Path const &path)
{
if (_header().empty())
return;
if (dst.append(_buffer, _header().total_length_bytes()) != New_file::Append_result::OK)
error("Write error for ", path);
_header().reset();
}
Genode::size_t bytes_remaining() { return BUFSIZE - _header().total_length_bytes(); }
};
#endif /* _CTF__WRITE_BUFFER_H_ */

View File

@ -0,0 +1,77 @@
/*
* \brief Record traces and store in file system
* \author Johannes Schlatow
* \date 2022-05-09
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include <monitor.h>
/* Genode includes */
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
namespace Trace_recorder {
using namespace Genode;
struct Main;
}
class Trace_recorder::Main
{
private:
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Monitor _monitor { _env, _heap };
Attached_rom_dataspace _config_rom { _env, "config" };
Signal_handler<Main> _config_handler { _env.ep(), *this, &Main::_handle_config };
bool _enabled { false };
void _handle_config();
public:
Main(Env & env)
: _env(env)
{
_config_rom.sigh(_config_handler);
_handle_config();
}
};
void Trace_recorder::Main::_handle_config()
{
_config_rom.update();
bool old_enabled { _enabled };
_enabled = _config_rom.xml().attribute_value("enable", false);
if (old_enabled == _enabled) {
warning("Config update postponed. Need to toggle 'enable' attribute.");
return;
}
if (_enabled)
_monitor.start(_config_rom.xml());
else
_monitor.stop();
}
void Component::construct(Genode::Env &env) { static Trace_recorder::Main main(env); }

View File

@ -0,0 +1,189 @@
/*
* \brief Frontend for controlling the TRACE session
* \author Johannes Schlatow
* \date 2022-05-09
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include "monitor.h"
using namespace Genode;
Directory::Path Trace_recorder::Monitor::Trace_directory::subject_path(::Subject_info const &info)
{
typedef Path<Session_label::capacity()> Label_path;
Label_path label_path = path_from_label<Label_path>(info.session_label().string());
Directory::Path subject_path(Directory::join(_path, label_path.string()));
return subject_path;
}
void Trace_recorder::Monitor::Attached_buffer::process_events(Trace_directory &trace_directory)
{
/* start iteration for every writer */
_writers.for_each([&] (Writer_base &writer) {
writer.start_iteration(trace_directory.root(),
trace_directory.subject_path(info()),
info());
});
/* iterate entries and pass each entry to every writer */
_buffer.for_each_new_entry([&] (Trace::Buffer::Entry &entry) {
if (entry.length() == 0)
return true;
_writers.for_each([&] (Writer_base &writer) {
writer.process_event(entry.object<Trace_event_base>(), entry.length());
});
return true;
});
/* end iteration for every writer */
_writers.for_each([&] (Writer_base &writer) { writer.end_iteration(); });
}
Session_policy Trace_recorder::Monitor::_session_policy(Trace::Subject_info const &info, Xml_node config)
{
Session_label const label(info.session_label());
Session_policy policy(label, config);
/* must have policy attribute */
if (!policy.has_attribute("policy"))
throw Session_policy::No_policy_defined();
if (policy.has_attribute("thread"))
if (policy.attribute_value("thread", Trace::Thread_name()) != info.thread_name())
throw Session_policy::No_policy_defined();
return policy;
}
void Trace_recorder::Monitor::_handle_timeout()
{
_trace_buffers.for_each([&] (Attached_buffer &buf) {
buf.process_events(*_trace_directory);
});
}
void Trace_recorder::Monitor::start(Xml_node config)
{
stop();
/* create new trace directory */
_trace_directory.construct(_env, _alloc, config, _rtc);
/* find matching subjects according to config and start tracing */
_trace.for_each_subject_info([&] (Trace::Subject_id const &id,
Trace::Subject_info const &info) {
try {
/* skip dead subjects */
if (info.state() == Trace::Subject_info::DEAD)
return;
/* check if there is a matching policy in the XML config */
Session_policy session_policy = _session_policy(info, config);
if (!session_policy.has_attribute("policy"))
return;
Number_of_bytes buffer_sz =
session_policy.attribute_value("buffer", Number_of_bytes(DEFAULT_BUFFER_SIZE));
/* find and assign policy; create/insert if not present */
Policy::Name const policy_name = session_policy.attribute_value("policy", Policy::Name());
bool create = true;
_policies.apply(policy_name, [&] (Policy & policy) {
_trace.trace(id, policy.id(), buffer_sz);
create = false;
});
/* create policy if it did not exist */
if (create) {
Policy &policy = *new (_alloc) Policy(_env, _trace, policy_name, _policies);
_trace.trace(id, policy.id(), buffer_sz);
}
log("Inserting trace policy \"", policy_name, "\" into ",
info.session_label(), " -> ", info.thread_name());
/* attach and remember trace buffer */
Attached_buffer &buffer = *new (_alloc) Attached_buffer(_trace_buffers,
_env,
_trace.buffer(id),
info,
id);
/* create and register writers at trace buffer */
session_policy.for_each_sub_node([&] (Xml_node & node) {
bool present = false;
_backends.apply(node.type(), [&] (Backend_base &backend) {
backend.create_writer(_alloc,
buffer.writers(),
_trace_directory->root(),
_trace_directory->subject_path(buffer.info()));
present = true;
});
if (!present)
error("No writer available for <", node.type(), "/>.");
else
log("Enabled ", node.type(), " writer for ", info.session_label(),
" -> ", info.thread_name());
});
}
catch (Session_policy::No_policy_defined) { return; }
});
/* register timeout */
unsigned period_ms { 0 };
if (!config.has_attribute("period_ms"))
error("missing XML attribute 'period_ms'");
else
period_ms = config.attribute_value("period_ms", period_ms);
_timer.trigger_periodic(period_ms * 1000);
}
void Trace_recorder::Monitor::stop()
{
_timer.trigger_periodic(0);
_trace_buffers.for_each([&] (Attached_buffer &buf) {
try {
/* stop tracing */
_trace.pause(buf.subject_id());
} catch (Trace::Nonexistent_subject) { }
/* read remaining events from buffers */
buf.process_events(*_trace_directory);
/* destroy writers */
buf.writers().for_each([&] (Writer_base &writer) {
destroy(_alloc, &writer);
});
/* destroy buffer */
destroy(_alloc, &buf);
try {
/* detach buffer */
_trace.free(buf.subject_id());
} catch (Trace::Nonexistent_subject) { }
});
_trace_directory.destruct();
}

View File

@ -0,0 +1,148 @@
/*
* \brief Frontend for controlling the TRACE session
* \author Johannes Schlatow
* \date 2022-05-09
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _MONITOR_H_
#define _MONITOR_H_
/* local includes */
#include <subject_info.h>
#include <policy.h>
#include <backend.h>
#include <ctf/backend.h>
#include <pcapng/backend.h>
#include <timestamp_calibrator.h>
/* Genode includes */
#include <base/registry.h>
#include <os/session_policy.h>
#include <os/vfs.h>
#include <trace/trace_buffer.h>
#include <rtc_session/connection.h>
namespace Trace_recorder {
using namespace Genode;
class Monitor;
}
class Trace_recorder::Monitor
{
private:
enum { DEFAULT_BUFFER_SIZE = 64 * 1024 };
enum { TRACE_SESSION_RAM = 1024 * 1024 };
enum { TRACE_SESSION_ARG_BUFFER = 128 * 1024 };
class Trace_directory
{
private:
Root_directory _root;
Directory::Path _path;
public:
static Directory::Path root_from_config(Xml_node &config) {
return config.attribute_value("target_root", Directory::Path("/")); }
Trace_directory(Env &env,
Allocator &alloc,
Xml_node &config,
Rtc::Connection &rtc)
: _root(env, alloc, config.sub_node("vfs")),
_path(Directory::join(root_from_config(config), rtc.current_time()))
{ };
Directory &root() { return _root; }
Directory::Path subject_path(::Subject_info const &info);
};
class Attached_buffer
{
private:
Env &_env;
Trace_buffer _buffer;
Registry<Attached_buffer>::Element _element;
Subject_info _info;
Trace::Subject_id _subject_id;
Registry<Writer_base> _writers { };
public:
Attached_buffer(Registry<Attached_buffer> &registry,
Genode::Env &env,
Genode::Dataspace_capability ds,
Trace::Subject_info const &info,
Trace::Subject_id id)
: _env(env),
_buffer(*((Trace::Buffer*)_env.rm().attach(ds))),
_element(registry, *this),
_info(info),
_subject_id(id)
{ }
~Attached_buffer()
{
_env.rm().detach(_buffer.address());
}
void process_events(Trace_directory &);
Registry<Writer_base> &writers() { return _writers; }
Subject_info const &info() const { return _info; }
Trace::Subject_id const subject_id() const { return _subject_id; }
};
Env &_env;
Allocator &_alloc;
Registry<Attached_buffer> _trace_buffers { };
Policies _policies { };
Backends _backends { };
Constructible<Trace_directory> _trace_directory { };
Rtc::Connection _rtc { _env };
Timer::Connection _timer { _env };
Trace::Connection _trace { _env,
TRACE_SESSION_RAM,
TRACE_SESSION_ARG_BUFFER,
0 };
Signal_handler<Monitor> _timeout_handler { _env.ep(),
*this,
&Monitor::_handle_timeout };
Timestamp_calibrator _ts_calibrator { _env, _rtc, _timer };
/* built-in backends */
Ctf::Backend _ctf_backend { _env, _ts_calibrator, _backends };
Pcapng::Backend _pcapng_backend { _alloc, _ts_calibrator, _backends };
/* methods */
Session_policy _session_policy(Trace::Subject_info const &info, Xml_node config);
void _handle_timeout();
public:
Monitor(Env &env, Allocator &alloc)
: _env(env),
_alloc(alloc)
{
_timer.sigh(_timeout_handler);
}
void start(Xml_node config);
void stop();
};
#endif /* _MONITOR_H_ */

View File

@ -0,0 +1,102 @@
/*
* \brief Utility for finding objecs by name
* \author Norman Feske
* \date 2021-11-11
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _NAMED_REGISTRY_H_
#define _NAMED_REGISTRY_H_
#include <util/string.h>
#include <util/avl_tree.h>
#include <util/noncopyable.h>
namespace Trace_recorder {
using namespace Genode;
template <typename T> class Named_registry;
template <size_t N1, size_t N2>
static inline bool operator > (String<N1> const &s1, String<N2> const &s2)
{
return strcmp(s1.string(), s2.string()) > 0;
}
}
template <typename T>
class Trace_recorder::Named_registry : Noncopyable
{
private:
Avl_tree<T> _tree { };
public:
class Element : private Avl_node<T>
{
public:
using Name = Genode::String<64>;
Name const name;
private:
Named_registry<T> &_registry;
bool higher(T const *other) const { return name > other->name; }
friend class Avl_tree<T>;
friend class Avl_node<T>;
friend class Named_registry<T>;
static T *_matching_sub_tree(T &curr, Name const &name)
{
typename Avl_node<T>::Side side = (curr.name > name);
return curr.Avl_node<T>::child(side);
}
public:
Element(Named_registry &registry, Name const &name)
:
name(name), _registry(registry)
{
_registry._tree.insert(this);
}
~Element()
{
_registry._tree.remove(this);
}
};
template <typename FN>
void apply(typename Element::Name const &name, FN && fn)
{
T *curr_ptr = _tree.first();
for (;;) {
if (!curr_ptr)
return;
if (curr_ptr->name == name) {
fn(*curr_ptr);
return;
}
curr_ptr = Element::_matching_sub_tree(*curr_ptr, name);
}
}
template <typename FN>
void for_each(FN && fn) { _tree.for_each(fn); }
};
#endif /* _NAMED_REGISTRY_H_ */

View File

@ -0,0 +1,136 @@
/*
* \brief PCAPNG backend
* \author Johannes Schlatow
* \date 2022-05-13
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include <pcapng/backend.h>
#include <pcapng/enhanced_packet_block.h>
#include <pcapng/interface_description_block.h>
#include <pcapng/section_header_block.h>
using namespace Pcapng;
using Append_error = Buffer::Append_error;
using Append_result = Buffer::Append_result;
using Pcapng_event = Trace_recorder::Pcapng_event;
void Writer::start_iteration(Directory &root,
Directory::Path const &path,
::Subject_info const &)
{
/* write to '${path}.pcapng */
Path<Directory::MAX_PATH_LEN> pcap_file { path };
pcap_file.append(".pcapng");
_file_path = Directory::Path(pcap_file.string());
/* append to file */
try {
_dst_file.construct(root, _file_path, true);
_interface_registry.clear();
_buffer.clear();
_buffer.append<Section_header_block>();
_empty_section = true;
}
catch (New_file::Create_failed) {
error("Could not create file."); }
}
void Writer::process_event(Trace_recorder::Trace_event_base const &trace_event, size_t length)
{
if (!_dst_file.constructed()) return;
if (trace_event.type() != Trace_recorder::Event_type::PCAPNG) return;
/* event is of type Pcapng::Trace_event */
Pcapng_event const &event = trace_event.event<Pcapng_event>();
/* map interface name to id of interface description block (IDB) */
unsigned id = 0;
bool buffer_full = false;
_interface_registry.from_name(event.interface(),
[&] (Interface const &iface) {
/* IDB alread exists */
id = iface.id();
},
[&] (Interface_name const &if_name, unsigned if_id) { /* IDB must be created */
id = if_id;
Append_result result =
_buffer.append<Interface_description_block>(if_name,
Enhanced_packet_block::MAX_CAPTURE_LENGTH);
result.with_error([&] (Append_error err) {
switch (err)
{
case Append_error::OUT_OF_MEM:
/* non-error, write to file and retry */
buffer_full = true;
break;
case Append_error::OVERFLOW:
error("Interface_description_block exceeds its MAX_SIZE");
break;
}
});
return !buffer_full;
}
);
/* add enhanced packet block to buffer */
if (!buffer_full) {
uint64_t us_since_epoch = _ts_calibrator.epoch_from_timestamp_in_us(event.timestamp());
Append_result result = _buffer.append<Enhanced_packet_block>(id, event.packet(), us_since_epoch);
result.with_error([&] (Append_error err) {
switch (err)
{
case Append_error::OUT_OF_MEM:
/* non-error, write to file and retry */
buffer_full = true;
break;
case Append_error::OVERFLOW:
error("Enhanced_packet_block exceeds its MAX_SIZE");
break;
}
});
}
/* write to file if buffer is full and process current event again */
if (buffer_full) {
_buffer.write_to_file(*_dst_file, _file_path);
process_event(event, length);
}
else {
_empty_section = false;
}
}
void Writer::end_iteration()
{
/* write buffer to file */
if (!_empty_section)
_buffer.write_to_file(*_dst_file, _file_path);
_buffer.clear();
_dst_file.destruct();
}
Trace_recorder::Writer_base &Backend::create_writer(Genode::Allocator &alloc,
Genode::Registry<Writer_base> &registry,
Directory &,
Directory::Path const &)
{
return *new (alloc) Writer(registry, _interface_registry, _buffer, _ts_calibrator);
}

View File

@ -0,0 +1,90 @@
/*
* \brief PCAPNG backend
* \author Johannes Schlatow
* \date 2022-05-13
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__BACKEND_H_
#define _PCAPNG__BACKEND_H_
/* local includes */
#include <backend.h>
#include <pcapng/write_buffer.h>
#include <pcapng/interface_registry.h>
#include <timestamp_calibrator.h>
/* Genode includes */
#include <os/vfs.h>
namespace Pcapng {
using namespace Trace_recorder;
using Genode::Directory;
using Genode::New_file;
using Buffer = Write_buffer<32*1024>;
class Backend;
class Writer;
}
class Pcapng::Writer : public Trace_recorder::Writer_base
{
private:
Interface_registry &_interface_registry;
Buffer &_buffer;
Timestamp_calibrator const &_ts_calibrator;
Constructible<New_file> _dst_file { };
Directory::Path _file_path { };
bool _empty_section { false };
public:
Writer(Genode::Registry<Writer_base> &registry, Interface_registry &interface_registry, Buffer &buffer, Timestamp_calibrator const &ts_calibrator)
: Writer_base(registry),
_interface_registry(interface_registry),
_buffer(buffer),
_ts_calibrator(ts_calibrator)
{ }
virtual void start_iteration(Directory &,
Directory::Path const &,
::Subject_info const &) override;
virtual void process_event(Trace_recorder::Trace_event_base const &, Genode::size_t) override;
virtual void end_iteration() override;
};
class Pcapng::Backend : Trace_recorder::Backend_base
{
private:
Interface_registry _interface_registry;
Buffer _buffer { };
Timestamp_calibrator const &_ts_calibrator;
public:
Backend(Allocator &alloc, Timestamp_calibrator const &ts_calibrator, Backends &backends)
: Backend_base(backends, "pcapng"),
_interface_registry(alloc),
_ts_calibrator(ts_calibrator)
{ }
Writer_base &create_writer(Genode::Allocator &,
Genode::Registry<Writer_base> &,
Directory &,
Directory::Path const &) override;
};
#endif /* _PCAPNG__BACKEND_H_ */

View File

@ -0,0 +1,88 @@
/*
* \brief Generic type for PCAPNG blocks
* \author Johannes Schlatow
* \date 2022-05-12
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__BLOCK_H_
#define _PCAPNG__BLOCK_H_
/* Genode includes */
#include <base/fixed_stdint.h>
namespace Pcapng {
struct Block_base;
template <unsigned ID>
struct Block;
}
struct Pcapng::Block_base
{
/**
* Layout: ----- 32-bit -----
* | Type |
* ------------------
* | Length |
* ------------------
* | ... |
* ------------------
* | Length |
* ------------------
*/
uint32_t const _type;
uint32_t _length { 0 };
static constexpr uint32_t padded_size(uint32_t hdr_sz)
{
/* add padding to 4-byte boundary */
const uint32_t hdr_sz_padded = Genode::align_addr(hdr_sz, 2);
return hdr_sz_padded;
}
static constexpr uint32_t block_size(uint32_t sz)
{
return padded_size(sz) + sizeof(uint32_t);
}
Block_base(uint32_t type)
: _type(type)
{ }
void commit(uint32_t hdr_sz)
{
const uint32_t hdr_sz_padded = padded_size(hdr_sz);
_length = hdr_sz_padded + sizeof(uint32_t);
/* store length also after payload to support backward navigation */
((uint32_t*)this)[hdr_sz_padded/4] = _length;
}
bool has_type(uint32_t type) const { return _type == type; }
uint32_t size() const { return _length; }
} __attribute__((packed));
template <unsigned TYPE_ID>
struct Pcapng::Block : Pcapng::Block_base
{
Block()
: Block_base(TYPE_ID)
{ }
static uint32_t type() { return TYPE_ID; };
} __attribute__((packed));
#endif /* _PCAPNG__BLOCK_H_ */

View File

@ -0,0 +1,81 @@
/*
* \brief Enhanced packet block
* \author Johannes Schlatow
* \date 2022-05-12
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__ENHANCED_PACKET_BLOCK_H_
#define _PCAPNG__ENHANCED_PACKET_BLOCK_H_
/* local includes */
#include <trace_recorder_policy/pcapng.h>
/* Genode includes */
#include <pcapng/block.h>
namespace Pcapng {
using namespace Genode;
struct Enhanced_packet_block;
}
/* converts Traced_packet into a Pcapng block structure */
struct Pcapng::Enhanced_packet_block : Block<0x6>
{
/**
* Layout: -------- 32-bit -------
* | 0x00000006 |
* -----------------------
* | Length |
* -----------------------
* | Interface ID |
* -----------------------
* | Timestamp High |
* -----------------------
* | Timestamp Low |
* -----------------------
* | Captured Length |
* -----------------------
* | Original Length |
* -----------------------
* | Packet Data |
* | ... |
* | (padded) |
* -----------------------
* | Length |
* -----------------------
*/
uint32_t _interface_id;
uint32_t _timestamp_high;
uint32_t _timestamp_low;
Traced_packet _data;
enum {
MAX_CAPTURE_LENGTH = 1600,
MAX_SIZE = block_size(sizeof(Block_base) +
sizeof(_interface_id) +
sizeof(_timestamp_high) +
sizeof(_timestamp_low) +
sizeof(Traced_packet) +
MAX_CAPTURE_LENGTH)
};
Enhanced_packet_block(uint32_t interface_id, Traced_packet const &packet, uint64_t timestamp)
: _interface_id(interface_id),
_timestamp_high((uint32_t)(timestamp >> 32)),
_timestamp_low(timestamp & 0xFFFFFFFF),
_data(packet)
{ commit(sizeof(Enhanced_packet_block) + _data.data_length()); }
} __attribute__((packed));
#endif /* _PCAPNG__ENHANCED_PACKET_BLOCK_H_ */

View File

@ -0,0 +1,82 @@
/*
* \brief Interface description block
* \author Johannes Schlatow
* \date 2022-05-12
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__INTERFACE_DESCRIPTION_BLOCK_H_
#define _PCAPNG__INTERFACE_DESCRIPTION_BLOCK_H_
/* local includes */
#include <pcapng/block.h>
#include <pcapng/option.h>
/* genode includes */
#include <trace_recorder_policy/pcapng.h>
namespace Pcapng {
using namespace Genode;
struct Interface_description_block;
}
struct Pcapng::Interface_description_block : Block<0x1>
{
/**
* Layout: -------- 32-bit -------
* | 0x00000001 |
* -----------------------
* | Length |
* -----------------------
* | LinkType | Reserved |
* -----------------------
* | SnapLen |
* -----------------------
* | 0x0002 | NameLen |
* -----------------------
* | Name |
* | ... |
* | (padded) |
* -----------------------
* | 0x0001 | 0x0000 |
* -----------------------
* | Length |
* -----------------------
*/
uint16_t const _link_type;
uint16_t const _reserved { 0 };
uint32_t _snaplen;
uint32_t _data[0] { };
enum {
MAX_SIZE = block_size(sizeof(Block_base) +
Interface_name::MAX_NAME_LEN +
sizeof(_link_type) +
sizeof(_reserved) +
sizeof(_snaplen) +
sizeof(Option_ifname) +
sizeof(Option_end))
};
Interface_description_block(Interface_name const &name, uint32_t snaplen)
: _link_type(name._link_type),
_snaplen(snaplen)
{
Option_ifname &opt_ifname = *construct_at<Option_ifname>(&_data[0], name);
Option_end &opt_end = *construct_at<Option_end> (&_data[opt_ifname.total_length()/4]);
commit((uint32_t)sizeof(Interface_description_block) + opt_ifname.total_length() + opt_end.total_length());
}
} __attribute__((packed));
#endif /* _PCAPNG__INTERFACE_DESCRIPTION_BLOCK_H_ */

View File

@ -0,0 +1,101 @@
/*
* \brief Registry for storing interfaces description blocks
* \author Johannes Schlatow
* \date 2022-05-16
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__INTERFACE_REGISTRY_H_
#define _PCAPNG__INTERFACE_REGISTRY_H_
/* local includes */
#include <base/registry.h>
#include <pcapng/interface_description_block.h>
namespace Pcapng {
using namespace Trace_recorder;
using namespace Genode;
class Interface;
class Interface_registry;
}
class Pcapng::Interface
{
public:
using Name = String<Interface_name::MAX_NAME_LEN>;
private:
Name const _name;
unsigned const _id;
Registry<Interface>::Element _element;
public:
Interface(Name const &name, unsigned id, Registry<Interface> &registry)
: _name(name),
_id(id),
_element(registry, *this)
{ }
/*************
* Accessors *
*************/
unsigned id() const { return _id; }
Name const &name() const { return _name; }
};
class Pcapng::Interface_registry : private Registry<Interface>
{
private:
unsigned _next_id { 0 };
Allocator &_alloc;
public:
Interface_registry(Allocator &alloc)
: _alloc(alloc)
{ }
/* apply to existing Interface or create new one */
template <typename FUNC_EXISTS, typename FUNC_NEW>
void from_name(Interface_name const &name, FUNC_EXISTS && fn_exists, FUNC_NEW && fn_new)
{
bool found = false;
for_each([&] (Interface const &iface) {
if (iface.name() == name.string()) {
found = true;
fn_exists(iface);
}
});
/* create new interface */
if (!found) {
if (fn_new(name, _next_id))
new (_alloc) Interface(Interface::Name(name.string()), _next_id++, *this);
}
}
void clear()
{
for_each([&] (Interface &iface) { destroy(_alloc, &iface); });
_next_id = 0;
}
};
#endif /* _PCAPNG__INTERFACE_REGISTRY_H_ */

View File

@ -0,0 +1,69 @@
/*
* \brief Option fields
* \author Johannes Schlatow
* \date 2022-05-12
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__OPTION_H_
#define _PCAPNG__OPTION_H_
#include <trace_recorder_policy/pcapng.h>
namespace Pcapng {
using namespace Genode;
template <uint16_t TYPE>
struct Option;
struct Option_ifname;
struct Option_end;
}
template <Genode::uint16_t TYPE>
struct Pcapng::Option
{
uint16_t _type { TYPE };
uint16_t _length;
uint32_t _data[0];
static uint16_t padded_size(uint16_t sz) {
return (uint16_t)align_addr((uint32_t)sz, 2); }
Option(uint16_t length)
: _length(length)
{ }
template <typename T>
T *data() { return (T*)_data; }
uint16_t total_length() const { return padded_size(sizeof(Option) + _length); }
} __attribute__((packed));
struct Pcapng::Option_end : Option<1>
{
Option_end() : Option(0) { }
} __attribute__((packed));
struct Pcapng::Option_ifname : Option<2>
{
static uint16_t padded_size(Interface_name const &name) {
return (uint16_t)align_addr((uint32_t)name.data_length() - 1, 2); }
Option_ifname(Interface_name const &name)
: Option((uint16_t)(name.data_length()-1))
{
/* copy string leaving out null-termination */
memcpy(data<void>(), name.string(), name.data_length()-1);
}
} __attribute__((packed));
#endif /* _PCAPNG__OPTION_H_ */

View File

@ -0,0 +1,71 @@
/*
* \brief Section header block
* \author Johannes Schlatow
* \date 2022-05-12
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__SECTION_HEADER_BLOCK_H_
#define _PCAPNG__SECTION_HEADER_BLOCK_H_
/* local includes */
#include <pcapng/block.h>
namespace Pcapng {
using namespace Genode;
struct Section_header_block;
}
struct Pcapng::Section_header_block : Block<0x0A0D0D0A>
{
/**
* Layout: ----- 32-bit ----
* | 0x0A0D0D0A |
* -----------------
* | Length |
* -----------------
* | 0x1A2B3C4D |
* -----------------
* | Major | Minor |
* -----------------
* | SectionLen Hi |
* -----------------
* | SectionLen Lo |
* -----------------
* | Length |
* -----------------
*/
uint32_t const _byte_order_magic { 0x1A2B3C4D };
uint16_t const _major_version { 1 };
uint16_t const _minor_version { 0 };
uint64_t const _section_length { 0xFFFFFFFFFFFFFFFF }; /* unspecified */
enum : size_t {
MAX_SIZE = block_size(sizeof(Block_base) +
sizeof(_byte_order_magic) +
sizeof(_major_version) +
sizeof(_minor_version) +
sizeof(_section_length))
};
Section_header_block()
{ commit(sizeof(Section_header_block)); }
/**
* XXX instead of using an unspecified section length, we could add an
* interface similar to Ctf::Packet_header for append sub-blocks and
* keeping track of the length field.
*/
} __attribute__((packed));
#endif /* _PCAPNG__SECTION_HEADER_BLOCK_H_ */

View File

@ -0,0 +1,79 @@
/*
* \brief Convenience helper for batching pcapng blocks before writing to file
* \author Johannes Schlatow
* \date 2022-05-16
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _PCAPNG__WRITE_BUFFER_H_
#define _PCAPNG__WRITE_BUFFER_H_
/* Genode includes */
#include <util/attempt.h>
#include <os/vfs.h>
namespace Pcapng
{
using namespace Genode;
template <unsigned>
class Write_buffer;
}
template <unsigned BUFSIZE>
class Pcapng::Write_buffer
{
public:
enum class Append_error { OUT_OF_MEM, OVERFLOW };
struct Append_ok { };
using Append_result = Attempt<Append_ok, Append_error>;
private:
size_t _total_length { 0 };
char _buffer[BUFSIZE] { };
public:
template <typename T, typename... ARGS>
Append_result append(ARGS &&... args)
{
if (T::MAX_SIZE > BUFSIZE || _total_length > BUFSIZE - T::MAX_SIZE)
return Append_error::OUT_OF_MEM;
void *ptr = &_buffer[_total_length];
T const &block = *construct_at<T>(ptr, args...);
if (block.size() > T::MAX_SIZE) {
error("block size of ", block.size(), " exceeds reserved size ", (unsigned)T::MAX_SIZE);
return Append_error::OVERFLOW;
}
_total_length += block.size();
return Append_ok();
}
void write_to_file(Genode::New_file &dst, Directory::Path const &path)
{
if (_total_length == 0)
return;
if (dst.append(_buffer, _total_length) != New_file::Append_result::OK)
error("Write error for ", path);
clear();
}
void clear() { _total_length = 0; }
};
#endif /* _PCAPNG__WRITE_BUFFER_H_ */

View File

@ -0,0 +1,33 @@
/*
* \brief Installs and maintains a tracing policy
* \author Johannes Schlatow
* \date 2022-05-10
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include <policy.h>
using namespace Genode;
Trace_recorder::Policy::Policy(Env &env,
Trace::Connection &trace,
Policy::Name const &name,
Policies &registry)
:
Policies::Element(registry, name),
_env(env), _trace(trace), _rom(env, name.string())
{
Dataspace_capability dst_ds = _trace.policy(_id);
void *dst = _env.rm().attach(dst_ds);
void *src = _env.rm().attach(_ds);
memcpy(dst, src, _size);
_env.rm().detach(dst);
_env.rm().detach(src);
}

View File

@ -0,0 +1,68 @@
/*
* \brief Installs and maintains a tracing policy
* \author Johannes Schlatow
* \date 2022-05-10
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _POLICY_H_
#define _POLICY_H_
/* local includes */
#include <named_registry.h>
/* Genode includes */
#include <rom_session/connection.h>
#include <trace_session/connection.h>
#include <dataspace/client.h>
namespace Trace_recorder {
class Policy;
using Policies = Named_registry<Policy>;
}
/**
* Installs and maintains a tracing policy
*/
class Trace_recorder::Policy : Policies::Element
{
private:
friend class Policies::Element;
friend class Genode::Avl_node<Policy>;
friend class Genode::Avl_tree<Policy>;
Genode::Env &_env;
Genode::Trace::Connection &_trace;
Genode::Rom_connection _rom;
Genode::Rom_dataspace_capability const _ds { _rom.dataspace() };
Genode::size_t const _size { Genode::Dataspace_client(_ds).size() };
Genode::Trace::Policy_id const _id { _trace.alloc_policy(_size) };
public:
using Name = Policies::Element::Name;
using Policies::Element::name;
using Policies::Element::Element;
Policy(Genode::Env &env,
Genode::Trace::Connection &trace,
Name const &name,
Policies &registry);
/***************
** Accessors **
***************/
Genode::Trace::Policy_id id() const { return _id; }
};
#endif /* _POLICY_H_ */

View File

@ -0,0 +1,39 @@
/*
* \brief Helper for storing static parts of Trace::Subject_info
* \author Johannes Schlatow
* \date 2021-08-06
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SUBJECT_INFO_H_
#define _SUBJECT_INFO_H_
#include <base/trace/types.h>
struct Subject_info
{
Genode::Session_label _session_label;
Genode::Trace::Thread_name _thread_name;
Genode::Affinity::Location _affinity;
unsigned _priority;
Subject_info(Genode::Trace::Subject_info const &info)
: _session_label(info.session_label()),
_thread_name(info.thread_name()),
_affinity(info.affinity()),
_priority(info.execution_time().priority)
{ }
Genode::Session_label const &session_label() const { return _session_label; }
Genode::Trace::Thread_name const &thread_name() const { return _thread_name; }
Genode::Affinity::Location const &affinity() const { return _affinity; }
unsigned priority() const { return _priority; }
};
#endif /* _SUBJECT_INFO_H_ */

View File

@ -0,0 +1,5 @@
TARGET = trace_recorder
INC_DIR += $(PRG_DIR)
SRC_CC = main.cc monitor.cc policy.cc ctf/backend.cc pcapng/backend.cc
CONFIG_XSD = config.xsd
LIBS += base vfs

View File

@ -0,0 +1,127 @@
/*
* \brief Helper for converting Trace::Timestamp to epoch
* \author Johannes Schlatow
* \date 2022-05-19
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _TIMESTAMP_CALIBRATOR_H_
#define _TIMESTAMP_CALIBRATOR_H_
/* Genode includes */
#include <trace/timestamp.h>
#include <rtc_session/connection.h>
#include <timer_session/connection.h>
#include <base/attached_rom_dataspace.h>
namespace Trace_recorder {
using namespace Genode;
class Timestamp_calibrator;
}
class Trace_recorder::Timestamp_calibrator
{
private:
uint64_t const _frequency_hz;
uint64_t const _epoch_start_in_us;
Trace::Timestamp const _ts_start { Trace::timestamp() };
enum : uint64_t {
USEC_PER_SEC = 1000ULL * 1000ULL,
USEC_PER_MIN = USEC_PER_SEC * 60,
USEC_PER_HOUR = USEC_PER_MIN * 60,
USEC_PER_DAY = USEC_PER_HOUR * 24,
};
static uint64_t _day_of_year(Rtc::Timestamp time)
{
/* look up table, starts with month=0 */
unsigned days_until_month[] = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
uint64_t result = time.day + days_until_month[time.month];
/* check for leap year */
if (time.month >= 3) {
if ((time.year % 1000) == 0 || ((time.year % 4) == 0 && !(time.year % 100) == 0))
return result + 1;
}
return result;
}
uint64_t _timestamp_frequency(Env &env, Timer::Connection &timer)
{
using namespace Genode;
/* try getting tsc frequency from platform info, measure if failed */
try {
Attached_rom_dataspace const platform_info (env, "platform_info");
Xml_node const hardware = platform_info.xml().sub_node("hardware");
uint64_t const tsc_freq = hardware.sub_node("tsc").attribute_value("freq_khz", 0ULL);
bool const invariant = hardware.sub_node("tsc").attribute_value("invariant", true);
if (!invariant)
error("No invariant TSC available");
if (tsc_freq)
return tsc_freq * 1000ULL;
} catch (...) { }
warning("Falling back to measured timestamp frequency");
/* measure frequency using timer */
Trace::Timestamp start = Trace::timestamp();
timer.msleep(1000);
return (Trace::timestamp() - start);
}
uint64_t _current_epoch_us(Rtc::Connection &rtc)
{
Rtc::Timestamp const current_time { rtc.current_time() };
// assuming year > 2000 or year == 0
uint64_t usec_until_y2k = (30*365 + 30/4) * USEC_PER_DAY;
uint64_t years_since_y2k = current_time.year ? current_time.year - 2000 : 0;
uint64_t days_since_y2k = years_since_y2k * 365 + years_since_y2k/4 -
years_since_y2k/100 +
years_since_y2k/1000 +
_day_of_year(current_time);
return usec_until_y2k +
days_since_y2k * USEC_PER_DAY +
current_time.hour * USEC_PER_HOUR +
current_time.minute * USEC_PER_MIN +
current_time.second * USEC_PER_SEC +
current_time.microsecond;
}
public:
Timestamp_calibrator(Env &env, Rtc::Connection &rtc, Timer::Connection &timer)
: _frequency_hz (_timestamp_frequency(env, timer)),
_epoch_start_in_us(_current_epoch_us(rtc))
{
log("Timestamp frequency is ", _frequency_hz, "Hz");
}
uint64_t ticks_per_second() const { return _frequency_hz; }
uint64_t epoch_from_timestamp_in_us(Trace::Timestamp ts) const
{
/* intentionally ignoring timestamp wraparounds */
uint64_t ts_diff = ts - _ts_start;
return _epoch_start_in_us + (ts_diff / (ticks_per_second() / USEC_PER_SEC));
}
};
#endif /* _TIMESTAMP_CALIBRATOR_H_ */

View File

@ -0,0 +1,62 @@
/*
* \brief Base class for processing traces and writing outputs
* \author Johannes Schlatow
* \date 2022-05-11
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _WRITER_H_
#define _WRITER_H_
/* local includes */
#include <subject_info.h>
/* Genode includes */
#include <base/registry.h>
#include <os/vfs.h>
#include <trace/trace_buffer.h>
#include <trace_recorder_policy/event.h>
namespace Trace_recorder {
class Writer_base;
using Directory = Genode::Directory;
using Writer_registry = Genode::Registry<Writer_base>;
}
class Trace_recorder::Writer_base
{
protected:
Writer_registry::Element _element;
public:
Writer_base(Writer_registry &registry)
: _element(registry, *this)
{ }
virtual ~Writer_base() { }
/***************
** Interface **
***************/
virtual void start_iteration(Directory &,
Directory::Path const &,
::Subject_info const &) = 0;
virtual void process_event(Trace_recorder::Trace_event_base const &, Genode::size_t) = 0;
virtual void end_iteration() = 0;
};
#endif /* _WRITER_H_ */