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