diff --git a/src/power_policy/_power_policy.cpp b/src/power_policy/_power_policy.cpp index 1c0d8fc0..06799220 100644 --- a/src/power_policy/_power_policy.cpp +++ b/src/power_policy/_power_policy.cpp @@ -15,6 +15,10 @@ #include "power_policy/minbe.hpp" #include "power_policy/none.hpp" #include "power_policy/per_process.hpp" +#include "power_policy/bets_skip.hpp" +#include "power_policy/bets_soft_throttle.hpp" +#include "power_policy/bets_hard_throttle.hpp" + // after multiple template-based attempts, a macro solution was chosen, @@ -51,6 +55,9 @@ static const std::mapwrite_frequency(cp->min_frequency); + } +}; diff --git a/src/power_policy/bets_policy.hpp b/src/power_policy/bets_policy.hpp new file mode 100644 index 00000000..0bf354bb --- /dev/null +++ b/src/power_policy/bets_policy.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "_power_policy.hpp" +#include "power_manager.hpp" +#include "lib/file_lib.hpp" +#include +#include +#include "slice.hpp" +#include +#include +#include +#include + + +/** + * Based on imx8_per_slice policy, intended for Best Effort Task Scheduler (BETS). + */ +class Bets_policy : public PmPowerPolicy +{ +protected: + // currently, this is hardcoded for i.MX8, but should be quite simply extensible + // for other CPU cluster layouts + std::array policies{ &pm.get_policy("policy0"), &pm.get_policy("policy4") }; + float upper_threshold; + float lower_threshold; + time_t timer; + const time_t limit = 10; + bool temperature_inertia_period = false; + int level = 0; + const string temp_path_c0 = "/sys/devices/virtual/thermal/thermal_zone0/temp"; + std::vector temperatures; + int oldest = 0; + +public: + Bets_policy(const std::string &temperature_threshold) + { + upper_threshold = std::stof(temperature_threshold); + lower_threshold = upper_threshold - 1; + temperatures = std::vector(10); + for (auto &p : pm.policy_iter()) + if (!p.available_frequencies) + throw runtime_error("Cannot list available frequencies for the CPU" + " - are you sure you're running DEmOS on an i.MX8?"); + } + + // Validate that the requested frequencies are available + void validate(const Windows &windows) override + { + for (const Window &win : windows) { + for (const Slice &slice : win.slices) { + if (!slice.requested_frequency) continue; + for (const CpufreqPolicy *policy : policies) { + if (!(slice.cpus & policy->affected_cores)) continue; + policy->validate_frequency(slice.requested_frequency.value()); + } + } + } + } + bool supports_per_slice_frequencies() override { return true; } + + float get_temperature_on_c0(){ + auto is = file_open(temp_path_c0); + float temp; + is >> temp; + return temp / 1000.0; + } + + float get_temp(){ + this->temperatures[oldest] = this->get_temperature_on_c0(); + oldest = (oldest + 1) % temperatures.size(); + float sum = std::accumulate(this->temperatures.begin(), this->temperatures.end(), 0); + return sum / this->temperatures.size(); + } + + virtual void execute_policy(CpufreqPolicy *cp, CpuFrequencyHz &freq, Window &win){ + cp->write_frequency(freq); + } + + void change_level(){ + float avg_temp = this->get_temp(); + if (this->temperature_inertia_period){ + if (time(0) - this->timer >= this->limit){ + this->temperature_inertia_period = false; + } + return; //Change is already in progress + } + + if (avg_temp > upper_threshold){ + this->temperature_inertia_period = true; + this->timer = time(0); + this->level++; + std::cout << "level=" << this->level << std::endl; + return; + } + + if (avg_temp < lower_threshold && level > 0){ + this->temperature_inertia_period = true; + this->timer = time(0); + this->level--; + std::cout << "level=" << this->level << std::endl; + return; + } + } + + void on_window_start(Window &win) override + { + for (CpufreqPolicy *cp : policies) { + std::optional freq = std::nullopt; + for (const Slice &slice : win.slices) { + if (slice.requested_frequency && slice.cpus & cp->affected_cores) { + // slice is requesting a fixed frequency, check that all slices on the + // same CPU cluster request the same frequency. + if (freq.has_value() && freq.value() != slice.requested_frequency) { + throw std::runtime_error(fmt::format( + "Scheduled slices require different frequencies on the CPU(s) " + "{}: '{}', '{}'", + cp->affected_cores.as_list(), + freq.value(), + slice.requested_frequency.value())); + } + freq = slice.requested_frequency; + } + } + if (freq.has_value()) { + change_level(); + if (this->level == 0){ + cp->write_frequency(freq.value()); + } else { + execute_policy(cp, freq.value(), win); + } + } + } + } +}; diff --git a/src/power_policy/bets_skip.hpp b/src/power_policy/bets_skip.hpp new file mode 100644 index 00000000..a5a9135b --- /dev/null +++ b/src/power_policy/bets_skip.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "bets_policy.hpp" +#include +#include +#include +#include + + +bool cmp(std::pair& a, std::pair& b) { + return a.second < b.second; +} + +/** + * In case of temperature threshold overstepping skip a task in a fair fashion. + * + * Based on imx8_per_slice policy. + */ +class Bets_skip : public Bets_policy +{ +private: + std::vector> candidates; + std::vector present; +public: + Bets_skip(const std::string &temperature_threshold) : Bets_policy(temperature_threshold) {} + + + void sort_candidates(){ + std::sort(candidates.begin(), candidates.end(), cmp); + } + + void safe_pushback(std::string key){ + for (auto &p : candidates){ + if (p.first == key){ + return; + } + } + candidates.push_back(std::pair(key, 0)); + } + + void load(Window &win){ + present = std::vector(); + for (auto &s : win.slices){ + std::string key = s.be->get_name(); + if (key.find("sleep") == std::string::npos){ //Sleep process is used for idle time. We are not skipping it. + present.push_back(key); + safe_pushback(key); + } + } + } + + int is_present(std::string key){ + for (int i = 0; i < present.size(); i++){ + if (present[i] == key){ + return i; + } + } + return -1; + } + + void execute_policy(CpufreqPolicy *cp, CpuFrequencyHz &freq, Window &win) override { + cp->write_frequency(freq); + if (cp->name != "policy0"){ //Skip policy works over all clusters, it makes settings only on the first one. + return; + } + + if(this->level > 5){ + this->level = 5; + } + int skip_counter = 0; + for (int j = 0; j < win.slices.size(); j++){ + win.to_be_skipped[j] = false; + } + load(win); + sort_candidates(); + // Iterate trough candidates, until up to the level amounth of them is skipped, but at least 1 is kept. + for(int i = 0; i < candidates.size() && skip_counter < this->level && present.size() - skip_counter > 1; i++){ + int idx = is_present(candidates[i].first); + if (idx != -1){ + win.to_be_skipped[idx] = true; + candidates[i].second += win.length.count(); + skip_counter++; + } + } + } +}; + diff --git a/src/power_policy/bets_skip.hpp.save b/src/power_policy/bets_skip.hpp.save new file mode 100644 index 00000000..17220ea9 --- /dev/null +++ b/src/power_policy/bets_skip.hpp.save @@ -0,0 +1,49 @@ +#pragma once + +#include "bets_policy.hpp" +#include +#include +#include + +/** + * In case of temperature threshold overstepping set frequencies to the lowest possible frequency. + * + * Based on imx8_per_slice policy. + */ +class Bets_skip : public Bets_policy +{ +private: + std::map skip_counter; +public: + Bets_skip(const std::string &temperature_threshold) : Bets_policy(temperature_threshold) {} + + void execute_policy(CpufreqPolicy *cp, CpuFrequencyHz &freq, Window &win) override { + cp->write_frequency(freq); + + int min = std::numeric_limits::max(); + std::string candidate; + int i = 0; + for (auto &s : win.slices){ + std::string key = s.be->get_name(); + if (!skip_counter.count(key)){ + skip_counter.insert(std::pair(key, 0)); + min = 0; + candidate = key; + } else { + if (skip_counter[key] < min){ + min = skip_counter[key]; + candidate = key; + } + } + i++; + } + for (auto pair : win.to_){ + win.to_be_skipped[j] = false; + } + + for (int j = 0; j < ) + + win.to_be_skipped[candidate] = true; + } +}; + diff --git a/src/power_policy/bets_soft_throttle.hpp b/src/power_policy/bets_soft_throttle.hpp new file mode 100644 index 00000000..de061021 --- /dev/null +++ b/src/power_policy/bets_soft_throttle.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "bets_policy.hpp" + + +/** + * In case of temperature threshold overstepping lower frequencies by one step (if possible). + * + * Based on imx8_per_slice policy. + */ +class Bets_soft_throttle : public Bets_policy +{ +public: + Bets_soft_throttle(const std::string &temperature_threshold) : Bets_policy(temperature_threshold) {} + + void execute_policy(CpufreqPolicy *cp, CpuFrequencyHz &freq, Window &win) override { + if (this->level > 3){ + this->level = 3; + } + auto freqs = cp->available_frequencies.value(); + auto it = std::find(freqs.begin(), freqs.end(), freq); + if (it != freqs.end()){ + for(int i = 0; i < this->level; i++){ + it = it == freqs.begin() ? it : --it; // If it can be lowered, lower it. + } + cp->write_frequency(*it); + } else { + throw std::runtime_error(fmt::format( + "Frequency not found in available frequencies " + "'{}'", + freq)); + } + } +}; diff --git a/src/window.cpp b/src/window.cpp index 48681a82..94fc3be0 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -11,6 +11,7 @@ Window::Window(ev::loop_ref loop_, std::chrono::milliseconds length_, PowerPolic Slice &Window::add_slice(Partition *sc, Partition *be, const cpu_set &cpus, std::optional req_freq) { auto sc_cb = [this](Slice &s, time_point t) { slice_sc_end_cb(s, t); }; + to_be_skipped.push_back(false); return slices.emplace_back(loop, power_policy, sc_cb, sc, be, req_freq, cpus); } @@ -26,6 +27,7 @@ void Window::start(time_point current_time) for (auto &s : slices) { s.start_sc(current_time); } + // call power policy handlers after we start the slices; // this way, even if the CPU frequency switching is slow, the processes are // still executing, although at an incorrect frequency; @@ -65,10 +67,15 @@ void Window::slice_sc_end_cb([[maybe_unused]] Slice &slice, time_point current_t // slice->start_be(current_time); // option 2) wait until all SC partitions finish + if (has_sc_finished()) { TRACE("Starting BE partitions"); - for (auto &sp : slices) { - sp.start_be(current_time); + int i = 0; + for (auto &s : slices) { + if (!to_be_skipped[i]){ + s.start_be(current_time); + } + i++; } // see `Window::start` for reasoning on why this is called AFTER partition start power_policy.on_be_start(*this); diff --git a/src/window.hpp b/src/window.hpp index 513afbd3..53321a4c 100644 --- a/src/window.hpp +++ b/src/window.hpp @@ -5,6 +5,7 @@ #include #include #include +#include class Window; class PowerPolicy; @@ -28,6 +29,7 @@ class Window const std::chrono::milliseconds length; // use std::list as Slice doesn't have move and copy constructors std::list slices{}; + std::vector to_be_skipped; Window(ev::loop_ref loop, std::chrono::milliseconds length, PowerPolicy &power_policy);