/usr/share/pyshared/brian/tools/remotecontrol.py is in python-brian 1.3.1-1build1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | '''
Remote control of a Brian process, for example using an IPython shell
The process running the simulation calls something like:
server = RemoteControlServer()
and the IPython shell calls:
client = RemoteControlClient()
The shell can now execute and evaluate in the server process via:
spikes = client.evaluate('M.spikes')
i, t = zip(*spikes)
plot(t, i, '.')
client.execute('stop()')
'''
from ..network import NetworkOperation
try:
import multiprocessing
from multiprocessing.connection import Listener, Client
except ImportError:
multiprocessing = None
import select
import inspect
__all__ = ['RemoteControlServer', 'RemoteControlClient']
class RemoteControlServer(NetworkOperation):
'''
Allows remote control (via IP) of a running Brian script
Initialisation arguments:
``server``
The IP server details, a pair (host, port). If you want to allow control
only on the one machine (for example, by an IPython shell), leave this
as ``None`` (which defaults to host='localhost', port=2719). To allow
remote control, use ('', portnumber).
``authkey``
The authentication key to allow access, change it from 'brian' if you
are allowing access from outside (otherwise you allow others to run
arbitrary code on your machine).
``clock``
The clock specifying how often to poll for incoming commands.
``global_ns``, ``local_ns``, ``level``
Namespaces in which incoming commands will be executed or evaluated,
if you leave them blank it will be the local and global namespace of
the frame from which this function was called (if level=1, or from
a higher level if you specify a different level here).
Once this object has been created, use a :class:`RemoteControlClient` to
issue commands.
**Example usage**
Main simulation code includes a line like this::
server = RemoteControlServer()
In an IPython shell you can do something like this::
client = RemoteControlClient()
spikes = client.evaluate('M.spikes')
i, t = zip(*spikes)
plot(t, i, '.')
client.execute('stop()')
'''
def __init__(self, server=None, authkey='brian', clock=None,
global_ns=None, local_ns=None, level=0):
if multiprocessing is None:
raise ImportError('Cannot import the required multiprocessing module.')
NetworkOperation.__init__(self, lambda:None, clock=clock)
if server is None:
server = ('localhost', 2719)
frame = inspect.stack()[level + 1][0]
ns_global, ns_local = frame.f_globals, frame.f_locals
if global_ns is None:
global_ns = frame.f_globals
if local_ns is None:
local_ns = frame.f_locals
self.local_ns = local_ns
self.global_ns = global_ns
self.listener = Listener(server, authkey=authkey)
self.conn = None
def __call__(self):
if self.conn is None:
# This is kind of a hack. The multiprocessing.Listener class doesn't
# allow you to tell if an incoming connection has been requested
# without accepting that connection, which means if nothing attempts
# to connect it will wait forever for something to connect. What
# we do here is check if there is any incoming data on the
# underlying IP socket used internally by multiprocessing.Listener.
socket = self.listener._listener._socket
sel, _, _ = select.select([socket], [], [], 0)
if len(sel):
self.conn = self.listener.accept()
if self.conn is None:
return
conn = self.conn
global_ns = self.global_ns
local_ns = self.local_ns
paused = 1
while conn and paused != 0:
if paused >= 0 and not conn.poll():
return
try:
job = conn.recv()
except:
self.conn = None
break
jobtype, jobargs = job
if paused == 1: paused = 0
try:
result = None
if jobtype == 'exec':
exec jobargs in global_ns, local_ns
elif jobtype == 'eval':
result = eval(jobargs, global_ns, local_ns)
elif jobtype == 'setvar':
varname, varval = jobargs
local_ns[varname] = varval
elif jobtype == 'pause':
paused = -1
elif jobtype == 'go':
paused = 0
except Exception, e:
# if it raised an exception, we return that exception and the
# client can then raise it.
result = e
conn.send(result)
class RemoteControlClient(object):
'''
Used to remotely control (via IP) a running Brian script
Initialisation arguments:
``server``
The IP server details, a pair (host, port). If you want to allow control
only on the one machine (for example, by an IPython shell), leave this
as ``None`` (which defaults to host='localhost', port=2719). To allow
remote control, use ('', portnumber).
``authkey``
The authentication key to allow access, change it from 'brian' if you
are allowing access from outside (otherwise you allow others to run
arbitrary code on your machine).
Use a :class:`RemoteControlServer` on the simulation you want to control.
Has the following methods:
.. method:: execute(code)
Executes the specified code in the server process.
If it raises an
exception, the server process will catch it and reraise it in the
client process.
.. method:: evaluate(code)
Evaluate the code in the server process and return the result.
If it raises an
exception, the server process will catch it and reraise it in the
client process.
.. method:: set(name, value)
Sets the variable ``name`` (a string) to the given value (can be an
array, etc.). Note that the variable is set in the local namespace, not
the global one, and so this cannot be used to modify global namespace
variables. To do that, set a local namespace variable and then
call :meth:`~RemoteControlClient.execute` with an instruction to change
the global namespace variable.
.. method:: pause()
Temporarily stop the simulation in the server process, continue
simulation with the :meth:'go' method.
.. method:: go()
Continue a simulation that was paused.
.. method:: stop()
Stop a simulation, equivalent to ``execute('stop()')``.
**Example usage**
Main simulation code includes a line like this::
server = RemoteControlServer()
In an IPython shell you can do something like this::
client = RemoteControlClient()
spikes = client.evaluate('M.spikes')
i, t = zip(*spikes)
plot(t, i, '.')
client.execute('stop()')
'''
def __init__(self, server=None, authkey='brian'):
if multiprocessing is None:
raise ImportError('Cannot import the required multiprocessing module.')
if server is None:
server = ('localhost', 2719)
self.client = Client(server, authkey=authkey)
def execute(self, code):
self.client.send(('exec', code))
result = self.client.recv()
if isinstance(result, Exception):
raise result
def evaluate(self, code):
self.client.send(('eval', code))
result = self.client.recv()
if isinstance(result, Exception):
raise result
return result
def set(self, name, value):
self.client.send(('setvar', (name, value)))
result = self.client.recv()
if isinstance(result, Exception):
raise result
def pause(self):
self.client.send(('pause', ''))
self.client.recv()
def go(self):
self.client.send(('go', ''))
self.client.recv()
def stop(self):
self.execute('stop()')
|