Logo AND Algorithmique Numérique Distribuée

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