From 84fddafda767fde387b17b34aaece3ea5e2ee72c Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Thu, 16 Feb 2017 17:25:59 +0100 Subject: [PATCH] init: enable init to report its internal state This patch equips init with the ability to report its internal state in the form of a "state" report. This feature can be enabled by placing a '' node in init's configuration. The report node accepts the following arguments (with their default values): 'delay_ms="100"': specifies the number of milliseconds to wait before producing a new report. This way, many consecutive state changes - like they occur during the startup - do not result in an overly large number of reports but are merged into one final report. 'buffer="4K"': the maximum size of the report in bytes. The attribute accepts the use of K/M/G as units. 'init_ram="no"': if enabled, the report will contain a '' node with the memory stats of init. 'ids="no"': supplement the children in the report with unique IDs, which may be used to infer the lifetime of children accross configuration updates in the future; 'requested="no"': if enabled, the report will contain information about all session requests initiated by the children. 'provided="no"': if enabled, the report will contain information about all sessions provided by all servers. 'session_args="no"': level of detail of the session information generated via 'requested' or 'provided'. 'child_ram="no"': if enabled, the report will contain a '' node for each child based on the information obtained from the child's RAM session. Issue #2246 --- repos/os/include/init/child.h | 80 +++++++++++++++- repos/os/include/init/report.h | 68 ++++++++++++++ repos/os/src/init/main.cc | 166 +++++++++++++++++++++++++++++++-- 3 files changed, 307 insertions(+), 7 deletions(-) create mode 100644 repos/os/include/init/report.h diff --git a/repos/os/include/init/child.h b/repos/os/include/init/child.h index 17137368a0..1dc75e7b3d 100644 --- a/repos/os/include/init/child.h +++ b/repos/os/include/init/child.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace Init { @@ -234,6 +235,21 @@ namespace Init { service = &s; }); return service; } + + + inline void generate_ram_info(Xml_generator &xml, Ram_session const &ram) + { + /* + * The const cast is needed because the 'Ram_session' accessors are + * non-const methods. + */ + Ram_session &ram_nonconst = const_cast(ram); + + typedef String<32> Value; + xml.attribute("quota", Value(Number_of_bytes(ram_nonconst.quota()))); + xml.attribute("used", Value(Number_of_bytes(ram_nonconst.used()))); + xml.attribute("avail", Value(Number_of_bytes(ram_nonconst.avail()))); + } } @@ -312,6 +328,11 @@ class Init::Child : Child_policy, Child_service::Wakeup */ class Child_name_is_not_unique { }; + /** + * Unique ID of the child, solely used for diagnostic purposes + */ + struct Id { unsigned value; }; + private: friend class Child_registry; @@ -320,6 +341,10 @@ class Init::Child : Child_policy, Child_service::Wakeup Verbose const &_verbose; + Id const _id; + + Report_update_trigger &_report_update_trigger; + List_element _list_element; Xml_node _start_node; @@ -488,6 +513,8 @@ class Init::Child : Child_policy, Child_service::Wakeup */ Child(Env &env, Verbose const &verbose, + Id id, + Report_update_trigger &report_update_trigger, Xml_node start_node, Xml_node default_route_node, Name_registry &name_registry, @@ -496,7 +523,8 @@ class Init::Child : Child_policy, Child_service::Wakeup Registry &parent_services, Registry &child_services) : - _env(env), _verbose(verbose), + _env(env), _verbose(verbose), _id(id), + _report_update_trigger(report_update_trigger), _list_element(this), _start_node(start_node), _default_route_node(default_route_node), @@ -564,6 +592,51 @@ class Init::Child : Child_policy, Child_service::Wakeup */ bool has_name(Child_policy::Name const &str) const { return str == name(); } + void report_state(Xml_generator &xml, Report_detail const &detail) const + { + xml.node("child", [&] () { + + xml.attribute("name", _name.unique); + xml.attribute("binary", _name.file); + + if (detail.ids()) + xml.attribute("id", _id.value); + + if (detail.child_ram() && _child.ram_session_cap().valid()) { + xml.node("ram", [&] () { + /* + * The const cast is needed because there is no const + * accessor for the RAM session of the child. + */ + auto &nonconst_child = const_cast(_child); + generate_ram_info(xml, nonconst_child.ram()); + }); + } + + Session_state::Detail const + session_detail { detail.session_args() ? Session_state::Detail::ARGS + : Session_state::Detail::NO_ARGS}; + + if (detail.requested()) { + xml.node("requested", [&] () { + _child.for_each_session([&] (Session_state const &session) { + xml.node("session", [&] () { + session.generate_client_side_info(xml, session_detail); }); }); }); + } + + if (detail.provided()) { + xml.node("provided", [&] () { + + auto fn = [&] (Session_state const &session) { + xml.node("session", [&] () { + session.generate_server_side_info(xml, session_detail); }); }; + + server_id_space().for_each(fn); + }); + } + }); + } + /**************************** ** Child-policy interface ** @@ -792,6 +865,11 @@ class Init::Child : Child_policy, Child_service::Wakeup */ Child_policy::exit(exit_value); } + + void session_state_changed() override + { + _report_update_trigger.trigger_report_update(); + } }; #endif /* _INCLUDE__INIT__CHILD_H_ */ diff --git a/repos/os/include/init/report.h b/repos/os/include/init/report.h new file mode 100644 index 0000000000..b13bbd407e --- /dev/null +++ b/repos/os/include/init/report.h @@ -0,0 +1,68 @@ +/* + * \brief Report configuration + * \author Norman Feske + * \date 2017-01-16 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _INCLUDE__INIT__REPORT_H_ +#define _INCLUDE__INIT__REPORT_H_ + +#include +#include + +namespace Init { + struct Report_update_trigger; + struct Report_detail; +} + + +class Init::Report_detail : Genode::Noncopyable +{ + private: + + bool _children = false; + bool _ids = false; + bool _requested = false; + bool _provided = false; + bool _session_args = false; + bool _child_ram = false; + bool _init_ram = false; + + public: + + Report_detail() { } + + Report_detail(Genode::Xml_node report) + { + _children = true; + _ids = report.attribute_value("ids", false); + _requested = report.attribute_value("requested", false); + _provided = report.attribute_value("provided", false); + _session_args = report.attribute_value("session_args", false); + _child_ram = report.attribute_value("child_ram", false); + _init_ram = report.attribute_value("init_ram", false); + } + + bool children() const { return _children; } + bool ids() const { return _ids; } + bool requested() const { return _requested; } + bool provided() const { return _provided; } + bool session_args() const { return _session_args; } + bool child_ram() const { return _child_ram; } + bool init_ram() const { return _init_ram; } +}; + + +struct Init::Report_update_trigger +{ + virtual void trigger_report_update() = 0; +}; + +#endif /* _INCLUDE__INIT__REPORT_H_ */ diff --git a/repos/os/src/init/main.cc b/repos/os/src/init/main.cc index 6e54036385..985462415a 100644 --- a/repos/os/src/init/main.cc +++ b/repos/os/src/init/main.cc @@ -11,8 +11,13 @@ * under the terms of the GNU General Public License version 2. */ +/* Genode includes */ #include #include +#include +#include + +/* init includes */ #include namespace Init { @@ -208,6 +213,22 @@ class Init::Child_registry : public Name_registry, Child_list return _aliases.first() ? _aliases.first() : 0; } + void report_state(Xml_generator &xml, Report_detail const &detail) const + { + Genode::List_element const *curr = first(); + for (; curr; curr = curr->next()) + curr->object()->report_state(xml, detail); + + /* check for name clash with an existing alias */ + for (Alias const *a = _aliases.first(); a; a = a->next()) { + xml.node("alias", [&] () { + xml.attribute("name", a->name); + xml.attribute("child", a->child); + }); + } + + } + /***************************** ** Name-registry interface ** @@ -241,10 +262,129 @@ class Init::Child_registry : public Name_registry, Child_list }; -namespace Init { struct Main; } +namespace Init { + struct State_reporter; + struct Main; +} -struct Init::Main +class Init::State_reporter : public Report_update_trigger +{ + public: + + struct Producer + { + virtual void produce_state_report(Xml_generator &xml, + Report_detail const &) const = 0; + }; + + private: + + Env &_env; + + Producer &_producer; + + Constructible _reporter; + + size_t _buffer_size = 0; + + Reconstructible _report_detail; + + unsigned _report_delay_ms = 0; + + /* version string from config, to be reflected in the report */ + typedef String<64> Version; + Version _version; + + Constructible _timer; + + Signal_handler _timer_handler { + _env.ep(), *this, &State_reporter::_handle_timer }; + + bool _scheduled = false; + + void _handle_timer() + { + _scheduled = false; + + try { + Reporter::Xml_generator xml(*_reporter, [&] () { + + if (_version.valid()) + xml.attribute("version", _version); + + _producer.produce_state_report(xml, *_report_detail); + }); + } + catch(Xml_generator::Buffer_exceeded) { + + error("state report exceeds maximum size"); + + /* try to reflect the error condition as state report */ + try { + Reporter::Xml_generator xml(*_reporter, [&] () { + xml.attribute("error", "report buffer exceeded"); }); + } + catch (...) { } + } + } + + public: + + State_reporter(Env &env, Producer &producer) + : + _env(env), _producer(producer) + { } + + void apply_config(Xml_node config) + { + try { + Xml_node report = config.sub_node("report"); + + /* (re-)construct reporter whenever the buffer size is changed */ + Number_of_bytes const buffer_size = + report.attribute_value("buffer", Number_of_bytes(4096)); + + if (buffer_size != _buffer_size || !_reporter.constructed()) { + _buffer_size = buffer_size; + _reporter.construct(_env, "state", "state", _buffer_size); + } + + _report_detail.construct(report); + _report_delay_ms = report.attribute_value("delay_ms", 100UL); + _reporter->enabled(true); + } + catch (Xml_node::Nonexistent_sub_node) { + _report_detail.construct(); + _report_delay_ms = 0; + if (_reporter.constructed()) + _reporter->enabled(false); + } + + _version = config.attribute_value("version", Version()); + + if (_report_delay_ms) { + + if (!_timer.constructed()) { + _timer.construct(_env); + _timer->sigh(_timer_handler); + } + + trigger_report_update(); + } + } + + void trigger_report_update() override + { + if (!_scheduled && _timer.constructed() && _report_delay_ms) { + _timer->trigger_once(_report_delay_ms*1000); + _scheduled = true; + } + } +}; + + +struct Init::Main : State_reporter::Producer { Env &_env; @@ -258,8 +398,21 @@ struct Init::Main Reconstructible _verbose { _config.xml() }; + unsigned _child_cnt = 0; + void _handle_resource_avail() { } + void produce_state_report(Xml_generator &xml, Report_detail const &detail) const + { + if (detail.init_ram()) + xml.node("ram", [&] () { generate_ram_info(xml, _env.ram()); }); + + if (detail.children()) + _children.report_state(xml, detail); + } + + State_reporter _state_reporter { _env, *this }; + Signal_handler
_resource_avail_handler { _env.ep(), *this, &Main::_handle_resource_avail }; @@ -300,12 +453,10 @@ void Init::Main::_handle_config() _parent_services.for_each([&] (Init::Parent_service &service) { destroy(_heap, &service); }); - /* reload config */ - try { config()->reload(); } catch (...) { } - _config.update(); _verbose.construct(_config.xml()); + _state_reporter.apply_config(_config.xml()); try { determine_parent_services(_parent_services, _config.xml(), _heap, _verbose->enabled()); } @@ -335,7 +486,10 @@ void Init::Main::_handle_config() try { _children.insert(new (_heap) - Init::Child(_env, *_verbose, start_node, default_route_node, + Init::Child(_env, *_verbose, + Init::Child::Id { ++_child_cnt }, + _state_reporter, + start_node, default_route_node, _children, read_prio_levels(_config.xml()), read_affinity_space(_config.xml()), _parent_services, _child_services));