Logo AND Algorithmique Numérique Distribuée

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