diff --git a/.ratexcludes b/.ratexcludes
index 8acb1c02ea..964a183a24 100644
--- a/.ratexcludes
+++ b/.ratexcludes
@@ -17,3 +17,4 @@
**/PACMAN/**
**/DataSpecification/**
**/spalloc/**
+**/unittests/**/*.rpt
diff --git a/spinn_front_end_common/interface/abstract_spinnaker_base.py b/spinn_front_end_common/interface/abstract_spinnaker_base.py
index f11fac6a12..1d3909fc9b 100644
--- a/spinn_front_end_common/interface/abstract_spinnaker_base.py
+++ b/spinn_front_end_common/interface/abstract_spinnaker_base.py
@@ -68,6 +68,8 @@
AbstractVertexWithEdgeToDependentVertices,
AbstractCanReset)
from spinn_front_end_common.interface.buffer_management import BufferManager
+from spinn_front_end_common.interface.buffer_management.storage_objects \
+ import BufferDatabase
from spinn_front_end_common.interface.config_handler import ConfigHandler
from spinn_front_end_common.interface.interface_functions import (
application_finisher, application_runner,
@@ -109,7 +111,7 @@
memory_map_on_host_chip_report, network_specification,
router_collision_potential_report,
routing_table_from_machine_report, tags_from_machine_report,
- write_json_machine, write_json_placements,
+ write_chip_active_report, write_json_machine, write_json_placements,
write_json_routing_tables, drift_report)
from spinn_front_end_common.utilities.iobuf_extractor import IOBufExtractor
from spinn_front_end_common.utilities.utility_objs import ExecutableType
@@ -2270,10 +2272,34 @@ def _print_iobuf(errors, warnings):
for error in errors:
logger.error(error)
+ def _execute_prepare_chip_power(self):
+ with FecTimer("Prepare Chip Power", TimerWork.REPORT) as timer:
+ if timer.skip_if_cfg_false("Reports", "write_energy_report"):
+ return
+ if timer.skip_if_virtual_board():
+ return
+ db = BufferDatabase()
+ db.store_placements()
+ db.store_chip_power_monitors()
+ db.close()
+
+ def _report_chip_active(self):
+ with FecTimer("Prepare Chip Power", TimerWork.REPORT) as timer:
+ if timer.skip_if_cfg_false("Reports", "write_energy_report"):
+ return
+ if timer.skip_if_virtual_board():
+ return
+ write_chip_active_report()
+
+ def _do_end_of_run(self):
+ if not self._data_writer.is_ran_last():
+ return
+ self._execute_prepare_chip_power()
+ self._report_chip_active()
+
def reset(self):
""" Code that puts the simulation back at time zero
"""
- FecTimer.start_category(TimerCategory.RESETTING)
if not self._data_writer.is_ran_last():
if not self._data_writer.is_ran_ever():
logger.error("Ignoring the reset before the run")
@@ -2281,8 +2307,11 @@ def reset(self):
logger.error("Ignoring the repeated reset call")
return
+ FecTimer.start_category(TimerCategory.RESETTING)
logger.info("Resetting")
+ self._do_end_of_run()
+
# rewind the buffers from the buffer manager, to start at the beginning
# of the simulation again and clear buffered out
if self._data_writer.has_buffer_manager():
@@ -2367,6 +2396,8 @@ def stop(self):
set_config("Reports", "read_provenance_data", "True")
self._do_read_provenance()
+ self._do_end_of_run()
+
except Exception as e:
self._recover_from_error(e)
self.write_errored_file()
diff --git a/spinn_front_end_common/interface/buffer_management/storage_objects/sqllite_database.py b/spinn_front_end_common/interface/buffer_management/storage_objects/sqllite_database.py
index 4bf8db2943..3d25121412 100644
--- a/spinn_front_end_common/interface/buffer_management/storage_objects/sqllite_database.py
+++ b/spinn_front_end_common/interface/buffer_management/storage_objects/sqllite_database.py
@@ -53,11 +53,10 @@ def __init__(self, database_file=None):
:param str database_file:
The name of a file that contains (or will contain) an SQLite
database holding the data.
- If omitted the default location will be used.
+ If omitted the default location will be used
"""
if database_file is None:
database_file = self.default_database_file()
-
super().__init__(database_file, ddl_file=_DDL_FILE)
@classmethod
@@ -274,7 +273,7 @@ def store_data_in_region_buffer(self, x, y, p, region, missing, data):
region_id, content, content_len)
VALUES (?, CAST(? AS BLOB), ?)
""", (region_id, datablob, len(data)))
- assert cursor.rowcount == 1
+ assert cursor.rowcount == 1
def __use_main_table(self, cursor, region_id):
"""
@@ -314,3 +313,100 @@ def get_region_data(self, x, y, p, region):
return data, False
except LookupError:
return memoryview(b''), True
+
+ def store_placements(self):
+ exists = False
+ with self.transaction() as cursor:
+ for row in cursor.execute("PRAGMA TABLE_INFO(core)"):
+ if row["name"] == "label":
+ exists = True
+
+ if exists:
+ return
+ # already done so no need to repeat
+
+ cursor.execute("ALTER TABLE core ADD COLUMN label STRING")
+
+ for placement in FecDataView.iterate_placemements():
+ core_id = self.__get_core_id(
+ cursor, placement.x, placement.y, placement.p)
+ cursor.execute(
+ "UPDATE core SET label = ? WHERE core_id = ?",
+ (placement.vertex.label, core_id))
+ assert cursor.rowcount == 1
+
+ for chip in FecDataView.get_machine().chips:
+ for processor in chip.processors:
+ if processor.is_monitor:
+ core_id = self.__get_core_id(
+ cursor, chip.x, chip.y, processor.processor_id)
+ cursor.execute(
+ """
+ UPDATE core SET label = 'MONITOR'
+ WHERE core_id = ?
+ """, [core_id])
+ assert cursor.rowcount == 1
+
+ def get_label(self, x, y, p):
+ with self.transaction() as cursor:
+ for row in cursor.execute(
+ """
+ SELECT label
+ FROM core
+ WHERE x = ? AND y = ? and processor = ?
+ """, (x, y, p)):
+ return str(row["label"], 'utf8')
+ return ""
+
+ def store_chip_power_monitors(self):
+ # delayed import due to circular refrences
+ from spinn_front_end_common.utility_models.\
+ chip_power_monitor_machine_vertex import (
+ ChipPowerMonitorMachineVertex)
+
+ with self.transaction() as cursor:
+ for _ in cursor.execute(
+ """
+ SELECT name FROM sqlite_master
+ WHERE type='table' AND name='chip_power_monitor'
+ """):
+ # Already exists so no need to run again
+ return
+
+ cursor.execute(
+ """
+ CREATE TABLE chip_power_monitors(
+ cpm_id INTEGER PRIMARY KEY autoincrement,
+ core_id INTEGER NOT NULL
+ REFERENCES core(core_id) ON DELETE RESTRICT,
+ sampling_frequency FLOAT NOT NULL)
+ """)
+
+ cursor.execute(
+ """
+ CREATE VIEW chip_power_monitors_view AS
+ SELECT core_id, x, y, processor, sampling_frequency
+ FROM core NATURAL JOIN chip_power_monitors
+ """)
+
+ for placement in FecDataView.iterate_placements_by_vertex_type(
+ ChipPowerMonitorMachineVertex):
+ core_id = self.__get_core_id(
+ cursor, placement.x, placement.y, placement.p)
+ cursor.execute(
+ """
+ INSERT INTO chip_power_monitors(
+ core_id, sampling_frequency)
+ VALUES (?, ?)
+ """, (core_id, placement.vertex.sampling_frequency))
+ assert cursor.rowcount == 1
+
+ def iterate_chip_power_monitor_cores(self):
+ with self.transaction() as cursor:
+ for row in cursor.execute(
+ """
+ SELECT x, y, processor, sampling_frequency
+ FROM chip_power_monitors_view
+ ORDER BY core_id
+ """):
+ yield row
diff --git a/spinn_front_end_common/utilities/report_functions/__init__.py b/spinn_front_end_common/utilities/report_functions/__init__.py
index 2a28071ecc..c66d0aa9e2 100644
--- a/spinn_front_end_common/utilities/report_functions/__init__.py
+++ b/spinn_front_end_common/utilities/report_functions/__init__.py
@@ -16,6 +16,7 @@
from .bit_field_compressor_report import bitfield_compressor_report
from .bit_field_summary import BitFieldSummary
from .board_chip_report import board_chip_report
+from .chip_active_report import write_chip_active_report
from .energy_report import EnergyReport
from .fixed_route_from_machine_report import fixed_route_from_machine_report
from .memory_map_on_host_chip_report import memory_map_on_host_chip_report
@@ -44,6 +45,7 @@
"router_collision_potential_report",
"routing_table_from_machine_report",
"tags_from_machine_report",
+ "write_chip_active_report",
"write_json_machine",
"write_json_placements",
"write_json_routing_tables",
diff --git a/spinn_front_end_common/utilities/report_functions/chip_active_report.py b/spinn_front_end_common/utilities/report_functions/chip_active_report.py
new file mode 100644
index 0000000000..6b4d872c41
--- /dev/null
+++ b/spinn_front_end_common/utilities/report_functions/chip_active_report.py
@@ -0,0 +1,101 @@
+# 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 .
+
+import logging
+import numpy
+import os
+from spinn_utilities.config_holder import get_config_int
+from spinn_utilities.exceptions import SpiNNUtilsException
+from spinn_utilities.log import FormatAdapter
+from spinn_front_end_common.data import FecDataView
+from spinn_front_end_common.interface.buffer_management.storage_objects \
+ import (BufferDatabase)
+from spinn_front_end_common.interface.interface_functions.compute_energy_used\
+ import (MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD)
+
+logger = FormatAdapter(logging.getLogger(__name__))
+
+#: converter between joules to kilowatt hours
+JOULES_TO_KILOWATT_HOURS = 3600000
+
+# energy report file name
+CHIP_ACTIVE_FILENAME = "chip_active_report.rpt"
+
+
+def write_chip_active_report(report_path=None, buffer_path=None):
+ """ Writes the report.
+
+ :param report_path: Where to write the report if not using the default
+ :type report_path: None or str
+ :param buffer_path: Where the provenance sqlite3 files is located
+ if not using the default.
+ :type buffer_path: None or str
+ :rtype: None
+ """
+ if report_path is None:
+ try:
+ report_dir = FecDataView.get_run_dir_path()
+ report_path = os.path.join(
+ report_dir, CHIP_ACTIVE_FILENAME)
+ except SpiNNUtilsException:
+ report_path = os.path.join(
+ os.path.curdir, CHIP_ACTIVE_FILENAME)
+ logger.warning(f"no report_path so writing to {report_path}")
+
+ # create detailed report
+ with open(report_path, "w", encoding="utf-8") as f:
+ __write_report(f, buffer_path)
+
+
+def __write_report(f, buffer_path):
+ db = BufferDatabase(buffer_path)
+ n_samples_per_recording = get_config_int(
+ "EnergyMonitor", "n_samples_per_recording_entry")
+
+ milliwatts = MILLIWATTS_PER_CHIP_ACTIVE_OVERHEAD / 18
+ activity_total = 0
+ energy_total = 0
+
+ for row in db.iterate_chip_power_monitor_cores():
+ record_raw, data_missing = db.get_region_data(
+ row["x"], row["y"], row["processor"], 0)
+ results = (
+ numpy.frombuffer(record_raw, dtype="uint32").reshape(-1, 18) /
+ n_samples_per_recording)
+ active_sums = numpy.sum(results, axis=0)
+ activity_count = numpy.sum(results)
+ time_for_recorded_sample =\
+ (row["sampling_frequency"] * n_samples_per_recording) / 1000
+ energy_factor = time_for_recorded_sample * milliwatts
+
+ for core in range(0, 18):
+ label = db.get_label(row["x"], row["y"], core)
+ if (active_sums[core] > 0) or label:
+ f.write(
+ f"processor {row['x']}:{row['y']}:{core}({label})"
+ f" was active for {active_sums[core]}ms "
+ f" using { active_sums[core] * energy_factor} Joules\n")
+
+ energy = activity_count * energy_factor
+ activity_total += activity_count
+ energy_total += energy
+ f.write(
+ f"Total for chip {row['x']}:{row['y']} "
+ f" was {activity_count}ms of activity "
+ f" using {energy} Joules\n\n")
+ f.write(
+ f"Total "
+ f" was {activity_total}ms of activity "
+ f" using {energy_total} Joules\n\n")
diff --git a/unittests/utilities/.gitignore b/unittests/utilities/.gitignore
new file mode 100644
index 0000000000..b911337edd
--- /dev/null
+++ b/unittests/utilities/.gitignore
@@ -0,0 +1 @@
+*.rpt
\ No newline at end of file
diff --git a/unittests/utilities/buffer.sqlite3 b/unittests/utilities/buffer.sqlite3
new file mode 100644
index 0000000000..c36eac0b3c
Binary files /dev/null and b/unittests/utilities/buffer.sqlite3 differ
diff --git a/unittests/utilities/test_chip_active.py b/unittests/utilities/test_chip_active.py
new file mode 100644
index 0000000000..422396e9c1
--- /dev/null
+++ b/unittests/utilities/test_chip_active.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2017-2022 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 .
+
+import os
+import unittest
+from spinn_utilities.exceptions import InvalidDirectory
+from spinn_front_end_common.data.fec_data_writer import FecDataWriter
+from spinn_front_end_common.interface.config_setup import unittest_setup
+from spinn_front_end_common.utilities.report_functions import (
+ write_chip_active_report)
+
+
+class TestChipActive(unittest.TestCase):
+
+ def setUp(cls):
+ unittest_setup()
+
+ def test_no_params(self):
+ try:
+ write_chip_active_report()
+ failed = False
+ except Exception as ex:
+ self.assertIn("no such table", str(ex))
+ failed = True
+ self.assertTrue(failed)
+
+ def test_db_only(self):
+ # make sure there is not run_dir_path so falls back on default
+ writer = FecDataWriter.setup()
+ try:
+ writer.set_run_dir_path("THIS DIRECTORY DOES NOT EXIST")
+ except InvalidDirectory:
+ pass
+ db_path = os.path.join(os.path.dirname(__file__), "buffer.sqlite3")
+ write_chip_active_report(buffer_path=db_path)
+
+ def test_all_params(self):
+ # make sure there is not run_dir_path so falls back on default
+ writer = FecDataWriter.setup()
+ try:
+ writer.set_run_dir_path("THIS DIRECTORY DOES NOT EXIST")
+ except InvalidDirectory:
+ pass
+ db_path = os.path.join(os.path.dirname(__file__), "buffer.sqlite3")
+ report = os.path.join(os.path.dirname(__file__), "my_active.rpt")
+ write_chip_active_report(report_path=report, buffer_path=db_path)
+
+
+if __name__ == '__main__':
+ unittest.main()