Skip to content

Commit

Permalink
Merge pull request #1252 from SpiNNakerManchester/energy_hard_reset
Browse files Browse the repository at this point in the history
Energy stuff with reset
  • Loading branch information
Christian-B authored Jan 3, 2025
2 parents d7ab4de + 39deb9e commit fc9d271
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 57 deletions.
3 changes: 2 additions & 1 deletion spinn_front_end_common/interface/abstract_spinnaker_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -173,14 +182,15 @@ 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")

chip_activity: ChipActiveTime = {}
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)
Expand Down
19 changes: 15 additions & 4 deletions spinn_front_end_common/interface/provenance/global.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;
Expand All @@ -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(
Expand Down
65 changes: 65 additions & 0 deletions spinn_front_end_common/interface/provenance/global_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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]:
"""
Expand Down Expand Up @@ -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
Expand Down
18 changes: 0 additions & 18 deletions spinn_front_end_common/interface/provenance/provenance_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions spinn_front_end_common/utilities/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions spinn_front_end_common/utilities/db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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)
4 changes: 3 additions & 1 deletion unittests/interface/provenance/test_provenance_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit fc9d271

Please sign in to comment.