1 /* Copyright (c) 2010-2019. 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/s4u/Engine.hpp"
8 #include "simgrid/s4u/Exec.hpp"
9 #include "src/include/surf/surf.hpp"
10 #include "src/kernel/activity/ExecImpl.hpp"
11 #include "src/plugins/vm/VirtualMachineImpl.hpp"
12 #include "src/surf/cpu_interface.hpp"
14 #include <boost/algorithm/string/classification.hpp>
15 #include <boost/algorithm/string/split.hpp>
17 SIMGRID_REGISTER_PLUGIN(host_energy, "Cpu energy consumption.", &sg_host_energy_plugin_init)
19 /** @defgroup plugin_host_energy
21 This is the energy plugin, enabling to account not only for computation time, but also for the dissipated energy in the
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.
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.
31 As a result, our energy model takes 4 parameters:
33 - @b Idle: wattage (i.e., instantaneous consumption in Watt) when your host is up and running, but without anything to
35 - @b Epsilon: wattage when all cores are at 0 or epsilon%, but not in Idle state.
36 - @b AllCores: wattage when all cores of the host are at 100%.
37 - @b Off: wattage when the host is turned off.
39 Here is an example of XML declaration:
42 <host id="HostA" speed="100.0Mf" core="4">
43 <prop id="wattage_per_state" value="100.0:120.0:200.0" />
44 <prop id="wattage_off" value="10" />
48 If the 'Epsilon' parameter is omitted in the XML declaration, 'Idle' is used instead.
50 This example gives the following parameters: @b Off is 10 Watts; @b Idle is 100 Watts; @b Epsilon is 120 Watts and @b
51 AllCores is 200 Watts.
52 This is enough to compute the wattage as a function of the amount of loaded cores:
55 <tr><th>@#Cores loaded</th><th>Wattage</th><th>Explanation</th></tr>
56 <tr><td>0 (idle)</td><td> 100 Watts</td><td>Idle value</td></tr>
57 <tr><td>0 (not idle)</td><td> 120 Watts</td><td>Epsilon value</td></tr>
58 <tr><td>1</td><td> 140 Watts</td><td>linear extrapolation between Epsilon and AllCores</td></tr>
59 <tr><td>2</td><td> 160 Watts</td><td>linear extrapolation between Epsilon and AllCores</td></tr>
60 <tr><td>3</td><td> 180 Watts</td><td>linear extrapolation between Epsilon and AllCores</td></tr>
61 <tr><td>4</td><td> 200 Watts</td><td>AllCores value</td></tr>
65 ### How does DVFS interact with the host energy model?
67 If your host has several DVFS levels (several pstates), then you should give the energetic profile of each pstate level:
70 <host id="HostC" speed="100.0Mf,50.0Mf,20.0Mf" core="4">
71 <prop id="wattage_per_state" value="95.0:120.0:200.0, 93.0:115.0:170.0, 90.0:110.0:150.0" />
72 <prop id="wattage_off" value="10" />
76 This encodes the following values
78 <tr><th>pstate</th><th>Performance</th><th>Idle</th><th>Epsilon</th><th>AllCores</th></tr>
79 <tr><td>0</td><td>100 Mflop/s</td><td>95 Watts</td><td>120 Watts</td><td>200 Watts</td></tr>
80 <tr><td>1</td><td>50 Mflop/s</td><td>93 Watts</td><td>115 Watts</td><td>170 Watts</td></tr>
81 <tr><td>2</td><td>20 Mflop/s</td><td>90 Watts</td><td>110 Watts</td><td>150 Watts</td></tr>
84 To change the pstate of a given CPU, use the following functions:
85 #MSG_host_get_nb_pstates(), simgrid#s4u#Host#setPstate(), #MSG_host_get_power_peak_at().
87 ### How accurate are these models?
89 This model cannot be more accurate than your instantiation: with the default values, your result will not be accurate at
90 all. You can still get accurate energy prediction, provided that you carefully instantiate the model.
91 The first step is to ensure that your timing prediction match perfectly. But this is only the first step of the path,
92 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
93 before you can get accurate energy predictions.
96 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(surf_energy, surf, "Logging specific to the SURF energy plugin");
98 // Forwards declaration needed to make this function a friend (because friends have external linkage by default)
99 static void on_simulation_end();
111 PowerRange(double idle, double epsilon, double max) : idle_(idle), epsilon_(epsilon), max_(max), slope_(max-epsilon) {}
115 friend void ::on_simulation_end(); // For access to host_was_used_
117 static simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> EXTENSION_ID;
119 explicit HostEnergy(simgrid::s4u::Host* ptr);
122 double get_current_watts_value();
123 double get_current_watts_value(double cpu_load);
124 double get_consumed_energy();
125 double get_idle_consumption();
126 double get_watt_min_at(int pstate);
127 double get_watt_max_at(int pstate);
128 double get_power_range_slope_at(int pstate);
132 void init_watts_range_list();
133 simgrid::s4u::Host* host_ = nullptr;
134 /*< List of (idle_power, epsilon_power, max_power) tuple corresponding to each cpu pstate */
135 std::vector<PowerRange> power_range_watts_list_;
137 /* We need to keep track of what pstate has been used, as we will sometimes be notified only *after* a pstate has been
138 * used (but we need to update the energy consumption with the old pstate!)
141 const int pstate_off_ = -1;
143 /* Only used to split total energy into unused/used hosts.
144 * If you want to get this info for something else, rather use the host_load plugin
146 bool host_was_used_ = false;
148 double watts_off_ = 0.0; /*< Consumption when the machine is turned off (shutdown) */
149 double total_energy_ = 0.0; /*< Total energy consumed by the host */
150 double last_updated_; /*< Timestamp of the last energy update event*/
153 simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> HostEnergy::EXTENSION_ID;
155 /* Computes the consumption so far. Called lazily on need. */
156 void HostEnergy::update()
158 double start_time = this->last_updated_;
159 double finish_time = surf_get_clock();
161 // We may have start == finish if the past consumption was updated since the simcall was started
162 // for example if 2 actors requested to update the same host's consumption in a given scheduling round.
164 // Even in this case, we need to save the pstate for the next call (after this if),
165 // which may have changed since that recent update.
166 if (start_time < finish_time) {
167 double previous_energy = this->total_energy_;
169 double instantaneous_power_consumption = this->get_current_watts_value();
171 double energy_this_step = instantaneous_power_consumption * (finish_time - start_time);
173 // TODO Trace: Trace energy_this_step from start_time to finish_time in host->getName()
175 this->total_energy_ = previous_energy + energy_this_step;
176 this->last_updated_ = finish_time;
178 XBT_DEBUG("[update_energy of %s] period=[%.8f-%.8f]; current speed=%.2E flop/s (pstate %i); total consumption before: %.8f J -> added now: %.8f J",
179 host_->get_cname(), start_time, finish_time, host_->pimpl_cpu->get_pstate_peak_speed(this->pstate_), this->pstate_, previous_energy,
183 /* Save data for the upcoming time interval: whether it's on/off and the pstate if it's on */
184 this->pstate_ = host_->is_on() ? host_->get_pstate() : pstate_off_;
187 HostEnergy::HostEnergy(simgrid::s4u::Host* ptr) : host_(ptr), last_updated_(surf_get_clock())
189 init_watts_range_list();
190 static bool warned = false;
192 const char* off_power_str = host_->get_property("wattage_off");
193 if (off_power_str == nullptr) {
194 off_power_str = host_->get_property("watt_off");
195 if (off_power_str != nullptr && not warned) {
197 XBT_WARN("Please use 'wattage_off' instead of 'watt_off' to define the idle wattage of hosts in your XML.");
200 if (off_power_str != nullptr) {
202 this->watts_off_ = std::stod(std::string(off_power_str));
203 } catch (const std::invalid_argument&) {
204 throw std::invalid_argument(std::string("Invalid value for property wattage_off of host ") + host_->get_cname() +
205 ": " + off_power_str);
208 /* watts_off is 0 by default */
211 HostEnergy::~HostEnergy() = default;
213 double HostEnergy::get_idle_consumption()
215 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
218 return power_range_watts_list_[0].idle_;
221 double HostEnergy::get_watt_min_at(int pstate)
223 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
225 return power_range_watts_list_[pstate].epsilon_;
228 double HostEnergy::get_watt_max_at(int pstate)
230 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
232 return power_range_watts_list_[pstate].max_;
235 double HostEnergy::get_power_range_slope_at(int pstate)
237 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
239 return power_range_watts_list_[pstate].slope_;
242 /** @brief Computes the power consumed by the host according to the current situation
244 * - If the host is off, that's the watts_off value
245 * - if it's on, take the current pstate and the current processor load into account */
246 double HostEnergy::get_current_watts_value()
248 if (this->pstate_ == pstate_off_) // The host is off (or was off at the beginning of this time interval)
249 return this->watts_off_;
251 double current_speed = host_->get_pstate_speed(this->pstate_);
255 if (current_speed <= 0)
256 // Some users declare a pstate of speed 0 flops (e.g., to model boot time).
257 // We consider that the machine is then fully loaded. That's arbitrary but it avoids a NaN
260 cpu_load = host_->pimpl_cpu->get_constraint()->get_usage() / current_speed;
262 /* Divide by the number of cores here to have a value between 0 and 1 */
263 cpu_load /= host_->pimpl_cpu->get_core_count();
265 if (cpu_load > 1) // A machine with a load > 1 consumes as much as a fully loaded machine, not more
268 host_was_used_ = true;
271 return get_current_watts_value(cpu_load);
274 /** @brief Computes the power that the host would consume at the provided processor load
276 * Whether the host is ON or OFF is not taken into account.
278 double HostEnergy::get_current_watts_value(double cpu_load)
280 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
283 /* Return watts_off if pstate == pstate_off (ie, if the host is off) */
284 if (this->pstate_ == pstate_off_) {
288 PowerRange power_range = power_range_watts_list_.at(this->pstate_);
289 double current_power;
294 * Something is going on, the host is not idle.
296 * The power consumption follows the regular model:
297 * P(cpu_load) = Pstatic + Pdynamic * cpu_load
298 * where Pstatic = power_range.epsilon_ and Pdynamic = power_range.slope_
299 * and the cpu_load is a value between 0 and 1.
301 current_power = power_range.epsilon_ + cpu_load * power_range.slope_;
305 /* The host is idle, take the dedicated value! */
306 current_power = power_range.idle_;
309 XBT_DEBUG("[get_current_watts] pstate=%i, epsilon_power=%f, max_power=%f, slope=%f", this->pstate_, power_range.epsilon_,
310 power_range.max_, power_range.slope_);
311 XBT_DEBUG("[get_current_watts] Current power (watts) = %f, load = %f", current_power, cpu_load);
313 return current_power;
316 double HostEnergy::get_consumed_energy()
318 if (last_updated_ < surf_get_clock()) // We need to simcall this as it modifies the environment
319 simgrid::kernel::actor::simcall(std::bind(&HostEnergy::update, this));
321 return total_energy_;
324 void HostEnergy::init_watts_range_list()
326 const char* old_prop = host_->get_property("watt_per_state");
327 if (old_prop != nullptr) {
328 std::vector<std::string> all_power_values;
329 boost::split(all_power_values, old_prop, boost::is_any_of(","));
331 std::string msg = std::string("DEPRECATION WARNING: Property 'watt_per_state' will not work after v3.28.\n");
332 msg += std::string("The old syntax 'Idle:OneCore:AllCores' must be converted into 'Idle:Epsilon:AllCores' to "
333 "properly model the consumption of non-whole tasks on mono-core hosts. Here are the values to "
335 host_->get_cname() + "' in your XML file:\n";
336 msg += " <prop id=\"wattage_per_state\" value=\"";
337 for (auto const& current_power_values_str : all_power_values) {
338 std::vector<std::string> current_power_values;
339 boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
340 double p_idle = xbt_str_parse_double((current_power_values.at(0)).c_str(),
341 "Invalid obsolete XML file. Fix your watt_per_state property.");
346 if (current_power_values.size() == 2) { // Case: Idle:AllCores
347 p_full = xbt_str_parse_double((current_power_values.at(1)).c_str(),
348 "Invalid obsolete XML file. Fix your watt_per_state property.");
350 } else { // Case: Idle:Epsilon:AllCores
351 p_one_core = xbt_str_parse_double((current_power_values.at(1)).c_str(),
352 "Invalid obsolete XML file. Fix your watt_per_state property.");
353 p_full = xbt_str_parse_double((current_power_values.at(2)).c_str(),
354 "Invalid obsolete XML file. Fix your watt_per_state property.");
355 if (host_->get_core_count() == 1)
358 p_epsilon = p_one_core - ((p_full - p_one_core) / (host_->get_core_count() - 1));
360 PowerRange range(p_idle, p_epsilon, p_full);
361 power_range_watts_list_.push_back(range);
363 msg += std::to_string(p_idle) + ":" + std::to_string(p_epsilon) + ":" + std::to_string(p_full);
366 msg.pop_back(); // Remove the extraneous ','
368 XBT_WARN("%s", msg.c_str());
371 const char* all_power_values_str = host_->get_property("wattage_per_state");
372 if (all_power_values_str == nullptr)
375 std::vector<std::string> all_power_values;
376 boost::split(all_power_values, all_power_values_str, boost::is_any_of(","));
377 XBT_DEBUG("%s: power properties: %s", host_->get_cname(), all_power_values_str);
380 for (auto const& current_power_values_str : all_power_values) {
381 /* retrieve the power values associated with the pstate i */
382 std::vector<std::string> current_power_values;
383 boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
385 xbt_assert(current_power_values.size() == 2 || current_power_values.size() == 3,
386 "Power properties incorrectly defined for host %s."
387 "It should be 'Idle:AllCores' (or 'Idle:Epsilon:AllCores') power values.",
391 double epsilon_power;
394 char* msg_idle = bprintf("Invalid Idle value for pstate %d on host %s: %%s", i, host_->get_cname());
395 char* msg_epsilon = bprintf("Invalid Epsilon value for pstate %d on host %s: %%s", i, host_->get_cname());
396 char* msg_max = bprintf("Invalid AllCores value for pstate %d on host %s: %%s", i, host_->get_cname());
398 idle_power = xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle);
399 if (current_power_values.size() == 2) { // Case: Idle:AllCores
400 epsilon_power = xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle);
401 max_power = xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_max);
402 } else { // Case: Idle:Epsilon:AllCores
403 epsilon_power = xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_epsilon);
404 max_power = xbt_str_parse_double((current_power_values.at(2)).c_str(), msg_max);
407 XBT_DEBUG("Creating PowerRange for host %s. Idle:%f, Epsilon:%f, AllCores:%f.", host_->get_cname(), idle_power, epsilon_power, max_power);
409 PowerRange range(idle_power, epsilon_power, max_power);
410 power_range_watts_list_.push_back(range);
412 xbt_free(msg_epsilon);
417 } // namespace plugin
418 } // namespace simgrid
420 using simgrid::plugin::HostEnergy;
422 /* **************************** events callback *************************** */
423 static void on_creation(simgrid::s4u::Host& host)
425 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
428 // TODO Trace: set to zero the energy variable associated to host->getName()
430 host.extension_set(new HostEnergy(&host));
433 static void on_action_state_change(simgrid::kernel::resource::CpuAction const& action,
434 simgrid::kernel::resource::Action::State /*previous*/)
436 for (simgrid::kernel::resource::Cpu* const& cpu : action.cpus()) {
437 simgrid::s4u::Host* host = cpu->get_host();
438 if (host != nullptr) {
440 // If it's a VM, take the corresponding PM
441 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
442 if (vm) // If it's a VM, take the corresponding PM
445 // Get the host_energy extension for the relevant host
446 HostEnergy* host_energy = host->extension<HostEnergy>();
448 if (host_energy->last_updated_ < surf_get_clock())
449 host_energy->update();
454 /* This callback is fired either when the host changes its state (on/off) ("onStateChange") or its speed
455 * (because the user changed the pstate, or because of external trace events) ("onSpeedChange") */
456 static void on_host_change(simgrid::s4u::Host const& host)
458 if (dynamic_cast<simgrid::s4u::VirtualMachine const*>(&host)) // Ignore virtual machines
461 HostEnergy* host_energy = host.extension<HostEnergy>();
463 host_energy->update();
466 static void on_host_destruction(simgrid::s4u::Host const& host)
468 if (dynamic_cast<simgrid::s4u::VirtualMachine const*>(&host)) // Ignore virtual machines
471 XBT_INFO("Energy consumption of host %s: %f Joules", host.get_cname(),
472 host.extension<HostEnergy>()->get_consumed_energy());
475 static void on_simulation_end()
477 std::vector<simgrid::s4u::Host*> hosts = simgrid::s4u::Engine::get_instance()->get_all_hosts();
479 double total_energy = 0.0; // Total energy consumption (whole platform)
480 double used_hosts_energy = 0.0; // Energy consumed by hosts that computed something
481 for (size_t i = 0; i < hosts.size(); i++) {
482 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(hosts[i]) == nullptr) { // Ignore virtual machines
484 double energy = hosts[i]->extension<HostEnergy>()->get_consumed_energy();
485 total_energy += energy;
486 if (hosts[i]->extension<HostEnergy>()->host_was_used_)
487 used_hosts_energy += energy;
490 XBT_INFO("Total energy consumption: %f Joules (used hosts: %f Joules; unused/idle hosts: %f)", total_energy,
491 used_hosts_energy, total_energy - used_hosts_energy);
494 /* **************************** Public interface *************************** */
496 /** @ingroup plugin_host_energy
497 * @brief Enable host energy plugin
498 * @details Enable energy plugin to get joules consumption of each cpu. Call this function before #MSG_init().
500 void sg_host_energy_plugin_init()
502 if (HostEnergy::EXTENSION_ID.valid())
505 HostEnergy::EXTENSION_ID = simgrid::s4u::Host::extension_create<HostEnergy>();
507 simgrid::s4u::Host::on_creation.connect(&on_creation);
508 simgrid::s4u::Host::on_state_change.connect(&on_host_change);
509 simgrid::s4u::Host::on_speed_change.connect(&on_host_change);
510 simgrid::s4u::Host::on_destruction.connect(&on_host_destruction);
511 simgrid::s4u::Engine::on_simulation_end.connect(&on_simulation_end);
512 simgrid::kernel::resource::CpuAction::on_state_change.connect(&on_action_state_change);
513 // We may only have one actor on a node. If that actor executes something like
514 // compute -> recv -> compute
515 // the recv operation will not trigger a "CpuAction::on_state_change". This means
516 // that the next trigger would be the 2nd compute, hence ignoring the idle time
517 // during the recv call. By updating at the beginning of a compute, we can
518 // fix that. (If the cpu is not idle, this is not required.)
519 simgrid::s4u::Exec::on_start.connect([](simgrid::s4u::Actor const&, simgrid::s4u::Exec const& activity) {
520 if (activity.get_host_number() == 1) { // We only run on one host
521 simgrid::s4u::Host* host = activity.get_host();
522 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
525 xbt_assert(host != nullptr);
526 host->extension<HostEnergy>()->update();
531 /** @ingroup plugin_host_energy
532 * @brief updates the consumption of all hosts
534 * After this call, sg_host_get_consumed_energy() will not interrupt your process
535 * (until after the next clock update).
537 void sg_host_energy_update_all()
539 simgrid::kernel::actor::simcall([]() {
540 std::vector<simgrid::s4u::Host*> list = simgrid::s4u::Engine::get_instance()->get_all_hosts();
541 for (auto const& host : list)
542 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(host) == nullptr) { // Ignore virtual machines
543 xbt_assert(host != nullptr);
544 host->extension<HostEnergy>()->update();
549 /** @ingroup plugin_host_energy
550 * @brief Returns the total energy consumed by the host so far (in Joules)
552 * Please note that since the consumption is lazily updated, it may require a simcall to update it.
553 * The result is that the actor requesting this value will be interrupted,
554 * the value will be updated in kernel mode before returning the control to the requesting actor.
556 double sg_host_get_consumed_energy(sg_host_t host)
558 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
559 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
560 return host->extension<HostEnergy>()->get_consumed_energy();
563 /** @ingroup plugin_host_energy
564 * @brief Get the amount of watt dissipated when the host is idling
566 double sg_host_get_idle_consumption(sg_host_t host)
568 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
569 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
570 return host->extension<HostEnergy>()->get_idle_consumption();
573 /** @ingroup plugin_host_energy
574 * @brief Get the amount of watt dissipated at the given pstate when the host is idling
576 double sg_host_get_wattmin_at(sg_host_t host, int pstate)
578 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
579 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
580 return host->extension<HostEnergy>()->get_watt_min_at(pstate);
582 /** @ingroup plugin_host_energy
583 * @brief Returns the amount of watt dissipated at the given pstate when the host burns CPU at 100%
585 double sg_host_get_wattmax_at(sg_host_t host, int pstate)
587 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
588 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
589 return host->extension<HostEnergy>()->get_watt_max_at(pstate);
591 /** @ingroup plugin_host_energy
592 * @brief Returns the power slope at the given pstate
594 double sg_host_get_power_range_slope_at(sg_host_t host, int pstate)
596 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
597 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
598 return host->extension<HostEnergy>()->get_power_range_slope_at(pstate);
600 /** @ingroup plugin_host_energy
601 * @brief Returns the current consumption of the host
603 double sg_host_get_current_consumption(sg_host_t host)
605 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
606 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
607 return host->extension<HostEnergy>()->get_current_watts_value();