os: add cpu balancer component

Issue #3843
This commit is contained in:
Alexander Boettcher 2020-07-17 13:21:41 +02:00 committed by Christian Helmuth
parent 6872fdb0de
commit aa7f5bc95f
24 changed files with 2167 additions and 0 deletions

View File

@ -0,0 +1,3 @@
A dynamic CPU balancer component.

View File

@ -0,0 +1 @@
_/src/cpu_balancer

View File

@ -0,0 +1 @@
2020-11-11 5977b1e4fb0a81bd364c2f33ef587c16b6b26dc7

View File

@ -0,0 +1,18 @@
<runtime ram="2M" caps="150" binary="cpu_balancer">
<provides> <cpu/> </provides>
<requires>
<trace/>
<report/>
<timer/>
</requires>
<content>
<rom label="ld.lib.so"/>
<rom label="cpu_balancer"/>
</content>
<config interval_us="1000000" report="yes" trace="yes"/>
</runtime>

View File

@ -0,0 +1,3 @@
A dynamic CPU balancer component reading config and reporting state via file.

View File

@ -0,0 +1,4 @@
_/src/cpu_balancer
_/src/init
_/src/fs_report
_/src/fs_rom

View File

@ -0,0 +1 @@
2020-10-02-b 2488f0453a59f0b3582cd4a041b4845ca3b74ca1

View File

@ -0,0 +1,73 @@
<runtime ram="7M" caps="700" binary="init">
<provides> <cpu/> </provides>
<requires>
<trace/>
<timer/>
<file_system label="recall" writeable="yes"/>
</requires>
<config>
<service name="CPU">
<default-policy> <child name="cpu_balancer"/> </default-policy>
</service>
<default-route> <any-service> <parent/> </any-service> </default-route>
<parent-provides>
<service name="PD"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="ROM"/>
<service name="TRACE"/>
<service name="File_system"/>
<service name="Gui"/>
<service name="Timer"/>
</parent-provides>
<start name="fs_report" caps="100">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> </provides>
<config> <vfs> <fs/> </vfs> </config>
<route>
<service name="File_system"> <parent label="recall"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="fs_rom" caps="100">
<resource name="RAM" quantum="1M"/>
<provides> <service name="ROM"/> </provides>
<config/>
<route>
<service name="File_system"> <parent label="recall"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="cpu_balancer" caps="200">
<resource name="RAM" quantum="2M"/>
<provides><service name="CPU"/></provides>
<!--
<config interval_us="1000000" report="yes" trace="yes"/>
-->
<route>
<service name="ROM" label="config"> <child name="fs_rom" label="config"/> </service>
<service name="Report" label="components"> <child name="fs_report" label="components"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</config>
<content>
<rom label="ld.lib.so"/>
<rom label="cpu_balancer"/>
<rom label="init"/>
<rom label="fs_report"/>
<rom label="fs_rom"/>
</content>
</runtime>

View File

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

View File

@ -0,0 +1 @@
2020-11-11 0cc7b984701f2f0ab0b20a59c8311ce147609834

View File

@ -0,0 +1,4 @@
base
os
timer_session
report_session

View File

@ -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 {
<config prio_levels="2">
<affinity-space width="} $cpu_width {" height="} $cpu_height {"/>
<parent-provides>
<service name="LOG"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="PD"/>
<service name="IO_PORT"/> <!-- timer on some kernels -->
<service name="IRQ"/> <!-- timer on some kernels -->
<service name="TRACE"/>
</parent-provides>
<default-route>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="ROM"> <parent/> </service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
<route>
<any-service> <parent/> </any-service>
</route>
</start>}
append_if [expr $report_config eq "yes"] config {
<start name="report_rom">
<binary name="report_rom"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes"/>
<route>
<any-service> <parent/> </any-service>
</route>
</start>}
append config {
<start name="cpu_balancer">
<resource name="RAM" quantum="2M"/>
<provides><service name="CPU"/></provides>
<config interval_us="2000000"
report="} $report_config {"
trace="} $use_trace {"
verbose="no">
<component label="cpu_burner" default_policy="none">
<!--
<thread name="signal handler" policy="pin" xpos="1" ypos="0"/>
-->
<thread name="signal handler" policy="max-utilize"/>
<thread name="burn_0x0" policy="round-robin"/>
<thread name="burn_1x0" policy="round-robin"/>
</component>
</config>
<route>
<service name="Timer"> <child name="timer"/> </service>
<service name="Report"> <child name="report_rom"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="cpu_burner" priority="-1">
<affinity xpos="1" ypos="0" width="2" height="1"/>
<resource name="RAM" quantum="2M"/>
<route>
<service name="CPU"> <child name="cpu_balancer"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<!--
<start name="top">
<resource name="RAM" quantum="2M"/>
<route>
<service name="Timer"> <child name="timer"/> </service>
<service name="CPU"> <child name="cpu_balancer"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
-->
</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

View File

@ -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 <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/registry.h>
#include <base/signal.h>
#include <cpu_session/cpu_session.h>
#include <timer_session/connection.h>
#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 <typename EXC, typename T, typename FUNC, typename HANDLER>
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<Genode::Registered<Cpu::Sleeper> > Sleeper_list;
typedef Genode::Tslab<Genode::Registered<Cpu::Sleeper>, 4096> Tslab_sleeper;
struct Cpu::Balancer : Rpc_object<Typed_root<Cpu_session>>
{
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> trace { };
Constructible<Reporter> 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<Balancer> 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<Balancer> signal_timeout {
ep, *this, &Balancer::handle_timeout };
Genode::Mutex list_mutex { };
template <typename FUNC>
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<Session>(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<Session *>(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>(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<Genode::Xml_generator::Buffer_exceeded>(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);
}

View File

@ -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 <base/session_label.h>
#include <base/thread.h>
#include "config.h"
void Cpu::Config::apply(Xml_node const &start, Child_list &sessions)
{
using Genode::Session_label;
using Genode::String;
typedef String<Session_label::capacity()> 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);
});
});
});
}

