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"
14 #include <boost/algorithm/string/classification.hpp>
15 #include <boost/algorithm/string/split.hpp>
20 /** @addtogroup SURF_plugin_energy
23 This is the energy plugin, enabling to account not only for computation time,
24 but also for the dissipated energy in the simulated platform.
26 The energy consumption of a CPU depends directly of its current load. Specify that consumption in your platform file as
30 <host id="HostA" power="100.0Mf" cores="8">
31 <prop id="watt_per_state" value="100.0:120.0:200.0" />
32 <prop id="watt_off" value="10" />
36 The first property means that when your host is up and running, but without anything to do, it will dissipate 100 Watts.
37 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.
38 The second property means that when your host is turned off, it will dissipate only 10 Watts (please note that these
39 values are arbitrary).
41 If your CPU is using pstates, then you can provide one consumption interval per pstate.
44 <host id="HostB" power="100.0Mf,50.0Mf,20.0Mf" pstate="0" >
45 <prop id="watt_per_state" value="95.0:120.0:200.0, 93.0:115.0:170.0, 90.0:110.0:150.0" />
46 <prop id="watt_off" value="10" />
50 That host has 3 levels of performance with the following performance: 100 Mflop/s, 50 Mflop/s or 20 Mflop/s.
51 It starts at pstate 0 (ie, at 100 Mflop/s). In this case, you have to specify one interval per pstate in the
52 watt_per_state property.
53 In this example, the idle consumption is 95 Watts, 93 Watts and 90 Watts in each pstate while the CPU burn consumption
54 are at 200 Watts, 170 Watts, and 150 Watts respectively. If only one core is active, this machine consumes 120 / 115 / 110 watts.
56 To change the pstate of a given CPU, use the following functions:
57 #MSG_host_get_nb_pstates(), simgrid#s4u#Host#setPstate(), #MSG_host_get_power_peak_at().
59 To simulate the energy-related elements, first call the simgrid#energy#sg_energy_plugin_init() before your #MSG_init(),
60 and then use the following function to retrieve the consumption of a given host: MSG_host_get_consumed_energy().
63 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(surf_energy, surf, "Logging specific to the SURF energy plugin");
74 PowerRange(double idle, double min, double max) : idle(idle), min(min), max(max) {}
79 static simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> EXTENSION_ID;
81 explicit HostEnergy(simgrid::s4u::Host* ptr);
84 double getCurrentWattsValue(double cpu_load);
85 double getConsumedEnergy();
86 double getWattMinAt(int pstate);
87 double getWattMaxAt(int pstate);
91 void initWattsRangeList();
92 simgrid::s4u::Host* host = nullptr;
93 std::vector<PowerRange>
94 power_range_watts_list; /*< List of (min_power,max_power) pairs corresponding to each cpu pstate */
96 /* We need to keep track of what pstate has been used, as we will sometimes
97 * be notified only *after* a pstate has been used (but we need to update the energy consumption
98 * with the old pstate!)
103 double watts_off = 0.0; /*< Consumption when the machine is turned off (shutdown) */
104 double total_energy = 0.0; /*< Total energy consumed by the host */
105 double last_updated; /*< Timestamp of the last energy update event*/
108 simgrid::xbt::Extension<simgrid::s4u::Host, HostEnergy> HostEnergy::EXTENSION_ID;
110 /* Computes the consumption so far. Called lazily on need. */
111 void HostEnergy::update()
113 double start_time = this->last_updated;
114 double finish_time = surf_get_clock();
116 double current_speed = host->speed();
118 if (start_time < finish_time) {
119 // We may have start == finish if the past consumption was updated since the simcall was started
120 // for example if 2 actors requested to update the same host's consumption in a given scheduling round.
122 // Even in this case, we need to save the pstate for the next call (after this big if),
123 // which may have changed since that recent update.
125 if (current_speed <= 0)
126 // Some users declare a pstate of speed 0 flops (e.g., to model boot time).
127 // We consider that the machine is then fully loaded. That's arbitrary but it avoids a NaN
130 cpu_load = lmm_constraint_get_usage(host->pimpl_cpu->constraint()) / current_speed;
132 /** Divide by the number of cores here **/
133 cpu_load /= host->pimpl_cpu->coreCount();
135 if (cpu_load > 1) // A machine with a load > 1 consumes as much as a fully loaded machine, not more
138 /* The problem with this model is that the load is always 0 or 1, never something less.
139 * Another possibility could be to model the total energy as
141 * X/(X+Y)*W_idle + Y/(X+Y)*W_burn
143 * where X is the amount of idling cores, and Y the amount of computing cores.
146 double previous_energy = this->total_energy;
148 double instantaneous_consumption;
149 if (this->pstate == -1) // The host was off at the beginning of this time interval
150 instantaneous_consumption = this->watts_off;
152 instantaneous_consumption = this->getCurrentWattsValue(cpu_load);
154 double energy_this_step = instantaneous_consumption * (finish_time - start_time);
156 // TODO Trace: Trace energy_this_step from start_time to finish_time in host->name()
158 this->total_energy = previous_energy + energy_this_step;
159 this->last_updated = finish_time;
161 XBT_DEBUG("[update_energy of %s] period=[%.2f-%.2f]; current power peak=%.0E flop/s; consumption change: %.2f J -> "
163 host->cname(), start_time, finish_time, host->pimpl_cpu->speed_.peak, previous_energy, energy_this_step);
166 /* Save data for the upcoming time interval: whether it's on/off and the pstate if it's on */
167 this->pstate = host->isOn() ? host->pstate() : -1;
170 HostEnergy::HostEnergy(simgrid::s4u::Host* ptr) : host(ptr), last_updated(surf_get_clock())
172 initWattsRangeList();
174 const char* off_power_str = host->property("watt_off");
175 if (off_power_str != nullptr) {
176 char* msg = bprintf("Invalid value for property watt_off of host %s: %%s", host->cname());
177 this->watts_off = xbt_str_parse_double(off_power_str, msg);
180 /* watts_off is 0 by default */
182 if (ptr->coreCount() == 1)
183 xbt_assert(std::all_of(power_range_watts_list.begin(), power_range_watts_list.end(),
184 [](PowerRange power_range) { return power_range.min == power_range.max; }),
185 "You only have one core in host %s, but the \
186 energy consumption for one core does not match the energy consumption for all (here: 1) cores). This is an error in your platform, please fix it.",
190 HostEnergy::~HostEnergy() = default;
192 double HostEnergy::getWattMinAt(int pstate)
194 xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->cname());
195 return power_range_watts_list[pstate].min;
198 double HostEnergy::getWattMaxAt(int pstate)
200 xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->cname());
201 return power_range_watts_list[pstate].max;
204 /** @brief Computes the power consumed by the host according to the current pstate and processor load */
205 double HostEnergy::getCurrentWattsValue(double cpu_load)
207 xbt_assert(not power_range_watts_list.empty(), "No power range properties specified for host %s", host->cname());
209 /* min_power corresponds to the power consumed when only one core is active */
210 /* max_power is the power consumed at 100% cpu load */
211 auto range = power_range_watts_list.at(this->pstate);
212 double current_power = 0;
213 double min_power = 0;
214 double max_power = 0;
215 double power_slope = 0;
217 if (cpu_load > 0) { /* Something is going on, the machine is not idle */
218 double min_power = range.min;
219 double max_power = range.max;
222 * The min_power states how much we consume when only one single
223 * core is working. This means that when cpu_load == 1/coreCount, then
224 * current_power == min_power.
226 * The maximum must be reached when all cores are working (but 1 core was
227 * already accounted for by min_power)
228 * i.e., we need min_power + (maxCpuLoad-1/coreCount)*power_slope == max_power
229 * (maxCpuLoad is by definition 1)
232 int coreCount = host->coreCount();
233 double coreReciprocal = static_cast<double>(1) / static_cast<double>(coreCount);
235 power_slope = (max_power - min_power) / (1 - coreReciprocal);
237 power_slope = 0; // Should be 0, since max_power == min_power (in this case)
239 current_power = min_power + (cpu_load - coreReciprocal) * power_slope;
240 } else { /* Our machine is idle, take the dedicated value! */
241 current_power = range.idle;
244 XBT_DEBUG("[get_current_watts] min_power=%f, max_power=%f, slope=%f", min_power, max_power, power_slope);
245 XBT_DEBUG("[get_current_watts] Current power (watts) = %f, load = %f", current_power, cpu_load);
247 return current_power;
250 double HostEnergy::getConsumedEnergy()
252 if (last_updated < surf_get_clock()) // We need to simcall this as it modifies the environment
253 simgrid::simix::kernelImmediate(std::bind(&HostEnergy::update, this));
258 void HostEnergy::initWattsRangeList()
260 const char* all_power_values_str = host->property("watt_per_state");
261 if (all_power_values_str == nullptr)
264 std::vector<std::string> all_power_values;
265 boost::split(all_power_values, all_power_values_str, boost::is_any_of(","));
268 for (auto current_power_values_str : all_power_values) {
269 /* retrieve the power values associated with the current pstate */
270 std::vector<std::string> current_power_values;
271 boost::split(current_power_values, current_power_values_str, boost::is_any_of(":"));
272 xbt_assert(current_power_values.size() == 3, "Power properties incorrectly defined - "
273 "could not retrieve idle, min and max power values for host %s",
276 /* min_power corresponds to the idle power (cpu load = 0) */
277 /* max_power is the power consumed at 100% cpu load */
278 char* msg_idle = bprintf("Invalid idle value for pstate %d on host %s: %%s", i, host->cname());
279 char* msg_min = bprintf("Invalid min value for pstate %d on host %s: %%s", i, host->cname());
280 char* msg_max = bprintf("Invalid max value for pstate %d on host %s: %%s", i, host->cname());
281 PowerRange range(xbt_str_parse_double((current_power_values.at(0)).c_str(), msg_idle),
282 xbt_str_parse_double((current_power_values.at(1)).c_str(), msg_min),
283 xbt_str_parse_double((current_power_values.at(2)).c_str(), msg_max));
284 power_range_watts_list.push_back(range);
294 using simgrid::energy::HostEnergy;
296 /* **************************** events callback *************************** */
297 static void onCreation(simgrid::s4u::Host& host)
299 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
302 //TODO Trace: set to zero the energy variable associated to host->name()
304 host.extension_set(new HostEnergy(&host));
307 static void onActionStateChange(simgrid::surf::CpuAction* action, simgrid::surf::Action::State previous)
309 for (simgrid::surf::Cpu* cpu : action->cpus()) {
310 simgrid::s4u::Host* host = cpu->getHost();
311 if (host != nullptr) {
313 // If it's a VM, take the corresponding PM
314 simgrid::s4u::VirtualMachine* vm = dynamic_cast<simgrid::s4u::VirtualMachine*>(host);
315 if (vm) // If it's a VM, take the corresponding PM
316 host = vm->pimpl_vm_->getPm();
318 // Get the host_energy extension for the relevant host
319 HostEnergy* host_energy = host->extension<HostEnergy>();
321 if (host_energy->last_updated < surf_get_clock())
322 host_energy->update();
327 /* This callback is fired either when the host changes its state (on/off) ("onStateChange") or its speed
328 * (because the user changed the pstate, or because of external trace events) ("onSpeedChange") */
329 static void onHostChange(simgrid::s4u::Host& host)
331 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
334 HostEnergy* host_energy = host.extension<HostEnergy>();
336 host_energy->update();
339 static void onHostDestruction(simgrid::s4u::Host& host)
341 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
344 HostEnergy* host_energy = host.extension<HostEnergy>();
345 host_energy->update();
346 XBT_INFO("Energy consumption of host %s: %f Joules", host.cname(), host_energy->getConsumedEnergy());
349 static void onSimulationEnd()
351 sg_host_t* host_list = sg_host_list();
352 int host_count = sg_host_count();
353 double total_energy = 0.0; // Total energy consumption (whole platform)
354 double used_hosts_energy = 0.0; // Energy consumed by hosts that computed something
355 for (int i = 0; i < host_count; i++) {
356 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(host_list[i]) == nullptr) { // Ignore virtual machines
358 bool host_was_used = (host_list[i]->extension<HostEnergy>()->last_updated != 0);
359 double energy = host_list[i]->extension<HostEnergy>()->getConsumedEnergy();
360 total_energy += energy;
362 used_hosts_energy += energy;
365 XBT_INFO("Total energy consumption: %f Joules (used hosts: %f Joules; unused/idle hosts: %f)",
366 total_energy, used_hosts_energy, total_energy - used_hosts_energy);
370 /* **************************** Public interface *************************** */
373 /** \ingroup SURF_plugin_energy
374 * \brief Enable host energy plugin
375 * \details Enable energy plugin to get joules consumption of each cpu. Call this function before #MSG_init().
377 void sg_host_energy_plugin_init()
379 if (HostEnergy::EXTENSION_ID.valid())
382 HostEnergy::EXTENSION_ID = simgrid::s4u::Host::extension_create<HostEnergy>();
384 simgrid::s4u::Host::onCreation.connect(&onCreation);
385 simgrid::s4u::Host::onStateChange.connect(&onHostChange);
386 simgrid::s4u::Host::onSpeedChange.connect(&onHostChange);
387 simgrid::s4u::Host::onDestruction.connect(&onHostDestruction);
388 simgrid::s4u::onSimulationEnd.connect(&onSimulationEnd);
389 simgrid::surf::CpuAction::onStateChange.connect(&onActionStateChange);
392 /** @brief updates the consumption of all hosts
394 * After this call, sg_host_get_consumed_energy() will not interrupt your process
395 * (until after the next clock update).
397 void sg_host_energy_update_all()
399 simgrid::simix::kernelImmediate([]() {
400 std::vector<simgrid::s4u::Host*> list;
401 simgrid::s4u::Engine::instance()->hostList(&list);
402 for (auto host : list)
403 host->extension<HostEnergy>()->update();
407 /** @brief Returns the total energy consumed by the host so far (in Joules)
409 * Please note that since the consumption is lazily updated, it may require a simcall to update it.
410 * The result is that the actor requesting this value will be interrupted,
411 * the value will be updated in kernel mode before returning the control to the requesting actor.
413 * See also @ref SURF_plugin_energy.
415 double sg_host_get_consumed_energy(sg_host_t host)
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>()->getConsumedEnergy();
422 /** @brief Get the amount of watt dissipated at the given pstate when the host is idling */
423 double sg_host_get_wattmin_at(sg_host_t host, int pstate)
425 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
426 "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
427 return host->extension<HostEnergy>()->getWattMinAt(pstate);
429 /** @brief Returns the amount of watt dissipated at the given pstate when the host burns CPU at 100% */
430 double sg_host_get_wattmax_at(sg_host_t host, int pstate)
432 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
433 "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
434 return host->extension<HostEnergy>()->getWattMaxAt(pstate);
437 /** @brief Returns the current consumption of the host */
438 double sg_host_get_current_consumption(sg_host_t host)
440 xbt_assert(HostEnergy::EXTENSION_ID.valid(),
441 "The Energy plugin is not active. Please call sg_energy_plugin_init() during initialization.");
442 double cpu_load = lmm_constraint_get_usage(host->pimpl_cpu->constraint()) / host->speed();
443 return host->extension<HostEnergy>()->getCurrentWattsValue(cpu_load);