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 "src/kernel/activity/ExecImpl.hpp"
9 #include "src/include/surf/surf.hpp"
10 #include "src/plugins/vm/VirtualMachineImpl.hpp"
11 #include "src/surf/cpu_interface.hpp"
13 #include <boost/algorithm/string/classification.hpp>
14 #include <boost/algorithm/string/split.hpp>
16 SIMGRID_REGISTER_PLUGIN(host_energy, "Cpu energy consumption.", &sg_host_energy_plugin_init)
18 /** @defgroup plugin_host_energy
20 This is the energy plugin, enabling to account not only for computation time, but also for the dissipated energy in the
22 To activate this plugin, first call sg_host_energy_plugin_init() before your #MSG_init(), and then use
23 MSG_host_get_consumed_energy() to retrieve the consumption of a given host.
25 When the host is on, this energy consumption naturally depends on both the current CPU load and the host energy profile.
26 According to our measurements, the consumption is somehow linear in the amount of cores at full speed, with an
27 abnormality when all the cores are idle. The full details are in
28 <a href="https://hal.inria.fr/hal-01523608">our scientific paper</a> on that topic.
30 As a result, our energy model takes 4 parameters:
32 - @b Idle: wattage (i.e., instantaneous consumption in Watt) when your host is up and running, but without anything to
34 - @b Epsilon: wattage when all cores are at 0 or epsilon%, but not in Idle state.
35 - @b AllCores: wattage when all cores of the host are at 100%.
36 - @b Off: wattage when the host is turned off.
38 Here is an example of XML declaration:
41 <host id="HostA" speed="100.0Mf" core="4">
42 <prop id="wattage_per_state" value="100.0:120.0:200.0" />
43 <prop id="wattage_off" value="10" />
47 If the 'Epsilon' parameter is omitted in the XML declaration, 'Idle' is used instead.
49 This example gives the following parameters: @b Off is 10 Watts; @b Idle is 100 Watts; @b Epsilon is 120 Watts and @b
50 AllCores is 200 Watts.
51 This is enough to compute the wattage as a function of the amount of loaded cores:
54 <tr><th>@#Cores loaded</th><th>Wattage</th><th>Explanation</th></tr>
55 <tr><td>0 (idle)</td><td> 100 Watts</td><td>Idle value</td></tr>
56 <tr><td>0 (not idle)</td><td> 120 Watts</td><td>Epsilon value</td></tr>
57 <tr><td>1</td><td> 140 Watts</td><td>linear extrapolation between Epsilon and AllCores</td></tr>
58 <tr><td>2</td><td> 160 Watts</td><td>linear extrapolation between Epsilon and AllCores</td></tr>
59 <tr><td>3</td><td> 180 Watts</td><td>linear extrapolation between Epsilon and AllCores</td></tr>
60 <tr><td>4</td><td> 200 Watts</td><td>AllCores value</td></tr>
64 ### How does DVFS interact with the host energy model?
66 If your host has several DVFS levels (several pstates), then you should give the energetic profile of each pstate level:
69 <host id="HostC" speed="100.0Mf,50.0Mf,20.0Mf" core="4">
70 <prop id="wattage_per_state" value="95.0:120.0:200.0, 93.0:115.0:170.0, 90.0:110.0:150.0" />
71 <prop id="wattage_off" value="10" />
75 This encodes the following values
77 <tr><th>pstate</th><th>Performance</th><th>Idle</th><th>Epsilon</th><th>AllCores</th></tr>
78 <tr><td>0</td><td>100 Mflop/s</td><td>95 Watts</td><td>120 Watts</td><td>200 Watts</td></tr>
79 <tr><td>1</td><td>50 Mflop/s</td><td>93 Watts</td><td>115 Watts</td><td>170 Watts</td></tr>
80 <tr><td>2</td><td>20 Mflop/s</td><td>90 Watts</td><td>110 Watts</td><td>150 Watts</td></tr>
83 To change the pstate of a given CPU, use the following functions:
84 #MSG_host_get_nb_pstates(), simgrid#s4u#Host#setPstate(), #MSG_host_get_power_peak_at().
86 ### How accurate are these models?
88 This model cannot be more accurate than your instantiation: with the default values, your result will not be accurate at
89 all. You can still get accurate energy prediction, provided that you carefully instantiate the model.
90 The first step is to ensure that your timing prediction match perfectly. But this is only the first step of the path,
91 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
92 before you can get accurate energy predictions.
95 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(surf_energy, surf, "Logging specific to the SURF energy plugin");
97 // Forwards declaration needed to make this function a friend (because friends have external linkage by default)
98 static void on_simulation_end();
110 PowerRange(double idle, double epsilon, double max) : idle_(idle), epsilon_(epsilon), max_(max), slope_(max-epsilon) {}
114 friend void ::on_simulation_end(); // For access to host_was_used_
116 static simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> EXTENSION_ID;
118 explicit HostEnergy(simgrid::s4u::Host* ptr);
121 double get_current_watts_value();
122 double get_current_watts_value(double cpu_load);
123 double get_consumed_energy();
124 double get_idle_consumption();
125 double get_watt_min_at(int pstate);
126 double get_watt_max_at(int pstate);
127 double get_power_range_slope_at(int pstate);
131 void init_watts_range_list();
132 simgrid::s4u::Host* host_ = nullptr;
133 /*< List of (idle_power, epsilon_power, max_power) tuple corresponding to each cpu pstate */
134 std::vector<PowerRange> power_range_watts_list_;
136 /* We need to keep track of what pstate has been used, as we will sometimes be notified only *after* a pstate has been
137 * used (but we need to update the energy consumption with the old pstate!)
140 const int pstate_off_ = -1;
142 /* Only used to split total energy into unused/used hosts.
143 * If you want to get this info for something else, rather use the host_load plugin
145 bool host_was_used_ = false;
147 double watts_off_ = 0.0; /*< Consumption when the machine is turned off (shutdown) */
148 double total_energy_ = 0.0; /*< Total energy consumed by the host */
149 double last_updated_; /*< Timestamp of the last energy update event*/
152 simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> HostEnergy::EXTENSION_ID;
154 /* Computes the consumption so far. Called lazily on need. */
155 void HostEnergy::update()
157 double start_time = this->last_updated_;
158 double finish_time = surf_get_clock();
160 // We may have start == finish if the past consumption was updated since the simcall was started
161 // for example if 2 actors requested to update the same host's consumption in a given scheduling round.
163 // Even in this case, we need to save the pstate for the next call (after this if),
164 // which may have changed since that recent update.
165 if (start_time < finish_time) {
166 double previous_energy = this->total_energy_;
168 double instantaneous_power_consumption = this->get_current_watts_value();
170 double energy_this_step = instantaneous_power_consumption * (finish_time - start_time);
172 // TODO Trace: Trace energy_this_step from start_time to finish_time in host->getName()
174 this->total_energy_ = previous_energy + energy_this_step;
175 this->last_updated_ = finish_time;
177 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",
178 host_->get_cname(), start_time, finish_time, host_->pimpl_cpu->get_pstate_peak_speed(this->pstate_), this->pstate_, previous_energy,
182 /* Save data for the upcoming time interval: whether it's on/off and the pstate if it's on */
183 this->pstate_ = host_->is_on() ? host_->get_pstate() : pstate_off_;
186 HostEnergy::HostEnergy(simgrid::s4u::Host* ptr) : host_(ptr), last_updated_(surf_get_clock())
188 init_watts_range_list();
189 static bool warned = false;
191 const char* off_power_str = host_->get_property("wattage_off");
192 if (off_power_str == nullptr) {
193 off_power_str = host_->get_property("watt_off");
194 if (off_power_str != nullptr && not warned) {
196 XBT_WARN("Please use 'wattage_off' instead of 'watt_off' to define the idle wattage of hosts in your XML.");
199 if (off_power_str != nullptr) {
201 this->watts_off_ = std::stod(std::string(off_power_str));
202 } catch (const std::invalid_argument&) {
203 throw std::invalid_argument(std::string("Invalid value for property wattage_off of host ") + host_->get_cname() +
204 ": " + off_power_str);
207 /* watts_off is 0 by default */
210 HostEnergy::~HostEnergy() = default;
212 double HostEnergy::get_idle_consumption()
214 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
217 return power_range_watts_list_[0].idle_;
220 double HostEnergy::get_watt_min_at(int pstate)
222 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
224 return power_range_watts_list_[pstate].epsilon_;
227 double HostEnergy::get_watt_max_at(int pstate)
229 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
231 return power_range_watts_list_[pstate].max_;
234 double HostEnergy::get_power_range_slope_at(int pstate)
236 xbt_assert(not power_range_watts_list_.empty(), "No power range properties specified for host %s",
238 return power_range_watts_list_[pstate].slope_;
241 /** @brief Computes the power consumed by the host according to the current situation
243 * - If the host is off, that's the watts_off value
244 * - if it's on, take the current pstate and the current processor load into account */
245 double HostEnergy::get_current_watts_value()
247 if (this->pstate_ == pstate_off_) // The host is off (or was off at the beginning of this time interval)
248 return this->watts_off_;
250 double current_speed = host_->get_pstate_speed(this->pstate_);
254 if (current_speed <= 0)
255 // Some users declare a pstate of speed 0 flops (e.g., to model boot time).
256 // We consider that the machine is then fully loaded. That's arbitrary but it avoids a NaN
259 cpu_load = host_->pimpl_cpu->get_constraint()->get_usage() / current_speed;
261 /* Divide by the number of cores here to have a value between 0 and 1 */
262 cpu_load /= host_->pimpl_cpu->get_core_count();
264 if (cpu_load > 1) // A machine with a load > 1 consumes as much as a fully loaded machine, not more
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() == 2) { // Case: Idle:AllCores
346 p_full = xbt_str_parse_double((current_power_values.at(1)).c_str(),
347 "Invalid obsolete XML file. Fix your watt_per_state property.");
349 } else { // Case: Idle:Epsilon:AllCores
350 p_one_core = xbt_str_parse_double((current_power_values.at(1)).c_str(),
351 "Invalid obsolete XML file. Fix your watt_per_state property.");
352 p_full = xbt_str_parse_double((current_power_values.at(2)).c_str(),
353 "Invalid obsolete XML file. Fix your watt_per_state property.");
354 if (host_->get_core_count() == 1)
357 p_epsilon = p_one_core - ((p_full - p_one_core) / (host_->get_core_count() - 1));
359 PowerRange range(p_idle, p_epsilon, p_full);
360 power_range_watts_list_.push_back(range);
362 msg += std::to_string(p_idle) + ":" + std::to_string(p_epsilon) + ":" + std::to_string(p_full);
365 msg.pop_back(); // Remove the extraneous ','
367 XBT_WARN("%s", msg.c_str());
370 const char* all_power_values_str = host_->get_property("wattage_per_state");
371 if (all_power_values_str == nullptr)
374 std::vector<std::string> all_power_values;
375 boost::split(all_power_values, all_power_values_str, boost::is_any_of(","));
376 XBT_DEBUG("%s: power properties: %s", host_->get_cname(), all_power_values_str);
379 for (auto const& current_power_values_str : all_power_values) {
380 /* retrieve the power values associated with the pstate i */
381 std::vector<std::string> current_power_values;
382 boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
384 xbt_assert(current_power_values.size() == 2 || current_power_values.size() == 3,
385 "Power properties incorrectly defined for host %s."
386 "It should be 'Idle:AllCores' (or 'Idle:Epsilon:AllCores') power values.",
390 double epsilon_power;
393 char* msg_idle = bprintf("Invalid Idle value for pstate %d on host %s: %%s", i, host_->get_cname());
394 char* msg_epsilon = bprintf("Invalid Epsilon value for pstate %d on host %s: %%s", i, host_->get_cname());
395 char* msg_max = bprintf("Invalid AllCores value for pstate %d on host %s: %%s", i, host_->get_cname());
397 idle_power = xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle);
398 if (current_power_values.size() == 2) { // Case: Idle:AllCores
399 epsilon_power = xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle);
400 max_power = xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_max);
401 } else { // Case: Idle:Epsilon:AllCores
402 epsilon_power = xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_epsilon);
403 max_power = xbt_str_parse_double((current_power_values.at(2)).c_str(), msg_max);
406 XBT_DEBUG("Creating PowerRange for host %s. Idle:%f, Epsilon:%f, AllCores:%f.", host_->get_cname(), idle_power, epsilon_power, max_power);
408 PowerRange range(idle_power, epsilon_power, max_power);
409 power_range_watts_list_.push_back(range);
411 xbt_free(msg_epsilon);
416 } // namespace plugin
417 } // namespace simgrid
419 using simgrid::plugin::HostEnergy;
421 /* **************************** events callback *************************** */
422 static void on_creation(simgrid::s4u::Host& host)
424 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
427 // TODO Trace: set to zero the energy variable associated to host->getName()
429 host.extension_set(new HostEnergy(&host));
432 static void on_action_state_change(simgrid::kernel::resource::CpuAction const& action,
433 simgrid::kernel::resource::Action::State /*previous*/)
435 for (simgrid::kernel::resource::Cpu* const& cpu : action.cpus()) {
436 simgrid::s4u::Host* host = cpu->get_host();
437 if (host != nullptr) {
439 // If it's a VM, take the corresponding PM
440 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
441 if (vm) // If it's a VM, take the corresponding PM
444 // Get the host_energy extension for the relevant host
445 HostEnergy* host_energy = host->extension<HostEnergy>();
447 if (host_energy->last_updated_ < surf_get_clock())
448 host_energy->update();
453 /* This callback is fired either when the host changes its state (on/off) ("onStateChange") or its speed
454 * (because the user changed the pstate, or because of external trace events) ("onSpeedChange") */
455 static void on_host_change(simgrid::s4u::Host const& host)
457 if (dynamic_cast<simgrid::s4u::VirtualMachine const*>(&host)) // Ignore virtual machines
460 HostEnergy* host_energy = host.extension<HostEnergy>();
462 host_energy->update();
465 static void on_host_destruction(simgrid::s4u::Host const& host)
467 if (dynamic_cast<simgrid::s4u::VirtualMachine const*>(&host)) // Ignore virtual machines
470 XBT_INFO("Energy consumption of host %s: %f Joules", host.get_cname(),
471 host.extension<HostEnergy>()->get_consumed_energy());
474 static void on_simulation_end()
476 std::vector<simgrid::s4u::Host*> hosts = simgrid::s4u::Engine::get_instance()->get_all_hosts();
478 double total_energy = 0.0; // Total energy consumption (whole platform)
479 double used_hosts_energy = 0.0; // Energy consumed by hosts that computed something
480 for (size_t i = 0; i < hosts.size(); i++) {
481 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(hosts[i]) == nullptr) { // Ignore virtual machines
483 double energy = hosts[i]->extension<HostEnergy>()->get_consumed_energy();
484 total_energy += energy;
485 if (hosts[i]->extension<HostEnergy>()->host_was_used_)
486 used_hosts_energy += energy;
489 XBT_INFO("Total energy consumption: %f Joules (used hosts: %f Joules; unused/idle hosts: %f)", total_energy,
490 used_hosts_energy, total_energy - used_hosts_energy);
493 /* **************************** Public interface *************************** */
495 /** @ingroup plugin_host_energy
496 * @brief Enable host energy plugin
497 * @details Enable energy plugin to get joules consumption of each cpu. Call this function before #MSG_init().
499 void sg_host_energy_plugin_init()
501 if (HostEnergy::EXTENSION_ID.valid())
504 HostEnergy::EXTENSION_ID = simgrid::s4u::Host::extension_create<HostEnergy>();
506 simgrid::s4u::Host::on_creation.connect(&on_creation);
507 simgrid::s4u::Host::on_state_change.connect(&on_host_change);
508 simgrid::s4u::Host::on_speed_change.connect(&on_host_change);
509 simgrid::s4u::Host::on_destruction.connect(&on_host_destruction);
510 simgrid::s4u::Engine::on_simulation_end.connect(&on_simulation_end);
511 simgrid::kernel::resource::CpuAction::on_state_change.connect(&on_action_state_change);
512 // We may only have one actor on a node. If that actor executes something like
513 // compute -> recv -> compute
514 // the recv operation will not trigger a "CpuAction::on_state_change". This means
515 // that the next trigger would be the 2nd compute, hence ignoring the idle time
516 // during the recv call. By updating at the beginning of a compute, we can
517 // fix that. (If the cpu is not idle, this is not required.)
518 simgrid::kernel::activity::ExecImpl::on_creation.connect([](simgrid::kernel::activity::ExecImpl const& activity) {
519 if (activity.get_host_number() == 1) { // We only run on one host
520 simgrid::s4u::Host* host = activity.get_host();
521 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
524 xbt_assert(host != nullptr);
525 host->extension<HostEnergy>()->update();
530 /** @ingroup plugin_host_energy
531 * @brief updates the consumption of all hosts
533 * After this call, sg_host_get_consumed_energy() will not interrupt your process
534 * (until after the next clock update).
536 void sg_host_energy_update_all()
538 simgrid::kernel::actor::simcall([]() {
539 std::vector<simgrid::s4u::Host*> list = simgrid::s4u::Engine::get_instance()->get_all_hosts();
540 for (auto const& host : list)
541 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(host) == nullptr) { // Ignore virtual machines
542 xbt_assert(host != nullptr);
543 host->extension<HostEnergy>()->update();
548 /** @ingroup plugin_host_energy
549 * @brief Returns the total energy consumed by the host so far (in Joules)
551 * Please note that since the consumption is lazily updated, it may require a simcall to update it.
552 * The result is that the actor requesting this value will be interrupted,
553 * the value will be updated in kernel mode before returning the control to the requesting actor.
555 double sg_host_get_consumed_energy(sg_host_t host)
557 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
558 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
559 return host->extension<HostEnergy>()->get_consumed_energy();
562 /** @ingroup plugin_host_energy
563 * @brief Get the amount of watt dissipated when the host is idling
565 double sg_host_get_idle_consumption(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_idle_consumption();
572 /** @ingroup plugin_host_energy
573 * @brief Get the amount of watt dissipated at the given pstate when the host is idling
575 double sg_host_get_wattmin_at(sg_host_t host, int pstate)
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_watt_min_at(pstate);
581 /** @ingroup plugin_host_energy
582 * @brief Returns the amount of watt dissipated at the given pstate when the host burns CPU at 100%
584 double sg_host_get_wattmax_at(sg_host_t host, int pstate)
586 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
587 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
588 return host->extension<HostEnergy>()->get_watt_max_at(pstate);
590 /** @ingroup plugin_host_energy
591 * @brief Returns the power slope at the given pstate
593 double sg_host_get_power_range_slope_at(sg_host_t host, int pstate)
595 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
596 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
597 return host->extension<HostEnergy>()->get_power_range_slope_at(pstate);
599 /** @ingroup plugin_host_energy
600 * @brief Returns the current consumption of the host
602 double sg_host_get_current_consumption(sg_host_t host)
604 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
605 "The Energy plugin is not active. Please call sg_host_energy_plugin_init() during initialization.");
606 return host->extension<HostEnergy>()->get_current_watts_value();