Logo AND Algorithmique Numérique Distribuée

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