Skip to content
1 change: 1 addition & 0 deletions .ratexcludes
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
**/PACMAN/**
**/DataSpecification/**
**/spalloc/**
**/unittests/**/*.rpt
35 changes: 33 additions & 2 deletions spinn_front_end_common/interface/abstract_spinnaker_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2270,19 +2272,46 @@ 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")
else:
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():
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions spinn_front_end_common/utilities/report_functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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")
1 change: 1 addition & 0 deletions unittests/utilities/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.rpt
Binary file added unittests/utilities/buffer.sqlite3
Binary file not shown.
62 changes: 62 additions & 0 deletions unittests/utilities/test_chip_active.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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()