Skip to content

Commit

Permalink
Implement count signal.
Browse files Browse the repository at this point in the history
Implements the count functionality as `count_cores_in_state`.

Additionally:
 - Adds debug logging to `send_scp`
 - Instantiates the default test logger to show DEBUG messages on fail.
 - Corrects some mistakes in `consts`
  • Loading branch information
mundya committed Mar 30, 2015
1 parent d20cd42 commit 0d32c0f
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 6 deletions.
15 changes: 10 additions & 5 deletions rig/machine_control/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,9 @@ class AppDiagnosticSignal(enum.IntEnum):
Note that a value is returned when any of these signals is sent.
"""
OR = 16 # Is ANY core in a given state
AND = 17 # Are ALL cores in a given state
count = 18 # How many cores are in a state
OR = 0 # Is ANY core in a given state
AND = 1 # Are ALL cores in a given state
count = 2 # How many cores are in a state


@int_enum_doc
Expand All @@ -269,13 +269,18 @@ class MessageType(enum.IntEnum):
AppSignal.usr1: MessageType.multicast,
AppSignal.usr2: MessageType.multicast,
AppSignal.usr3: MessageType.multicast,
}
"""Mapping from an :py:class:`.AppSignal` to the :py:class:`.MessageType`
used to transmit it.
"""

diagnostic_signal_types = {
AppDiagnosticSignal.AND: MessageType.peer_to_peer,
AppDiagnosticSignal.OR: MessageType.peer_to_peer,
AppDiagnosticSignal.count: MessageType.peer_to_peer,
}
"""Mapping from an :py:class:`.AppSignal` to the :py:class:`.MessageType`
used to transmit it.
"""Mapping from an :py:class:`.AppDiagnosticSignal` to the
:py:class:`.MessageType` used to transmit it.
"""


Expand Down
32 changes: 32 additions & 0 deletions rig/machine_control/machine_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,38 @@ def send_signal(self, signal, app_id=Required):
arg3 = 0x0000ffff # Meaning "transmit to all"
self._send_scp(0, 0, 0, SCPCommands.signal, arg1, arg2, arg3)

@ContextMixin.use_contextual_arguments
def count_cores_in_state(self, state, app_id=Required):
"""Count the number of cores in a given state.
.. warning::
In current implementations of SARK, signals (which are used to
determine the state of cores) are highly likely to arrive but this
is not guaranteed (especially when the system's network is heavily
utilised). Users should treat this mechanism with caution.
Parameters
----------
state : :py:class:`~rig.machine_control.consts.AppState`
Count the number of cores currently in this state.
"""
# TODO Determine a way to nicely express a way to use the region data
# stored in arg3.
region = 0x0000ffff # Largest possible machine, level 0
level = (region >> 16) & 0x3
mask = region & 0x0000ffff

# Construct the packet
arg1 = consts.diagnostic_signal_types[consts.AppDiagnosticSignal.count]
arg2 = ((level << 26) | (1 << 22) |
(consts.AppDiagnosticSignal.count << 20) | (state << 16) |
(0xff << 8) | app_id) # App mask for 1 app_id = 0xff
arg3 = mask

# Transmit and return the count
return self._send_scp(
0, 0, 0, SCPCommands.signal, arg1, arg2, arg3).arg1

@ContextMixin.use_contextual_arguments
def load_routing_tables(self, routing_tables, app_id=Required):
"""Allocate space for an load multicast routing tables.
Expand Down
12 changes: 12 additions & 0 deletions rig/machine_control/scp_connection.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""A blocking implementation of the SCP protocol.
"""
import logging
import socket
from . import consts, packets

logger = logging.getLogger(__name__)


class SCPConnection(object):
"""Implements the SCP protocol for communicating with a SpiNNaker chip.
Expand Down Expand Up @@ -79,6 +82,15 @@ def send_scp(self, buffer_size, x, y, p, cmd, arg1=0, arg2=0, arg3=0,
The packet that was received in acknowledgement of the transmitted
packet.
"""
logger.debug(
"SCP transmit cmd={}, arg1={}, arg2={}, arg3={} [{}]".format(
cmd,
"NA" if arg1 is None else hex(arg1),
"NA" if arg2 is None else hex(arg2),
"NA" if arg3 is None else hex(arg3),
len(data)
)
)
self.sock.settimeout(self.default_timeout + timeout)

