sandbox: improve CPU-quota accounting

The existing assignment of CPU quotas did not anticipate the dynamic
reconfiguration of init. It merely tracked the available CPU quota by
deducing the consumed amount from a global variable but never
replenished the value. This worked for static scenarios but failed in
situations where components are dynamically re-started.

So far this deficiency remained detected because CPU quotas were not
used in highly dynamic systems like Sculpt OS. However, this has
recently changed by commit "sculpt: assign CPU quotas".

The patch improves the accounting by mirroring the existing handling of
RAM and cap quotas. Note that the CPU-quota accounting is still rather
limited. In particular the dynamic rebalancing is not yet supported.

Issue #4445
This commit is contained in:
Norman Feske 2022-03-10 15:23:54 +01:00 committed by Christian Helmuth
parent d182b20705
commit a9022d8451
4 changed files with 84 additions and 28 deletions

View File

@ -484,20 +484,16 @@ void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap)
void Sandbox::Child::init(Cpu_session &session, Cpu_session_capability cap)
{
static size_t avail = Cpu_session::quota_lim_upscale( 100, 100);
size_t const need = Cpu_session::quota_lim_upscale(_resources.cpu_quota_pc, 100);
size_t need_adj = 0;
Cpu_quota const assigned = _resources.assigned_cpu_quota;
Cpu_quota const effective = _effective_cpu_quota;
if (assigned.percent > effective.percent)
warning(name(), ": configured CPU quota of ", assigned, " exceeds "
"available quota, proceeding with a quota of ", effective);
if (need > avail || avail == 0) {
warn_insuff_quota(Cpu_session::quota_lim_downscale(avail, 100));
need_adj = Cpu_session::quota_lim_upscale(100, 100);
avail = 0;
} else {
need_adj = Cpu_session::quota_lim_upscale(need, avail);
avail -= need;
}
session.ref_account(_env.cpu_session_cap());
_env.cpu().transfer_quota(cap, need_adj);
_cpu_quota_transfer.transfer_cpu_quota(cap, effective);
}
@ -743,6 +739,8 @@ Sandbox::Child::Child(Env &env,
Name_registry &name_registry,
Ram_limit_accessor &ram_limit_accessor,
Cap_limit_accessor &cap_limit_accessor,
Cpu_limit_accessor &cpu_limit_accessor,
Cpu_quota_transfer &cpu_quota_transfer,
Prio_levels prio_levels,
Affinity::Space const &affinity_space,
Registry<Parent_service> &parent_services,
@ -757,6 +755,8 @@ Sandbox::Child::Child(Env &env,
_default_caps_accessor(default_caps_accessor),
_ram_limit_accessor(ram_limit_accessor),
_cap_limit_accessor(cap_limit_accessor),
_cpu_limit_accessor(cpu_limit_accessor),
_cpu_quota_transfer(cpu_quota_transfer),
_name_registry(name_registry),
_heartbeat_enabled(start_node.has_sub_node("heartbeat")),
_resources(_resources_from_start_node(start_node, prio_levels, affinity_space,

View File

@ -64,6 +64,12 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
typedef Resource_limit_accessor<Ram_quota> Ram_limit_accessor;
typedef Resource_limit_accessor<Cap_quota> Cap_limit_accessor;
typedef Resource_limit_accessor<Cpu_quota> Cpu_limit_accessor;
struct Cpu_quota_transfer : Interface
{
virtual void transfer_cpu_quota(Cpu_session_capability, Cpu_quota) = 0;
};
enum class Sample_state_result { CHANGED, UNCHANGED };
@ -145,6 +151,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Default_caps_accessor &_default_caps_accessor;
Ram_limit_accessor &_ram_limit_accessor;
Cap_limit_accessor &_cap_limit_accessor;
Cpu_limit_accessor &_cpu_limit_accessor;
Cpu_quota_transfer &_cpu_quota_transfer;
Name_registry &_name_registry;
@ -206,7 +214,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Affinity affinity;
Ram_quota assigned_ram_quota;
Cap_quota assigned_cap_quota;
size_t cpu_quota_pc;
Cpu_quota assigned_cpu_quota;
Ram_quota effective_ram_quota() const
{
@ -240,8 +248,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Affinity::Space const &affinity_space,
Cap_quota default_cap_quota)
{
size_t cpu_quota_pc = 0;
Number_of_bytes ram_bytes = 0;
unsigned cpu_percent = 0;
Number_of_bytes ram_bytes = 0;
size_t caps = start_node.attribute_value("caps", default_cap_quota.value);
@ -250,17 +258,14 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
typedef String<8> Name;
Name const name = rsc.attribute_value("name", Name());
if (name == "RAM") {
if (name == "RAM")
ram_bytes = rsc.attribute_value("quantum", ram_bytes);
}
if (name == "CPU") {
cpu_quota_pc = rsc.attribute_value("quantum", 0UL);
}
if (name == "CPU")
cpu_percent = rsc.attribute_value("quantum", 0U);
if (name == "CAP") {
if (name == "CAP")
caps = rsc.attribute_value("quantum", 0UL);
}
});
return Resources { log2(prio_levels.value),
@ -269,7 +274,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
affinity_location_from_xml(affinity_space, start_node)),
Ram_quota { ram_bytes },
Cap_quota { caps },
cpu_quota_pc };
Cpu_quota { cpu_percent } };
}
Resources _resources;
@ -342,6 +347,10 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
long const _prio_levels_log2 { _resources.prio_levels_log2 };
long const _priority { _resources.priority };
Cpu_quota const _effective_cpu_quota {
min(_cpu_limit_accessor.resource_limit(Cpu_quota{}).percent,
_resources.assigned_cpu_quota.percent) };
/**
* If set to true, the child is allowed to do system management,
* e.g., constrain physical RAM allocations.
@ -527,9 +536,6 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
* allocations on behalf of the child's
* behavior.
*
* \param ram_limit maximum amount of RAM to be consumed at
* creation time.
*
* \param ram_limit_accessor interface for querying the available
* RAM, used for dynamic RAM balancing at
* runtime.
@ -547,6 +553,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Name_registry &name_registry,
Ram_limit_accessor &ram_limit_accessor,
Cap_limit_accessor &cap_limit_accessor,
Cpu_limit_accessor &cpu_limit_accessor,
Cpu_quota_transfer &cpu_quota_transfer,
Prio_levels prio_levels,
Affinity::Space const &affinity_space,
Registry<Parent_service> &parent_services,
@ -564,6 +572,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Ram_quota ram_quota() const { return _resources.assigned_ram_quota; }
Cap_quota cap_quota() const { return _resources.assigned_cap_quota; }
Cpu_quota cpu_quota() const { return _effective_cpu_quota; }
void try_start()
{

View File

@ -27,6 +27,8 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
::Sandbox::Child::Default_caps_accessor,
::Sandbox::Child::Ram_limit_accessor,
::Sandbox::Child::Cap_limit_accessor,
::Sandbox::Child::Cpu_limit_accessor,
::Sandbox::Child::Cpu_quota_transfer,
::Sandbox::Start_model::Factory,
::Sandbox::Parent_provides_model::Factory
{
@ -44,6 +46,7 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
using Prio_levels = ::Sandbox::Prio_levels;
using Ram_info = ::Sandbox::Ram_info;
using Cap_info = ::Sandbox::Cap_info;
using Cpu_quota = ::Sandbox::Cpu_quota;
using Config_model = ::Sandbox::Config_model;
using Start_model = ::Sandbox::Start_model;
using Preservation = ::Sandbox::Preservation;
@ -90,6 +93,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
unsigned _child_cnt = 0;
Cpu_quota _avail_cpu { .percent = 100 };
Cpu_quota _transferred_cpu { .percent = 0 };
Ram_quota _avail_ram() const
{
Ram_quota avail_ram = _env.pd().avail_ram();
@ -129,6 +135,30 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
*/
Cap_quota resource_limit(Cap_quota const &) const override { return _avail_caps(); }
/**
* Child::Cpu_limit_accessor interface
*/
Cpu_quota resource_limit(Cpu_quota const &) const override { return _avail_cpu; }
/**
* Child::Cpu_quota_transfer interface
*/
void transfer_cpu_quota(Cpu_session_capability cap, Cpu_quota quota) override
{
Cpu_quota const remaining { 100 - min(100u, _transferred_cpu.percent) };
/* prevent division by zero in 'quota_lim_upscale' */
if (remaining.percent == 0)
return;
size_t const fraction =
Cpu_session::quota_lim_upscale(quota.percent, remaining.percent);
_env.cpu().transfer_quota(cap, fraction);
_transferred_cpu.percent += quota.percent;
}
/**
* State_reporter::Producer interface
*/
@ -256,7 +286,15 @@ void Genode::Sandbox::Library::_destroy_abandoned_children()
/* destroy child once all environment sessions are gone */
if (child.env_sessions_closed()) {
_children.remove(&child);
Cpu_quota const child_cpu_quota = child.cpu_quota();
destroy(_heap, &child);
/* replenish available CPU quota */
_avail_cpu.percent += child_cpu_quota.percent;
_transferred_cpu.percent -= min(_transferred_cpu.percent,
child_cpu_quota.percent);
}
});
}
@ -301,11 +339,13 @@ bool Genode::Sandbox::Library::ready_to_create_child(Start_model::Name const
Child &child = *new (_heap)
Child(_env, _heap, *_verbose,
Child::Id { ++_child_cnt }, _state_reporter,
start_node, *this, *this, _children,
*this, *this, _prio_levels, _effective_affinity_space(),
start_node, *this, *this, _children, *this, *this, *this, *this,
_prio_levels, _effective_affinity_space(),
_parent_services, _child_services, _local_services);
_children.insert(&child);
_avail_cpu.percent -= min(_avail_cpu.percent, child.cpu_quota().percent);
if (start_node.has_sub_node("provides"))
_server_appeared_or_disappeared = true;

View File

@ -28,6 +28,13 @@ namespace Sandbox {
struct Prio_levels { long value; };
struct Cpu_quota
{
unsigned percent;
void print(Output &out) const { Genode::print(out, percent, "%"); }
};
typedef List<List_element<Child> > Child_list;
template <typename T>