Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'master' into fix/execute_benched
[simgrid.git] / src / surf / plugins / host_energy.cpp
1 /* Copyright (c) 2010-2017. 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/energy.h"
7 #include "simgrid/simix.hpp"
8 #include "src/plugins/vm/VirtualMachineImpl.hpp"
9 #include "src/surf/cpu_interface.hpp"
10
11 #include "simgrid/s4u/Engine.hpp"
12
13 #include <boost/algorithm/string/classification.hpp>
14 #include <boost/algorithm/string/split.hpp>
15 #include <string>
16 #include <utility>
17 #include <vector>
18
19 /** @addtogroup plugin_energy
20
21 This is the energy plugin, enabling to account not only for computation time, but also for the dissipated energy in the
22 simulated platform.
23 To activate this plugin, first call sg_host_energy_plugin_init() before your #MSG_init(), and then use
24 MSG_host_get_consumed_energy() to retrieve the consumption of a given host.
25
26 When the host is on, this energy consumption naturally depends on both the current CPU load and the host energy profile.
27 According to our measurements, the consumption is somehow linear in the amount of cores at full speed, with an
28 abnormality when all the cores are idle. The full details are in
29 <a href="https://hal.inria.fr/hal-01523608">our scientific paper</a> on that topic.
30
31 As a result, our energy model takes 4 parameters:
32
33   - \b Idle: instantaneous consumption (in Watt) when your host is up and running, but without anything to do.
34   - \b OneCore: instantaneous consumption (in Watt) when only one core is active, at 100%.
35   - \b AllCores: instantaneous consumption (in Watt) when all cores of the host are at 100%.
36   - \b Off: instantaneous consumption (in Watt) when the host is turned off.
37
38 Here is an example of XML declaration:
39
40 \code{.xml}
41 <host id="HostA" power="100.0Mf" cores="4">
42     <prop id="watt_per_state" value="100.0:120.0:200.0" />
43     <prop id="watt_off" value="10" />
44 </host>
45 \endcode
46
47 This example gives the following parameters: \b Off is 10 Watts; \b Idle is 100 Watts; \b OneCore is 120 Watts and \b
48 AllCores is 200 Watts.
49 This is enough to compute the consumption as a function of the amount of loaded cores:
50
51 <table>
52 <tr><th>#Cores loaded</th><th>Consumption</th><th>Explanation</th></tr>
53 <tr><td>0</td><td> 100 Watts</td><td>Idle value</td></tr>
54 <tr><td>1</td><td> 120 Watts</td><td>OneCore value</td></tr>
55 <tr><td>2</td><td> 147 Watts</td><td>linear extrapolation between OneCore and AllCores</td></tr>
56 <tr><td>3</td><td> 173 Watts</td><td>linear extrapolation between OneCore and AllCores</td></tr>
57 <tr><td>4</td><td> 200 Watts</td><td>AllCores value</td></tr>
58 </table>
59
60 ### What if a given core is only at load 50%?
61
62 This is impossible in SimGrid because we recompute everything each time that the CPU starts or stops doing something.
63 So if a core is at load 50% over a period, it means that it is at load 100% half of the time and at load 0% the rest of
64 the time, and our model holds.
65
66 ### What if the host has only one core?
67
68 In this case, the parameters \b OneCore and \b AllCores are obviously the same.
69 Actually, SimGrid expect an energetic profile formatted as 'Idle:Running' for mono-cores hosts.
70 If you insist on passing 3 parameters in this case, then you must have the same value for \b OneCore and \b AllCores.
71
72 \code{.xml}
73 <host id="HostC" power="100.0Mf" cores="1">
74     <prop id="watt_per_state" value="95.0:200.0" /> <!-- we may have used '95:200:200' instead -->
75     <prop id="watt_off" value="10" />
76 </host>
77 \endcode
78
79 ### How does DVFS interact with the host energy model?
80
81 If your host has several DVFS levels (several pstates), then you should give the energetic profile of each pstate level:
82
83 \code{.xml}
84 <host id="HostC" power="100.0Mf,50.0Mf,20.0Mf" cores="4">
85     <prop id="watt_per_state" value="95.0:120.0:200.0, 93.0:115.0:170.0, 90.0:110.0:150.0" />
86     <prop id="watt_off" value="10" />
87 </host>
88 \endcode
89
90 This encodes the following values
91 <table>
92 <tr><th>pstate</th><th>Performance</th><th>Idle</th><th>OneCore</th><th>AllCores</th></tr>
93 <tr><td>0</td><td>100 Mflop/s</td><td>95 Watts</td><td>120 Watts</td><td>200 Watts</td></tr>
94 <tr><td>1</td><td>50 Mflop/s</td><td>93 Watts</td><td>115 Watts</td><td>170 Watts</td></tr>
95 <tr><td>2</td><td>20 Mflop/s</td><td>90 Watts</td><td>110 Watts</td><td>150 Watts</td></tr>
96 </table>
97
98 To change the pstate of a given CPU, use the following functions:
99 #MSG_host_get_nb_pstates(), simgrid#s4u#Host#setPstate(), #MSG_host_get_power_peak_at().
100
101 ### How accurate are these models?
102
103 This model cannot be more accurate than your instantiation: with the default values, your result will not be accurate at
104 all. You can still get accurate energy prediction, provided that you carefully instantiate the model.
105 The first step is to ensure that your timing prediction match perfectly. But this is only the first step of the path,
106 and you really want to read <a href="https://hal.inria.fr/hal-01523608">this paper</a> to see all what you need to do
107 before you can get accurate energy predictions.
108  */
109
110 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(surf_energy, surf, "Logging specific to the SURF energy plugin");
111
112 namespace simgrid {
113 namespace plugin {
114
115 class PowerRange {
116 public:
117   double idle;
118   double min;
119   double max;
120
121   PowerRange(double idle, double min, double max) : idle(idle), min(min), max(max) {}
122 };
123
124 class HostEnergy {
125 public:
126   static simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> EXTENSION_ID;
127
128   explicit HostEnergy(simgrid::s4u::Host* ptr);
129   ~HostEnergy();
130
131   double getCurrentWattsValue(double cpu_load);
132   double getConsumedEnergy();
133   double getWattMinAt(int pstate);
134   double getWattMaxAt(int pstate);
135   void update();
136
137 private:
138   void initWattsRangeList();
139   simgrid::s4u::Host* host = nullptr;
140   std::vector<PowerRange>
141       power_range_watts_list; /*< List of (min_power,max_power) pairs corresponding to each cpu pstate */
142
143   /* We need to keep track of what pstate has been used, as we will sometimes be notified only *after* a pstate has been
144    * used (but we need to update the energy consumption with the old pstate!)
145    */
146   int pstate = 0;
147   const int pstate_off = -1;
148
149 public:
150   double watts_off    = 0.0; /*< Consumption when the machine is turned off (shutdown) */
151   double total_energy = 0.0; /*< Total energy consumed by the host */
152   double last_updated;       /*< Timestamp of the last energy update event*/
153 };
154
155 simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> HostEnergy::EXTENSION_ID;
156
157 /* Computes the consumption so far. Called lazily on need. */
158 void HostEnergy::update()
159 {
160   double start_time  = this->last_updated;
161   double finish_time = surf_get_clock();
162   double current_speed = host->getSpeed();
163
164   if (start_time < finish_time) {
165     double cpu_load;
166     // We may have start == finish if the past consumption was updated since the simcall was started
167     // for example if 2 actors requested to update the same host's consumption in a given scheduling round.
168     //
169     // Even in this case, we need to save the pstate for the next call (after this big if),
170     // which may have changed since that recent update.
171
172     if (current_speed <= 0)
173       // Some users declare a pstate of speed 0 flops (e.g., to model boot time).
174       // We consider that the machine is then fully loaded. That's arbitrary but it avoids a NaN
175       cpu_load = 1;
176     else
177       cpu_load = host->pimpl_cpu->constraint()->get_usage() / current_speed;
178
179     /** Divide by the number of cores here **/
180     cpu_load /= host->pimpl_cpu->coreCount();
181
182     if (cpu_load > 1) // A machine with a load > 1 consumes as much as a fully loaded machine, not more
183       cpu_load = 1;
184
185     /* The problem with this model is that the load is always 0 or 1, never something less.
186      * Another possibility could be to model the total energy as
187      *
188      *   X/(X+Y)*W_idle + Y/(X+Y)*W_burn
189      *
190      * where X is the amount of idling cores, and Y the amount of computing cores.
191      */
192
193     double previous_energy = this->total_energy;
194
195     double instantaneous_consumption;
196     if (this->pstate == pstate_off) // The host was off at the beginning of this time interval
197       instantaneous_consumption = this->watts_off;
198     else
199       instantaneous_consumption = this->getCurrentWattsValue(cpu_load);
200
201     double energy_this_step = instantaneous_consumption * (finish_time - start_time);
202
203     // TODO Trace: Trace energy_this_step from start_time to finish_time in host->getName()
204
205     this->total_energy = previous_energy + energy_this_step;
206     this->last_updated = finish_time;
207
208     XBT_DEBUG("[update_energy of %s] period=[%.2f-%.2f]; current power peak=%.0E flop/s; consumption change: %.2f J -> "
209               "%.2f J",
210               host->getCname(), start_time, finish_time, host->pimpl_cpu->speed_.peak, previous_energy,
211               energy_this_step);
212   }
213
214   /* Save data for the upcoming time interval: whether it's on/off and the pstate if it's on */
215   this->pstate = host->isOn() ? host->getPstate() : pstate_off;
216 }
217
218 HostEnergy::HostEnergy(simgrid::s4u::Host* ptr) : host(ptr), last_updated(surf_get_clock())
219 {
220   initWattsRangeList();
221
222   const char* off_power_str = host->getProperty("watt_off");
223   if (off_power_str != nullptr) {
224     try {
225       this->watts_off = std::stod(std::string(off_power_str));
226     } catch (std::invalid_argument& ia) {
227       throw std::invalid_argument(std::string("Invalid value for property watt_off of host ") + host->getCname() +
228                                   ": " + off_power_str);
229     }
230   }
231   /* watts_off is 0 by default */
232 }
233
234 HostEnergy::~HostEnergy() = default;
235
236 double HostEnergy::getWattMinAt(int pstate)
237 {
238   xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->getCname());
239   return power_range_watts_list[pstate].min;
240 }
241
242 double HostEnergy::getWattMaxAt(int pstate)
243 {
244   xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->getCname());
245   return power_range_watts_list[pstate].max;
246 }
247
248 /** @brief Computes the power consumed by the host according to the current pstate and processor load */
249 double HostEnergy::getCurrentWattsValue(double cpu_load)
250 {
251   xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->getCname());
252
253  /*
254   *    * Return watts_off if pstate == pstate_off
255   *       * this happens when host is off
256   */
257   if (this->pstate == pstate_off) {
258     return watts_off;
259   }
260
261   /* min_power corresponds to the power consumed when only one core is active */
262   /* max_power is the power consumed at 100% cpu load       */
263   auto range           = power_range_watts_list.at(this->pstate);
264   double current_power = 0;
265   double min_power     = 0;
266   double max_power     = 0;
267   double power_slope   = 0;
268
269   if (cpu_load > 0) { /* Something is going on, the machine is not idle */
270     double min_power = range.min;
271     double max_power = range.max;
272
273     /**
274      * The min_power states how much we consume when only one single
275      * core is working. This means that when cpu_load == 1/coreCount, then
276      * current_power == min_power.
277      *
278      * The maximum must be reached when all cores are working (but 1 core was
279      * already accounted for by min_power)
280      * i.e., we need min_power + (maxCpuLoad-1/coreCount)*power_slope == max_power
281      * (maxCpuLoad is by definition 1)
282      */
283     double power_slope;
284     int coreCount         = host->getCoreCount();
285     double coreReciprocal = static_cast<double>(1) / static_cast<double>(coreCount);
286     if (coreCount > 1)
287       power_slope = (max_power - min_power) / (1 - coreReciprocal);
288     else
289       power_slope = 0; // Should be 0, since max_power == min_power (in this case)
290
291     current_power = min_power + (cpu_load - coreReciprocal) * power_slope;
292   } else { /* Our machine is idle, take the dedicated value! */
293     current_power = range.idle;
294   }
295
296   XBT_DEBUG("[get_current_watts] min_power=%f, max_power=%f, slope=%f", min_power, max_power, power_slope);
297   XBT_DEBUG("[get_current_watts] Current power (watts) = %f, load = %f", current_power, cpu_load);
298
299   return current_power;
300 }
301
302 double HostEnergy::getConsumedEnergy()
303 {
304   if (last_updated < surf_get_clock()) // We need to simcall this as it modifies the environment
305     simgrid::simix::kernelImmediate(std::bind(&HostEnergy::update, this));
306
307   return total_energy;
308 }
309
310 void HostEnergy::initWattsRangeList()
311 {
312   const char* all_power_values_str = host->getProperty("watt_per_state");
313   if (all_power_values_str == nullptr)
314     return;
315
316   std::vector<std::string> all_power_values;
317   boost::split(all_power_values, all_power_values_str, boost::is_any_of(","));
318   XBT_DEBUG("%s: profile: %s, cores: %d", host->getCname(), all_power_values_str, host->getCoreCount());
319
320   int i = 0;
321   for (auto const& current_power_values_str : all_power_values) {
322     /* retrieve the power values associated with the current pstate */
323     std::vector<std::string> current_power_values;
324     boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
325     if (host->getCoreCount() == 1) {
326       xbt_assert(current_power_values.size() == 2 || current_power_values.size() == 3,
327                  "Power properties incorrectly defined for host %s."
328                  "It should be 'Idle:FullSpeed' power values because you have one core only.",
329                  host->getCname());
330       if (current_power_values.size() == 2) {
331         // In this case, 1core == AllCores
332         current_power_values.push_back(current_power_values.at(1));
333       } else { // size == 3
334         xbt_assert((current_power_values.at(1)) == (current_power_values.at(2)),
335                    "Power properties incorrectly defined for host %s.\n"
336                    "The energy profile of mono-cores should be formatted as 'Idle:FullSpeed' only.\n"
337                    "If you go for a 'Idle:OneCore:AllCores' power profile on mono-cores, then OneCore and AllCores "
338                    "must be equal.",
339                    host->getCname());
340       }
341     } else {
342       xbt_assert(current_power_values.size() == 3,
343                  "Power properties incorrectly defined for host %s."
344                  "It should be 'Idle:OneCore:AllCores' power values because you have more than one core.",
345                  host->getCname());
346     }
347
348     /* min_power corresponds to the idle power (cpu load = 0) */
349     /* max_power is the power consumed at 100% cpu load       */
350     char* msg_idle = bprintf("Invalid idle value for pstate %d on host %s: %%s", i, host->getCname());
351     char* msg_min  = bprintf("Invalid OneCore value for pstate %d on host %s: %%s", i, host->getCname());
352     char* msg_max  = bprintf("Invalid AllCores value for pstate %d on host %s: %%s", i, host->getCname());
353     PowerRange range(xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle),
354                      xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_min),
355                      xbt_str_parse_double((current_power_values.at(2)).c_str(), msg_max));
356     power_range_watts_list.push_back(range);
357     xbt_free(msg_idle);
358     xbt_free(msg_min);
359     xbt_free(msg_max);
360     i++;
361   }
362 }
363 }
364 }
365
366 using simgrid::plugin::HostEnergy;
367
368 /* **************************** events  callback *************************** */
369 static void onCreation(simgrid::s4u::Host& host)
370 {
371   if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
372     return;
373
374   // TODO Trace: set to zero the energy variable associated to host->getName()
375
376   host.extension_set(new HostEnergy(&host));
377 }
378
379 static void onActionStateChange(simgrid::surf::CpuAction* action, simgrid::surf::Action::State previous)
380 {
381   for (simgrid::surf::Cpu* const& cpu : action->cpus()) {
382     simgrid::s4u::Host* host = cpu->getHost();
383     if (host != nullptr) {
384
385       // If it's a VM, take the corresponding PM
386       simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
387       if (vm) // If it's a VM, take the corresponding PM
388         host = vm->pimpl_vm_->getPm();
389
390       // Get the host_energy extension for the relevant host
391       HostEnergy* host_energy = host->extension<HostEnergy>();
392
393       if (host_energy->last_updated < surf_get_clock())
394         host_energy->update();
395     }
396   }
397 }
398
399 /* This callback is fired either when the host changes its state (on/off) ("onStateChange") or its speed
400  * (because the user changed the pstate, or because of external trace events) ("onSpeedChange") */
401 static void onHostChange(simgrid::s4u::Host& host)
402 {
403   if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
404     return;
405
406   HostEnergy* host_energy = host.extension<HostEnergy>();
407
408   host_energy->update();
409 }
410
411 static void onHostDestruction(simgrid::s4u::Host& host)
412 {
413   if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
414     return;
415
416   HostEnergy* host_energy = host.extension<HostEnergy>();
417   host_energy->update();
418   XBT_INFO("Energy consumption of host %s: %f Joules", host.getCname(), host_energy->getConsumedEnergy());
419 }
420
421 static void onSimulationEnd()
422 {
423   sg_host_t* host_list     = sg_host_list();
424   int host_count           = sg_host_count();
425   double total_energy      = 0.0; // Total energy consumption (whole platform)
426   double used_hosts_energy = 0.0; // Energy consumed by hosts that computed something
427   for (int i = 0; i < host_count; i++) {
428     if (dynamic_cast<simgrid::s4u::VirtualMachine*>(host_list[i]) == nullptr) { // Ignore virtual machines
429
430       bool host_was_used = (host_list[i]->extension<HostEnergy>()->last_updated != 0);
431       double energy      = host_list[i]->extension<HostEnergy>()->getConsumedEnergy();
432       total_energy      += energy;
433       if (host_was_used)
434         used_hosts_energy += energy;
435     }
436   }
437   XBT_INFO("Total energy consumption: %f Joules (used hosts: %f Joules; unused/idle hosts: %f)",
438            total_energy, used_hosts_energy, total_energy - used_hosts_energy);
439   xbt_free(host_list);
440 }
441
442 /* **************************** Public interface *************************** */
443 extern "C" {
444
445 /** \ingroup plugin_energy
446  * \brief Enable host energy plugin
447  * \details Enable energy plugin to get joules consumption of each cpu. Call this function before #MSG_init().
448  */
449 void sg_host_energy_plugin_init()
450 {
451   if (HostEnergy::EXTENSION_ID.valid())
452     return;
453
454   HostEnergy::EXTENSION_ID = simgrid::s4u::Host::extension_create<HostEnergy>();
455
456   simgrid::s4u::Host::onCreation.connect(&onCreation);
457   simgrid::s4u::Host::onStateChange.connect(&onHostChange);
458   simgrid::s4u::Host::onSpeedChange.connect(&onHostChange);
459   simgrid::s4u::Host::onDestruction.connect(&onHostDestruction);
460   simgrid::s4u::onSimulationEnd.connect(&onSimulationEnd);
461   simgrid::surf::CpuAction::onStateChange.connect(&onActionStateChange);
462 }
463
464 /** @ingroup plugin_energy
465  *  @brief updates the consumption of all hosts
466  *
467  * After this call, sg_host_get_consumed_energy() will not interrupt your process
468  * (until after the next clock update).
469  */
470 void sg_host_energy_update_all()
471 {
472   simgrid::simix::kernelImmediate([]() {
473     std::vector<simgrid::s4u::Host*> list;
474     simgrid::s4u::Engine::getInstance()->getHostList(&list);
475     for (auto const& host : list)
476       if (dynamic_cast<simgrid::s4u::VirtualMachine*>(host) == nullptr) // Ignore virtual machines
477         host->extension<HostEnergy>()->update();
478   });
479 }
480
481 /** @ingroup plugin_energy
482  *  @brief Returns the total energy consumed by the host so far (in Joules)
483  *
484  *  Please note that since the consumption is lazily updated, it may require a simcall to update it.
485  *  The result is that the actor requesting this value will be interrupted,
486  *  the value will be updated in kernel mode before returning the control to the requesting actor.
487  */
488 double sg_host_get_consumed_energy(sg_host_t host)
489 {
490   xbt_assert(HostEnergy::EXTENSION_ID.valid(),
491              "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
492   return host->extension<HostEnergy>()->getConsumedEnergy();
493 }
494
495 /** @ingroup plugin_energy
496  *  @brief Get the amount of watt dissipated at the given pstate when the host is idling
497  */
498 double sg_host_get_wattmin_at(sg_host_t host, int pstate)
499 {
500   xbt_assert(HostEnergy::EXTENSION_ID.valid(),
501              "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
502   return host->extension<HostEnergy>()->getWattMinAt(pstate);
503 }
504 /** @ingroup plugin_energy
505  *  @brief  Returns the amount of watt dissipated at the given pstate when the host burns CPU at 100%
506  */
507 double sg_host_get_wattmax_at(sg_host_t host, int pstate)
508 {
509   xbt_assert(HostEnergy::EXTENSION_ID.valid(),
510              "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
511   return host->extension<HostEnergy>()->getWattMaxAt(pstate);
512 }
513
514 /** @ingroup plugin_energy
515  *  @brief Returns the current consumption of the host
516  */
517 double sg_host_get_current_consumption(sg_host_t host)
518 {
519   xbt_assert(HostEnergy::EXTENSION_ID.valid(),
520              "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
521   double cpu_load = host->pimpl_cpu->constraint()->get_usage() / host->getSpeed();
522   return host->extension<HostEnergy>()->getCurrentWattsValue(cpu_load);
523 }
524 }