# Construct the packet that will be sent
Expand Down
56 changes: 55 additions & 1 deletion rig/machine_control/tests/test_machine_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pkg_resources
import pytest
import six
from six import iteritems
from six import iteritems, itervalues
import struct
import tempfile
import os
Expand Down Expand Up @@ -173,6 +173,12 @@ def test_led_toggle(self, controller):
for y in range(2):
controller.set_led(1, x=x, y=y, action=None)

def test_count_cores_in_state_idle(self, controller):
"""Check that we have no idle cores as there are no cores assigned to
the application yet.
"""
assert controller.count_cores_in_state(consts.AppState.idle) == 0

@pytest.mark.parametrize(
"targets",
[{(1, 1): {3, 4}, (1, 0): {5}},
Expand Down Expand Up @@ -208,6 +214,14 @@ def test_load_application(self, controller, targets):
p = (data & 0x0000ffff)
assert p == t_p and x == t_x and y == t_y

@pytest.mark.parametrize(
"all_targets",
[{(1, 1): {3, 4}, (1, 0): {5}, (0, 1): {2}}]
)
def test_count_cores_in_state_run(self, controller, all_targets):
expected = sum(len(cs) for cs in itervalues(all_targets))
assert expected == controller.count_cores_in_state(consts.AppState.run)

@pytest.mark.parametrize(
"targets",
[{(1, 1): {3, 4}, (1, 0): {5}},
Expand Down Expand Up @@ -319,6 +333,10 @@ def test_load_and_retrieve_routing_tables(self, controller, routes,
for route in routes:
assert (route, app_id, 0) in loaded

def test_app_stop_and_count(self, controller):
controller.send_signal(consts.AppSignal.stop)
assert controller.count_cores_in_state(consts.AppState.run) == 0


class TestMachineController(object):
"""Test the machine controller against the ideal protocol.
Expand Down Expand Up @@ -1193,6 +1211,42 @@ def test_send_signal_one_target(self, app_id, signal):
assert arg2 & 0x00ff0000 == signal << 16
assert arg3 == 0x0000ffff # Transmit to all

@pytest.mark.parametrize("app_id, count", [(16, 3), (30, 68)])
@pytest.mark.parametrize("state", [consts.AppState.idle,
consts.AppState.run])
def test_count_cores_in_state(self, app_id, count, state):
# Create the controller
cn = MachineController("localhost")
cn._send_scp = mock.Mock()
cn._send_scp.return_value = mock.Mock(spec_set=SCPPacket)
cn._send_scp.return_value.arg1 = count

# Count the cores
with cn(app_id=app_id):
assert cn.count_cores_in_state(state) == count

# Check an appropriate packet was sent
assert cn._send_scp.call_count == 1
cargs = cn._send_scp.call_args[0]
assert cargs[:3] == (0, 0, 0) # x, y, p

(cmd, arg1, arg2, arg3) = cargs[3:8]
assert cmd == SCPCommands.signal
assert (
arg1 ==
consts.diagnostic_signal_types[consts.AppDiagnosticSignal.count]
)

# level | op | mode | state | app_mask | app_id
assert arg2 & 0x000000ff == app_id
assert arg2 & 0x0000ff00 == 0xff00 # App mask for 1 app_id
assert arg2 & 0x000f0000 == state << 16
assert arg2 & 0x00300000 == consts.AppDiagnosticSignal.count << 20
assert arg2 & 0x03c00000 == 1 << 22 # op == 1
assert arg2 & 0x0c000000 == 0 # level == 0

assert arg3 == 0x0000ffff # Transmit to all

@pytest.mark.parametrize("x, y, app_id", [(1, 2, 32), (4, 10, 17)])
@pytest.mark.parametrize(
"entries",
Expand Down
3 changes: 3 additions & 0 deletions rig/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import pytest
import _pytest

import logging
from collections import defaultdict
from toposort import toposort

logging.basicConfig(level=logging.DEBUG)


@pytest.fixture(scope='session')
def spinnaker_ip(request):
Expand Down

0 comments on commit 0d32c0f

Please sign in to comment.