1 /* Copyright (c) 2010-2017. The SimGrid Team. All rights reserved. */
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. */
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"
11 #include "simgrid/s4u/Engine.hpp"
13 #include <boost/algorithm/string/classification.hpp>
14 #include <boost/algorithm/string/split.hpp>
19 /** @addtogroup SURF_plugin_energy
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.
25 The energy consumption of a CPU depends directly of its current load. Specify that consumption in your platform file as
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" />
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).
40 If your CPU is using pstates, then you can provide one consumption interval per pstate.
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" />
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.
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().
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().
62 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(surf_energy, surf, "Logging specific to the SURF energy plugin");
73 PowerRange(double idle, double min, double max) : idle(idle), min(min), max(max) {}
78 static simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> EXTENSION_ID;
80 explicit HostEnergy(simgrid::s4u::Host* ptr);
83 double getCurrentWattsValue(double cpu_load);
84 double getConsumedEnergy();
85 double getWattMinAt(int pstate);
86 double getWattMaxAt(int pstate);
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 */
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!)
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*/
107 simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> HostEnergy::EXTENSION_ID;
109 /* Computes the consumption so far. Called lazily on need. */
110 void HostEnergy::update()
112 double start_time = this->last_updated;
113 double finish_time = surf_get_clock();
115 double current_speed = host->speed();
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.
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.
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
129 cpu_load = lmm_constraint_get_usage(host->pimpl_cpu->constraint()) / current_speed;
131 /** Divide by the number of cores here **/
132 cpu_load /= host->pimpl_cpu->coreCount();
134 if (cpu_load > 1) // A machine with a load > 1 consumes as much as a fully loaded machine, not more
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
140 * X/(X+Y)*W_idle + Y/(X+Y)*W_burn
142 * where X is the amount of idling cores, and Y the amount of computing cores.
145 double previous_energy = this->total_energy;
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;
151 instantaneous_consumption = this->getCurrentWattsValue(cpu_load);
153 double energy_this_step = instantaneous_consumption * (finish_time - start_time);
155 // TODO Trace: Trace energy_this_step from start_time to finish_time in host->name()
157 this->total_energy = previous_energy + energy_this_step;
158 this->last_updated = finish_time;
160 XBT_DEBUG("[update_energy of %s] period=[%.2f-%.2f]; current power peak=%.0E flop/s; consumption change: %.2f J -> "
162 host->cname(), start_time, finish_time, host->pimpl_cpu->speed_.peak, previous_energy, energy_this_step);
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;
169 HostEnergy::HostEnergy(simgrid::s4u::Host* ptr) : host(ptr), last_updated(surf_get_clock())
171 initWattsRangeList();
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);
179 /* watts_off is 0 by default */
182 HostEnergy::~HostEnergy() = default;
184 double HostEnergy::getWattMinAt(int pstate)
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;
190 double HostEnergy::getWattMaxAt(int pstate)
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;
196 /** @brief Computes the power consumed by the host according to the current pstate and processor load */
197 double HostEnergy::getCurrentWattsValue(double cpu_load)
199 xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->cname());
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;
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;
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.
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)
224 int coreCount = host->coreCount();
225 double coreReciprocal = static_cast<double>(1) / static_cast<double>(coreCount);
227 power_slope = (max_power - min_power) / (1 - coreReciprocal);
229 power_slope = 0; // Should be 0, since max_power == min_power (in this case)
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;
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);
239 return current_power;
242 double HostEnergy::getConsumedEnergy()
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));
250 void HostEnergy::initWattsRangeList()
252 const char* all_power_values_str = host->property("watt_per_state");
253 if (all_power_values_str == nullptr)
256 std::vector<std::string> all_power_values;
257 boost::split(all_power_values, all_power_values_str, boost::is_any_of(","));
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",
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);
286 using simgrid::energy::HostEnergy;
288 /* **************************** events callback *************************** */
289 static void onCreation(simgrid::s4u::Host& host)
291 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
294 //TODO Trace: set to zero the energy variable associated to host->name()
296 host.extension_set(new HostEnergy(&host));
299 static void onActionStateChange(simgrid::surf::CpuAction* action, simgrid::surf::Action::State previous)
301 for (simgrid::surf::Cpu* cpu : action->cpus()) {
302 simgrid::s4u::Host* host = cpu->getHost();
303 if (host != nullptr) {
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();
310 // Get the host_energy extension for the relevant host
311 HostEnergy* host_energy = host->extension<HostEnergy>();
313 if (host_energy->last_updated < surf_get_clock())
314 host_energy->update();
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)
323 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
326 HostEnergy* host_energy = host.extension<HostEnergy>();
328 host_energy->update();
331 static void onHostDestruction(simgrid::s4u::Host& host)
333 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
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());
341 static void onSimulationEnd()
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
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;
354 used_hosts_energy += energy;
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);
362 /* **************************** Public interface *************************** */
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().
369 void sg_host_energy_plugin_init()
371 if (HostEnergy::EXTENSION_ID.valid())
374 HostEnergy::EXTENSION_ID = simgrid::s4u::Host::extension_create<HostEnergy>();
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);
384 /** @brief updates the consumption of all hosts
386 * After this call, sg_host_get_consumed_energy() will not interrupt your process
387 * (until after the next clock update).
389 void sg_host_energy_update_all()
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();
399 /** @brief Returns the total energy consumed by the host so far (in Joules)
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.
405 * See also @ref SURF_plugin_energy.
407 double sg_host_get_consumed_energy(sg_host_t host)
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();
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)
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);
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)
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);