Logo AND Algorithmique Numérique Distribuée

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