Source code for spinn_front_end_common.utilities.report_functions.energy_report

# Copyright (c) 2017-2019 The University of Manchester
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import logging
import os
from spinn_front_end_common.utility_models import ChipPowerMonitorMachineVertex
from spinn_front_end_common.utilities.exceptions import ConfigurationException
from spinn_front_end_common.utilities.helpful_functions import (
    convert_time_diff_to_total_milliseconds)

logger = logging.getLogger(__name__)


[docs]class EnergyReport(object): """ Creates a report about the approximate total energy consumed by a\ SpiNNaker job execution. """ # given from indar measurements MILLIWATTS_PER_FPGA = 0.000584635 # stated in papers (SpiNNaker: A 1-W 18 core system-on-Chip for # Massively-Parallel Neural Network Simulation) JOULES_PER_SPIKE = 0.000000000800 # stated in papers (SpiNNaker: A 1-W 18 core system-on-Chip for # Massively-Parallel Neural Network Simulation) MILLIWATTS_PER_IDLE_CHIP = 0.000360 # stated in papers (SpiNNaker: A 1-W 18 core system-on-Chip for # Massively-Parallel Neural Network Simulation) MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD = 0.001 - MILLIWATTS_PER_IDLE_CHIP # converter between joules to kilowatt hours JOULES_TO_KILOWATT_HOURS = 3600000 # measured from the real power meter and timing between # the photos for a days powered off MILLIWATTS_FOR_FRAME_IDLE_COST = 0.117 # measured from the loading of the column and extrapolated MILLIWATTS_PER_FRAME_ACTIVE_COST = 0.154163558 # measured from the real power meter and timing between the photos # for a day powered off MILLIWATTS_FOR_BOXED_48_CHIP_FRAME_IDLE_COST = 0.0045833333 # TODO needs filling in MILLIWATTS_PER_UNBOXED_48_CHIP_FRAME_IDLE_COST = 0.01666667 # TODO verify this is correct when doing multiboard comms N_MONITORS_ACTIVE_DURING_COMMS = 2 # energy report file name ENERGY_DETAILED_FILENAME = "Detailed_energy_report.rpt" ENERGY_SUMMARY_FILENAME = "energy_summary_report.rpt" def __call__( self, placements, machine, report_default_directory, version, spalloc_server, remote_spinnaker_url, time_scale_factor, machine_time_step, pacman_provenance, router_provenance, machine_graph, runtime, buffer_manager, mapping_time, load_time, execute_time, dsg_time, extraction_time, machine_allocation_controller=None): """ :param placements: the placements :param machine: the machine :param report_default_directory: location for reports :param version: version of machine :param spalloc_server: spalloc server IP :param remote_spinnaker_url: remote SpiNNaker URL :param time_scale_factor: the time scale factor :param machine_time_step: the machine time step :param pacman_provenance: the PACMAN provenance :param router_provenance: the router provenance :param machine_graph: the machine graph :param buffer_manager: the buffer manager :param mapping_time: time taken by the mapping process :param load_time: the time taken by the load process :param execute_time: the time taken by the execute time process :param dsg_time: the time taken by the DSG time :param extraction_time: the time taken by data extraction time :param machine_allocation_controller: \ the machine controller for spalloc :rtype: None """ # pylint: disable=too-many-arguments, too-many-locals if buffer_manager is None: logger.info("Skipping Energy report as no buffer_manager set") return # detailed report path detailed_report = os.path.join( report_default_directory, self.ENERGY_DETAILED_FILENAME) # summary report path summary_report = os.path.join( report_default_directory, self.ENERGY_SUMMARY_FILENAME) # overall time taken up total_time = ( execute_time + load_time + extraction_time + dsg_time + mapping_time) # total time the machine was booted total_booted_time = execute_time + load_time + extraction_time # figure runtime in milliseconds with time scale factor runtime_total_ms = runtime * time_scale_factor # create detailed report with open(detailed_report, "w") as f: active_chip_cost, fpga_cost_total, fpga_cost_runtime, \ packet_cost, mapping_cost, load_time_cost, \ data_extraction_cost, dsg_cost, router_cooling_runtime_cost = \ self._write_detailed_report( placements, machine, version, spalloc_server, remote_spinnaker_url, pacman_provenance, router_provenance, dsg_time, buffer_manager, f, load_time, mapping_time, total_booted_time, machine_allocation_controller, runtime_total_ms) # create summary report with open(summary_report, "w") as f: self._write_summary_report( active_chip_cost, fpga_cost_total, fpga_cost_runtime, packet_cost, mapping_cost, load_time_cost, data_extraction_cost, runtime_total_ms, f, mapping_time, load_time, dsg_time, dsg_cost, extraction_time, total_time, total_booted_time, router_cooling_runtime_cost) @staticmethod def _write_summary_report( active_chip_cost, fpga_cost_total, fpga_cost_runtime, packet_cost, mapping_cost, load_time_cost, data_extraction_cost, runtime_total_ms, f, mapping_time, load_time, dsg_time, dsg_cost, extraction_time, total_time, total_booted_time, router_cooling_runtime_cost): """ Write summary file :param active_chip_cost: active chip cost :param fpga_cost_total: FPGA cost over all booted time :param fpga_cost_runtime: FPGA cost during runtime :param mapping_time: the time taken by the mapping process in ms :param mapping_cost: the energy used by the mapping process :param packet_cost: packet cost :param load_time_cost: load time cost :param data_extraction_cost: data extraction cost :param runtime_total_ms: \ Runtime with time scale factor taken into account :param f: file writer :rtype: None """ # pylint: disable=too-many-arguments, too-many-locals # total the energy costs total_joules = ( active_chip_cost + fpga_cost_total + packet_cost + mapping_cost + load_time_cost + data_extraction_cost + dsg_cost + fpga_cost_runtime + router_cooling_runtime_cost) # deduce wattage from the runtime total_watts = total_joules / (total_time / 1000) # figure total kilowatt hour kilowatt_hours = total_joules / EnergyReport.JOULES_TO_KILOWATT_HOURS # write summary data f.write("Summary energy file\n-------------------\n\n") f.write( "Energy used by chips during runtime is {} Joules (over {} " "milliseconds)\n".format(active_chip_cost, runtime_total_ms)) f.write( "Energy used by FPGAs is {} Joules (over the entire time the " "machine was booted {} milliseconds)\n".format( fpga_cost_total, total_booted_time)) f.write( "Energy used by FPGAs is {} Joules (over the runtime period of " "{} milliseconds)\n".format( fpga_cost_runtime, runtime_total_ms)) f.write( "Energy used by outside router / cooling during the runtime " "period is {} Joules\n".format(router_cooling_runtime_cost)) f.write( "Energy used by packet transmissions is {} Joules (over {} " "milliseconds)\n".format(packet_cost, total_time)) f.write( "Energy used during the mapping process is {} Joules (over {} " "milliseconds)\n".format(mapping_cost, mapping_time)) f.write( "Energy used by the data generation process is {} Joules (over {} " "milliseconds)\n".format(dsg_cost, dsg_time)) f.write( "Energy used during the loading process is {} Joules (over {} " "milliseconds)\n".format(load_time_cost, load_time)) f.write( "Energy used during the data extraction process is {} Joules " "(over {} milliseconds\n".format( data_extraction_cost, extraction_time)) f.write( "Total energy used by the simulation over {} milliseconds is:\n" " {} Joules, or\n" " {} estimated average Watts, or\n" " {} kWh\n".format( total_time, total_joules, total_watts, kilowatt_hours)) def _write_detailed_report( self, placements, machine, version, spalloc_server, remote_spinnaker_url, pacman_provenance, router_provenance, dsg_time, buffer_manager, f, load_time, mapping_time, total_booted_time, machine_allocation_controller, runtime_total_ms): """ Write detailed report and calculate costs :param placements: placements :param machine: machine representation :param version: machine version :param spalloc_server: spalloc server :param remote_spinnaker_url: remote SpiNNaker URL :param pacman_provenance: provenance generated by PACMAN :param router_provenance: provenance generated by the router :param buffer_manager: buffer manager :param f: file writer :param total_booted_time: time in milliseconds where machine is booted :param machine_allocation_controller: :param runtime_total_ms: \ total runtime with time scale factor taken into account :return: machine_active_cost, machine_idle_chips_cost, \ fpga_cost, packet_cost, load_time_cost, extraction_time_cost :rtype: tuple(float,float,float,float,float,float) """ # pylint: disable=too-many-arguments, too-many-locals # write warning about accuracy etc self._write_warning(f) # figure active chips active_chips = set() for placement in placements: if not isinstance(placement.vertex, ChipPowerMonitorMachineVertex): active_chips.add(machine.get_chip_at(placement.x, placement.y)) # figure out packet cost packet_cost = self._router_packet_cost(router_provenance, f) # figure FPGA cost over all booted and during runtime cost fpga_cost_total, fpga_cost_runtime = self._calculate_fpga_cost( machine, version, spalloc_server, remote_spinnaker_url, total_booted_time, f, runtime_total_ms) # figure how many frames are using, as this is a constant cost of # routers, cooling etc n_frames = self._calculate_n_frames( machine, machine_allocation_controller) # figure load time cost load_time_cost = self._calculate_load_time_cost( pacman_provenance, machine, f, load_time, active_chips, n_frames) # figure the down time idle cost for mapping mapping_cost = self._calculate_power_down_cost( mapping_time, machine, machine_allocation_controller, version, n_frames) # figure the down time idle cost for DSG dsg_cost = self._calculate_power_down_cost( dsg_time, machine, machine_allocation_controller, version, n_frames) # figure extraction time cost extraction_time_cost = self._calculate_data_extraction_time_cost( pacman_provenance, machine, f, active_chips, n_frames) # figure out active chips idle time machine_active_cost = 0.0 for chip in active_chips: machine_active_cost += self._calculate_chips_active_cost( chip, placements, buffer_manager, f, runtime_total_ms) # figure out router idle cost during runtime router_cooling_runtime_cost = ( runtime_total_ms * n_frames * self.MILLIWATTS_FOR_FRAME_IDLE_COST) # return all magic values return machine_active_cost, fpga_cost_total, fpga_cost_runtime, \ packet_cost, mapping_cost, load_time_cost, extraction_time_cost, \ dsg_cost, router_cooling_runtime_cost def _write_warning(self, f): """ Writes the warning about this being only an estimate :param f: the writer :rtype: None """ f.write( "This report is based off energy estimates for individual " "components of the SpiNNaker machine. It is not meant to be " "completely accurate. But does use provenance data gathered from " "the machine to estimate the energy usage and therefore should " "be in the right ballpark.\n\n\n") f.write( "The energy components we use are as follows:\n\n" "The energy usage for a chip when all cores are 100% active for " "a millisecond is {} Joules.\n" "The energy usage for a chip when all cores are not active for a " "millisecond is {} Joules.\n" "The energy used by the machine for firing a packet is {} " "Joules.\n" "The energy used by each active FPGA per millisecond is {} " "Joules.\n\n\n" .format( self.MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD, self.MILLIWATTS_PER_IDLE_CHIP, self.JOULES_PER_SPIKE, self.MILLIWATTS_PER_FPGA)) def _calculate_fpga_cost( self, machine, version, spalloc_server, remote_spinnaker_url, total_runtime, f, runtime_total_ms): """ FPGA cost calculation :param machine: machine representation :param version: machine version :param spalloc_server: spalloc server IP :param remote_spinnaker_url: remote SpiNNaker URL :param total_runtime: the runtime :param f: the file writer :param runtime_total_ms: :return: power usage of FPGAs :rtype: tuple(float,float) """ # pylint: disable=too-many-arguments # if not spalloc, then could be any type of board if spalloc_server is None and remote_spinnaker_url is None: # if a spinn2 or spinn3 (4 chip boards) then they have no fpgas if int(version) in (2, 3): f.write( "A SpiNN-{} board does not contain any FPGA's, and so " "its energy cost is 0 \n".format(version)) return 0, 0 # if the spinn4 or spinn5 board, need to verify if wrap-arounds # are there, if not then assume fpgas are turned off. if int(version) in (4, 5): # how many fpgas are active n_operational_fpgas = self._board_n_operational_fpgas( machine, machine.ethernet_connected_chips[0]) # active fpgas if n_operational_fpgas > 0: return self._print_out_fpga_cost( total_runtime, n_operational_fpgas, f, version, runtime_total_ms) # no active fpgas f.write( "The FPGA's on the SpiNN-{} board are turned off and " "therefore the energy used by the FPGA is 0\n".format( version)) return 0, 0 # no idea where we are; version unrecognised raise ConfigurationException( "Do not know what the FPGA setup is for this version of " "SpiNNaker machine.") else: # spalloc machine, need to check each board total_fpgas = 0 for ethernet_connected_chip in machine.ethernet_connected_chips: total_fpgas += self._board_n_operational_fpgas( machine, ethernet_connected_chip) return self._print_out_fpga_cost( total_runtime, total_fpgas, f, version, runtime_total_ms) def _print_out_fpga_cost( self, total_runtime, n_operational_fpgas, f, version, runtime_total_ms): """ Prints out to file and returns cost :param total_runtime: all runtime :param n_operational_fpgas: number of operational FPGAs :param f: file writer :param version: machine version :param runtime_total_ms: runtime in milliseconds :return: power usage """ # pylint: disable=too-many-arguments power_usage_total = ( total_runtime * self.MILLIWATTS_PER_FPGA * n_operational_fpgas) power_usage_runtime = ( runtime_total_ms * self.MILLIWATTS_PER_FPGA * n_operational_fpgas) # print out as needed for spalloc and non-spalloc versions if version is None: f.write( "{} FPGAs on the Spalloc-ed boards are turned on and " "therefore the energy used by the FPGA during the entire time " "the machine was booted (which was {} ms) is {}. " "The usage during execution was {}".format( n_operational_fpgas, total_runtime, power_usage_total, power_usage_runtime)) else: f.write( "{} FPGA's on the SpiNN-{} board are turned on and " "therefore the energy used by the FPGA during the entire time " "the machine was booted (which was {} ms) is {}. " "The usage during execution was {}".format( n_operational_fpgas, version, total_runtime, power_usage_total, power_usage_runtime)) return power_usage_total, power_usage_runtime def _board_n_operational_fpgas(self, machine, ethernet_connected_chip): """ Figures out how many FPGAs were switched on. :param machine: SpiNNaker machine :param ethernet_connected_chip: the ethernet chip to look from :return: number of FPGAs on, on this board """ # pylint: disable=too-many-locals # positions to check for active links left_additions = [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)] right_additions = [(7, 3), (7, 4), (7, 5), (7, 6), (7, 7)] top_additions = [(4, 7), (5, 7), (6, 7), (7, 7)] bottom_additions = [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)] top_left_additions = [(0, 3), (1, 4), (2, 5), (3, 6), (4, 7)] bottom_right_additions = [(0, 4), (1, 5), (2, 6), (3, 7)] ethernet_chip_x = ethernet_connected_chip.x ethernet_chip_y = ethernet_connected_chip.y # bottom left, bottom fpga_0 = self._deduce_fpga( [bottom_additions, bottom_right_additions], [(5, 4), (0, 5)], ethernet_chip_x, ethernet_chip_y, machine) # left, and top right fpga_1 = self._deduce_fpga( [left_additions, top_left_additions], [(3, 4), (3, 2)], ethernet_chip_x, ethernet_chip_y, machine) # top and right fpga_2 = self._deduce_fpga( [top_additions, right_additions], [(2, 1), (0, 1)], ethernet_chip_x, ethernet_chip_y, machine) return fpga_1 + fpga_0 + fpga_2 @staticmethod def _deduce_fpga( shifts, overall_link_ids, ethernet_chip_x, ethernet_chip_y, machine): """ Figure out if each FPGA was on or not :param shifts: shifts from ethernet to find a FPGA edge :type shifts: iterable(iterable(int)) :param overall_link_ids: which link IDs to check :type overall_link_ids: iterable(iterable(int)) :param ethernet_chip_x: ethernet chip x :param ethernet_chip_y: ethernet chip y :param machine: machine rep :return: 0 if not on, 1 if on """ # pylint: disable=too-many-arguments for shift_group, link_ids in zip(shifts, overall_link_ids): for shift in shift_group: new_x = (ethernet_chip_x + shift[0]) % (machine.width) new_y = (ethernet_chip_y + shift[1]) % (machine.height) chip = machine.get_chip_at(new_x, new_y) if chip is not None: for link_id in link_ids: if chip.router.get_link(link_id) is not None: return 1 return 0 def _get_chip_power_monitor(self, chip, placements): """ Locate chip power monitor :param chip: the chip to consider :param placements: placements :return: the machine vertex coupled to the monitor :raises Exception: if it can't find the monitor """ # start at top, as more likely it was placed on the top for processor_id in range(17, -1, -1): processor = chip.get_processor_with_id(processor_id) if processor is not None and placements.is_processor_occupied( chip.x, chip.y, processor_id): # check if vertex is a chip power monitor vertex = placements.get_vertex_on_processor( chip.x, chip.y, processor_id) if isinstance(vertex, ChipPowerMonitorMachineVertex): return vertex raise Exception("expected to find a chip power monitor!") def _calculate_chips_active_cost( self, chip, placements, buffer_manager, f, runtime_total_ms): """ Figure out the chip active cost during simulation :param chip: the chip to consider :param placements: placements :param buffer_manager: buffer manager :param f: file writer :return: energy cost """ # pylint: disable=too-many-arguments # locate chip power monitor chip_power_monitor = self._get_chip_power_monitor(chip, placements) # get recordings from the chip power monitor recorded_measurements = chip_power_monitor.get_recorded_data( placement=placements.get_placement_of_vertex(chip_power_monitor), buffer_manager=buffer_manager) # deduce time in milliseconds per recording element time_for_recorded_sample = ( chip_power_monitor.sampling_frequency * chip_power_monitor.n_samples_per_recording) / 1000 cores_power_cost = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] # accumulate costs for recorded_measurement in recorded_measurements: for core in range(0, 18): cores_power_cost[core] += ( recorded_measurement[core] * time_for_recorded_sample * self.MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD / 18) # detailed report print out for core in range(0, 18): f.write( "processor {}:{}:{} used {} Joules of energy by being active " "during the execution of the simulation\n".format( chip.x, chip.y, core, cores_power_cost[core])) total_energy_cost = 0.0 for core_power_usage in cores_power_cost: total_energy_cost += core_power_usage # TAKE INTO ACCOUNT IDLE COST idle_cost = runtime_total_ms * self.MILLIWATTS_PER_IDLE_CHIP total_energy_cost += idle_cost f.write( "The machine used {} Joules of energy by being idle " "during the execution of the simulation".format(idle_cost)) return total_energy_cost _PER_CHIP_NAMES = set(( "expected_routers", "unexpected_routers")) _MULTICAST_COUNTER_NAMES = set(( "Local_Multicast_Packets", "External_Multicast_Packets", "Reinjected")) _PEER_TO_PEER_COUNTER_NAMES = set(( "Local_P2P_Packets", "External_P2P_Packets")) _NEAREST_NEIGHBOUR_COUNTER_NAMES = set(( "Local_NN_Packets", "External_NN_Packets")) _FIXED_ROUTE_COUNTER_NAMES = set(( "Local_FR_Packets", "External_FR_Packets")) def _router_packet_cost(self, router_provenance, f): """ Figure out the packet cost; includes MC, P2P, FR, NN packets :param router_provenance: the provenance gained from the router :param f: file writer :rtype: energy usage value """ energy_cost = 0.0 for element in router_provenance: # only process per chip counters, not summary counters. if element.names[1] not in self._PER_CHIP_NAMES: continue # process MC packets if element.names[3] in self._MULTICAST_COUNTER_NAMES: energy_cost += float(element.value) * self.JOULES_PER_SPIKE # process p2p packets elif element.names[3] in self._PEER_TO_PEER_COUNTER_NAMES: energy_cost += \ float(element.value) * self.JOULES_PER_SPIKE * 2 # process NN packets elif element.names[3] in self._NEAREST_NEIGHBOUR_COUNTER_NAMES: energy_cost += float(element.value) * self.JOULES_PER_SPIKE # process FR packets elif element.names[3] in self._FIXED_ROUTE_COUNTER_NAMES: energy_cost += \ float(element.value) * self.JOULES_PER_SPIKE * 2 # detailed report print f.write("The packet cost is {} Joules\n".format(energy_cost)) return energy_cost def _calculate_load_time_cost( self, pacman_provenance, machine, f, load_time, active_chips, n_frames): """ Energy usage from the loading phase :param pacman_provenance: provenance items from the PACMAN set :param machine: machine description :param f: file writer :param active_chips: the chips which have end user code in them :param load_time: the time of the entire load time phase in ms :return: load time energy value in Joules """ # pylint: disable=too-many-arguments # find time in milliseconds total_time_ms = 0.0 for element in pacman_provenance: if element.names[1] == "loading": total_time_ms += convert_time_diff_to_total_milliseconds( element.value) # handle monitor core active cost # min between chips that are active and fixed monitor, as when 1 # chip is used its one monitor, if more than 1 chip, # the ethernet connected chip and the monitor handling the read/write # this is checked by min energy_cost = ( total_time_ms * min(self.N_MONITORS_ACTIVE_DURING_COMMS, len(active_chips)) * (self.MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD / 18)) # handle all idle cores energy_cost += self._calculate_idle_cost(total_time_ms, machine) # handle time diff between load time and total laod phase of ASB energy_cost += ( (load_time - total_time_ms) * len(list(machine.chips)) * self.MILLIWATTS_PER_IDLE_CHIP) # handle active routers etc active_router_cost = ( load_time * n_frames * self.MILLIWATTS_PER_FRAME_ACTIVE_COST) # accumulate energy_cost += active_router_cost # detailed report write f.write( "The amount of time used during the loading process is {} " "milliseconds.\nAssumed only 2 monitor cores is executing that " "this point. We also assume that there is a baseline active " "router/cooling component that is using {} Joules. " "Overall the energy usage is {} Joules.\n".format( total_time_ms, active_router_cost, energy_cost)) return energy_cost def _calculate_data_extraction_time_cost( self, pacman_provenance, machine, f, active_chips, n_frames): """ Data extraction cost :param pacman_provenance: provenance items from the PACMAN set :param machine: machine description :param f: file writer :param active_chips: :return: cost of data extraction in Joules """ # pylint: disable=too-many-arguments # find time total_time_ms = 0.0 for element in pacman_provenance: if (element.names[1] == "Execution" and element.names[2] != "run_time_of_FrontEndCommonApplicationRunner"): total_time_ms += convert_time_diff_to_total_milliseconds( element.value) # min between chips that are active and fixed monitor, as when 1 # chip is used its one monitor, if more than 1 chip, # the ethernet connected chip and the monitor handling the read/write # this is checked by min energy_cost = ( total_time_ms * min(self.N_MONITORS_ACTIVE_DURING_COMMS, len(active_chips)) * self.MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD / 18) # add idle chip cost energy_cost += self._calculate_idle_cost(total_time_ms, machine) # handle active routers etc energy_cost_of_active_router = ( total_time_ms * n_frames * self.MILLIWATTS_PER_FRAME_ACTIVE_COST) energy_cost += energy_cost_of_active_router # detailed report f.write( "The amount of time used during the data extraction process is {} " "milliseconds.\nAssumed only 2 monitor cores is executing at " "this point. We also assume that there is a baseline active " "router/cooling component that is using {} Joules. Hence the " "overall energy usage is {} Joules.\n".format( total_time_ms, energy_cost_of_active_router, energy_cost)) return energy_cost def _calculate_idle_cost(self, time, machine): """ Calculate energy used by being idle. :param machine: machine description :param time: time machine was idle :type time: float :return: cost in joules """ return time * machine.total_available_user_cores * ( self.MILLIWATTS_PER_IDLE_CHIP / 18) def _calculate_power_down_cost( self, time, machine, machine_allocation_controller, version, n_frames): """ Calculate power down costs :param time: time powered down :param n_frames: number of frames used by this machine :return: energy in joules """ # pylint: disable=too-many-arguments # if spalloc or hbp if machine_allocation_controller is not None: return time * n_frames * self.MILLIWATTS_FOR_FRAME_IDLE_COST # if 48 chip if version == 5 or version == 4: return time * self.MILLIWATTS_FOR_BOXED_48_CHIP_FRAME_IDLE_COST # if 4 chip if version == 3 or version == 2: return (len(list(machine.chips)) * time * self.MILLIWATTS_PER_IDLE_CHIP) # boom raise ConfigurationException("don't know what to do here") @staticmethod def _calculate_n_frames(machine, machine_allocation_controller): """ Figures out how many frames are being used in this setup.\ A key of cabinet,frame will be used to identify unique frame. :param machine: the machine object :param machine_allocation_controller: the spalloc job object :return: number of frames """ # if not spalloc, then could be any type of board, but unknown cooling if machine_allocation_controller is None: return 0 # if using spalloc in some form cabinet_frame = set() for ethernet_connected_chip in machine.ethernet_connected_chips: cabinet, frame, _ = machine_allocation_controller.where_is_machine( chip_x=ethernet_connected_chip.x, chip_y=ethernet_connected_chip.y) cabinet_frame.add((cabinet, frame)) return len(list(cabinet_frame))