View File

@ -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 <util/xml_node.h>
#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_ */

View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="base_types.xsd"/>
<xs:simpleType name="Policy">
<xs:restriction base="xs:string">
<xs:enumeration value="none" />
<xs:enumeration value="pin" />
<xs:enumeration value="round-robin" />
<xs:enumeration value="max-utilize" />
</xs:restriction>
</xs:simpleType><!-- Policy -->
<xs:element name="config">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="component">
<xs:complexType>
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="thread">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="policy" type="Policy" />
</xs:complexType>
</xs:element> <!-- thread -->
</xs:choice>
<xs:attribute name="default_policy" type="xs:string" />
<xs:attribute name="label" type="Session_label" />
</xs:complexType>
</xs:element> <!-- component -->
</xs:choice>
<xs:attribute name="verbose" type="Boolean" />
<xs:attribute name="interval_us" type="xs:positiveInteger" />
<xs:attribute name="report" type="Boolean" />
<xs:attribute name="trace" type="Boolean" />
<xs:attribute name="sleeper" type="Boolean" />
</xs:complexType>
</xs:element> <!-- config -->
</xs:schema>

View File

@ -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 <base/affinity.h>
#include <base/output.h>
#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 &current)
{
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 &current, 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 &current, 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 &current, Execution_time const &time) override {
_last = _time;
_last_valid = _time_valid;
_time = time;
_time_valid = true;
return _update(base, current); }
template <typename T>
bool _migrate(T const current_idle, T const thread_time,
T const remote_idle, Location const &current,
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 &current, 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

View File

@ -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 <cpu_thread/client.h>
#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;
});
}

View File

@ -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<Cpu_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> 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;
}

View File

@ -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 <base/env.h>
#include <base/heap.h>
#include <base/registry.h>
#include <base/rpc_server.h>
#include <base/trace/types.h>
#include <cpu_session/client.h>
#include <os/reporter.h>
#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<Parent::Client>::Element Client_id;
typedef Registry<Registered<Session> > Child_list;
typedef Registry<Registered<Thread_client> > 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<Cpu_session>
{
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 <typename FUNC>
void _for_each_thread(FUNC const &fn)
{
bool done = false;
_threads.for_each([&](auto &thread) {
if (done)
return;
done = fn(thread);
});
}
template <typename FUNC>
void _for_each_thread(FUNC const &fn) const
{
bool done = false;
_threads.for_each([&](auto const &thread) {
if (done)
return;
done = fn(thread);
});
}
template <typename FUNC>
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 <typename FUNC>
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 <typename FUNC>
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 <typename FUNC>
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 <typename FUNC>
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 <typename FUNC>
void construct(Cpu::Policy::Name const &policy_name, FUNC const &fn)
{
Thread_client * thread = nullptr;
try {
try {
thread = new (_md_alloc) Registered<Thread_client>(_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<Cpu_session::Native_cpu> 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 <typename FUNC>
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_ */

View File

@ -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

View File

@ -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;
}

View File

@ -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 <util/reconstructible.h>
#include <trace_session/connection.h>
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<Connection> _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<Genode::Trace::Session_client::Rpc_subjects>();
});
_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 <typename FUNC>
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_ */

View File

@ -2,6 +2,7 @@ aes_cbc_4k
bomb
cbe_tester
cpu_bench
cpu_balancer
cpu_quota
cpu_sampler
demo