diff --git a/repos/os/recipes/pkg/cpu_balancer/README b/repos/os/recipes/pkg/cpu_balancer/README new file mode 100644 index 0000000000..b0425d6217 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer/README @@ -0,0 +1,3 @@ + + A dynamic CPU balancer component. + diff --git a/repos/os/recipes/pkg/cpu_balancer/archives b/repos/os/recipes/pkg/cpu_balancer/archives new file mode 100644 index 0000000000..9c175c9e85 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer/archives @@ -0,0 +1 @@ +_/src/cpu_balancer diff --git a/repos/os/recipes/pkg/cpu_balancer/hash b/repos/os/recipes/pkg/cpu_balancer/hash new file mode 100644 index 0000000000..4570379788 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer/hash @@ -0,0 +1 @@ +2020-11-11 5977b1e4fb0a81bd364c2f33ef587c16b6b26dc7 diff --git a/repos/os/recipes/pkg/cpu_balancer/runtime b/repos/os/recipes/pkg/cpu_balancer/runtime new file mode 100644 index 0000000000..2c63568dda --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer/runtime @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/repos/os/recipes/pkg/cpu_balancer_config/README b/repos/os/recipes/pkg/cpu_balancer_config/README new file mode 100644 index 0000000000..03dc289699 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer_config/README @@ -0,0 +1,3 @@ + + A dynamic CPU balancer component reading config and reporting state via file. + diff --git a/repos/os/recipes/pkg/cpu_balancer_config/archives b/repos/os/recipes/pkg/cpu_balancer_config/archives new file mode 100644 index 0000000000..03875e6e28 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer_config/archives @@ -0,0 +1,4 @@ +_/src/cpu_balancer +_/src/init +_/src/fs_report +_/src/fs_rom diff --git a/repos/os/recipes/pkg/cpu_balancer_config/hash b/repos/os/recipes/pkg/cpu_balancer_config/hash new file mode 100644 index 0000000000..145853b3b7 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer_config/hash @@ -0,0 +1 @@ +2020-10-02-b 2488f0453a59f0b3582cd4a041b4845ca3b74ca1 diff --git a/repos/os/recipes/pkg/cpu_balancer_config/runtime b/repos/os/recipes/pkg/cpu_balancer_config/runtime new file mode 100644 index 0000000000..be8cfaf3a9 --- /dev/null +++ b/repos/os/recipes/pkg/cpu_balancer_config/runtime @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/os/recipes/src/cpu_balancer/content.mk b/repos/os/recipes/src/cpu_balancer/content.mk new file mode 100644 index 0000000000..76143361a9 --- /dev/null +++ b/repos/os/recipes/src/cpu_balancer/content.mk @@ -0,0 +1,2 @@ +SRC_DIR = src/server/cpu_balancer +include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/repos/os/recipes/src/cpu_balancer/hash b/repos/os/recipes/src/cpu_balancer/hash new file mode 100644 index 0000000000..3bdb9960f4 --- /dev/null +++ b/repos/os/recipes/src/cpu_balancer/hash @@ -0,0 +1 @@ +2020-11-11 0cc7b984701f2f0ab0b20a59c8311ce147609834 diff --git a/repos/os/recipes/src/cpu_balancer/used_apis b/repos/os/recipes/src/cpu_balancer/used_apis new file mode 100644 index 0000000000..da77de6c7f --- /dev/null +++ b/repos/os/recipes/src/cpu_balancer/used_apis @@ -0,0 +1,4 @@ +base +os +timer_session +report_session diff --git a/repos/os/run/cpu_balancer.run b/repos/os/run/cpu_balancer.run new file mode 100644 index 0000000000..5208a5ead8 --- /dev/null +++ b/repos/os/run/cpu_balancer.run @@ -0,0 +1,117 @@ +build "core init timer server/cpu_balancer app/cpu_burner app/top" + +if {![have_include "power_on/qemu"]} { + puts "Run script is not supported on this platform" + exit 0 +} +if {[have_spec foc] && ([have_spec pbxa9] || [have_spec rpi3])} { + # foc kernel does detect solely 1 CPU */ + puts "Run script is not supported on this platform" + exit 0 +} +if {![have_spec nova] && ![have_spec foc] && ![have_spec sel4]} { + puts "Run script is not supported on this platform" + exit 0 +} + +set cpu_width 4 +set cpu_height 1 +set report_config "yes" +set use_trace "yes" + +create_boot_directory + +import_from_depot [depot_user]/src/report_rom + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + } + +append_if [expr $report_config eq "yes"] config { + + + + + + + + + } + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +build_boot_image { core ld.lib.so init timer cpu_balancer cpu_burner top } + +append qemu_args " -nographic" +append qemu_args " -smp [expr $cpu_width * $cpu_height],cores=$cpu_width,threads=$cpu_height" + +run_genode_until {.*thread xpos="1" ypos="0" name="signal handler" policy="max-utilize".*\n} 60 diff --git a/repos/os/src/server/cpu_balancer/component.cc b/repos/os/src/server/cpu_balancer/component.cc new file mode 100644 index 0000000000..a81e30e56d --- /dev/null +++ b/repos/os/src/server/cpu_balancer/component.cc @@ -0,0 +1,353 @@ +/* + * \brief CPU service proxy that migrates threads depending on policies + * \author Alexander Boettcher + * \date 2020-07-16 + */ + +/* + * Copyright (C) 2020 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. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "session.h" +#include "config.h" +#include "trace.h" + +namespace Cpu { + struct Balancer; + + using Genode::Affinity; + using Genode::Attached_rom_dataspace; + using Genode::Constructible; + using Genode::Cpu_session; + using Genode::Insufficient_ram_quota; + using Genode::Ram_quota; + using Genode::Rpc_object; + using Genode::Session_capability; + using Genode::Signal_handler; + using Genode::Sliced_heap; + using Genode::Typed_root; +} + +template +auto retry(T &env, FUNC func, HANDLER handler, + unsigned attempts = ~0U) -> decltype(func()) +{ + try { + for (unsigned i = 0; attempts == ~0U || i < attempts; i++) + try { return func(); } + catch (EXC) { + if ((i + 1) % 5 == 0 || env.pd().avail_ram().value < 8192) + Genode::warning(i, ". attempt to extend dialog report " + "size, ram_avail=", env.pd().avail_ram()); + + if (env.pd().avail_ram().value < 8192) + throw; + + handler(); + } + + throw EXC(); + } catch (Genode::Xml_generator::Buffer_exceeded) { + Genode::error("not enough memory for xml"); + } + return; +} + +typedef Genode::Registry > Sleeper_list; +typedef Genode::Tslab, 4096> Tslab_sleeper; + +struct Cpu::Balancer : Rpc_object> +{ + Genode::Env &env; + Attached_rom_dataspace config { env, "config" }; + Timer::Connection timer { env }; + Sliced_heap slice { env.ram(), env.rm() }; + Child_list list { }; + Constructible trace { }; + Constructible reporter { }; + uint64_t timer_us { 1000 * 1000UL }; + Session_label label { }; + unsigned report_size { 4096 * 1 }; + Tslab_sleeper alloc_thread { slice }; + Sleeper_list sleeper { }; + bool verbose { false }; + bool update_report { false }; + bool use_sleeper { true }; + + Signal_handler signal_config { + env.ep(), *this, &Balancer::handle_config }; + + void handle_config(); + void handle_timeout(); + + /* + * Need extra EP to avoid dead-lock/live-lock (depending on kernel) + * due to down-calls by this component, e.g. parent.upgrade(...), and + * up-calls by parent using this CPU service, e.g. to create initial thread + * + * Additionally, a list_mutex is required due to having 2 EPs now. + */ + Entrypoint ep { env, 4 * 4096, "live/dead-lock", Affinity::Location() }; + + Signal_handler signal_timeout { + ep, *this, &Balancer::handle_timeout }; + + Genode::Mutex list_mutex { }; + + template + Session_capability _withdraw_quota(Root::Session_args const &args, FUNC const &fn) + { + /* + * We need to decrease 'ram_quota' by + * the size of the session object. + */ + Ram_quota const ram_quota = ram_quota_from_args(args.string()); + + size_t needed = sizeof(Session) + slice.overhead(sizeof(Session)); + + if (needed > ram_quota.value) + throw Insufficient_ram_quota(); + + Ram_quota const remaining_ram_quota { ram_quota.value - needed }; + + /* + * Validate that the client provided the amount of caps as mandated + * for the session interface. + */ + Cap_quota const cap_quota = cap_quota_from_args(args.string()); + + if (cap_quota.value < Session::CAP_QUOTA) + throw Insufficient_cap_quota(); + + /* + * Account for the dataspace capability needed for allocating the + * session object from the sliced heap. + */ + if (cap_quota.value < 1) + throw Insufficient_cap_quota(); + + Cap_quota const remaining_cap_quota { cap_quota.value - 1 }; + + /* + * Deduce ram quota needed for allocating the session object from the + * donated ram quota. + */ + char argbuf[Parent::Session_args::MAX_SIZE]; + copy_cstring(argbuf, args.string(), sizeof(argbuf)); + + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", remaining_ram_quota.value); + Arg_string::set_arg(argbuf, sizeof(argbuf), "cap_quota", remaining_cap_quota.value); + + return fn(argbuf); + } + + /*********************** + ** Session interface ** + ***********************/ + + Session_capability session(Root::Session_args const &args, + Affinity const &affinity) override + { + return _withdraw_quota(args, [&] (char const * const session_args) { + if (verbose) + log("new session '", args.string(), "' -> '", session_args, "' ", + affinity.space().width(), "x", affinity.space().height(), " ", + affinity.location().xpos(), "x", affinity.location().ypos(), + " ", affinity.location().width(), "x", affinity.location().height()); + + Mutex::Guard guard(list_mutex); + + auto * session = new (slice) Registered(list, env, + affinity, + session_args, + list, verbose); + + /* check for config of new session */ + Cpu::Config::apply(config.xml(), list); + + return session->cap(); + }); + } + + void upgrade(Session_capability const cap, Root::Upgrade_args const &args) override + { + if (!args.valid_string()) return; + + auto lambda = [&] (Rpc_object_base *rpc_obj) { + if (!rpc_obj) + return; + + Session *session = dynamic_cast(rpc_obj); + if (!session) + return; + + session->upgrade(args, [&](auto id, auto const &adjusted_args) { + env.upgrade(id, adjusted_args); + }); + }; + + Mutex::Guard guard(list_mutex); + + env.ep().rpc_ep().apply(cap, lambda); + } + + void close(Session_capability const cap) override + { + if (!cap.valid()) return; + + Mutex::Guard guard(list_mutex); + + Session *object = nullptr; + + env.ep().rpc_ep().apply(cap, + [&] (Session *source) { + object = source; + }); + + if (!object) + return; + + destroy(slice, object); + + update_report = true; + } + + /***************** + ** Constructor ** + *****************/ + + Balancer(Genode::Env &env) : env(env) + { + config.sigh(signal_config); + timer.sigh(signal_timeout); + + Affinity::Space const space = env.cpu().affinity_space(); + Genode::log("affinity space=", + space.width(), "x", space.height()); + + for (unsigned i = 0; i < space.total(); i++) { + Affinity::Location location = env.cpu().affinity_space().location_of_index(i); + Sleeper *t = new (alloc_thread) Genode::Registered(sleeper, env, location); + t->start(); + } + + handle_config(); + + /* first time start ever timeout */ + timer.trigger_periodic(timer_us); + + env.parent().announce(env.ep().manage(*this)); + } +}; + +void Cpu::Balancer::handle_config() +{ + config.update(); + + bool use_trace = true; + bool use_report = true; + uint64_t time_us = timer_us; + + if (config.valid()) { + use_trace = config.xml().attribute_value("trace", use_trace); + use_report = config.xml().attribute_value("report", use_report); + time_us = config.xml().attribute_value("interval_us", timer_us); + verbose = config.xml().attribute_value("verbose", verbose); + use_sleeper = config.xml().attribute_value("sleeper", use_sleeper); + + /* read in components configuration */ + Cpu::Config::apply(config.xml(), list); + } + + if (verbose) + log("config update - verbose=", verbose, ", trace=", use_trace, + ", report=", use_report, ", interval=", timer_us,"us"); + + /* also start all subsystem if no valid config is available */ + trace.conditional(use_trace, env); + if (use_trace && !label.valid()) + label = trace->lookup_my_label(); + + reporter.conditional(use_report, env, "components", "components", report_size); + if (use_report) + reporter->enabled(true); + + if (timer_us != time_us) + timer.trigger_periodic(time_us); +} + +void Cpu::Balancer::handle_timeout() +{ + Mutex::Guard guard(list_mutex); + + if (use_sleeper) { + /* wake all sleepers to get more accurate idle CPU utilization times */ + sleeper.for_each([](auto &thread) { + thread._block.wakeup(); }); + } + + /* remember current reread state */ + unsigned reread_subjects = 0; + if (trace.constructed()) { + reread_subjects = trace->subject_id_reread(); + trace->read_idle_times(); + } + + /* update all sessions */ + list.for_each([&](auto &session) { + if (trace.constructed()) { + session.update_threads(*trace, label); + } + else + session.update_threads(); + + if (session.report_update()) + update_report = true; + }); + + /* reset reread state if it did not change in between */ + if (trace.constructed() && trace->subject_id_reread() && + reread_subjects == trace->subject_id_reread()) + trace->subject_id_reread_reset(); + + if (reporter.constructed() && update_report) { + bool reset_report = false; + + retry(env, [&] () { + Reporter::Xml_generator xml(*reporter, [&] () { + list.for_each([&](auto &session) { + reset_report |= session.report_state(xml); + }); + }); + }, [&] () { + report_size += 4096; + reporter.construct(env, "components", "components", report_size); + reporter->enabled(true); + }); + + if (reset_report) { + list.for_each([](auto &session) { + session.reset_report_state(); + }); + } + + update_report = false; + } +} + +void Component::construct(Genode::Env &env) +{ + static Cpu::Balancer server(env); +} diff --git a/repos/os/src/server/cpu_balancer/config.cc b/repos/os/src/server/cpu_balancer/config.cc new file mode 100644 index 0000000000..01e5ab7088 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/config.cc @@ -0,0 +1,61 @@ +/* + * \brief Config evaluation + * \author Alexander Boettcher + * \date 2020-07-20 + */ + +/* + * Copyright (C) 2020 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. + */ + +#include +#include + +#include "config.h" + +void Cpu::Config::apply(Xml_node const &start, Child_list &sessions) +{ + using Genode::Session_label; + using Genode::String; + + typedef String Label; + + start.for_each_sub_node("component", [&](Xml_node const &node) { + if (!node.has_attribute("label")) + return; + + Label const label = node.attribute_value("label", Label("")); + + sessions.for_each([&](auto &session) { + if (!session.match(label)) + return; + + if (node.has_attribute("default_policy")) { + Cpu::Policy::Name const policy = node.attribute_value("default_policy", Cpu::Policy::Name()); + session.default_policy(policy); + } + + node.for_each_sub_node("thread", [&](Xml_node const &thread) { + if (!thread.has_attribute("name") || !thread.has_attribute("policy")) + return; + + Thread::Name const name = thread.attribute_value("name", Thread::Name()); + Cpu::Policy::Name const policy = thread.attribute_value("policy", Cpu::Policy::Name()); + + /* explicitly create invalid width/height */ + /* used during thread construction in policy static case */ + Affinity::Location location { 0, 0, 0, 0}; + + if (thread.has_attribute("xpos") && thread.has_attribute("ypos")) + location = Affinity::Location(thread.attribute_value("xpos", 0U), + thread.attribute_value("ypos", 0U), + 1, 1); + + session.config(name, policy, location); + }); + }); + }); +} diff --git a/repos/os/src/server/cpu_balancer/config.h b/repos/os/src/server/cpu_balancer/config.h new file mode 100644 index 0000000000..0cdce6a025 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/config.h @@ -0,0 +1,34 @@ +/* + * \brief Config evaluation + * \author Alexander Boettcher + * \date 2020-07-20 + */ + +/* + * Copyright (C) 2020 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 _CONFIG_H_ +#define _CONFIG_H_ + +#include + +#include "session.h" + +namespace Cpu { + class Config; + + using Genode::Xml_node; +} + +class Cpu::Config { + + public: + + static void apply(Xml_node const &, Child_list &); +}; + +#endif /* _CONFIG_H_ */ diff --git a/repos/os/src/server/cpu_balancer/config.xsd b/repos/os/src/server/cpu_balancer/config.xsd new file mode 100644 index 0000000000..65e2f02154 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/config.xsd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/os/src/server/cpu_balancer/policy.h b/repos/os/src/server/cpu_balancer/policy.h new file mode 100644 index 0000000000..dc9da9c081 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/policy.h @@ -0,0 +1,315 @@ +/* + * \brief Policy evaluation + * \author Alexander Boettcher + * \date 2020-10-08 + */ + +/* + * Copyright (C) 2020 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_ + +#include +#include + +#include "trace.h" + +namespace Cpu +{ + typedef Genode::Affinity::Location Location; + typedef Genode::Trace::Execution_time Execution_time; + typedef Genode::Cpu_session::Name Name; + + class Policy; + class Policy_none; + class Policy_pin; + class Policy_round_robin; + class Policy_max_utilize; +}; + +class Cpu::Policy { + + protected: + + bool _update(Location const &base, Location ¤t) + { + Location now = Location(base.xpos() + location.xpos(), + base.ypos() + location.ypos()); + + if ((now.xpos() == current.xpos()) && (now.ypos() == current.ypos())) + return false; + + if (current.xpos() < base.xpos() || current.ypos() < base.ypos()) { + Genode::error("affinity location strange, current below base"); + return false; + } + + unsigned const xpos = current.xpos() - base.xpos(); + unsigned const ypos = current.ypos() - base.ypos(); + + if (xpos >= base.width() || ypos >= base.height()) { + Genode::error("affinity dimension raised"); + return false; + } + + location = Location(xpos, ypos); + + return true; + } + + public: + + typedef Genode::String<16> Name; + + Location location { }; + + virtual ~Policy() { } + virtual void config(Location const &) = 0; + virtual bool update(Location const &, Location &, Execution_time const &) = 0; + virtual void thread_create(Location const &) = 0; + virtual bool migrate(Location const &, Location &, Trace *) = 0; + + virtual void print(Genode::Output &output) const = 0; + + virtual bool same_type(Name const &) const = 0; + virtual char const * string() const = 0; +}; + +class Cpu::Policy_none : public Cpu::Policy +{ + public: + + void config(Location const &) override { }; + void thread_create(Location const &loc) override { location = loc; } + bool migrate(Location const &, Location &, Trace *) override { + return false; } + + bool update(Location const &, Location &, Execution_time const &) override { + return false; } + + void print(Genode::Output &output) const override { + Genode::print(output, "none"); } + + bool same_type(Name const &name) const override { + return name == "none"; } + + char const * string() const override { + return "none"; } +}; + +class Cpu::Policy_pin : public Cpu::Policy +{ + public: + + void config(Location const &rel) override { + location = rel; }; + + void thread_create(Location const &loc) override { + /* for static case with valid location, don't overwrite config */ + if (location.width() * location.height() == 0) + location = loc; + } + + bool migrate(Location const &base, Location ¤t, Trace *) override + { + Location to = Location(base.xpos() + location.xpos(), + base.ypos() + location.ypos()); + + if ((to.xpos() == current.xpos()) && (to.ypos() == current.ypos())) + return false; + + current = to; + return true; + } + + bool update(Location const &, Location &, Execution_time const &) override { + return false; } + + void print(Genode::Output &output) const override { + Genode::print(output, "pin"); } + + bool same_type(Name const &name) const override { + return name == "pin"; } + + char const * string() const override { + return "pin"; } +}; + +class Cpu::Policy_round_robin : public Cpu::Policy +{ + public: + + void config(Location const &) override { }; + void thread_create(Location const &loc) override { location = loc; } + + bool migrate(Location const &base, Location &out, Trace *) override + { + int const xpos = (location.xpos() + 1) % base.width(); + int const step = (xpos <= location.xpos()) ? 1 : 0; + int const ypos = (location.ypos() + step) % base.height(); + + Location rel { xpos, ypos, 1, 1 }; + + out = Location { int(base.xpos() + rel.xpos()), + base.ypos() + rel.ypos(), 1, 1 }; + return true; + } + + bool update(Location const &base, Location ¤t, Execution_time const &) override { + return _update(base, current); } + + void print(Genode::Output &output) const override { + Genode::print(output, "round-robin"); } + + bool same_type(Name const &name) const override { + return name == "round-robin"; } + + char const * string() const override { + return "round-robin"; } +}; + +class Cpu::Policy_max_utilize : public Cpu::Policy +{ + private: + + Execution_time _last { }; + Execution_time _time { }; + + bool _last_valid { false }; + bool _time_valid { false }; + + Execution_time _last_utilization() const + { + using Genode::uint64_t; + + uint64_t ec = (_last.thread_context < _time.thread_context) ? + _time.thread_context - _last.thread_context : 0; + uint64_t sc = (_last.scheduling_context < _time.scheduling_context) ? + _time.scheduling_context - _last.scheduling_context : 0; + return Execution_time(ec, sc); + } + + public: + + void config(Location const &) override { }; + void thread_create(Location const &loc) override { location = loc; } + + bool update(Location const &base, Location ¤t, Execution_time const &time) override { + _last = _time; + _last_valid = _time_valid; + + _time = time; + _time_valid = true; + + return _update(base, current); } + + template + bool _migrate(T const current_idle, T const thread_time, + T const remote_idle, Location const ¤t, + Location const &to, T const max_idle) + { + /* + * T - thread + * It - idle time on CPU T is on + * Ir - idle time on remote CPU with was most idle + * + * T % | It % | Ir % | desired behaviour + * ------------------------|----------------------------------------------------------------- + * A: x | ~0 | z | no migration - on current CPU is idle time + * B: x | y | z | migrate if z > x -- check whether remote CPU could handle this extra utilization + * C: + * + */ + bool case_a = current_idle > 1000; /* XXX which threshold ? */ + bool case_b = thread_time > remote_idle; + + if (false) + Genode::log("at ", current.xpos(), "x", current.ypos(), + " idle=", current_idle, + " last=", thread_time, + ", at ", to.xpos(), "x", to.ypos(), + " most_idle=", remote_idle, + " (max_idle=", max_idle, ")", + " case_b=", case_b); + + if (case_a) + return false; + if (case_b) + return false; + + return true; + } + + bool migrate(Location const &base, Location ¤t, Trace * trace) override + { + if (!trace || !_last_valid || !_time_valid) + return false; + + Execution_time most_idle { 0UL, 0UL }; + Execution_time current_idle { 0UL, 0UL }; + Location to { current }; /* in case of no idle info */ + + for (unsigned x = base.xpos(); x < base.xpos() + base.width(); x++) { + for (unsigned y = base.ypos(); y < base.ypos() + base.height(); y++) { + + Location const loc(x, y); + Execution_time const idle = trace->diff_idle_times(loc); + + if (idle.scheduling_context) { + if (idle.scheduling_context > most_idle.scheduling_context) { + most_idle = idle; + to = loc; + } + } else { + if (idle.thread_context > most_idle.thread_context) { + most_idle = idle; + to = loc; + } + } + + if ((loc.xpos() == current.xpos()) && (loc.ypos() == current.ypos())) + current_idle = idle; + } + } + + if ((to.xpos() == current.xpos()) && (to.ypos() == current.ypos())) + return false; + + Execution_time const last_util = _last_utilization(); + + /* heuristics to not migrate and better stay on same CPU */ + if (last_util.scheduling_context && !last_util.thread_context) { + if (!_migrate(current_idle.scheduling_context, last_util.scheduling_context, + most_idle.scheduling_context, current, to, + trace->read_max_idle(current).scheduling_context)) + return false; + } else { + if (!_migrate(current_idle.thread_context, last_util.thread_context, + most_idle.thread_context, current, to, + trace->read_max_idle(current).thread_context)) + return false; + } + + current = to; + _last_valid = false; + _time_valid = false; + + return true; + } + + void print(Genode::Output &output) const override { + Genode::print(output, "max-utilize"); } + + bool same_type(Name const &name) const override { + return name == "max-utilize"; } + + char const * string() const override { + return "max-utilize"; } +}; + +#endif diff --git a/repos/os/src/server/cpu_balancer/schedule.cc b/repos/os/src/server/cpu_balancer/schedule.cc new file mode 100644 index 0000000000..e84ce6a6c5 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/schedule.cc @@ -0,0 +1,107 @@ +/* + * \brief Taking migrate decision depending on policy + * \author Alexander Boettcher + * \date 2020-07-16 + */ + +/* + * Copyright (C) 2020 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. + */ + +#include + +#include "session.h" +#include "trace.h" + +void Cpu::Session::update_threads(Trace &trace, Session_label const &cpu_balancer) +{ + apply([&](Thread_capability const &cap, + Name const &name, + Subject_id &subject_id, + Cpu::Policy &policy, bool) + { + if (!subject_id.id || trace.subject_id_reread()) { + Session_label const label(cpu_balancer, " -> ", _label); + subject_id = trace.lookup_missing_id(label, name); + } + + if (!subject_id.id) { + Genode::error("subject id ", name, " missing - still !!!!"); + return false; + } + + Affinity::Location const &base = _affinity.location(); + Affinity::Location current { base.xpos() + policy.location.xpos(), + base.ypos() + policy.location.ypos(), 1, 1 }; + Execution_time time { }; + + /* request execution time and current location */ + try { + trace.retrieve(subject_id.id, [&] (Execution_time const time_current, + Affinity::Location const current_loc) + { + current = current_loc; + time = time_current; + + if (_verbose) + log("[", _label, "] name='", name, "' at ", + current_loc.xpos(), "x", current_loc.ypos(), + " has ec/sc time ", time.thread_context, "/", + time.scheduling_context); + }); + } catch (Genode::Trace::Nonexistent_subject) { + /* how could that be ? */ + error("[", _label, "] name='", name, "'" + " subject id invalid ?? ", subject_id.id); + + subject_id = Subject_id(); + } + + /* update current location of thread if changed */ + if (policy.update(base, current, time)) + _report = true; + + Affinity::Location migrate_to = current; + if (policy.migrate(_affinity.location(), migrate_to, &trace)) { + if (_verbose) + log("[", _label, "] name='", name, "' request to", + " migrate from ", current.xpos(), "x", current.ypos(), + " to most idle CPU at ", + migrate_to.xpos(), "x", migrate_to.ypos()); + + Cpu_thread_client thread(cap); + thread.affinity(migrate_to); + } + + return false; + }); +} + +void Cpu::Session::update_threads() +{ + apply([&](Thread_capability const &cap, + Name const &, + Subject_id &, + Cpu::Policy &policy, bool) + { + Affinity::Location const &base = _affinity.location(); + Location current = Location(base.xpos() + policy.location.xpos(), + base.ypos() + policy.location.xpos(), + 1, 1); + + if (!cap.valid()) + return false; + + Affinity::Location migrate_to = current; + if (!policy.migrate(_affinity.location(), migrate_to, nullptr)) + return false; + + Cpu_thread_client thread(cap); + thread.affinity(migrate_to); + + return false; + }); +} diff --git a/repos/os/src/server/cpu_balancer/session.cc b/repos/os/src/server/cpu_balancer/session.cc new file mode 100644 index 0000000000..509102b766 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/session.cc @@ -0,0 +1,229 @@ +/* + * \brief CPU session implementation + * \author Alexander Boettcher + * \date 2020-07-16 + */ + +/* + * Copyright (C) 2020 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. + */ + +#include "session.h" + +using namespace Genode; +using namespace Cpu; + +Thread_capability +Cpu::Session::create_thread(Pd_session_capability const pd, + Name const &name_by_client, + Affinity::Location const location, + Weight const weight, + addr_t const utcb) +{ + Thread_capability cap { }; + + Name name = name_by_client; + if (!name.valid()) + name = Name("nobody"); + + /* quirk since init can't handle Out_of_* during first create_thread call */ + if ((_reclaim_ram.value || _reclaim_cap.value) && _one_valid_thread()) { + if (_reclaim_ram.value) + throw Out_of_ram(); + if (_reclaim_cap.value) + throw Out_of_caps(); + } + + lookup(name, [&](Thread_capability &store_cap, + Cpu::Policy &policy) + { + if (store_cap.valid()) + return false; + + cap = _parent.create_thread(pd, name, location, weight, utcb); + if (!cap.valid()) + /* stop creation attempt by saying done */ + return true; + + /* policy and name are set beforehand */ + store_cap = cap; + + /* for static case with valid location, don't overwrite config */ + policy.thread_create(location); + + if (_verbose) + log("[", _label, "] new thread at ", + policy.location.xpos(), "x", policy.location.ypos(), + ", policy=", policy, ", name='", name, "'"); + + return true; + }); + + if (cap.valid()) { + _report = true; + return cap; + } + + cap = _parent.create_thread(pd, name, location, weight, utcb); + if (!cap.valid()) + return cap; + + /* unknown thread without any configuration */ + construct(_default_policy, [&](Thread_capability &store_cap, + Name &store_name, Cpu::Policy &policy) + { + policy.location = location; + + store_cap = cap; + store_name = name; + + if (_verbose) + log("[", _label, "] new thread at ", + location.xpos(), "x", location.ypos(), + ", no policy defined", + ", name='", name, "'"); + }); + + if (cap.valid()) + _report = true; + + return cap; +} + + +void Cpu::Session::kill_thread(Thread_capability const thread_cap) +{ + if (!thread_cap.valid()) + return; + + kill(thread_cap, [&](Thread_capability &cap, Thread::Name &name, + Subject_id &, Cpu::Policy &) + { + cap = Thread_capability(); + name = Thread::Name(); + + _parent.kill_thread(thread_cap); + + _report = true; + }); +} + +void Cpu::Session::exception_sigh(Signal_context_capability const h) +{ + _parent.exception_sigh(h); +} + +Affinity::Space Cpu::Session::affinity_space() const +{ + return _parent.affinity_space(); +} + +Dataspace_capability Cpu::Session::trace_control() +{ + return _parent.trace_control(); +} + +Cpu::Session::Session(Env &env, + Affinity const &affinity, + char const * args, + Child_list &list, bool const verbose) +: + _list(list), + _env(env), + _ram_guard(ram_quota_from_args(args)), + _cap_guard(cap_quota_from_args(args)), + _parent(_env.session(_id.id(), _withdraw_quota(args), affinity)), + _label(session_label_from_args(args)), + _affinity(affinity.space().total() ? affinity : Affinity(Affinity::Space(1,1), Affinity::Location(0,0,1,1))), + _verbose(verbose) +{ + try { + _env.ep().rpc_ep().manage(this); + } catch (...) { + env.close(_id.id()); + throw; + } +} + +Cpu::Session::~Session() +{ + /* _threads don't need to be cleaned up, but cause warnings */ + + _env.ep().rpc_ep().dissolve(this); + + _env.close(_id.id()); +} + +int Cpu::Session::ref_account(Cpu_session_capability const cap) +{ + return _parent.ref_account(cap); +} + +int Cpu::Session::transfer_quota(Cpu_session_capability const cap, + size_t const size) +{ + return _parent.transfer_quota(cap, size); +} + +Cpu_session::Quota Cpu::Session::quota() +{ + return _parent.quota(); +} + +Capability Cpu::Session::native_cpu() +{ + return _parent.native_cpu(); +} + +bool Cpu::Session::report_state(Xml_generator &xml) const +{ + xml.node("component", [&] () { + xml.attribute("xpos", _affinity.location().xpos()); + xml.attribute("ypos", _affinity.location().ypos()); + xml.attribute("width", _affinity.location().width()); + xml.attribute("height", _affinity.location().height()); + xml.attribute("label", _label); + + xml.attribute("default_policy", _default_policy); + + apply([&](Thread_capability const &, + Thread::Name const &name, + Subject_id const &, Cpu::Policy const &policy, + bool const enforced_policy) + { + xml.node("thread", [&] () { + xml.attribute("xpos", policy.location.xpos()); + xml.attribute("ypos", policy.location.ypos()); + xml.attribute("name", name); + xml.attribute("policy", policy.string()); + if (enforced_policy) + xml.attribute("enforced", enforced_policy); + }); + return false; + }); + }); + + return _report; +} + +void Cpu::Session::config(Thread::Name const &thread, + Cpu::Policy::Name const &policy_name, + Affinity::Location const &relativ) +{ + reconstruct(policy_name, thread, [&](Thread_capability const &, + Cpu::Policy &policy) + { + policy.config(relativ); + + if (_verbose) { + String<12> const loc { policy.location.xpos(), "x", policy.location.ypos() }; + log("[", _label, "] name='", thread, "' " + "update policy to '", policy, "' ", loc); + } + }); + + _report = true; +} diff --git a/repos/os/src/server/cpu_balancer/session.h b/repos/os/src/server/cpu_balancer/session.h new file mode 100644 index 0000000000..48c1c3dd33 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/session.h @@ -0,0 +1,421 @@ +/* + * \brief CPU session definition + * \author Alexander Boettcher + * \date 2020-07-16 + */ + +/* + * Copyright (C) 2020 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 _SESSION_H_ +#define _SESSION_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include + +#include "policy.h" + +namespace Cpu { + using namespace Genode; + + using Genode::Trace::Subject_id; + + class Session; + class Trace; + class Policy; + struct Thread_client; + typedef Id_space::Element Client_id; + typedef Registry > Child_list; + typedef Registry > Thread_list; + typedef Constrained_ram_allocator Ram_allocator; +} + +struct Cpu::Thread_client : Interface +{ + Thread_capability _cap { }; + Genode::Thread::Name _name { }; + Subject_id _id { }; + Cpu::Policy * _policy { nullptr }; + bool _fix { false }; +}; + +class Cpu::Session : public Rpc_object +{ + private: + + Child_list &_list; + Env &_env; + + Ram_quota_guard _ram_guard; + Cap_quota_guard _cap_guard; + Ram_allocator _ram { _env.pd(), _ram_guard, _cap_guard }; + Heap _md_alloc { _ram, _env.rm() }; + + Ram_quota _reclaim_ram { 0 }; + Cap_quota _reclaim_cap { 0 }; + + Parent::Client _parent_client { }; + Client_id const _id { _parent_client, + _env.id_space() }; + Cpu_session_client _parent; + Cpu::Policy::Name _default_policy { "none" }; + + Session::Label const _label; + Affinity const _affinity; + + Thread_list _threads { }; + + bool _report { true }; + bool _verbose; + bool _by_us { false }; + + void construct_policy(Thread::Name const &name, Cpu::Policy **policy, + Affinity::Location const loc) + { + try { + if (name == "pin") + *policy = new (_md_alloc) Policy_pin(); + else if (name == "round-robin") + *policy = new (_md_alloc) Policy_round_robin(); + else if (name == "max-utilize") + *policy = new (_md_alloc) Policy_max_utilize(); + else + *policy = new (_md_alloc) Policy_none(); + + (*policy)->location = loc; + } catch (Out_of_ram) { _by_us = true; throw; + } catch (Out_of_caps) { _by_us = true; throw; + } catch (Insufficient_ram_quota) { _by_us = true; throw; + } catch (Insufficient_cap_quota) { _by_us = true; throw; } + } + + bool _one_valid_thread() const + { + bool valid = false; + + _threads.for_each([&](auto &thread) { + if (valid) + return; + + if (thread._cap.valid()) + valid = true; + }); + + return valid; + } + + template + void _for_each_thread(FUNC const &fn) + { + bool done = false; + + _threads.for_each([&](auto &thread) { + if (done) + return; + + done = fn(thread); + }); + } + + template + void _for_each_thread(FUNC const &fn) const + { + bool done = false; + + _threads.for_each([&](auto const &thread) { + if (done) + return; + + done = fn(thread); + }); + } + + template + void kill(Thread_capability const &cap, FUNC const &fn) + { + _for_each_thread([&](Thread_client &thread) { + if (!(thread._cap.valid()) || !(thread._cap == cap)) + return false; + + fn(thread._cap, thread._name, thread._id, *thread._policy); + + destroy(_md_alloc, thread._policy); + destroy(_md_alloc, &thread); + + return true; + }); + } + + template + void apply(FUNC const &fn) + { + _for_each_thread([&](Thread_client &thread) { + if (!thread._cap.valid()) + return false; + + return fn(thread._cap, thread._name, thread._id, + *thread._policy, thread._fix); + }); + } + + template + void apply(FUNC const &fn) const + { + _for_each_thread([&](Thread_client const &thread) { + if (!thread._cap.valid()) + return false; + + return fn(thread._cap, thread._name, + thread._id, *thread._policy, thread._fix); + }); + } + + template + void lookup(Thread::Name const &name, FUNC const &fn) + { + if (!name.valid()) + return; + + _for_each_thread([&](Thread_client &thread) { + if (thread._name != name) + return false; + + return fn(thread._cap, *thread._policy); + }); + } + + template + void reconstruct(Cpu::Policy::Name const &policy_name, + Thread::Name const &thread_name, + FUNC const &fn) + { + if (!thread_name.valid()) + return; + + bool done = false; + + _for_each_thread([&](Thread_client &thread) { + if (thread._name != thread_name) + return false; + + if (thread._fix) { + done = true; + return true; + } + + if (!thread._policy->same_type(policy_name)) { + Cpu::Policy * new_policy = nullptr; + construct_policy(policy_name, &new_policy, thread._policy->location); + + /* construct policy may throw, so we keep old policy up to here */ + destroy(_md_alloc, thread._policy); + thread._policy = new_policy; + } + + fn(thread._cap, *thread._policy); + done = true; + return true; + }); + + if (done) + return; + + construct(policy_name, [&](Thread_capability const &cap, + Thread::Name &store_name, + Cpu::Policy &policy) { + store_name = thread_name; + fn(cap, policy); + }); + } + + template + void construct(Cpu::Policy::Name const &policy_name, FUNC const &fn) + { + Thread_client * thread = nullptr; + + try { + try { + thread = new (_md_alloc) Registered(_threads); + + construct_policy(policy_name, &thread->_policy, Affinity::Location()); + + fn(thread->_cap, thread->_name, *thread->_policy); + + /* XXX - heuristic */ + thread->_fix = (thread->_name == _label.last_element()) || + (thread->_name == "ep") || + (thread->_name == "signal_proxy") || + (thread->_name == "root"); + + if (thread->_fix) { + if (thread->_policy) { + destroy(_md_alloc, thread->_policy); + thread->_policy = nullptr; + } + construct_policy("none", &thread->_policy, Affinity::Location()); + } + } catch (Out_of_ram) { _by_us = true; throw; + } catch (Out_of_caps) { _by_us = true; throw; + } catch (Insufficient_ram_quota) { _by_us = true; throw; + } catch (Insufficient_cap_quota) { _by_us = true; throw; } + } catch (...) { + if (thread) { + if (thread->_policy) + destroy(_md_alloc, thread->_policy); + + destroy(_md_alloc, thread); + } + throw; + } + } + + char const * _withdraw_quota(char const * args) + { + /* + * Sandbox library can't handle the case of insufficient ram/cap + * exception during session creation nor during first + * create_thread RPC XXX + */ + + static char argbuf[Parent::Session_args::MAX_SIZE]; + copy_cstring(argbuf, args, sizeof(argbuf)); + + size_t extra_ram = (_ram_guard.avail().value < 24 * 1024) ? 24 * 1024 - _ram_guard.avail().value : 0; + + Ram_quota ram { _ram_guard.avail().value + extra_ram }; + Cap_quota cap { _cap_guard.avail().value }; + + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", ram.value); + Arg_string::set_arg(argbuf, sizeof(argbuf), "cap_quota", cap.value); + +/* + _ram_guard.withdraw(ram); + _cap_guard.withdraw(cap); +*/ + + _reclaim_ram.value += ram.value; + _reclaim_cap.value += cap.value; + + return argbuf; + } + + /* + * Noncopyable + */ + Session(Session const &); + Session &operator = (Session const &); + + public: + + Session(Env &, Affinity const &, char const *, Child_list &, bool); + ~Session(); + + /*************************** + ** CPU session interface ** + ***************************/ + + Thread_capability create_thread(Pd_session_capability, + Thread::Name const &, + Affinity::Location, Weight, + addr_t) override; + void kill_thread(Thread_capability) override; + void exception_sigh(Signal_context_capability) override; + Affinity::Space affinity_space() const override; + Dataspace_capability trace_control() override; + int ref_account(Cpu_session_capability) override; + int transfer_quota(Cpu_session_capability, size_t) override; + Quota quota() override; + Capability native_cpu() override; + + /************************ + ** internal interface ** + ************************/ + bool match(Label const &label) const { return _label == label; }; + void config(Thread::Name const &, Cpu::Policy::Name const &, + Affinity::Location const &); + void update_threads(); + void update_threads(Trace &, Session_label const &); + bool report_state(Xml_generator &) const; + void reset_report_state() { _report = false; } + bool report_update() const { return _report; } + + void default_policy(Cpu::Policy::Name const &policy) + { + if (policy != _default_policy) + _report = true; + _default_policy = policy; + } + + template + void upgrade(Root::Upgrade_args const &args, FUNC const &fn) + { + Ram_quota ram_args = ram_quota_from_args(args.string()); + Cap_quota cap_args = cap_quota_from_args(args.string()); + + bool recreate_args = false; + + if (_reclaim_ram.value) { + Ram_quota remove { min(_reclaim_ram.value, ram_args.value) }; + + _reclaim_ram.value -= remove.value; + ram_args.value -= remove.value; + recreate_args = true; + + if (remove.value > _ram_guard.avail().value) + _ram_guard.upgrade(Ram_quota{remove.value - _ram_guard.avail().value}); + _ram_guard.withdraw(remove); + } + + if (_reclaim_cap.value) { + Cap_quota remove { min(_reclaim_cap.value, cap_args.value) }; + + _reclaim_cap.value -= remove.value; + cap_args.value -= remove.value; + recreate_args = true; + + if (remove.value > _cap_guard.avail().value) + _cap_guard.upgrade(Cap_quota{remove.value - _cap_guard.avail().value}); + _cap_guard.withdraw(remove); + } + + _ram_guard.upgrade(ram_args); + _cap_guard.upgrade(cap_args); + + /* request originated by us */ + if (_by_us) { + _by_us = false; + /* due to upgrade ram/cap the next call should succeed */ + return; + } + + /* track how many resources we donated to parent done by fn() call */ + _ram_guard.withdraw(ram_args); + _cap_guard.withdraw(cap_args); + + /* rewrite args if we removed some for reclaim quirk */ + if (recreate_args) { + if (ram_args.value || cap_args.value) { + char argbuf[Root::Upgrade_args::MAX_SIZE]; + copy_cstring(argbuf, args.string(), sizeof(argbuf)); + + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", ram_args.value); + Arg_string::set_arg(argbuf, sizeof(argbuf), "cap_quota", cap_args.value); + + fn(_id.id(), argbuf); + } /* else no upgrade to parent, we consumed all */ + } else + fn(_id.id(), args); + } +}; + +#endif /* _SESSION_H_ */ diff --git a/repos/os/src/server/cpu_balancer/target.mk b/repos/os/src/server/cpu_balancer/target.mk new file mode 100644 index 0000000000..9a511f847b --- /dev/null +++ b/repos/os/src/server/cpu_balancer/target.mk @@ -0,0 +1,5 @@ +TARGET = cpu_balancer +SRC_CC = component.cc session.cc config.cc trace.cc schedule.cc +LIBS = base + +CONFIG_XSD = config.xsd diff --git a/repos/os/src/server/cpu_balancer/trace.cc b/repos/os/src/server/cpu_balancer/trace.cc new file mode 100644 index 0000000000..936d291b99 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/trace.cc @@ -0,0 +1,182 @@ +/* + * \brief Data from Trace session, + * e.g. CPU idle times && thread execution time + * \author Alexander Boettcher + * \date 2020-07-22 + */ + +/* + * Copyright (C) 2020 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. + */ + +#include "trace.h" + +void Cpu::Trace::_read_idle_times(bool skip_max_idle) +{ + if (!_trace.constructed()) + return; + + _idle_slot = (_idle_slot + 1) % HISTORY; + + for (unsigned x = 0; x < _space.width(); x++) { + for (unsigned y = 0; y < _space.height(); y++) { + Affinity::Location const idle_location(x,y); + Subject_id const &subject_id = _idle_id[x][y]; + + if (!subject_id.id || _subject_id_reread) + _lookup_missing_idle_id(idle_location); + + if (!subject_id.id) { + _idle_times[x][y][_idle_slot] = Execution_time(0, 0); + continue; + } + + Subject_info const info = _trace->subject_info(subject_id); + + Affinity::Location location = info.affinity(); + + if (location.xpos() != int(x) || location.ypos() != int(y)) { + Subject_id const subject_id_old = subject_id; + + _lookup_missing_idle_id(idle_location); + + Genode::warning("idle location mismatch ", x, "x", y, " vs ", + location.xpos(), "x", location.ypos(), " subject id=", + subject_id_old.id, " vs ", _idle_id[x][y].id); + } + + _idle_times[x][y][_idle_slot] = info.execution_time(); + + if (skip_max_idle) + continue; + + /* determine max available execution time by monitoring idle */ + auto const time = diff_idle_times(idle_location); + auto &max = _idle_max[x][y]; + if (time.thread_context > max.thread_context || + time.scheduling_context > max.scheduling_context) + max = time; + } + } +} + +void Cpu::Trace::_lookup_missing_idle_id(Affinity::Location const &location) +{ + Subject_id found_id { }; + + do { + auto count = _trace->for_each_subject_info([&](Subject_id const &id, + Subject_info const &info) + { + if (found_id.id) + return; + + if (info.affinity().xpos() != location.xpos() || + info.affinity().ypos() != location.ypos()) + return; + + if (info.session_label() != "kernel" || info.thread_name() != "idle") + return; + + _idle_id[location.xpos()][location.ypos()] = id; + found_id = id; + }); + + + if (count.count == count.limit) { + Genode::log("reconstruct trace session, subject_count=", count.count); + _reconstruct(); + found_id = Subject_id(); + continue; + } + + if (!found_id.id) { + Genode::error("idle trace id missing"); + break; + } + } while (!found_id.id); +} + +Genode::Trace::Subject_id +Cpu::Trace::lookup_missing_id(Session_label const &label, + Thread_name const &thread) +{ + Subject_id found_id { }; + + do { + auto count = _trace->for_each_subject_info([&](Subject_id const &id, + Subject_info const &info) + { + if (found_id.id) + return; + + if (thread != info.thread_name()) + return; + + if (label != info.session_label()) + return; + + found_id = id; + }); + + if (count.count == count.limit) { + Genode::log("reconstruct trace session, subject_count=", count.count); + _reconstruct(); + found_id = Subject_id(); + continue; + } + + if (!found_id.id) { + Genode::error("trace id missing"); + break; + } + } while (!found_id.id); + + return found_id; +} + +Genode::Session_label Cpu::Trace::lookup_my_label() +{ + Subject_id found_id { }; + Session_label label("cpu_balancer"); + + do { + auto count = _trace->for_each_subject_info([&](Subject_id const &id, + Subject_info const &info) + { + if (info.thread_name() != label) + return; + + Session_label match { info.session_label().prefix(), " -> ", label }; + if (info.session_label() != match) + return; + + if (found_id.id) + Genode::warning("Multiple CPU balancer are running, " + "can't determine myself for sure."); + + found_id = id; + label = info.session_label(); + }); + + if (count.count == count.limit) { + Genode::log("reconstruct trace session, subject_count=", count.count); + found_id = Subject_id(); + _reconstruct(); + continue; + } + + if (!found_id.id) { + Genode::error("could not lookup my label"); + break; + } + } while (!found_id.id); + + if (found_id.id) + warning("My label seems to be: '", label, "'"); + + return label; +} diff --git a/repos/os/src/server/cpu_balancer/trace.h b/repos/os/src/server/cpu_balancer/trace.h new file mode 100644 index 0000000000..f716fb3dd9 --- /dev/null +++ b/repos/os/src/server/cpu_balancer/trace.h @@ -0,0 +1,189 @@ +/* + * \brief Data from Trace session, + * e.g. CPU idle times && thread execution time + * \author Alexander Boettcher + * \date 2020-07-22 + */ + +/* + * Copyright (C) 2020 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 _TRACE_H_ +#define _TRACE_H_ + +#include +#include + +namespace Cpu { + class Trace; + class Sleeper; + + using Genode::Constructible; + using Genode::Trace::Subject_id; + using Genode::Trace::Subject_info; + using Genode::Affinity; + using Genode::Session_label; + using Genode::Trace::Thread_name; + using Genode::Trace::Connection; + using Genode::Trace::Execution_time; +}; + +class Cpu::Trace +{ + private: + + Genode::Env &_env; + Affinity::Space const _space; + Constructible _trace { }; + + Genode::size_t _arg_quota { 12 * 4096 }; + Genode::size_t _ram_quota { _arg_quota + 4 * 4096 }; + + enum { MAX_CORES = 64, MAX_THREADS = 2, HISTORY = 4 }; + + Subject_id _idle_id [MAX_CORES][MAX_THREADS]; + Execution_time _idle_times[MAX_CORES][MAX_THREADS][HISTORY]; + Execution_time _idle_max [MAX_CORES][MAX_THREADS]; + unsigned _idle_slot { HISTORY - 1 }; + + unsigned _subject_id_reread { 0 }; + + void _lookup_missing_idle_id(Affinity::Location const &); + + void _reconstruct(Genode::size_t const upgrade = 4 * 4096) + { + _ram_quota += upgrade; + _arg_quota += upgrade; + + _trace.destruct(); + _trace.construct(_env, _ram_quota, _arg_quota, 0 /* parent levels */); + + /* + * Explicitly re-trigger import of subjects. Otherwise + * stored trace ids are not valid if used with subject_info(id) + * and we get exception thrown about unknown ids. + */ + _trace->_retry([&] () { + _trace->call(); + }); + + _subject_id_reread ++; + } + + Affinity::Space _sanitize_space(Affinity::Space const space) + { + unsigned width = space.width(); + unsigned height = space.height(); + + if (_space.width() > MAX_CORES) + width = MAX_CORES; + if (_space.height() > MAX_THREADS) + height = MAX_THREADS; + + if (width != space.width() || height != space.height()) + Genode::error("supported affinity space too small"); + + return Affinity::Space(width, height); + } + + void _read_idle_times(bool); + + public: + + Trace(Genode::Env &env) + : _env(env), _space(_sanitize_space(env.cpu().affinity_space())) + { + _reconstruct(); + _read_idle_times(true); + } + + void read_idle_times() { _read_idle_times(false); } + + unsigned subject_id_reread() const { return _subject_id_reread; } + void subject_id_reread_reset() { _subject_id_reread = 0; } + + Execution_time read_max_idle(Affinity::Location const &location) + { + unsigned const xpos = location.xpos(); + unsigned const ypos = location.ypos(); + + if (xpos >= MAX_CORES || ypos >= MAX_THREADS) + return Execution_time(0, 0); + + return _idle_max[xpos][ypos]; + } + + + Subject_id lookup_missing_id(Session_label const &, + Thread_name const &); + + Session_label lookup_my_label(); + + template + void retrieve(Subject_id const id, FUNC const &fn) + { + if (!_trace.constructed()) + return; + + /* Ieegs, XXX, avoid copying whole object */ + Subject_info info = _trace->subject_info(id); + fn(info.execution_time(), info.affinity()); + } + + Execution_time abs_idle_times(Affinity::Location const &location) + { + if (location.xpos() >= MAX_CORES || location.ypos() >= MAX_THREADS) + return Execution_time(0, 0); + return _idle_times[location.xpos()][location.ypos()][_idle_slot]; + } + + Execution_time diff_idle_times(Affinity::Location const &location) + { + unsigned const xpos = location.xpos(); + unsigned const ypos = location.ypos(); + + if (xpos >= MAX_CORES || ypos >= MAX_THREADS) + return Execution_time(0, 0); + + Execution_time const &prev = _idle_times[xpos][ypos][((_idle_slot == 0) ? unsigned(HISTORY) : _idle_slot) - 1]; + Execution_time const &curr = _idle_times[location.xpos()][location.ypos()][_idle_slot]; + + using Genode::uint64_t; + + uint64_t ec = (prev.thread_context < curr.thread_context) ? + curr.thread_context - prev.thread_context : 0; + uint64_t sc = (prev.scheduling_context < curr.scheduling_context) ? + curr.scheduling_context - prev.scheduling_context : 0; + + /* strange case where idle times are not reported if no threads are on CPU */ + if (!ec && !sc && curr.thread_context == 0 && curr.scheduling_context == 0) + return Execution_time { 1, 1 }; + + return Execution_time { ec, sc }; + } +}; + +struct Cpu::Sleeper : Genode::Thread +{ + Genode::Env &_env; + Genode::Blockade _block { }; + + Sleeper(Genode::Env &env, Location const &location) + : + Genode::Thread(env, Name("sleep_", location.xpos(), "x", location.ypos()), + 2 * 4096, location, Weight(), env.cpu()), + _env(env) + { } + + void entry() override + { + while (true) { + _block.block(); + } + } +}; +#endif /* _TRACE_H_ */ diff --git a/tool/autopilot.list b/tool/autopilot.list index 66b83f1ae2..968a74fe3b 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -2,6 +2,7 @@ aes_cbc_4k bomb cbe_tester cpu_bench +cpu_balancer cpu_quota cpu_sampler demo