diff --git a/spinn_front_end_common/interface/abstract_spinnaker_base.py b/spinn_front_end_common/interface/abstract_spinnaker_base.py index c430c1af1..975337a0b 100644 --- a/spinn_front_end_common/interface/abstract_spinnaker_base.py +++ b/spinn_front_end_common/interface/abstract_spinnaker_base.py @@ -530,7 +530,8 @@ def _calc_run_time(self, run_time: Optional[float]) -> Union[ def _run(self, run_time: Optional[float], sync_time: float) -> None: self._data_writer.start_run() - + with GlobalProvenance() as db: + db.insert_run_reset_mapping() try: self.__run(run_time, sync_time) self._data_writer.finish_run() diff --git a/spinn_front_end_common/interface/interface_functions/compute_energy_used.py b/spinn_front_end_common/interface/interface_functions/compute_energy_used.py index fb2d7f673..9a438fc7f 100644 --- a/spinn_front_end_common/interface/interface_functions/compute_energy_used.py +++ b/spinn_front_end_common/interface/interface_functions/compute_energy_used.py @@ -50,19 +50,25 @@ def compute_energy_used(checkpoint: Optional[int] = None) -> PowerUsed: """ # Get data from provenance with GlobalProvenance() as db: - waiting_ms = db.get_category_timer_sum(TimerCategory.WAITING) - setup_ms = db.get_timer_sum_by_category(TimerCategory.SETTING_UP) - get_machine_ms = db.get_timer_sum_by_category( + waiting_ms = db.get_category_timer_sum_by_reset(TimerCategory.WAITING) + setup_ms = db.get_timer_sum_by_category_and_reset( + TimerCategory.SETTING_UP) + get_machine_ms = db.get_timer_sum_by_category_and_reset( TimerCategory.GET_MACHINE) - mapping_ms = db.get_timer_sum_by_category(TimerCategory.MAPPING) - loading_ms = db.get_timer_sum_by_category(TimerCategory.LOADING) + mapping_ms = db.get_timer_sum_by_category_and_reset( + TimerCategory.MAPPING) + loading_ms = db.get_timer_sum_by_category_and_reset( + TimerCategory.LOADING) - run_other_ms = db.get_timer_sum_by_category(TimerCategory.RUN_OTHER) - run_loop_ms = db.get_timer_sum_by_category(TimerCategory.RUN_LOOP) - resetting_ms = db.get_timer_sum_by_category(TimerCategory.RESETTING) + run_other_ms = db.get_timer_sum_by_category_and_reset( + TimerCategory.RUN_OTHER) + run_loop_ms = db.get_timer_sum_by_category_and_reset( + TimerCategory.RUN_LOOP) + resetting_ms = db.get_timer_sum_by_category_and_reset( + TimerCategory.RESETTING) - shutting_down_ms = db.get_timer_sum_by_category( + shutting_down_ms = db.get_timer_sum_by_category_and_reset( TimerCategory.SHUTTING_DOWN) # Separate out processes that are part of the others but that happen @@ -97,16 +103,19 @@ def compute_energy_used(checkpoint: Optional[int] = None) -> PowerUsed: n_frames = _calculate_n_frames(machine) active_cores: Dict[Tuple[int, int], int] = defaultdict(int) + power_cores: Dict[Tuple[int, int], int] = {} n_active_cores = 0 for pl in FecDataView.iterate_placemements(): if not isinstance(pl.vertex, AbstractHasAssociatedBinary): continue vertex: AbstractHasAssociatedBinary = cast( AbstractHasAssociatedBinary, pl.vertex) - if (vertex.get_binary_start_type() != ExecutableType.SYSTEM and - not isinstance(vertex, ChipPowerMonitorMachineVertex)): - active_cores[(pl.x, pl.y)] += 1 - n_active_cores += 1 + if vertex.get_binary_start_type() != ExecutableType.SYSTEM: + if isinstance(vertex, ChipPowerMonitorMachineVertex): + power_cores[(pl.x, pl.y)] = pl.p + else: + active_cores[(pl.x, pl.y)] += 1 + n_active_cores += 1 n_active_chips = len(active_cores) # TODO confirm Power monitors are not included here @@ -115,7 +124,7 @@ def compute_energy_used(checkpoint: Optional[int] = None) -> PowerUsed: extra_monitors_per_board = (version.n_scamp_cores + FecDataView.get_ethernet_monitor_cores() - 1) run_chip_active_time = _extract_cores_active_time( - checkpoint, active_cores, version) + checkpoint, active_cores, power_cores, version) load_chip_active_time = _make_extra_monitor_core_use( data_loading_ms, machine, extra_monitors_per_board, extra_monitors_per_chip) @@ -173,6 +182,7 @@ def _extract_router_packets( def _extract_cores_active_time( checkpoint: Optional[int], active_cores: Dict[Tuple[int, int], int], + power_cores: Dict[Tuple[int, int], int], version: AbstractVersion) -> ChipActiveTime: sampling_frequency = get_config_int("EnergyMonitor", "sampling_frequency") @@ -180,7 +190,7 @@ def _extract_cores_active_time( with BufferDatabase() as buff_db: for (x, y), n_cores in active_cores.items(): # Find the core that was used on this chip for power monitoring - p = buff_db.get_power_monitor_core(x, y) + p = power_cores[(x, y)] # Get time per sample in seconds (frequency in microseconds) time_for_recorded_sample_s = sampling_frequency / _US_PER_SECOND data, _missing = buff_db.get_recording(x, y, p, RECORDING_CHANNEL) diff --git a/spinn_front_end_common/interface/provenance/global.sql b/spinn_front_end_common/interface/provenance/global.sql index 2009e6ecf..c91db4b24 100644 --- a/spinn_front_end_common/interface/provenance/global.sql +++ b/spinn_front_end_common/interface/provenance/global.sql @@ -22,6 +22,10 @@ CREATE TABLE IF NOT EXISTS version_provenance( description STRING NOT NULL, the_value STRING NOT NULL); +CREATE TABLE IF NOT EXISTS run_reset_mapping( + n_run INTEGER NOT NULL PRIMARY KEY, + n_reset INTEGER NOT NULL); + -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- A table holding the values for algorithm timings CREATE TABLE IF NOT EXISTS timer_provenance( @@ -33,13 +37,13 @@ CREATE TABLE IF NOT EXISTS timer_provenance( skip_reason STRING); CREATE VIEW IF NOT EXISTS full_timer_view AS - SELECT timer_id, category, algorithm, work, machine_on, timer_provenance.time_taken, n_run, n_loop, skip_reason - FROM timer_provenance ,category_timer_provenance - WHERE timer_provenance.category_id = category_timer_provenance.category_id + SELECT timer_id, category, algorithm, work, machine_on, timer_provenance.time_taken, n_reset, n_run, n_loop, skip_reason + FROM timer_provenance ,category_timer_view + WHERE timer_provenance.category_id = category_timer_view.category_id ORDER BY timer_id; CREATE VIEW IF NOT EXISTS timer_view AS - SELECT category, algorithm, work, machine_on, time_taken, n_run, n_loop + SELECT category, algorithm, work, machine_on, time_taken, n_reset, n_run, n_loop FROM full_timer_view WHERE skip_reason is NULL ORDER BY timer_id; @@ -54,6 +58,13 @@ CREATE TABLE IF NOT EXISTS category_timer_provenance( n_run INTEGER NOT NULL, n_loop INTEGER); +CREATE VIEW IF NOT EXISTS category_timer_view as + SELECT category_id, category, time_taken, machine_on, n_reset, n_run, n_loop + FROM + category_timer_provenance + NATURAL JOIN + run_reset_mapping; + --------------------------------------------------------------------- -- A table to store log.info CREATE TABLE IF NOT EXISTS p_log_provenance( diff --git a/spinn_front_end_common/interface/provenance/global_provenance.py b/spinn_front_end_common/interface/provenance/global_provenance.py index 78a6d6cbf..d6dfda12a 100644 --- a/spinn_front_end_common/interface/provenance/global_provenance.py +++ b/spinn_front_end_common/interface/provenance/global_provenance.py @@ -100,6 +100,20 @@ def insert_version(self, description: str, the_value: str) -> None: VALUES(?, ?) """, [description, the_value]) + def insert_run_reset_mapping(self) -> None: + """ + Inserts a mapping between rest number and run number + + :return: + """ + self.cursor().execute( + """ + INSERT INTO run_reset_mapping( + n_run, n_reset) + VALUES(?, ?) + """, + [FecDataView.get_run_number(), FecDataView.get_reset_number()]) + def insert_category( self, category: TimerCategory, machine_on: bool) -> int: """ @@ -309,6 +323,31 @@ def get_category_timer_sum(self, category: TimerCategory) -> int: except IndexError: return 0 + def get_category_timer_sum_by_reset(self, category: TimerCategory, + n_reset: Optional[int] = None) -> int: + """ + Get the total runtime for one category of algorithms + + :param TimerCategory category: + :return: total off all run times with this category + :rtype: int + """ + if n_reset is None: + n_reset = FecDataView.get_reset_number() + query = """ + SELECT sum(time_taken) + FROM category_timer_view + WHERE category = ? AND n_reset = ? + """ + data = self.run_query(query, [category.category_name, n_reset]) + try: + info = data[0][0] + if info is None: + return 0 + return info + except IndexError: + return 0 + def get_category_timer_sums( self, category: TimerCategory) -> Tuple[int, int]: """ @@ -359,6 +398,32 @@ def get_timer_sum_by_category(self, category: TimerCategory) -> int: except IndexError: return 0 + def get_timer_sum_by_category_and_reset( + self, category: TimerCategory, + n_reset: Optional[int] = None) -> int: + """ + Get the total runtime for one category of algorithms + + :param TimerCategory category: + :return: total of all run times with this category + :rtype: int + """ + if n_reset is None: + n_reset = FecDataView.get_reset_number() + query = """ + SELECT sum(time_taken) + FROM full_timer_view + WHERE category = ? AND n_reset = ? + """ + data = self.run_query(query, [category.category_name, n_reset]) + try: + info = data[0][0] + if info is None: + return 0 + return info + except IndexError: + return 0 + def get_timer_sum_by_work(self, work: TimerWork) -> int: """ Get the total runtime for one work type of algorithms diff --git a/spinn_front_end_common/interface/provenance/provenance_reader.py b/spinn_front_end_common/interface/provenance/provenance_reader.py index 187d33aa9..3b90b4f91 100644 --- a/spinn_front_end_common/interface/provenance/provenance_reader.py +++ b/spinn_front_end_common/interface/provenance/provenance_reader.py @@ -16,8 +16,6 @@ from typing import Iterable, List, Optional, Sequence, Tuple, cast from typing_extensions import TypeAlias from spinn_utilities.typing.coords import XYP -from spinn_front_end_common.data import FecDataView -from spinn_front_end_common.utilities.constants import PROVENANCE_DB from spinn_front_end_common.utilities.base_database import ( BaseDatabase, _SqliteTypes) @@ -46,22 +44,6 @@ class ProvenanceReader(BaseDatabase): __slots__ = () - @classmethod - def get_last_run_database_path(cls) -> str: - """ - Get the path of the current provenance database of the last run. - - .. warning:: - Calling this method between start/reset and run may result in a - path to a database not yet created. - - :raises ValueError: - if the system is in a state where path can't be retrieved, - for example before run is called - """ - return os.path.join( - FecDataView.get_provenance_dir_path(), PROVENANCE_DB) - def __init__(self, provenance_data_path: Optional[str] = None): """ Create a wrapper around the database. diff --git a/spinn_front_end_common/interface/provenance/provenance_writer.py b/spinn_front_end_common/interface/provenance/provenance_writer.py index 528b85d0d..c6fc2a5a1 100644 --- a/spinn_front_end_common/interface/provenance/provenance_writer.py +++ b/spinn_front_end_common/interface/provenance/provenance_writer.py @@ -17,6 +17,7 @@ from spinn_utilities.config_holder import ( get_config_int_or_none, get_config_bool) from spinn_utilities.log import FormatAdapter +from spinn_front_end_common.data import FecDataView from spinn_front_end_common.utilities.base_database import ( BaseDatabase, _SqliteTypes) @@ -61,12 +62,13 @@ def insert_power(self, description: str, the_value: _SqliteTypes) -> None: """ if not get_config_bool("Reports", "write_provenance"): return + run = FecDataView.get_run_number() self.cursor().execute( """ INSERT INTO power_provenance( - description, the_value) - VALUES(?, ?) - """, [description, the_value]) + run, description, the_value) + VALUES(?, ?, ?) + """, [run, description, the_value]) def insert_gatherer( self, x: int, y: int, address: int, bytes_read: int, run: int, diff --git a/spinn_front_end_common/utilities/constants.py b/spinn_front_end_common/utilities/constants.py index e6e491d13..b61cf9092 100644 --- a/spinn_front_end_common/utilities/constants.py +++ b/spinn_front_end_common/utilities/constants.py @@ -126,8 +126,6 @@ class BufferingOperations(Enum): #: The number of clock cycles per micro-second (at 200Mhz) CLOCKS_PER_US = 200 -PROVENANCE_DB = "provenance.sqlite3" - #: SDRAM Tag used by the compressor to find the routing tables COMPRESSOR_SDRAM_TAG = 1 diff --git a/spinn_front_end_common/utilities/db.sql b/spinn_front_end_common/utilities/db.sql index 3deb186e0..0e1c86643 100644 --- a/spinn_front_end_common/utilities/db.sql +++ b/spinn_front_end_common/utilities/db.sql @@ -133,6 +133,7 @@ CREATE TABLE IF NOT EXISTS proxy_configuration( -- Except for engery used by cores or routers CREATE TABLE IF NOT EXISTS power_provenance( power_id INTEGER PRIMARY KEY AUTOINCREMENT, + run INTEGER NOT NULL, description STRING NOT NULL, the_value FLOAT NOT NULL); diff --git a/spinn_front_end_common/utilities/report_functions/energy_report.py b/spinn_front_end_common/utilities/report_functions/energy_report.py index b660f4f22..10abbea0e 100644 --- a/spinn_front_end_common/utilities/report_functions/energy_report.py +++ b/spinn_front_end_common/utilities/report_functions/energy_report.py @@ -30,8 +30,10 @@ class EnergyReport(object): __slots__ = () - # energy report file name - _SUMMARY_FILENAME = "energy_report.rpt" + @classmethod + def file_name(cls, n_run: int) -> str: + """ Name of the Energy report file for this run """ + return f"energy_report_{n_run}.rpt" def write_energy_report(self, power_used: PowerUsed) -> None: """ @@ -43,7 +45,8 @@ def write_energy_report(self, power_used: PowerUsed) -> None: report_dir = FecDataView.get_run_dir_path() # summary report path - summary_report = os.path.join(report_dir, self._SUMMARY_FILENAME) + summary_report = os.path.join( + report_dir, self.file_name(FecDataView.get_run_number())) # create summary report with open(summary_report, "w", encoding="utf-8") as f: diff --git a/spinn_front_end_common/utility_models/chip_power_monitor_machine_vertex.py b/spinn_front_end_common/utility_models/chip_power_monitor_machine_vertex.py index 252c93a57..3040ae605 100644 --- a/spinn_front_end_common/utility_models/chip_power_monitor_machine_vertex.py +++ b/spinn_front_end_common/utility_models/chip_power_monitor_machine_vertex.py @@ -35,15 +35,12 @@ from spinn_front_end_common.interface.buffer_management.buffer_models import ( AbstractReceiveBuffersToHost) from spinn_front_end_common.interface.ds import DataSpecificationGenerator -from spinn_front_end_common.interface.provenance import ProvenanceWriter from spinn_front_end_common.utilities.constants import ( SYSTEM_BYTES_REQUIREMENT, SIMULATION_N_BYTES, BYTES_PER_WORD) from spinn_front_end_common.utilities.helpful_functions import ( locate_memory_region_for_placement) from spinn_front_end_common.interface.simulation.simulation_utilities import ( get_simulation_header_array) -from spinn_front_end_common.interface.buffer_management.storage_objects\ - .buffer_database import PROVENANCE_CORE_KEY logger = FormatAdapter(logging.getLogger(__name__)) BINARY_FILE_NAME = "chip_power_monitor.aplx" @@ -134,8 +131,6 @@ def generate_data_specification(self, spec: DataSpecificationGenerator, # End-of-Spec: spec.end_specification() - self.__write_recording_metadata(placement) - def _write_configuration_region( self, spec: DataSpecificationGenerator) -> None: """ @@ -224,8 +219,3 @@ def _deduce_sdram_requirements_per_timer_tick(self) -> int: n_entries = math.floor(FecDataView.get_hardware_time_step_us() / recording_time) return int(math.ceil(n_entries * RECORDING_SIZE_PER_ENTRY)) - - def __write_recording_metadata(self, placement: Placement) -> None: - with ProvenanceWriter() as db: - db.insert_monitor_value( - placement.x, placement.y, PROVENANCE_CORE_KEY, placement.p) diff --git a/unittests/interface/provenance/test_provenance_database.py b/unittests/interface/provenance/test_provenance_database.py index 3159be52a..b721c5094 100644 --- a/unittests/interface/provenance/test_provenance_database.py +++ b/unittests/interface/provenance/test_provenance_database.py @@ -61,11 +61,13 @@ def test_power(self): db.insert_power("total time (seconds)", 6.81) with ProvenanceReader() as db: data = db.run_query("select * from power_provenance") - power = [(1, 'num_cores', 34.0), (2, 'total time (seconds)', 6.81)] + power = [(1, 1, 'num_cores', 34.0), + (2, 1, 'total time (seconds)', 6.81)] self.assertListEqual(data, power) def test_timings(self): with GlobalProvenance() as db: + db.insert_run_reset_mapping() mapping_id = db.insert_category(TimerCategory.MAPPING, False) db.insert_timing( mapping_id, "compressor", TimerWork.OTHER,