Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
2873d0b8ccfb61421b1912fb85c37615a7fb3f46
[simgrid.git] / src / plugins / host_dvfs.cpp
1 /* Copyright (c) 2010-2018. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "simgrid/plugins/dvfs.h"
7 #include "simgrid/plugins/load.h"
8 #include "src/plugins/vm/VirtualMachineImpl.hpp"
9 #include <xbt/config.hpp>
10
11 #include <boost/algorithm/string.hpp>
12
13 SIMGRID_REGISTER_PLUGIN(host_dvfs, "Dvfs support", &sg_host_dvfs_plugin_init)
14
15 static simgrid::config::Flag<double> cfg_sampling_rate("plugin/dvfs/sampling-rate", {"plugin/dvfs/sampling_rate"},
16     "How often should the dvfs plugin check whether the frequency needs to be changed?", 0.1,
17     [](double val){if (val != 0.1) sg_host_dvfs_plugin_init();});
18
19 static simgrid::config::Flag<std::string> cfg_governor("plugin/dvfs/governor",
20     "Which Governor should be used that adapts the CPU frequency?", "performance",
21
22     std::map<std::string, std::string>({
23         {"performance", "TODO: add some doc"},
24         {"conservative", "TODO: Doc"},
25         {"ondemand", "TODO: Doc"},
26         {"performance", "TODO: Doc"},
27         {"powersave", "TODO: Doc"},
28     }),
29
30     [](std::string val){if (val != "performance") sg_host_dvfs_plugin_init();});
31
32 /** @addtogroup SURF_plugin_load
33
34   This plugin makes it very simple for users to obtain the current load for each host.
35
36 */
37
38 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(surf_plugin_dvfs, surf, "Logging specific to the SURF HostDvfs plugin");
39
40 namespace simgrid {
41 namespace plugin {
42
43 namespace dvfs {
44 class Governor {
45
46 private:
47   simgrid::s4u::Host* const host_;
48   double sampling_rate_;
49
50 protected:
51   simgrid::s4u::Host* get_host() const { return host_; }
52
53 public:
54
55   explicit Governor(simgrid::s4u::Host* ptr) : host_(ptr) { init(); }
56   virtual ~Governor() = default;
57   virtual std::string get_name() = 0;
58
59   void init()
60   {
61     const char* local_sampling_rate_config = host_->get_property(cfg_sampling_rate.get_name());
62     double global_sampling_rate_config     = cfg_sampling_rate;
63     if (local_sampling_rate_config != nullptr) {
64       sampling_rate_ = std::stod(local_sampling_rate_config);
65     } else {
66       sampling_rate_ = global_sampling_rate_config;
67     }
68   }
69
70   virtual void update()         = 0;
71   double get_sampling_rate() { return sampling_rate_; }
72 };
73
74 /**
75  * The linux kernel doc describes this governor as follows:
76  * https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
77  *
78  * > The CPUfreq governor "performance" sets the CPU statically to the
79  * > highest frequency within the borders of scaling_min_freq and
80  * > scaling_max_freq.
81  *
82  * We do not support scaling_min_freq/scaling_max_freq -- we just pick the lowest frequency.
83  */
84 class Performance : public Governor {
85 public:
86   explicit Performance(simgrid::s4u::Host* ptr) : Governor(ptr) {}
87   std::string get_name() override { return "Performance"; }
88
89   void update() override { get_host()->set_pstate(0); }
90 };
91
92 /**
93  * The linux kernel doc describes this governor as follows:
94  * https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
95  *
96  * > The CPUfreq governor "powersave" sets the CPU statically to the
97  * > lowest frequency within the borders of scaling_min_freq and
98  * > scaling_max_freq.
99  *
100  * We do not support scaling_min_freq/scaling_max_freq -- we just pick the lowest frequency.
101  */
102 class Powersave : public Governor {
103 public:
104   explicit Powersave(simgrid::s4u::Host* ptr) : Governor(ptr) {}
105   std::string get_name() override { return "Powersave"; }
106
107   void update() override { get_host()->set_pstate(get_host()->get_pstate_count() - 1); }
108 };
109
110 /**
111  * The linux kernel doc describes this governor as follows:
112  * https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
113  *
114  * > The CPUfreq governor "ondemand" sets the CPU frequency depending on the
115  * > current system load. [...] when triggered, cpufreq checks
116  * > the CPU-usage statistics over the last period and the governor sets the
117  * > CPU accordingly.
118  */
119 class OnDemand : public Governor {
120   /**
121    * See https://elixir.bootlin.com/linux/v4.15.4/source/drivers/cpufreq/cpufreq_ondemand.c
122    * DEF_FREQUENCY_UP_THRESHOLD and od_update()
123    */
124   double freq_up_threshold_ = 0.80;
125
126 public:
127   explicit OnDemand(simgrid::s4u::Host* ptr) : Governor(ptr) {}
128   std::string get_name() override { return "OnDemand"; }
129
130   void update() override
131   {
132     double load = get_host()->get_core_count() * sg_host_get_avg_load(get_host());
133     sg_host_load_reset(get_host()); // Only consider the period between two calls to this method!
134
135     if (load > freq_up_threshold_) {
136       get_host()->set_pstate(0); /* Run at max. performance! */
137       XBT_INFO("Load: %f > threshold: %f --> changed to pstate %i", load, freq_up_threshold_, 0);
138     } else {
139       /* The actual implementation uses a formula here: (See Kernel file cpufreq_ondemand.c:158)
140        *
141        *    freq_next = min_f + load * (max_f - min_f) / 100
142        *
143        * So they assume that frequency increases by 100 MHz. We will just use
144        * lowest_pstate - load*pstatesCount()
145        */
146       int max_pstate = get_host()->get_pstate_count() - 1;
147       // Load is now < freq_up_threshold; exclude pstate 0 (the fastest)
148       // because pstate 0 can only be selected if load > freq_up_threshold_
149       int new_pstate = max_pstate - load * (max_pstate + 1);
150       get_host()->set_pstate(new_pstate);
151
152       XBT_DEBUG("Load: %f < threshold: %f --> changed to pstate %i", load, freq_up_threshold_, new_pstate);
153     }
154   }
155 };
156
157 /**
158  * This is the conservative governor, which is very similar to the
159  * OnDemand governor. The Linux Kernel Documentation describes it
160  * very well, see https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt:
161  *
162  * > The CPUfreq governor "conservative", much like the "ondemand"
163  * > governor, sets the CPU frequency depending on the current usage.  It
164  * > differs in behaviour in that it gracefully increases and decreases the
165  * > CPU speed rather than jumping to max speed the moment there is any load
166  * > on the CPU. This behaviour is more suitable in a battery powered
167  * > environment.
168  */
169 class Conservative : public Governor {
170   double freq_up_threshold_   = .8;
171   double freq_down_threshold_ = .2;
172
173 public:
174   explicit Conservative(simgrid::s4u::Host* ptr) : Governor(ptr) {}
175   virtual std::string get_name() override { return "Conservative"; }
176
177   virtual void update() override
178   {
179     double load = get_host()->get_core_count() * sg_host_get_avg_load(get_host());
180     int pstate  = get_host()->get_pstate();
181     sg_host_load_reset(get_host()); // Only consider the period between two calls to this method!
182
183     if (load > freq_up_threshold_) {
184       if (pstate != 0) {
185         get_host()->set_pstate(pstate - 1);
186         XBT_INFO("Load: %f > threshold: %f -> increasing performance to pstate %d", load, freq_up_threshold_,
187                  pstate - 1);
188       } else {
189         XBT_DEBUG("Load: %f > threshold: %f -> but cannot speed up even more, already in highest pstate %d", load,
190                   freq_up_threshold_, pstate);
191       }
192     } else if (load < freq_down_threshold_) {
193       int max_pstate = get_host()->get_pstate_count() - 1;
194       if (pstate != max_pstate) { // Are we in the slowest pstate already?
195         get_host()->set_pstate(pstate + 1);
196         XBT_INFO("Load: %f < threshold: %f -> slowing down to pstate %d", load, freq_down_threshold_, pstate + 1);
197       } else {
198         XBT_DEBUG("Load: %f < threshold: %f -> cannot slow down even more, already in slowest pstate %d", load,
199                   freq_down_threshold_, pstate);
200       }
201     }
202   }
203 };
204
205 /**
206  *  Add this to your host tag:
207  *    - \<prop id="plugin/dvfs/governor" value="performance" /\>
208  *
209  *  Valid values as of now are: performance, powersave, ondemand, conservative
210  *  It doesn't matter if you use uppercase or lowercase.
211  *
212  *  For the sampling rate, use this:
213  *
214  *    - \<prop id="plugin/dvfs/sampling-rate" value="2" /\>
215  *
216  *  This will run the update() method of the specified governor every 2 seconds
217  *  on that host.
218  *
219  *  These properties can also be used within the \<config\> tag to configure
220  *  these values globally. Using them within the \<host\> will overwrite this
221  *  global configuration
222  */
223 } // namespace dvfs
224 } // namespace plugin
225 } // namespace simgrid
226
227 /* **************************** events  callback *************************** */
228 static void on_host_added(simgrid::s4u::Host& host)
229 {
230   if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
231     return;
232
233   std::string name              = std::string("dvfs-daemon-") + host.get_cname();
234   simgrid::s4u::ActorPtr daemon = simgrid::s4u::Actor::create(name.c_str(), &host, []() {
235     /**
236      * This lambda function is the function the actor (daemon) will execute
237      * all the time - in the case of the dvfs plugin, this controls when to
238      * lower/raise the frequency.
239      */
240     simgrid::s4u::ActorPtr daemon_proc = simgrid::s4u::Actor::self();
241
242     XBT_DEBUG("DVFS process on %s is a daemon: %d", daemon_proc->get_host()->get_cname(), daemon_proc->is_daemon());
243
244     std::string dvfs_governor;
245     const char* host_conf = daemon_proc->get_host()->get_property("plugin/dvfs/governor");
246     if (host_conf != nullptr) {
247       dvfs_governor = std::string(host_conf);
248       boost::algorithm::to_lower(dvfs_governor);
249     } else {
250       dvfs_governor = cfg_governor;
251       boost::algorithm::to_lower(dvfs_governor);
252     }
253
254     auto governor = [&dvfs_governor, &daemon_proc]() {
255       if (dvfs_governor == "conservative") {
256         return std::unique_ptr<simgrid::plugin::dvfs::Governor>(
257             new simgrid::plugin::dvfs::Conservative(daemon_proc->get_host()));
258       } else if (dvfs_governor == "ondemand") {
259         return std::unique_ptr<simgrid::plugin::dvfs::Governor>(
260             new simgrid::plugin::dvfs::OnDemand(daemon_proc->get_host()));
261       } else if (dvfs_governor == "performance") {
262         return std::unique_ptr<simgrid::plugin::dvfs::Governor>(
263             new simgrid::plugin::dvfs::Performance(daemon_proc->get_host()));
264       } else if (dvfs_governor == "powersave") {
265         return std::unique_ptr<simgrid::plugin::dvfs::Governor>(
266             new simgrid::plugin::dvfs::Powersave(daemon_proc->get_host()));
267       } else {
268         XBT_CRITICAL("No governor specified for host %s, falling back to Performance",
269                      daemon_proc->get_host()->get_cname());
270         return std::unique_ptr<simgrid::plugin::dvfs::Governor>(
271             new simgrid::plugin::dvfs::Performance(daemon_proc->get_host()));
272       }
273     }();
274
275     while (1) {
276       // Sleep *before* updating; important for startup (i.e., t = 0).
277       // In the beginning, we want to go with the pstates specified in the platform file
278       // (so we sleep first)
279       simgrid::s4u::this_actor::sleep_for(governor->get_sampling_rate());
280       governor->update();
281       XBT_DEBUG("Governor (%s) just updated!", governor->get_name().c_str());
282     }
283
284     XBT_WARN("I should have never reached this point: daemons should be killed when all regular processes are done");
285     return 0;
286   });
287
288   // This call must be placed in this function. Otherwise, the daemonize() call comes too late and
289   // SMPI will take this process as an MPI process!
290   daemon->daemonize();
291 }
292
293 /* **************************** Public interface *************************** */
294
295
296 /** \ingroup SURF_plugin_load
297  * \brief Initializes the HostDvfs plugin
298  * \details The HostDvfs plugin provides an API to get the current load of each host.
299  */
300 void sg_host_dvfs_plugin_init()
301 {
302   static bool inited = false;
303   if (inited)
304     return;
305   inited = true;
306
307   sg_host_load_plugin_init();
308
309   simgrid::s4u::Host::on_creation.connect(&on_host_added);
310 }