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();
264 xbt_assert(not(cpu_load > 1), "The impossible did happen, as usual.");
267 host_was_used_ = true;
270 return get_current_watts_value(cpu_load);
273 /** @brief Computes the power that the host would consume at the provided processor load
275 * Whether the host is ON or OFF is not taken into account.
277 double HostEnergy::get_current_watts_value(double cpu_load)
279 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
282 /* Return watts_off if pstate == pstate_off (ie, if the host is off) */
283 if (this->pstate_ == pstate_off_) {
287 PowerRange power_range = power_range_watts_list_.at(this->pstate_);
288 double current_power;
293 * Something is going on, the host is not idle.
295 * The power consumption follows the regular model:
296 * P(cpu_load) = Pstatic + Pdynamic * cpu_load
297 * where Pstatic = power_range.epsilon_ and Pdynamic = power_range.slope_
298 * and the cpu_load is a value between 0 and 1.
300 current_power = power_range.epsilon_ + cpu_load * power_range.slope_;
304 /* The host is idle, take the dedicated value! */
305 current_power = power_range.idle_;
308 XBT_DEBUG("[get_current_watts] pstate=%i, epsilon_power=%f, max_power=%f, slope=%f", this->pstate_, power_range.epsilon_,
309 power_range.max_, power_range.slope_);
310 XBT_DEBUG("[get_current_watts] Current power (watts) = %f, load = %f", current_power, cpu_load);
312 return current_power;
315 double HostEnergy::get_consumed_energy()
317 if (last_updated_ < surf_get_clock()) // We need to simcall this as it modifies the environment
318 simgrid::kernel::actor::simcall(std::bind(&HostEnergy::update, this));
320 return total_energy_;
323 void HostEnergy::init_watts_range_list()
325 const char* old_prop = host_->get_property("watt_per_state");
326 if (old_prop != nullptr) {
327 std::vector<std::string> all_power_values;
328 boost::split(all_power_values, old_prop, boost::is_any_of(","));
330 std::string msg = std::string("DEPRECATION WARNING: Property 'watt_per_state' will not work after v3.28.\n");
331 msg += std::string("The old syntax 'Idle:OneCore:AllCores' must be converted into 'Idle:Epsilon:AllCores' to "
332 "properly model the consumption of non-whole tasks on mono-core hosts. Here are the values to "
334 host_->get_cname() + "' in your XML file:\n";
335 msg += " <prop id=\"wattage_per_state\" value=\"";
336 for (auto const& current_power_values_str : all_power_values) {
337 std::vector<std::string> current_power_values;
338 boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
339 double p_idle = xbt_str_parse_double((current_power_values.at(0)).c_str(),
340 "Invalid obsolete XML file. Fix your watt_per_state property.");
345 if (current_power_values.size() == 3) {
346 p_idle = xbt_str_parse_double((current_power_values.at(0)).c_str(),
347 "Invalid obsolete XML file. Fix your watt_per_state property.");
348 p_one_core = xbt_str_parse_double((current_power_values.at(1)).c_str(),
349 "Invalid obsolete XML file. Fix your watt_per_state property.");
350 p_full = xbt_str_parse_double((current_power_values.at(2)).c_str(),
351 "Invalid obsolete XML file. Fix your watt_per_state property.");
352 if (host_->get_core_count() == 1) {
355 p_epsilon = p_one_core - ((p_full - p_one_core) / (host_->get_core_count() - 1));
357 } else { // consuption given with idle and full only
358 p_idle = xbt_str_parse_double((current_power_values.at(0)).c_str(),
359 "Invalid obsolete XML file. Fix your watt_per_state property.");
360 p_full = xbt_str_parse_double((current_power_values.at(1)).c_str(),
361 "Invalid obsolete XML file. Fix your watt_per_state property.");
362 if (host_->get_core_count() == 1) {
369 PowerRange range(p_idle, p_epsilon, p_full);
370 power_range_watts_list_.push_back(range);
372 msg += std::to_string(p_idle) + ":" + std::to_string(p_epsilon) + ":" + std::to_string(p_full);
375 msg.pop_back(); // Remove the extraneous ','
377 XBT_WARN("%s", msg.c_str());
380 const char* all_power_values_str = host_->get_property("wattage_per_state");
381 if (all_power_values_str == nullptr)
384 std::vector<std::string> all_power_values;
385 boost::split(all_power_values, all_power_values_str, boost::is_any_of(","));
386 XBT_DEBUG("%s: power properties: %s", host_->get_cname(), all_power_values_str);
389 for (auto const& current_power_values_str : all_power_values) {
390 /* retrieve the power values associated with the pstate i */
391 std::vector<std::string> current_power_values;
392 boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
394 xbt_assert(current_power_values.size() == 2 || current_power_values.size() == 3,
395 "Power properties incorrectly defined for host %s."
396 "It should be 'Idle:AllCores' (or 'Idle:Epsilon:AllCores') power values.",
400 double epsilon_power;
403 char* msg_idle = bprintf("Invalid Idle value for pstate %d on host %s: %%s", i, host_->get_cname());
404 char* msg_epsilon = bprintf("Invalid Epsilon value for pstate %d on host %s: %%s", i, host_->get_cname());
405 char* msg_max = bprintf("Invalid AllCores value for pstate %d on host %s: %%s", i, host_->get_cname());
407 idle_power = xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle);
408 if (current_power_values.size() == 2) { // Case: Idle:AllCores
409 epsilon_power = xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle);
410 max_power = xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_max);
411 } else { // Case: Idle:Epsilon:AllCores
412 epsilon_power = xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_epsilon);
413 max_power = xbt_str_parse_double((current_power_values.at(2)).c_str(), msg_max);
416 XBT_DEBUG("Creating PowerRange for host %s. Idle:%f, Epsilon:%f, AllCores:%f.", host_->get_cname(), idle_power, epsilon_power, max_power);
418 PowerRange range(idle_power, epsilon_power, max_power);
419 power_range_watts_list_.push_back(range);
421 xbt_free(msg_epsilon);
426 } // namespace plugin
427 } // namespace simgrid
429 using simgrid::plugin::HostEnergy;
431 /* **************************** events callback *************************** */
432 static void on_creation(simgrid::s4u::Host& host)
434 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
437 // TODO Trace: set to zero the energy variable associated to host->getName()
439 host.extension_set(new HostEnergy(&host));
442 static void on_action_state_change(simgrid::kernel::resource::CpuAction const& action,
443 simgrid::kernel::resource::Action::State /*previous*/)
445 for (simgrid::kernel::resource::Cpu* const& cpu : action.cpus()) {
446 simgrid::s4u::Host* host = cpu->get_host();
447 if (host != nullptr) {
449 // If it's a VM, take the corresponding PM
450 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
451 if (vm) // If it's a VM, take the corresponding PM
454 // Get the host_energy extension for the relevant host
455 HostEnergy* host_energy = host->extension<HostEnergy>();
457 if (host_energy->last_updated_ < surf_get_clock())
458 host_energy->update();
463 /* This callback is fired either when the host changes its state (on/off) ("onStateChange") or its speed
464 * (because the user changed the pstate, or because of external trace events) ("onSpeedChange") */
465 static void on_host_change(simgrid::s4u::Host const& host)
467 if (dynamic_cast<simgrid::s4u::VirtualMachine const*>(&host)) // Ignore virtual machines
470 HostEnergy* host_energy = host.extension<HostEnergy>();
472 host_energy->update();
475 static void on_host_destruction(simgrid::s4u::Host const& host)
477 if (dynamic_cast<simgrid::s4u::VirtualMachine const*>(&host)) // Ignore virtual machines
480 XBT_INFO("Energy consumption of host %s: %f Joules", host.get_cname(),
481 host.extension<HostEnergy>()->get_consumed_energy());
484 static void on_simulation_end()
486 std::vector<simgrid::s4u::Host*> hosts = simgrid::s4u::Engine::get_instance()->get_all_hosts();
488 double total_energy = 0.0; // Total energy consumption (whole platform)
489 double used_hosts_energy = 0.0; // Energy consumed by hosts that computed something
490 for (size_t i = 0; i < hosts.size(); i++) {
491 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(hosts[i]) == nullptr) { // Ignore virtual machines
493 double energy = hosts[i]->extension<HostEnergy>()->get_consumed_energy();
494 total_energy += energy;
495 if (hosts[i]->extension<HostEnergy>()->host_was_used_)
496 used_hosts_energy += energy;
499 XBT_INFO("Total energy consumption: %f Joules (used hosts: %f Joules; unused/idle hosts: %f)", total_energy,
500 used_hosts_energy, total_energy - used_hosts_energy);
503 /* **************************** Public interface *************************** */
505 /** @ingroup plugin_host_energy
506 * @brief Enable host energy plugin
507 * @details Enable energy plugin to get joules consumption of each cpu. Call this function before #MSG_init().
509 void sg_host_energy_plugin_init()
511 if (HostEnergy::EXTENSION_ID.valid())
514 HostEnergy::EXTENSION_ID = simgrid::s4u::Host::extension_create<HostEnergy>();
516 simgrid::s4u::Host::on_creation.connect(&on_creation);
517 simgrid::s4u::Host::on_state_change.connect(&on_host_change);
518 simgrid::s4u::Host::on_speed_change.connect(&on_host_change);
519 simgrid::s4u::Host::on_destruction.connect(&on_host_destruction);
520 simgrid::s4u::Engine::on_simulation_end.connect(&on_simulation_end);
521 simgrid::kernel::resource::CpuAction::on_state_change.connect(&on_action_state_change);
522 // We may only have one actor on a node. If that actor executes something like
523 // compute -> recv -> compute
524 // the recv operation will not trigger a "CpuAction::on_state_change". This means
525 // that the next trigger would be the 2nd compute, hence ignoring the idle time
526 // during the recv call. By updating at the beginning of a compute, we can
527 // fix that. (If the cpu is not idle, this is not required.)
528 simgrid::s4u::Exec::on_start.connect([](simgrid::s4u::Actor const&, simgrid::s4u::Exec const& activity) {
529 if (activity.get_host_number() == 1) { // We only run on one host
530 simgrid::s4u::Host* host = activity.get_host();
531 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
534 xbt_assert(host != nullptr);
535 host->extension<HostEnergy>()->update();
540 /** @ingroup plugin_host_energy
541 * @brief updates the consumption of all hosts
543 * After this call, sg_host_get_consumed_energy() will not interrupt your process
544 * (until after the next clock update).
546 void sg_host_energy_update_all()
548 simgrid::kernel::actor::simcall([]() {
549 std::vector<simgrid::s4u::Host*> list = simgrid::s4u::Engine::get_instance()->get_all_hosts();
550 for (auto const& host : list)
551 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(host) == nullptr) { // Ignore virtual machines
552 xbt_assert(host != nullptr);
553 host->extension<HostEnergy>()->update();
558 /** @ingroup plugin_host_energy
559 * @brief Returns the total energy consumed by the host so far (in Joules)
561 * Please note that since the consumption is lazily updated, it may require a simcall to update it.
562 * The result is that the actor requesting this value will be interrupted,
563 * the value will be updated in kernel mode before returning the control to the requesting actor.
565 double sg_host_get_consumed_energy(sg_host_t host)
567 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
568 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
569 return host->extension<HostEnergy>()->get_consumed_energy();
572 /** @ingroup plugin_host_energy
573 * @brief Get the amount of watt dissipated when the host is idling
575 double sg_host_get_idle_consumption(sg_host_t host)
577 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
578 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
579 return host->extension<HostEnergy>()->get_idle_consumption();
582 /** @ingroup plugin_host_energy
583 * @brief Get the amount of watt dissipated at the given pstate when the host is idling
585 double sg_host_get_wattmin_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_min_at(pstate);
591 /** @ingroup plugin_host_energy
592 * @brief Returns the amount of watt dissipated at the given pstate when the host burns CPU at 100%
594 double sg_host_get_wattmax_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_watt_max_at(pstate);
600 /** @ingroup plugin_host_energy
601 * @brief Returns the power slope at the given pstate
603 double sg_host_get_power_range_slope_at(sg_host_t host, int pstate)
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_power_range_slope_at(pstate);
609 /** @ingroup plugin_host_energy
610 * @brief Returns the current consumption of the host
612 double sg_host_get_current_consumption(sg_host_t host)
614 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
615 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
616 return host->extension<HostEnergy>()->get_current_watts_value();