diff --git a/repos/gems/recipes/pkg/trace_recorder/README b/repos/gems/recipes/pkg/trace_recorder/README
new file mode 100644
index 0000000000..4c6233b377
--- /dev/null
+++ b/repos/gems/recipes/pkg/trace_recorder/README
@@ -0,0 +1 @@
+Trace recorder for collecting continuous traces in file system.
diff --git a/repos/gems/recipes/pkg/trace_recorder/archives b/repos/gems/recipes/pkg/trace_recorder/archives
new file mode 100644
index 0000000000..15ce2383f4
--- /dev/null
+++ b/repos/gems/recipes/pkg/trace_recorder/archives
@@ -0,0 +1,4 @@
+_/src/trace_recorder_policy
+_/src/trace_recorder
+_/raw/trace_recorder
+_/src/vfs
diff --git a/repos/gems/recipes/pkg/trace_recorder/hash b/repos/gems/recipes/pkg/trace_recorder/hash
new file mode 100644
index 0000000000..74b75c363c
--- /dev/null
+++ b/repos/gems/recipes/pkg/trace_recorder/hash
@@ -0,0 +1 @@
+2022-05-11 b0ff8e7876af3b8bd5fcf7d4055290d029e29198
diff --git a/repos/gems/recipes/pkg/trace_recorder/runtime b/repos/gems/recipes/pkg/trace_recorder/runtime
new file mode 100644
index 0000000000..ac1937b812
--- /dev/null
+++ b/repos/gems/recipes/pkg/trace_recorder/runtime
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/gems/recipes/raw/trace_recorder/content.mk b/repos/gems/recipes/raw/trace_recorder/content.mk
new file mode 100644
index 0000000000..aee25ae047
--- /dev/null
+++ b/repos/gems/recipes/raw/trace_recorder/content.mk
@@ -0,0 +1,4 @@
+content: metadata
+
+metadata:
+ cp $(REP_DIR)/src/app/trace_recorder/ctf/$@ $@
diff --git a/repos/gems/recipes/raw/trace_recorder/hash b/repos/gems/recipes/raw/trace_recorder/hash
new file mode 100644
index 0000000000..4d5fe455a4
--- /dev/null
+++ b/repos/gems/recipes/raw/trace_recorder/hash
@@ -0,0 +1 @@
+2022-05-11 5e78bddea021b50315ea907a1cd003162439da22
diff --git a/repos/gems/recipes/src/trace_recorder/content.mk b/repos/gems/recipes/src/trace_recorder/content.mk
new file mode 100644
index 0000000000..b922f72a76
--- /dev/null
+++ b/repos/gems/recipes/src/trace_recorder/content.mk
@@ -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 $@
diff --git a/repos/gems/recipes/src/trace_recorder/hash b/repos/gems/recipes/src/trace_recorder/hash
new file mode 100644
index 0000000000..d7866bc6f8
--- /dev/null
+++ b/repos/gems/recipes/src/trace_recorder/hash
@@ -0,0 +1 @@
+2022-05-11 2cc9c248476c5b323740496cc1da0bf4fdbae1f9
diff --git a/repos/gems/recipes/src/trace_recorder/used_apis b/repos/gems/recipes/src/trace_recorder/used_apis
new file mode 100644
index 0000000000..0652c7ef28
--- /dev/null
+++ b/repos/gems/recipes/src/trace_recorder/used_apis
@@ -0,0 +1,8 @@
+base
+os
+so
+vfs
+ctf
+rtc_session
+trace
+trace_recorder_policy
diff --git a/repos/gems/recipes/src/trace_recorder_policy/content.mk b/repos/gems/recipes/src/trace_recorder_policy/content.mk
new file mode 100644
index 0000000000..efd6f92d7f
--- /dev/null
+++ b/repos/gems/recipes/src/trace_recorder_policy/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR = src/lib/trace_recorder/policy
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/gems/recipes/src/trace_recorder_policy/hash b/repos/gems/recipes/src/trace_recorder_policy/hash
new file mode 100644
index 0000000000..63238dc288
--- /dev/null
+++ b/repos/gems/recipes/src/trace_recorder_policy/hash
@@ -0,0 +1 @@
+2022-05-17 2c7fca33754a0cb26bc494a276cb285cdb6650d7
diff --git a/repos/gems/recipes/src/trace_recorder_policy/used_apis b/repos/gems/recipes/src/trace_recorder_policy/used_apis
new file mode 100644
index 0000000000..b55b67f02f
--- /dev/null
+++ b/repos/gems/recipes/src/trace_recorder_policy/used_apis
@@ -0,0 +1,5 @@
+base
+os
+ctf
+trace
+trace_recorder_policy
diff --git a/repos/gems/run/trace_recorder_ctf.run b/repos/gems/run/trace_recorder_ctf.run
new file mode 100644
index 0000000000..4310428a11
--- /dev/null
+++ b/repos/gems/run/trace_recorder_ctf.run
@@ -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 {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+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 {} 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
diff --git a/repos/gems/run/trace_recorder_pcapng.run b/repos/gems/run/trace_recorder_pcapng.run
new file mode 100644
index 0000000000..27d6770b7c
--- /dev/null
+++ b/repos/gems/run/trace_recorder_pcapng.run
@@ -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 {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+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
+
diff --git a/repos/gems/src/app/trace_recorder/README b/repos/gems/src/app/trace_recorder/README
new file mode 100644
index 0000000000..47c3ef5990
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/README
@@ -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:
+
+!
+!
+!
+!
+!
+!
+
+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 '' node can contain an arbitray number of '' 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 '' 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 '' 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 '' 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').
diff --git a/repos/gems/src/app/trace_recorder/backend.h b/repos/gems/src/app/trace_recorder/backend.h
new file mode 100644
index 0000000000..0dbb4ac1dc
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/backend.h
@@ -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
+#include
+
+namespace Trace_recorder {
+ class Backend_base;
+
+ using Backends = Named_registry;
+}
+
+
+class Trace_recorder::Backend_base : Backends::Element
+{
+ protected:
+ friend class Backends::Element;
+ friend class Avl_node;
+ friend class Avl_tree;
+
+ 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 &,
+ Directory &,
+ Directory::Path const &) = 0;
+};
+
+#endif /* _BACKEND_H_ */
diff --git a/repos/gems/src/app/trace_recorder/config.xsd b/repos/gems/src/app/trace_recorder/config.xsd
new file mode 100644
index 0000000000..737dc5bac3
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/config.xsd
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/gems/src/app/trace_recorder/ctf/backend.cc b/repos/gems/src/app/trace_recorder/ctf/backend.cc
new file mode 100644
index 0000000000..ad1188d4fd
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/ctf/backend.cc
@@ -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
+
+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(), 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();
+}
diff --git a/repos/gems/src/app/trace_recorder/ctf/backend.h b/repos/gems/src/app/trace_recorder/ctf/backend.h
new file mode 100644
index 0000000000..77dd6596d1
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/ctf/backend.h
@@ -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
+#include
+#include
+#include
+
+#include
+#include
+
+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 _dst_file { };
+ Directory::Path _file_path { };
+
+ public:
+ Writer(Genode::Registry ®istry, 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 ®istry,
+ 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_ */
diff --git a/repos/gems/src/app/trace_recorder/ctf/metadata b/repos/gems/src/app/trace_recorder/ctf/metadata
new file mode 100644
index 0000000000..2e314f6ce2
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/ctf/metadata
@@ -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;
+ };
+};
diff --git a/repos/gems/src/app/trace_recorder/ctf/metadata.h b/repos/gems/src/app/trace_recorder/ctf/metadata.h
new file mode 100644
index 0000000000..e4978afa06
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/ctf/metadata.h
@@ -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
+#include
+#include
+
+namespace Ctf {
+ using namespace Genode;
+
+ class Metadata;
+}
+
+class Ctf::Metadata
+{
+ private:
+ Attached_rom_dataspace &_metadata_rom;
+ uint64_t _timestamp_freq;
+
+ template
+ bool _with_metadata_rom(PROLOGUE && prologue,
+ EPILOGUE && epilogue) const
+ {
+ using Token = Genode::Token;
+ char const * rom_start = _metadata_rom.local_addr();
+ 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_ */
diff --git a/repos/gems/include/ctf/packet_types.h b/repos/gems/src/app/trace_recorder/ctf/packet_header.h
similarity index 96%
rename from repos/gems/include/ctf/packet_types.h
rename to repos/gems/src/app/trace_recorder/ctf/packet_header.h
index 5e4d7a6ed8..1dfeaef220 100644
--- a/repos/gems/include/ctf/packet_types.h
+++ b/repos/gems/src/app/trace_recorder/ctf/packet_header.h
@@ -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
#include
@@ -130,4 +130,4 @@ struct Ctf::Packet_header
} __attribute__((packed));
-#endif /* _CTF__PACKET_TYPES_H_ */
+#endif /* _CTF__PACKET_HEADER_H_ */
diff --git a/repos/gems/src/app/trace_recorder/ctf/write_buffer.h b/repos/gems/src/app/trace_recorder/ctf/write_buffer.h
new file mode 100644
index 0000000000..5ee76f3122
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/ctf/write_buffer.h
@@ -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
+#include
+
+/* Genode includes */
+#include
+#include
+
+namespace Ctf {
+ template
+ class Write_buffer;
+}
+
+template
+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(_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_ */
diff --git a/repos/gems/src/app/trace_recorder/main.cc b/repos/gems/src/app/trace_recorder/main.cc
new file mode 100644
index 0000000000..7650f3e18b
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/main.cc
@@ -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
+
+/* Genode includes */
+#include
+#include
+#include
+
+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 _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); }
diff --git a/repos/gems/src/app/trace_recorder/monitor.cc b/repos/gems/src/app/trace_recorder/monitor.cc
new file mode 100644
index 0000000000..f10cc016d3
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/monitor.cc
@@ -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 Label_path;
+
+ Label_path label_path = path_from_label(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(), 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();
+}
diff --git a/repos/gems/src/app/trace_recorder/monitor.h b/repos/gems/src/app/trace_recorder/monitor.h
new file mode 100644
index 0000000000..64b5511bff
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/monitor.h
@@ -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
+#include
+#include
+#include
+#include
+#include
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+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::Element _element;
+ Subject_info _info;
+ Trace::Subject_id _subject_id;
+ Registry _writers { };
+
+ public:
+
+ Attached_buffer(Registry ®istry,
+ 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 &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 _trace_buffers { };
+ Policies _policies { };
+ Backends _backends { };
+ Constructible _trace_directory { };
+
+ Rtc::Connection _rtc { _env };
+ Timer::Connection _timer { _env };
+ Trace::Connection _trace { _env,
+ TRACE_SESSION_RAM,
+ TRACE_SESSION_ARG_BUFFER,
+ 0 };
+
+ Signal_handler _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_ */
diff --git a/repos/gems/src/app/trace_recorder/named_registry.h b/repos/gems/src/app/trace_recorder/named_registry.h
new file mode 100644
index 0000000000..51e29f98b1
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/named_registry.h
@@ -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
+#include
+#include
+
+namespace Trace_recorder {
+ using namespace Genode;
+
+ template class Named_registry;
+
+ template
+ static inline bool operator > (String const &s1, String const &s2)
+ {
+ return strcmp(s1.string(), s2.string()) > 0;
+ }
+}
+
+template
+class Trace_recorder::Named_registry : Noncopyable
+{
+ private:
+
+ Avl_tree _tree { };
+
+ public:
+
+ class Element : private Avl_node
+ {
+ public:
+
+ using Name = Genode::String<64>;
+ Name const name;
+
+ private:
+
+ Named_registry &_registry;
+
+ bool higher(T const *other) const { return name > other->name; }
+
+ friend class Avl_tree;
+ friend class Avl_node;
+ friend class Named_registry;
+
+ static T *_matching_sub_tree(T &curr, Name const &name)
+ {
+ typename Avl_node::Side side = (curr.name > name);
+
+ return curr.Avl_node::child(side);
+ }
+
+ public:
+
+ Element(Named_registry ®istry, Name const &name)
+ :
+ name(name), _registry(registry)
+ {
+ _registry._tree.insert(this);
+ }
+
+ ~Element()
+ {
+ _registry._tree.remove(this);
+ }
+ };
+
+ template
+ 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
+ void for_each(FN && fn) { _tree.for_each(fn); }
+};
+
+#endif /* _NAMED_REGISTRY_H_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/backend.cc b/repos/gems/src/app/trace_recorder/pcapng/backend.cc
new file mode 100644
index 0000000000..20f5da2ca2
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/backend.cc
@@ -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
+#include
+#include
+#include
+
+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 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();
+ _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();
+
+ /* 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(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(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 ®istry,
+ Directory &,
+ Directory::Path const &)
+{
+ return *new (alloc) Writer(registry, _interface_registry, _buffer, _ts_calibrator);
+}
diff --git a/repos/gems/src/app/trace_recorder/pcapng/backend.h b/repos/gems/src/app/trace_recorder/pcapng/backend.h
new file mode 100644
index 0000000000..957b6d1590
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/backend.h
@@ -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
+#include
+#include
+#include
+
+/* Genode includes */
+#include
+
+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 _dst_file { };
+ Directory::Path _file_path { };
+ bool _empty_section { false };
+
+ public:
+ Writer(Genode::Registry ®istry, 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 &,
+ Directory &,
+ Directory::Path const &) override;
+};
+
+
+#endif /* _PCAPNG__BACKEND_H_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/block.h b/repos/gems/src/app/trace_recorder/pcapng/block.h
new file mode 100644
index 0000000000..c621f864bb
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/block.h
@@ -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
+
+namespace Pcapng {
+ struct Block_base;
+
+ template
+ 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
+struct Pcapng::Block : Pcapng::Block_base
+{
+ Block()
+ : Block_base(TYPE_ID)
+ { }
+
+ static uint32_t type() { return TYPE_ID; };
+
+} __attribute__((packed));
+
+#endif /* _PCAPNG__BLOCK_H_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/enhanced_packet_block.h b/repos/gems/src/app/trace_recorder/pcapng/enhanced_packet_block.h
new file mode 100644
index 0000000000..f9ec1fdd25
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/enhanced_packet_block.h
@@ -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
+
+/* Genode includes */
+#include
+
+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_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/interface_description_block.h b/repos/gems/src/app/trace_recorder/pcapng/interface_description_block.h
new file mode 100644
index 0000000000..55ac4e7b3e
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/interface_description_block.h
@@ -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
+#include
+
+/* genode includes */
+#include
+
+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(&_data[0], name);
+ Option_end &opt_end = *construct_at (&_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_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/interface_registry.h b/repos/gems/src/app/trace_recorder/pcapng/interface_registry.h
new file mode 100644
index 0000000000..de1063cbf8
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/interface_registry.h
@@ -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
+#include
+
+namespace Pcapng {
+ using namespace Trace_recorder;
+ using namespace Genode;
+
+ class Interface;
+ class Interface_registry;
+}
+
+
+class Pcapng::Interface
+{
+ public:
+
+ using Name = String;
+
+ private:
+
+ Name const _name;
+ unsigned const _id;
+
+ Registry::Element _element;
+
+ public:
+
+ Interface(Name const &name, unsigned id, Registry ®istry)
+ : _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
+{
+ private:
+
+ unsigned _next_id { 0 };
+ Allocator &_alloc;
+
+ public:
+
+ Interface_registry(Allocator &alloc)
+ : _alloc(alloc)
+ { }
+
+ /* apply to existing Interface or create new one */
+ template
+ 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_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/option.h b/repos/gems/src/app/trace_recorder/pcapng/option.h
new file mode 100644
index 0000000000..38addc84ad
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/option.h
@@ -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
+
+namespace Pcapng {
+ using namespace Genode;
+
+ template
+ struct Option;
+
+ struct Option_ifname;
+ struct Option_end;
+}
+
+template
+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
+ 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(), name.string(), name.data_length()-1);
+ }
+} __attribute__((packed));
+
+#endif /* _PCAPNG__OPTION_H_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/section_header_block.h b/repos/gems/src/app/trace_recorder/pcapng/section_header_block.h
new file mode 100644
index 0000000000..4bec4917bc
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/section_header_block.h
@@ -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
+
+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_ */
diff --git a/repos/gems/src/app/trace_recorder/pcapng/write_buffer.h b/repos/gems/src/app/trace_recorder/pcapng/write_buffer.h
new file mode 100644
index 0000000000..e7c0f34bd8
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/pcapng/write_buffer.h
@@ -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
+#include
+
+namespace Pcapng
+{
+ using namespace Genode;
+
+ template
+ class Write_buffer;
+}
+
+template
+class Pcapng::Write_buffer
+{
+ public:
+
+ enum class Append_error { OUT_OF_MEM, OVERFLOW };
+ struct Append_ok { };
+ using Append_result = Attempt;
+
+ private:
+
+ size_t _total_length { 0 };
+ char _buffer[BUFSIZE] { };
+
+ public:
+
+ template
+ 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(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_ */
diff --git a/repos/gems/src/app/trace_recorder/policy.cc b/repos/gems/src/app/trace_recorder/policy.cc
new file mode 100644
index 0000000000..03bd389a42
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/policy.cc
@@ -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
+
+using namespace Genode;
+
+Trace_recorder::Policy::Policy(Env &env,
+ Trace::Connection &trace,
+ Policy::Name const &name,
+ Policies ®istry)
+:
+ 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);
+}
diff --git a/repos/gems/src/app/trace_recorder/policy.h b/repos/gems/src/app/trace_recorder/policy.h
new file mode 100644
index 0000000000..1c7737b870
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/policy.h
@@ -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
+
+/* Genode includes */
+#include
+#include
+#include
+
+namespace Trace_recorder {
+ class Policy;
+
+ using Policies = Named_registry;
+}
+
+
+/**
+ * Installs and maintains a tracing policy
+ */
+class Trace_recorder::Policy : Policies::Element
+{
+ private:
+ friend class Policies::Element;
+ friend class Genode::Avl_node;
+ friend class Genode::Avl_tree;
+
+ 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 ®istry);
+
+
+ /***************
+ ** Accessors **
+ ***************/
+
+ Genode::Trace::Policy_id id() const { return _id; }
+};
+
+#endif /* _POLICY_H_ */
diff --git a/repos/gems/src/app/trace_recorder/subject_info.h b/repos/gems/src/app/trace_recorder/subject_info.h
new file mode 100644
index 0000000000..c26dd35411
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/subject_info.h
@@ -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
+
+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_ */
diff --git a/repos/gems/src/app/trace_recorder/target.mk b/repos/gems/src/app/trace_recorder/target.mk
new file mode 100644
index 0000000000..4660175b93
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/target.mk
@@ -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
diff --git a/repos/gems/src/app/trace_recorder/timestamp_calibrator.h b/repos/gems/src/app/trace_recorder/timestamp_calibrator.h
new file mode 100644
index 0000000000..2675890fe6
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/timestamp_calibrator.h
@@ -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
+#include
+#include
+#include
+
+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_ */
diff --git a/repos/gems/src/app/trace_recorder/writer.h b/repos/gems/src/app/trace_recorder/writer.h
new file mode 100644
index 0000000000..5dd95074e0
--- /dev/null
+++ b/repos/gems/src/app/trace_recorder/writer.h
@@ -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
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+
+namespace Trace_recorder {
+ class Writer_base;
+
+ using Directory = Genode::Directory;
+ using Writer_registry = Genode::Registry;
+}
+
+
+class Trace_recorder::Writer_base
+{
+ protected:
+
+ Writer_registry::Element _element;
+
+ public:
+
+ Writer_base(Writer_registry ®istry)
+ : _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_ */