##// END OF EJS Templates
move IPython.zmq.parallel to IPython.parallel
MinRK -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,316 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 Job and task components for writing .xml files that the Windows HPC Server
5 2008 can use to start jobs.
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2009 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 from __future__ import with_statement
20
21 import os
22 import re
23 import uuid
24
25 from xml.etree import ElementTree as ET
26
27 from IPython.config.configurable import Configurable
28 from IPython.utils.traitlets import (
29 Str, Int, List, Instance,
30 Enum, Bool, CStr
31 )
32
33 #-----------------------------------------------------------------------------
34 # Job and Task classes
35 #-----------------------------------------------------------------------------
36
37
38 def as_str(value):
39 if isinstance(value, str):
40 return value
41 elif isinstance(value, bool):
42 if value:
43 return 'true'
44 else:
45 return 'false'
46 elif isinstance(value, (int, float)):
47 return repr(value)
48 else:
49 return value
50
51
52 def indent(elem, level=0):
53 i = "\n" + level*" "
54 if len(elem):
55 if not elem.text or not elem.text.strip():
56 elem.text = i + " "
57 if not elem.tail or not elem.tail.strip():
58 elem.tail = i
59 for elem in elem:
60 indent(elem, level+1)
61 if not elem.tail or not elem.tail.strip():
62 elem.tail = i
63 else:
64 if level and (not elem.tail or not elem.tail.strip()):
65 elem.tail = i
66
67
68 def find_username():
69 domain = os.environ.get('USERDOMAIN')
70 username = os.environ.get('USERNAME','')
71 if domain is None:
72 return username
73 else:
74 return '%s\\%s' % (domain, username)
75
76
77 class WinHPCJob(Configurable):
78
79 job_id = Str('')
80 job_name = Str('MyJob', config=True)
81 min_cores = Int(1, config=True)
82 max_cores = Int(1, config=True)
83 min_sockets = Int(1, config=True)
84 max_sockets = Int(1, config=True)
85 min_nodes = Int(1, config=True)
86 max_nodes = Int(1, config=True)
87 unit_type = Str("Core", config=True)
88 auto_calculate_min = Bool(True, config=True)
89 auto_calculate_max = Bool(True, config=True)
90 run_until_canceled = Bool(False, config=True)
91 is_exclusive = Bool(False, config=True)
92 username = Str(find_username(), config=True)
93 job_type = Str('Batch', config=True)
94 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
95 default_value='Highest', config=True)
96 requested_nodes = Str('', config=True)
97 project = Str('IPython', config=True)
98 xmlns = Str('http://schemas.microsoft.com/HPCS2008/scheduler/')
99 version = Str("2.000")
100 tasks = List([])
101
102 @property
103 def owner(self):
104 return self.username
105
106 def _write_attr(self, root, attr, key):
107 s = as_str(getattr(self, attr, ''))
108 if s:
109 root.set(key, s)
110
111 def as_element(self):
112 # We have to add _A_ type things to get the right order than
113 # the MSFT XML parser expects.
114 root = ET.Element('Job')
115 self._write_attr(root, 'version', '_A_Version')
116 self._write_attr(root, 'job_name', '_B_Name')
117 self._write_attr(root, 'unit_type', '_C_UnitType')
118 self._write_attr(root, 'min_cores', '_D_MinCores')
119 self._write_attr(root, 'max_cores', '_E_MaxCores')
120 self._write_attr(root, 'min_sockets', '_F_MinSockets')
121 self._write_attr(root, 'max_sockets', '_G_MaxSockets')
122 self._write_attr(root, 'min_nodes', '_H_MinNodes')
123 self._write_attr(root, 'max_nodes', '_I_MaxNodes')
124 self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled')
125 self._write_attr(root, 'is_exclusive', '_K_IsExclusive')
126 self._write_attr(root, 'username', '_L_UserName')
127 self._write_attr(root, 'job_type', '_M_JobType')
128 self._write_attr(root, 'priority', '_N_Priority')
129 self._write_attr(root, 'requested_nodes', '_O_RequestedNodes')
130 self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax')
131 self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin')
132 self._write_attr(root, 'project', '_R_Project')
133 self._write_attr(root, 'owner', '_S_Owner')
134 self._write_attr(root, 'xmlns', '_T_xmlns')
135 dependencies = ET.SubElement(root, "Dependencies")
136 etasks = ET.SubElement(root, "Tasks")
137 for t in self.tasks:
138 etasks.append(t.as_element())
139 return root
140
141 def tostring(self):
142 """Return the string representation of the job description XML."""
143 root = self.as_element()
144 indent(root)
145 txt = ET.tostring(root, encoding="utf-8")
146 # Now remove the tokens used to order the attributes.
147 txt = re.sub(r'_[A-Z]_','',txt)
148 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
149 return txt
150
151 def write(self, filename):
152 """Write the XML job description to a file."""
153 txt = self.tostring()
154 with open(filename, 'w') as f:
155 f.write(txt)
156
157 def add_task(self, task):
158 """Add a task to the job.
159
160 Parameters
161 ----------
162 task : :class:`WinHPCTask`
163 The task object to add.
164 """
165 self.tasks.append(task)
166
167
168 class WinHPCTask(Configurable):
169
170 task_id = Str('')
171 task_name = Str('')
172 version = Str("2.000")
173 min_cores = Int(1, config=True)
174 max_cores = Int(1, config=True)
175 min_sockets = Int(1, config=True)
176 max_sockets = Int(1, config=True)
177 min_nodes = Int(1, config=True)
178 max_nodes = Int(1, config=True)
179 unit_type = Str("Core", config=True)
180 command_line = CStr('', config=True)
181 work_directory = CStr('', config=True)
182 is_rerunnaable = Bool(True, config=True)
183 std_out_file_path = CStr('', config=True)
184 std_err_file_path = CStr('', config=True)
185 is_parametric = Bool(False, config=True)
186 environment_variables = Instance(dict, args=(), config=True)
187
188 def _write_attr(self, root, attr, key):
189 s = as_str(getattr(self, attr, ''))
190 if s:
191 root.set(key, s)
192
193 def as_element(self):
194 root = ET.Element('Task')
195 self._write_attr(root, 'version', '_A_Version')
196 self._write_attr(root, 'task_name', '_B_Name')
197 self._write_attr(root, 'min_cores', '_C_MinCores')
198 self._write_attr(root, 'max_cores', '_D_MaxCores')
199 self._write_attr(root, 'min_sockets', '_E_MinSockets')
200 self._write_attr(root, 'max_sockets', '_F_MaxSockets')
201 self._write_attr(root, 'min_nodes', '_G_MinNodes')
202 self._write_attr(root, 'max_nodes', '_H_MaxNodes')
203 self._write_attr(root, 'command_line', '_I_CommandLine')
204 self._write_attr(root, 'work_directory', '_J_WorkDirectory')
205 self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable')
206 self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath')
207 self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath')
208 self._write_attr(root, 'is_parametric', '_N_IsParametric')
209 self._write_attr(root, 'unit_type', '_O_UnitType')
210 root.append(self.get_env_vars())
211 return root
212
213 def get_env_vars(self):
214 env_vars = ET.Element('EnvironmentVariables')
215 for k, v in self.environment_variables.iteritems():
216 variable = ET.SubElement(env_vars, "Variable")
217 name = ET.SubElement(variable, "Name")
218 name.text = k
219 value = ET.SubElement(variable, "Value")
220 value.text = v
221 return env_vars
222
223
224
225 # By declaring these, we can configure the controller and engine separately!
226
227 class IPControllerJob(WinHPCJob):
228 job_name = Str('IPController', config=False)
229 is_exclusive = Bool(False, config=True)
230 username = Str(find_username(), config=True)
231 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
232 default_value='Highest', config=True)
233 requested_nodes = Str('', config=True)
234 project = Str('IPython', config=True)
235
236
237 class IPEngineSetJob(WinHPCJob):
238 job_name = Str('IPEngineSet', config=False)
239 is_exclusive = Bool(False, config=True)
240 username = Str(find_username(), config=True)
241 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
242 default_value='Highest', config=True)
243 requested_nodes = Str('', config=True)
244 project = Str('IPython', config=True)
245
246
247 class IPControllerTask(WinHPCTask):
248
249 task_name = Str('IPController', config=True)
250 controller_cmd = List(['ipcontroller.exe'], config=True)
251 controller_args = List(['--log-to-file', '--log-level', '40'], config=True)
252 # I don't want these to be configurable
253 std_out_file_path = CStr('', config=False)
254 std_err_file_path = CStr('', config=False)
255 min_cores = Int(1, config=False)
256 max_cores = Int(1, config=False)
257 min_sockets = Int(1, config=False)
258 max_sockets = Int(1, config=False)
259 min_nodes = Int(1, config=False)
260 max_nodes = Int(1, config=False)
261 unit_type = Str("Core", config=False)
262 work_directory = CStr('', config=False)
263
264 def __init__(self, config=None):
265 super(IPControllerTask, self).__init__(config=config)
266 the_uuid = uuid.uuid1()
267 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
268 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
269
270 @property
271 def command_line(self):
272 return ' '.join(self.controller_cmd + self.controller_args)
273
274
275 class IPEngineTask(WinHPCTask):
276
277 task_name = Str('IPEngine', config=True)
278 engine_cmd = List(['ipengine.exe'], config=True)
279 engine_args = List(['--log-to-file', '--log-level', '40'], config=True)
280 # I don't want these to be configurable
281 std_out_file_path = CStr('', config=False)
282 std_err_file_path = CStr('', config=False)
283 min_cores = Int(1, config=False)
284 max_cores = Int(1, config=False)
285 min_sockets = Int(1, config=False)
286 max_sockets = Int(1, config=False)
287 min_nodes = Int(1, config=False)
288 max_nodes = Int(1, config=False)
289 unit_type = Str("Core", config=False)
290 work_directory = CStr('', config=False)
291
292 def __init__(self, config=None):
293 super(IPEngineTask,self).__init__(config=config)
294 the_uuid = uuid.uuid1()
295 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
296 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
297
298 @property
299 def command_line(self):
300 return ' '.join(self.engine_cmd + self.engine_args)
301
302
303 # j = WinHPCJob(None)
304 # j.job_name = 'IPCluster'
305 # j.username = 'GNET\\bgranger'
306 # j.requested_nodes = 'GREEN'
307 #
308 # t = WinHPCTask(None)
309 # t.task_name = 'Controller'
310 # t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10"
311 # t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default"
312 # t.std_out_file_path = 'controller-out.txt'
313 # t.std_err_file_path = 'controller-err.txt'
314 # t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages"
315 # j.add_task(t)
316
@@ -1,238 +1,238 b''
1 import os
1 import os
2
2
3 c = get_config()
3 c = get_config()
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Select which launchers to use
6 # Select which launchers to use
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # This allows you to control what method is used to start the controller
9 # This allows you to control what method is used to start the controller
10 # and engines. The following methods are currently supported:
10 # and engines. The following methods are currently supported:
11 # - Start as a regular process on localhost.
11 # - Start as a regular process on localhost.
12 # - Start using mpiexec.
12 # - Start using mpiexec.
13 # - Start using the Windows HPC Server 2008 scheduler
13 # - Start using the Windows HPC Server 2008 scheduler
14 # - Start using PBS/SGE
14 # - Start using PBS/SGE
15 # - Start using SSH
15 # - Start using SSH
16
16
17
17
18 # The selected launchers can be configured below.
18 # The selected launchers can be configured below.
19
19
20 # Options are:
20 # Options are:
21 # - LocalControllerLauncher
21 # - LocalControllerLauncher
22 # - MPIExecControllerLauncher
22 # - MPIExecControllerLauncher
23 # - PBSControllerLauncher
23 # - PBSControllerLauncher
24 # - SGEControllerLauncher
24 # - SGEControllerLauncher
25 # - WindowsHPCControllerLauncher
25 # - WindowsHPCControllerLauncher
26 # c.Global.controller_launcher = 'IPython.zmq.parallel.launcher.LocalControllerLauncher'
26 # c.Global.controller_launcher = 'IPython.parallel.launcher.LocalControllerLauncher'
27 c.Global.controller_launcher = 'IPython.zmq.parallel.launcher.PBSControllerLauncher'
27 c.Global.controller_launcher = 'IPython.parallel.launcher.PBSControllerLauncher'
28
28
29 # Options are:
29 # Options are:
30 # - LocalEngineSetLauncher
30 # - LocalEngineSetLauncher
31 # - MPIExecEngineSetLauncher
31 # - MPIExecEngineSetLauncher
32 # - PBSEngineSetLauncher
32 # - PBSEngineSetLauncher
33 # - SGEEngineSetLauncher
33 # - SGEEngineSetLauncher
34 # - WindowsHPCEngineSetLauncher
34 # - WindowsHPCEngineSetLauncher
35 # c.Global.engine_launcher = 'IPython.zmq.parallel.launcher.LocalEngineSetLauncher'
35 # c.Global.engine_launcher = 'IPython.parallel.launcher.LocalEngineSetLauncher'
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Global configuration
38 # Global configuration
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 # The default number of engines that will be started. This is overridden by
41 # The default number of engines that will be started. This is overridden by
42 # the -n command line option: "ipcluster start -n 4"
42 # the -n command line option: "ipcluster start -n 4"
43 # c.Global.n = 2
43 # c.Global.n = 2
44
44
45 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
45 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
46 # c.Global.log_to_file = False
46 # c.Global.log_to_file = False
47
47
48 # Remove old logs from cluster_dir/log before starting.
48 # Remove old logs from cluster_dir/log before starting.
49 # c.Global.clean_logs = True
49 # c.Global.clean_logs = True
50
50
51 # The working directory for the process. The application will use os.chdir
51 # The working directory for the process. The application will use os.chdir
52 # to change to this directory before starting.
52 # to change to this directory before starting.
53 # c.Global.work_dir = os.getcwd()
53 # c.Global.work_dir = os.getcwd()
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Local process launchers
57 # Local process launchers
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 # The command line arguments to call the controller with.
60 # The command line arguments to call the controller with.
61 # c.LocalControllerLauncher.controller_args = \
61 # c.LocalControllerLauncher.controller_args = \
62 # ['--log-to-file','--log-level', '40']
62 # ['--log-to-file','--log-level', '40']
63
63
64 # The working directory for the controller
64 # The working directory for the controller
65 # c.LocalEngineSetLauncher.work_dir = u''
65 # c.LocalEngineSetLauncher.work_dir = u''
66
66
67 # Command line argument passed to the engines.
67 # Command line argument passed to the engines.
68 # c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
68 # c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # MPIExec launchers
71 # MPIExec launchers
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 # The mpiexec/mpirun command to use in both the controller and engines.
74 # The mpiexec/mpirun command to use in both the controller and engines.
75 # c.MPIExecLauncher.mpi_cmd = ['mpiexec']
75 # c.MPIExecLauncher.mpi_cmd = ['mpiexec']
76
76
77 # Additional arguments to pass to the actual mpiexec command.
77 # Additional arguments to pass to the actual mpiexec command.
78 # c.MPIExecLauncher.mpi_args = []
78 # c.MPIExecLauncher.mpi_args = []
79
79
80 # The mpiexec/mpirun command and args can be overridden if they should be different
80 # The mpiexec/mpirun command and args can be overridden if they should be different
81 # for controller and engines.
81 # for controller and engines.
82 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
82 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
83 # c.MPIExecControllerLauncher.mpi_args = []
83 # c.MPIExecControllerLauncher.mpi_args = []
84 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
84 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
85 # c.MPIExecEngineSetLauncher.mpi_args = []
85 # c.MPIExecEngineSetLauncher.mpi_args = []
86
86
87 # The command line argument to call the controller with.
87 # The command line argument to call the controller with.
88 # c.MPIExecControllerLauncher.controller_args = \
88 # c.MPIExecControllerLauncher.controller_args = \
89 # ['--log-to-file','--log-level', '40']
89 # ['--log-to-file','--log-level', '40']
90
90
91 # Command line argument passed to the engines.
91 # Command line argument passed to the engines.
92 # c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
92 # c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
93
93
94 # The default number of engines to start if not given elsewhere.
94 # The default number of engines to start if not given elsewhere.
95 # c.MPIExecEngineSetLauncher.n = 1
95 # c.MPIExecEngineSetLauncher.n = 1
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # SSH launchers
98 # SSH launchers
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 # ipclusterz can be used to launch controller and engines remotely via ssh.
101 # ipclusterz can be used to launch controller and engines remotely via ssh.
102 # Note that currently ipclusterz does not do any file distribution, so if
102 # Note that currently ipclusterz does not do any file distribution, so if
103 # machines are not on a shared filesystem, config and json files must be
103 # machines are not on a shared filesystem, config and json files must be
104 # distributed. For this reason, the reuse_files defaults to True on an
104 # distributed. For this reason, the reuse_files defaults to True on an
105 # ssh-launched Controller. This flag can be overridded by the program_args
105 # ssh-launched Controller. This flag can be overridded by the program_args
106 # attribute of c.SSHControllerLauncher.
106 # attribute of c.SSHControllerLauncher.
107
107
108 # set the ssh cmd for launching remote commands. The default is ['ssh']
108 # set the ssh cmd for launching remote commands. The default is ['ssh']
109 # c.SSHLauncher.ssh_cmd = ['ssh']
109 # c.SSHLauncher.ssh_cmd = ['ssh']
110
110
111 # set the ssh cmd for launching remote commands. The default is ['ssh']
111 # set the ssh cmd for launching remote commands. The default is ['ssh']
112 # c.SSHLauncher.ssh_args = ['tt']
112 # c.SSHLauncher.ssh_args = ['tt']
113
113
114 # Set the user and hostname for the controller
114 # Set the user and hostname for the controller
115 # c.SSHControllerLauncher.hostname = 'controller.example.com'
115 # c.SSHControllerLauncher.hostname = 'controller.example.com'
116 # c.SSHControllerLauncher.user = os.environ.get('USER','username')
116 # c.SSHControllerLauncher.user = os.environ.get('USER','username')
117
117
118 # Set the arguments to be passed to ipcontrollerz
118 # Set the arguments to be passed to ipcontrollerz
119 # note that remotely launched ipcontrollerz will not get the contents of
119 # note that remotely launched ipcontrollerz will not get the contents of
120 # the local ipcontrollerz_config.py unless it resides on the *remote host*
120 # the local ipcontrollerz_config.py unless it resides on the *remote host*
121 # in the location specified by the --cluster_dir argument.
121 # in the location specified by the --cluster_dir argument.
122 # c.SSHControllerLauncher.program_args = ['-r', '-ip', '0.0.0.0', '--cluster_dir', '/path/to/cd']
122 # c.SSHControllerLauncher.program_args = ['-r', '-ip', '0.0.0.0', '--cluster_dir', '/path/to/cd']
123
123
124 # Set the default args passed to ipenginez for SSH launched engines
124 # Set the default args passed to ipenginez for SSH launched engines
125 # c.SSHEngineSetLauncher.engine_args = ['--mpi', 'mpi4py']
125 # c.SSHEngineSetLauncher.engine_args = ['--mpi', 'mpi4py']
126
126
127 # SSH engines are launched as a dict of locations/n-engines.
127 # SSH engines are launched as a dict of locations/n-engines.
128 # if a value is a tuple instead of an int, it is assumed to be of the form
128 # if a value is a tuple instead of an int, it is assumed to be of the form
129 # (n, [args]), setting the arguments to passed to ipenginez on `host`.
129 # (n, [args]), setting the arguments to passed to ipenginez on `host`.
130 # otherwise, c.SSHEngineSetLauncher.engine_args will be used as the default.
130 # otherwise, c.SSHEngineSetLauncher.engine_args will be used as the default.
131
131
132 # In this case, there will be 3 engines at my.example.com, and
132 # In this case, there will be 3 engines at my.example.com, and
133 # 2 at you@ipython.scipy.org with a special json connector location.
133 # 2 at you@ipython.scipy.org with a special json connector location.
134 # c.SSHEngineSetLauncher.engines = {'my.example.com' : 3,
134 # c.SSHEngineSetLauncher.engines = {'my.example.com' : 3,
135 # 'you@ipython.scipy.org' : (2, ['-f', '/path/to/ipcontroller-engine.json']}
135 # 'you@ipython.scipy.org' : (2, ['-f', '/path/to/ipcontroller-engine.json']}
136 # }
136 # }
137
137
138 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
139 # Unix batch (PBS) schedulers launchers
139 # Unix batch (PBS) schedulers launchers
140 #-----------------------------------------------------------------------------
140 #-----------------------------------------------------------------------------
141
141
142 # SGE and PBS are very similar. All configurables in this section called 'PBS*'
142 # SGE and PBS are very similar. All configurables in this section called 'PBS*'
143 # also exist as 'SGE*'.
143 # also exist as 'SGE*'.
144
144
145 # The command line program to use to submit a PBS job.
145 # The command line program to use to submit a PBS job.
146 # c.PBSLauncher.submit_command = ['qsub']
146 # c.PBSLauncher.submit_command = ['qsub']
147
147
148 # The command line program to use to delete a PBS job.
148 # The command line program to use to delete a PBS job.
149 # c.PBSLauncher.delete_command = ['qdel']
149 # c.PBSLauncher.delete_command = ['qdel']
150
150
151 # The PBS queue in which the job should run
151 # The PBS queue in which the job should run
152 # c.PBSLauncher.queue = 'myqueue'
152 # c.PBSLauncher.queue = 'myqueue'
153
153
154 # A regular expression that takes the output of qsub and find the job id.
154 # A regular expression that takes the output of qsub and find the job id.
155 # c.PBSLauncher.job_id_regexp = r'\d+'
155 # c.PBSLauncher.job_id_regexp = r'\d+'
156
156
157 # If for some reason the Controller and Engines have different options above, they
157 # If for some reason the Controller and Engines have different options above, they
158 # can be set as c.PBSControllerLauncher.<option> etc.
158 # can be set as c.PBSControllerLauncher.<option> etc.
159
159
160 # The batch submission script used to start the controller. This is where
160 # The batch submission script used to start the controller. This is where
161 # environment variables would be setup, etc. This string is interpreted using
161 # environment variables would be setup, etc. This string is interpreted using
162 # the Itpl module in IPython.external. Basically, you can use ${n} for the
162 # the Itpl module in IPython.external. Basically, you can use ${n} for the
163 # number of engine and ${cluster_dir} for the cluster_dir.
163 # number of engine and ${cluster_dir} for the cluster_dir.
164 # c.PBSControllerLauncher.batch_template = """
164 # c.PBSControllerLauncher.batch_template = """
165 # #PBS -N ipcontroller
165 # #PBS -N ipcontroller
166 # #PBS -q $queue
166 # #PBS -q $queue
167 #
167 #
168 # ipcontrollerz --cluster-dir $cluster_dir
168 # ipcontrollerz --cluster-dir $cluster_dir
169 # """
169 # """
170
170
171 # You can also load this template from a file
171 # You can also load this template from a file
172 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
172 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
173
173
174 # The name of the instantiated batch script that will actually be used to
174 # The name of the instantiated batch script that will actually be used to
175 # submit the job. This will be written to the cluster directory.
175 # submit the job. This will be written to the cluster directory.
176 # c.PBSControllerLauncher.batch_file_name = u'pbs_controller'
176 # c.PBSControllerLauncher.batch_file_name = u'pbs_controller'
177
177
178 # The batch submission script used to start the engines. This is where
178 # The batch submission script used to start the engines. This is where
179 # environment variables would be setup, etc. This string is interpreted using
179 # environment variables would be setup, etc. This string is interpreted using
180 # the Itpl module in IPython.external. Basically, you can use ${n} for the
180 # the Itpl module in IPython.external. Basically, you can use ${n} for the
181 # number of engine and ${cluster_dir} for the cluster_dir.
181 # number of engine and ${cluster_dir} for the cluster_dir.
182 # c.PBSEngineSetLauncher.batch_template = """
182 # c.PBSEngineSetLauncher.batch_template = """
183 # #PBS -N ipcontroller
183 # #PBS -N ipcontroller
184 # #PBS -l nprocs=$n
184 # #PBS -l nprocs=$n
185 #
185 #
186 # ipenginez --cluster-dir $cluster_dir$s
186 # ipenginez --cluster-dir $cluster_dir$s
187 # """
187 # """
188
188
189 # You can also load this template from a file
189 # You can also load this template from a file
190 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
190 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
191
191
192 # The name of the instantiated batch script that will actually be used to
192 # The name of the instantiated batch script that will actually be used to
193 # submit the job. This will be written to the cluster directory.
193 # submit the job. This will be written to the cluster directory.
194 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_engines'
194 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_engines'
195
195
196
196
197
197
198 #-----------------------------------------------------------------------------
198 #-----------------------------------------------------------------------------
199 # Windows HPC Server 2008 launcher configuration
199 # Windows HPC Server 2008 launcher configuration
200 #-----------------------------------------------------------------------------
200 #-----------------------------------------------------------------------------
201
201
202 # c.IPControllerJob.job_name = 'IPController'
202 # c.IPControllerJob.job_name = 'IPController'
203 # c.IPControllerJob.is_exclusive = False
203 # c.IPControllerJob.is_exclusive = False
204 # c.IPControllerJob.username = r'USERDOMAIN\USERNAME'
204 # c.IPControllerJob.username = r'USERDOMAIN\USERNAME'
205 # c.IPControllerJob.priority = 'Highest'
205 # c.IPControllerJob.priority = 'Highest'
206 # c.IPControllerJob.requested_nodes = ''
206 # c.IPControllerJob.requested_nodes = ''
207 # c.IPControllerJob.project = 'MyProject'
207 # c.IPControllerJob.project = 'MyProject'
208
208
209 # c.IPControllerTask.task_name = 'IPController'
209 # c.IPControllerTask.task_name = 'IPController'
210 # c.IPControllerTask.controller_cmd = [u'ipcontroller.exe']
210 # c.IPControllerTask.controller_cmd = [u'ipcontroller.exe']
211 # c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40']
211 # c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40']
212 # c.IPControllerTask.environment_variables = {}
212 # c.IPControllerTask.environment_variables = {}
213
213
214 # c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
214 # c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
215 # c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml'
215 # c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml'
216
216
217
217
218 # c.IPEngineSetJob.job_name = 'IPEngineSet'
218 # c.IPEngineSetJob.job_name = 'IPEngineSet'
219 # c.IPEngineSetJob.is_exclusive = False
219 # c.IPEngineSetJob.is_exclusive = False
220 # c.IPEngineSetJob.username = r'USERDOMAIN\USERNAME'
220 # c.IPEngineSetJob.username = r'USERDOMAIN\USERNAME'
221 # c.IPEngineSetJob.priority = 'Highest'
221 # c.IPEngineSetJob.priority = 'Highest'
222 # c.IPEngineSetJob.requested_nodes = ''
222 # c.IPEngineSetJob.requested_nodes = ''
223 # c.IPEngineSetJob.project = 'MyProject'
223 # c.IPEngineSetJob.project = 'MyProject'
224
224
225 # c.IPEngineTask.task_name = 'IPEngine'
225 # c.IPEngineTask.task_name = 'IPEngine'
226 # c.IPEngineTask.engine_cmd = [u'ipengine.exe']
226 # c.IPEngineTask.engine_cmd = [u'ipengine.exe']
227 # c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40']
227 # c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40']
228 # c.IPEngineTask.environment_variables = {}
228 # c.IPEngineTask.environment_variables = {}
229
229
230 # c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE'
230 # c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE'
231 # c.WindowsHPCEngineSetLauncher.job_file_name = u'ipengineset_job.xml'
231 # c.WindowsHPCEngineSetLauncher.job_file_name = u'ipengineset_job.xml'
232
232
233
233
234
234
235
235
236
236
237
237
238
238
@@ -1,180 +1,180 b''
1 from IPython.config.loader import Config
1 from IPython.config.loader import Config
2
2
3 c = get_config()
3 c = get_config()
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Global configuration
6 # Global configuration
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Basic Global config attributes
9 # Basic Global config attributes
10
10
11 # Start up messages are logged to stdout using the logging module.
11 # Start up messages are logged to stdout using the logging module.
12 # These all happen before the twisted reactor is started and are
12 # These all happen before the twisted reactor is started and are
13 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
13 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
14 # and smaller is more verbose.
14 # and smaller is more verbose.
15 # c.Global.log_level = 20
15 # c.Global.log_level = 20
16
16
17 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
17 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
18 # c.Global.log_to_file = False
18 # c.Global.log_to_file = False
19
19
20 # Remove old logs from cluster_dir/log before starting.
20 # Remove old logs from cluster_dir/log before starting.
21 # c.Global.clean_logs = True
21 # c.Global.clean_logs = True
22
22
23 # A list of Python statements that will be run before starting the
23 # A list of Python statements that will be run before starting the
24 # controller. This is provided because occasionally certain things need to
24 # controller. This is provided because occasionally certain things need to
25 # be imported in the controller for pickling to work.
25 # be imported in the controller for pickling to work.
26 # c.Global.import_statements = ['import math']
26 # c.Global.import_statements = ['import math']
27
27
28 # Reuse the controller's JSON files. If False, JSON files are regenerated
28 # Reuse the controller's JSON files. If False, JSON files are regenerated
29 # each time the controller is run. If True, they will be reused, *but*, you
29 # each time the controller is run. If True, they will be reused, *but*, you
30 # also must set the network ports by hand. If set, this will override the
30 # also must set the network ports by hand. If set, this will override the
31 # values set for the client and engine connections below.
31 # values set for the client and engine connections below.
32 # c.Global.reuse_files = True
32 # c.Global.reuse_files = True
33
33
34 # Enable exec_key authentication on all messages. Default is True
34 # Enable exec_key authentication on all messages. Default is True
35 # c.Global.secure = True
35 # c.Global.secure = True
36
36
37 # The working directory for the process. The application will use os.chdir
37 # The working directory for the process. The application will use os.chdir
38 # to change to this directory before starting.
38 # to change to this directory before starting.
39 # c.Global.work_dir = os.getcwd()
39 # c.Global.work_dir = os.getcwd()
40
40
41 # The log url for logging to an `iploggerz` application. This will override
41 # The log url for logging to an `iploggerz` application. This will override
42 # log-to-file.
42 # log-to-file.
43 # c.Global.log_url = 'tcp://127.0.0.1:20202'
43 # c.Global.log_url = 'tcp://127.0.0.1:20202'
44
44
45 # The specific external IP that is used to disambiguate multi-interface URLs.
45 # The specific external IP that is used to disambiguate multi-interface URLs.
46 # The default behavior is to guess from external IPs gleaned from `socket`.
46 # The default behavior is to guess from external IPs gleaned from `socket`.
47 # c.Global.location = '192.168.1.123'
47 # c.Global.location = '192.168.1.123'
48
48
49 # The ssh server remote clients should use to connect to this controller.
49 # The ssh server remote clients should use to connect to this controller.
50 # It must be a machine that can see the interface specified in client_ip.
50 # It must be a machine that can see the interface specified in client_ip.
51 # The default for client_ip is localhost, in which case the sshserver must
51 # The default for client_ip is localhost, in which case the sshserver must
52 # be an external IP of the controller machine.
52 # be an external IP of the controller machine.
53 # c.Global.sshserver = 'controller.example.com'
53 # c.Global.sshserver = 'controller.example.com'
54
54
55 # the url to use for registration. If set, this overrides engine-ip,
55 # the url to use for registration. If set, this overrides engine-ip,
56 # engine-transport client-ip,client-transport, and regport.
56 # engine-transport client-ip,client-transport, and regport.
57 # c.RegistrationFactory.url = 'tcp://*:12345'
57 # c.RegistrationFactory.url = 'tcp://*:12345'
58
58
59 # the port to use for registration. Clients and Engines both use this
59 # the port to use for registration. Clients and Engines both use this
60 # port for registration.
60 # port for registration.
61 # c.RegistrationFactory.regport = 10101
61 # c.RegistrationFactory.regport = 10101
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Configure the Task Scheduler
64 # Configure the Task Scheduler
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 # The routing scheme. 'pure' will use the pure-ZMQ scheduler. Any other
67 # The routing scheme. 'pure' will use the pure-ZMQ scheduler. Any other
68 # value will use a Python scheduler with various routing schemes.
68 # value will use a Python scheduler with various routing schemes.
69 # python schemes are: lru, weighted, random, twobin. Default is 'weighted'.
69 # python schemes are: lru, weighted, random, twobin. Default is 'weighted'.
70 # Note that the pure ZMQ scheduler does not support many features, such as
70 # Note that the pure ZMQ scheduler does not support many features, such as
71 # dying engines, dependencies, or engine-subset load-balancing.
71 # dying engines, dependencies, or engine-subset load-balancing.
72 # c.ControllerFactory.scheme = 'pure'
72 # c.ControllerFactory.scheme = 'pure'
73
73
74 # The pure ZMQ scheduler can limit the number of outstanding tasks per engine
74 # The pure ZMQ scheduler can limit the number of outstanding tasks per engine
75 # by using the ZMQ HWM option. This allows engines with long-running tasks
75 # by using the ZMQ HWM option. This allows engines with long-running tasks
76 # to not steal too many tasks from other engines. The default is 0, which
76 # to not steal too many tasks from other engines. The default is 0, which
77 # means agressively distribute messages, never waiting for them to finish.
77 # means agressively distribute messages, never waiting for them to finish.
78 # c.ControllerFactory.hwm = 1
78 # c.ControllerFactory.hwm = 1
79
79
80 # Whether to use Threads or Processes to start the Schedulers. Threads will
80 # Whether to use Threads or Processes to start the Schedulers. Threads will
81 # use less resources, but potentially reduce throughput. Default is to
81 # use less resources, but potentially reduce throughput. Default is to
82 # use processes. Note that the a Python scheduler will always be in a Process.
82 # use processes. Note that the a Python scheduler will always be in a Process.
83 # c.ControllerFactory.usethreads
83 # c.ControllerFactory.usethreads
84
84
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86 # Configure the Hub
86 # Configure the Hub
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88
88
89 # Which class to use for the db backend. Currently supported are DictDB (the
89 # Which class to use for the db backend. Currently supported are DictDB (the
90 # default), and MongoDB. Uncomment this line to enable MongoDB, which will
90 # default), and MongoDB. Uncomment this line to enable MongoDB, which will
91 # slow-down the Hub's responsiveness, but also reduce its memory footprint.
91 # slow-down the Hub's responsiveness, but also reduce its memory footprint.
92 # c.HubFactory.db_class = 'IPython.zmq.parallel.mongodb.MongoDB'
92 # c.HubFactory.db_class = 'IPython.parallel.mongodb.MongoDB'
93
93
94 # The heartbeat ping frequency. This is the frequency (in ms) at which the
94 # The heartbeat ping frequency. This is the frequency (in ms) at which the
95 # Hub pings engines for heartbeats. This determines how quickly the Hub
95 # Hub pings engines for heartbeats. This determines how quickly the Hub
96 # will react to engines coming and going. A lower number means faster response
96 # will react to engines coming and going. A lower number means faster response
97 # time, but more network activity. The default is 100ms
97 # time, but more network activity. The default is 100ms
98 # c.HubFactory.ping = 100
98 # c.HubFactory.ping = 100
99
99
100 # HubFactory queue port pairs, to set by name: mux, iopub, control, task. Set
100 # HubFactory queue port pairs, to set by name: mux, iopub, control, task. Set
101 # each as a tuple of length 2 of ints. The default is to find random
101 # each as a tuple of length 2 of ints. The default is to find random
102 # available ports
102 # available ports
103 # c.HubFactory.mux = (10102,10112)
103 # c.HubFactory.mux = (10102,10112)
104
104
105 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
106 # Configure the client connections
106 # Configure the client connections
107 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
108
108
109 # Basic client connection config attributes
109 # Basic client connection config attributes
110
110
111 # The network interface the controller will listen on for client connections.
111 # The network interface the controller will listen on for client connections.
112 # This should be an IP address or interface on the controller. An asterisk
112 # This should be an IP address or interface on the controller. An asterisk
113 # means listen on all interfaces. The transport can be any transport
113 # means listen on all interfaces. The transport can be any transport
114 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
114 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
115 # c.HubFactory.client_ip = '*'
115 # c.HubFactory.client_ip = '*'
116 # c.HubFactory.client_transport = 'tcp'
116 # c.HubFactory.client_transport = 'tcp'
117
117
118 # individual client ports to configure by name: query_port, notifier_port
118 # individual client ports to configure by name: query_port, notifier_port
119 # c.HubFactory.query_port = 12345
119 # c.HubFactory.query_port = 12345
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # Configure the engine connections
122 # Configure the engine connections
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124
124
125 # Basic config attributes for the engine connections.
125 # Basic config attributes for the engine connections.
126
126
127 # The network interface the controller will listen on for engine connections.
127 # The network interface the controller will listen on for engine connections.
128 # This should be an IP address or interface on the controller. An asterisk
128 # This should be an IP address or interface on the controller. An asterisk
129 # means listen on all interfaces. The transport can be any transport
129 # means listen on all interfaces. The transport can be any transport
130 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
130 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
131 # c.HubFactory.engine_ip = '*'
131 # c.HubFactory.engine_ip = '*'
132 # c.HubFactory.engine_transport = 'tcp'
132 # c.HubFactory.engine_transport = 'tcp'
133
133
134 # set the engine heartbeat ports to use:
134 # set the engine heartbeat ports to use:
135 # c.HubFactory.hb = (10303,10313)
135 # c.HubFactory.hb = (10303,10313)
136
136
137 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
138 # Configure the TaskRecord database backend
138 # Configure the TaskRecord database backend
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140
140
141 # For memory/persistance reasons, tasks can be stored out-of-memory in a database.
141 # For memory/persistance reasons, tasks can be stored out-of-memory in a database.
142 # Currently, only sqlite and mongodb are supported as backends, but the interface
142 # Currently, only sqlite and mongodb are supported as backends, but the interface
143 # is fairly simple, so advanced developers could write their own backend.
143 # is fairly simple, so advanced developers could write their own backend.
144
144
145 # ----- in-memory configuration --------
145 # ----- in-memory configuration --------
146 # this line restores the default behavior: in-memory storage of all results.
146 # this line restores the default behavior: in-memory storage of all results.
147 # c.HubFactory.db_class = 'IPython.zmq.parallel.dictdb.DictDB'
147 # c.HubFactory.db_class = 'IPython.parallel.dictdb.DictDB'
148
148
149 # ----- sqlite configuration --------
149 # ----- sqlite configuration --------
150 # use this line to activate sqlite:
150 # use this line to activate sqlite:
151 # c.HubFactory.db_class = 'IPython.zmq.parallel.sqlitedb.SQLiteDB'
151 # c.HubFactory.db_class = 'IPython.parallel.sqlitedb.SQLiteDB'
152
152
153 # You can specify the name of the db-file. By default, this will be located
153 # You can specify the name of the db-file. By default, this will be located
154 # in the active cluster_dir, e.g. ~/.ipython/clusterz_default/tasks.db
154 # in the active cluster_dir, e.g. ~/.ipython/clusterz_default/tasks.db
155 # c.SQLiteDB.filename = 'tasks.db'
155 # c.SQLiteDB.filename = 'tasks.db'
156
156
157 # You can also specify the location of the db-file, if you want it to be somewhere
157 # You can also specify the location of the db-file, if you want it to be somewhere
158 # other than the cluster_dir.
158 # other than the cluster_dir.
159 # c.SQLiteDB.location = '/scratch/'
159 # c.SQLiteDB.location = '/scratch/'
160
160
161 # This will specify the name of the table for the controller to use. The default
161 # This will specify the name of the table for the controller to use. The default
162 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
162 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
163 # this will result in results persisting for multiple sessions.
163 # this will result in results persisting for multiple sessions.
164 # c.SQLiteDB.table = 'results'
164 # c.SQLiteDB.table = 'results'
165
165
166 # ----- mongodb configuration --------
166 # ----- mongodb configuration --------
167 # use this line to activate mongodb:
167 # use this line to activate mongodb:
168 # c.HubFactory.db_class = 'IPython.zmq.parallel.mongodb.MongoDB'
168 # c.HubFactory.db_class = 'IPython.parallel.mongodb.MongoDB'
169
169
170 # You can specify the args and kwargs pymongo will use when creating the Connection.
170 # You can specify the args and kwargs pymongo will use when creating the Connection.
171 # For more information on what these options might be, see pymongo documentation.
171 # For more information on what these options might be, see pymongo documentation.
172 # c.MongoDB.connection_kwargs = {}
172 # c.MongoDB.connection_kwargs = {}
173 # c.MongoDB.connection_args = []
173 # c.MongoDB.connection_args = []
174
174
175 # This will specify the name of the mongo database for the controller to use. The default
175 # This will specify the name of the mongo database for the controller to use. The default
176 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
176 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
177 # this will result in task results persisting through multiple sessions.
177 # this will result in task results persisting through multiple sessions.
178 # c.MongoDB.database = 'ipythondb'
178 # c.MongoDB.database = 'ipythondb'
179
179
180
180
@@ -1,295 +1,295 b''
1 """Basic ssh tunneling utilities."""
1 """Basic ssh tunneling utilities."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2008-2010 The IPython Development Team
4 # Copyright (C) 2008-2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10
10
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 import os,sys, atexit
18 import os,sys, atexit
19 from multiprocessing import Process
19 from multiprocessing import Process
20 from getpass import getpass, getuser
20 from getpass import getpass, getuser
21 import warnings
21 import warnings
22
22
23 try:
23 try:
24 with warnings.catch_warnings():
24 with warnings.catch_warnings():
25 warnings.simplefilter('ignore', DeprecationWarning)
25 warnings.simplefilter('ignore', DeprecationWarning)
26 import paramiko
26 import paramiko
27 except ImportError:
27 except ImportError:
28 paramiko = None
28 paramiko = None
29 else:
29 else:
30 from forward import forward_tunnel
30 from forward import forward_tunnel
31
31
32 try:
32 try:
33 from IPython.external import pexpect
33 from IPython.external import pexpect
34 except ImportError:
34 except ImportError:
35 pexpect = None
35 pexpect = None
36
36
37 from IPython.zmq.parallel.entry_point import select_random_ports
37 from IPython.parallel.entry_point import select_random_ports
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Code
40 # Code
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Check for passwordless login
44 # Check for passwordless login
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 def try_passwordless_ssh(server, keyfile, paramiko=None):
47 def try_passwordless_ssh(server, keyfile, paramiko=None):
48 """Attempt to make an ssh connection without a password.
48 """Attempt to make an ssh connection without a password.
49 This is mainly used for requiring password input only once
49 This is mainly used for requiring password input only once
50 when many tunnels may be connected to the same server.
50 when many tunnels may be connected to the same server.
51
51
52 If paramiko is None, the default for the platform is chosen.
52 If paramiko is None, the default for the platform is chosen.
53 """
53 """
54 if paramiko is None:
54 if paramiko is None:
55 paramiko = sys.platform == 'win32'
55 paramiko = sys.platform == 'win32'
56 if not paramiko:
56 if not paramiko:
57 f = _try_passwordless_openssh
57 f = _try_passwordless_openssh
58 else:
58 else:
59 f = _try_passwordless_paramiko
59 f = _try_passwordless_paramiko
60 return f(server, keyfile)
60 return f(server, keyfile)
61
61
62 def _try_passwordless_openssh(server, keyfile):
62 def _try_passwordless_openssh(server, keyfile):
63 """Try passwordless login with shell ssh command."""
63 """Try passwordless login with shell ssh command."""
64 if pexpect is None:
64 if pexpect is None:
65 raise ImportError("pexpect unavailable, use paramiko")
65 raise ImportError("pexpect unavailable, use paramiko")
66 cmd = 'ssh -f '+ server
66 cmd = 'ssh -f '+ server
67 if keyfile:
67 if keyfile:
68 cmd += ' -i ' + keyfile
68 cmd += ' -i ' + keyfile
69 cmd += ' exit'
69 cmd += ' exit'
70 p = pexpect.spawn(cmd)
70 p = pexpect.spawn(cmd)
71 while True:
71 while True:
72 try:
72 try:
73 p.expect('[Ppassword]:', timeout=.1)
73 p.expect('[Ppassword]:', timeout=.1)
74 except pexpect.TIMEOUT:
74 except pexpect.TIMEOUT:
75 continue
75 continue
76 except pexpect.EOF:
76 except pexpect.EOF:
77 return True
77 return True
78 else:
78 else:
79 return False
79 return False
80
80
81 def _try_passwordless_paramiko(server, keyfile):
81 def _try_passwordless_paramiko(server, keyfile):
82 """Try passwordless login with paramiko."""
82 """Try passwordless login with paramiko."""
83 if paramiko is None:
83 if paramiko is None:
84 raise ImportError("paramiko unavailable, use openssh")
84 raise ImportError("paramiko unavailable, use openssh")
85 username, server, port = _split_server(server)
85 username, server, port = _split_server(server)
86 client = paramiko.SSHClient()
86 client = paramiko.SSHClient()
87 client.load_system_host_keys()
87 client.load_system_host_keys()
88 client.set_missing_host_key_policy(paramiko.WarningPolicy())
88 client.set_missing_host_key_policy(paramiko.WarningPolicy())
89 try:
89 try:
90 client.connect(server, port, username=username, key_filename=keyfile,
90 client.connect(server, port, username=username, key_filename=keyfile,
91 look_for_keys=True)
91 look_for_keys=True)
92 except paramiko.AuthenticationException:
92 except paramiko.AuthenticationException:
93 return False
93 return False
94 else:
94 else:
95 client.close()
95 client.close()
96 return True
96 return True
97
97
98
98
99 def tunnel_connection(socket, addr, server, keyfile=None, password=None, paramiko=None):
99 def tunnel_connection(socket, addr, server, keyfile=None, password=None, paramiko=None):
100 """Connect a socket to an address via an ssh tunnel.
100 """Connect a socket to an address via an ssh tunnel.
101
101
102 This is a wrapper for socket.connect(addr), when addr is not accessible
102 This is a wrapper for socket.connect(addr), when addr is not accessible
103 from the local machine. It simply creates an ssh tunnel using the remaining args,
103 from the local machine. It simply creates an ssh tunnel using the remaining args,
104 and calls socket.connect('tcp://localhost:lport') where lport is the randomly
104 and calls socket.connect('tcp://localhost:lport') where lport is the randomly
105 selected local port of the tunnel.
105 selected local port of the tunnel.
106
106
107 """
107 """
108 lport = select_random_ports(1)[0]
108 lport = select_random_ports(1)[0]
109 transport, addr = addr.split('://')
109 transport, addr = addr.split('://')
110 ip,rport = addr.split(':')
110 ip,rport = addr.split(':')
111 rport = int(rport)
111 rport = int(rport)
112 if paramiko is None:
112 if paramiko is None:
113 paramiko = sys.platform == 'win32'
113 paramiko = sys.platform == 'win32'
114 if paramiko:
114 if paramiko:
115 tunnelf = paramiko_tunnel
115 tunnelf = paramiko_tunnel
116 else:
116 else:
117 tunnelf = openssh_tunnel
117 tunnelf = openssh_tunnel
118 tunnel = tunnelf(lport, rport, server, remoteip=ip, keyfile=keyfile, password=password)
118 tunnel = tunnelf(lport, rport, server, remoteip=ip, keyfile=keyfile, password=password)
119 socket.connect('tcp://127.0.0.1:%i'%lport)
119 socket.connect('tcp://127.0.0.1:%i'%lport)
120 return tunnel
120 return tunnel
121
121
122 def openssh_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
122 def openssh_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
123 """Create an ssh tunnel using command-line ssh that connects port lport
123 """Create an ssh tunnel using command-line ssh that connects port lport
124 on this machine to localhost:rport on server. The tunnel
124 on this machine to localhost:rport on server. The tunnel
125 will automatically close when not in use, remaining open
125 will automatically close when not in use, remaining open
126 for a minimum of timeout seconds for an initial connection.
126 for a minimum of timeout seconds for an initial connection.
127
127
128 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
128 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
129 as seen from `server`.
129 as seen from `server`.
130
130
131 keyfile and password may be specified, but ssh config is checked for defaults.
131 keyfile and password may be specified, but ssh config is checked for defaults.
132
132
133 Parameters
133 Parameters
134 ----------
134 ----------
135
135
136 lport : int
136 lport : int
137 local port for connecting to the tunnel from this machine.
137 local port for connecting to the tunnel from this machine.
138 rport : int
138 rport : int
139 port on the remote machine to connect to.
139 port on the remote machine to connect to.
140 server : str
140 server : str
141 The ssh server to connect to. The full ssh server string will be parsed.
141 The ssh server to connect to. The full ssh server string will be parsed.
142 user@server:port
142 user@server:port
143 remoteip : str [Default: 127.0.0.1]
143 remoteip : str [Default: 127.0.0.1]
144 The remote ip, specifying the destination of the tunnel.
144 The remote ip, specifying the destination of the tunnel.
145 Default is localhost, which means that the tunnel would redirect
145 Default is localhost, which means that the tunnel would redirect
146 localhost:lport on this machine to localhost:rport on the *server*.
146 localhost:lport on this machine to localhost:rport on the *server*.
147
147
148 keyfile : str; path to public key file
148 keyfile : str; path to public key file
149 This specifies a key to be used in ssh login, default None.
149 This specifies a key to be used in ssh login, default None.
150 Regular default ssh keys will be used without specifying this argument.
150 Regular default ssh keys will be used without specifying this argument.
151 password : str;
151 password : str;
152 Your ssh password to the ssh server. Note that if this is left None,
152 Your ssh password to the ssh server. Note that if this is left None,
153 you will be prompted for it if passwordless key based login is unavailable.
153 you will be prompted for it if passwordless key based login is unavailable.
154
154
155 """
155 """
156 if pexpect is None:
156 if pexpect is None:
157 raise ImportError("pexpect unavailable, use paramiko_tunnel")
157 raise ImportError("pexpect unavailable, use paramiko_tunnel")
158 ssh="ssh "
158 ssh="ssh "
159 if keyfile:
159 if keyfile:
160 ssh += "-i " + keyfile
160 ssh += "-i " + keyfile
161 cmd = ssh + " -f -L 127.0.0.1:%i:%s:%i %s sleep %i"%(lport, remoteip, rport, server, timeout)
161 cmd = ssh + " -f -L 127.0.0.1:%i:%s:%i %s sleep %i"%(lport, remoteip, rport, server, timeout)
162 tunnel = pexpect.spawn(cmd)
162 tunnel = pexpect.spawn(cmd)
163 failed = False
163 failed = False
164 while True:
164 while True:
165 try:
165 try:
166 tunnel.expect('[Pp]assword:', timeout=.1)
166 tunnel.expect('[Pp]assword:', timeout=.1)
167 except pexpect.TIMEOUT:
167 except pexpect.TIMEOUT:
168 continue
168 continue
169 except pexpect.EOF:
169 except pexpect.EOF:
170 if tunnel.exitstatus:
170 if tunnel.exitstatus:
171 print (tunnel.exitstatus)
171 print (tunnel.exitstatus)
172 print (tunnel.before)
172 print (tunnel.before)
173 print (tunnel.after)
173 print (tunnel.after)
174 raise RuntimeError("tunnel '%s' failed to start"%(cmd))
174 raise RuntimeError("tunnel '%s' failed to start"%(cmd))
175 else:
175 else:
176 return tunnel.pid
176 return tunnel.pid
177 else:
177 else:
178 if failed:
178 if failed:
179 print("Password rejected, try again")
179 print("Password rejected, try again")
180 password=None
180 password=None
181 if password is None:
181 if password is None:
182 password = getpass("%s's password: "%(server))
182 password = getpass("%s's password: "%(server))
183 tunnel.sendline(password)
183 tunnel.sendline(password)
184 failed = True
184 failed = True
185
185
186 def _split_server(server):
186 def _split_server(server):
187 if '@' in server:
187 if '@' in server:
188 username,server = server.split('@', 1)
188 username,server = server.split('@', 1)
189 else:
189 else:
190 username = getuser()
190 username = getuser()
191 if ':' in server:
191 if ':' in server:
192 server, port = server.split(':')
192 server, port = server.split(':')
193 port = int(port)
193 port = int(port)
194 else:
194 else:
195 port = 22
195 port = 22
196 return username, server, port
196 return username, server, port
197
197
198 def paramiko_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
198 def paramiko_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
199 """launch a tunner with paramiko in a subprocess. This should only be used
199 """launch a tunner with paramiko in a subprocess. This should only be used
200 when shell ssh is unavailable (e.g. Windows).
200 when shell ssh is unavailable (e.g. Windows).
201
201
202 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
202 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
203 as seen from `server`.
203 as seen from `server`.
204
204
205 If you are familiar with ssh tunnels, this creates the tunnel:
205 If you are familiar with ssh tunnels, this creates the tunnel:
206
206
207 ssh server -L localhost:lport:remoteip:rport
207 ssh server -L localhost:lport:remoteip:rport
208
208
209 keyfile and password may be specified, but ssh config is checked for defaults.
209 keyfile and password may be specified, but ssh config is checked for defaults.
210
210
211
211
212 Parameters
212 Parameters
213 ----------
213 ----------
214
214
215 lport : int
215 lport : int
216 local port for connecting to the tunnel from this machine.
216 local port for connecting to the tunnel from this machine.
217 rport : int
217 rport : int
218 port on the remote machine to connect to.
218 port on the remote machine to connect to.
219 server : str
219 server : str
220 The ssh server to connect to. The full ssh server string will be parsed.
220 The ssh server to connect to. The full ssh server string will be parsed.
221 user@server:port
221 user@server:port
222 remoteip : str [Default: 127.0.0.1]
222 remoteip : str [Default: 127.0.0.1]
223 The remote ip, specifying the destination of the tunnel.
223 The remote ip, specifying the destination of the tunnel.
224 Default is localhost, which means that the tunnel would redirect
224 Default is localhost, which means that the tunnel would redirect
225 localhost:lport on this machine to localhost:rport on the *server*.
225 localhost:lport on this machine to localhost:rport on the *server*.
226
226
227 keyfile : str; path to public key file
227 keyfile : str; path to public key file
228 This specifies a key to be used in ssh login, default None.
228 This specifies a key to be used in ssh login, default None.
229 Regular default ssh keys will be used without specifying this argument.
229 Regular default ssh keys will be used without specifying this argument.
230 password : str;
230 password : str;
231 Your ssh password to the ssh server. Note that if this is left None,
231 Your ssh password to the ssh server. Note that if this is left None,
232 you will be prompted for it if passwordless key based login is unavailable.
232 you will be prompted for it if passwordless key based login is unavailable.
233
233
234 """
234 """
235 if paramiko is None:
235 if paramiko is None:
236 raise ImportError("Paramiko not available")
236 raise ImportError("Paramiko not available")
237
237
238 if password is None:
238 if password is None:
239 if not _check_passwordless_paramiko(server, keyfile):
239 if not _check_passwordless_paramiko(server, keyfile):
240 password = getpass("%s's password: "%(server))
240 password = getpass("%s's password: "%(server))
241
241
242 p = Process(target=_paramiko_tunnel,
242 p = Process(target=_paramiko_tunnel,
243 args=(lport, rport, server, remoteip),
243 args=(lport, rport, server, remoteip),
244 kwargs=dict(keyfile=keyfile, password=password))
244 kwargs=dict(keyfile=keyfile, password=password))
245 p.daemon=False
245 p.daemon=False
246 p.start()
246 p.start()
247 atexit.register(_shutdown_process, p)
247 atexit.register(_shutdown_process, p)
248 return p
248 return p
249
249
250 def _shutdown_process(p):
250 def _shutdown_process(p):
251 if p.isalive():
251 if p.isalive():
252 p.terminate()
252 p.terminate()
253
253
254 def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
254 def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
255 """Function for actually starting a paramiko tunnel, to be passed
255 """Function for actually starting a paramiko tunnel, to be passed
256 to multiprocessing.Process(target=this), and not called directly.
256 to multiprocessing.Process(target=this), and not called directly.
257 """
257 """
258 username, server, port = _split_server(server)
258 username, server, port = _split_server(server)
259 client = paramiko.SSHClient()
259 client = paramiko.SSHClient()
260 client.load_system_host_keys()
260 client.load_system_host_keys()
261 client.set_missing_host_key_policy(paramiko.WarningPolicy())
261 client.set_missing_host_key_policy(paramiko.WarningPolicy())
262
262
263 try:
263 try:
264 client.connect(server, port, username=username, key_filename=keyfile,
264 client.connect(server, port, username=username, key_filename=keyfile,
265 look_for_keys=True, password=password)
265 look_for_keys=True, password=password)
266 # except paramiko.AuthenticationException:
266 # except paramiko.AuthenticationException:
267 # if password is None:
267 # if password is None:
268 # password = getpass("%s@%s's password: "%(username, server))
268 # password = getpass("%s@%s's password: "%(username, server))
269 # client.connect(server, port, username=username, password=password)
269 # client.connect(server, port, username=username, password=password)
270 # else:
270 # else:
271 # raise
271 # raise
272 except Exception as e:
272 except Exception as e:
273 print ('*** Failed to connect to %s:%d: %r' % (server, port, e))
273 print ('*** Failed to connect to %s:%d: %r' % (server, port, e))
274 sys.exit(1)
274 sys.exit(1)
275
275
276 # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport))
276 # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport))
277
277
278 try:
278 try:
279 forward_tunnel(lport, remoteip, rport, client.get_transport())
279 forward_tunnel(lport, remoteip, rport, client.get_transport())
280 except KeyboardInterrupt:
280 except KeyboardInterrupt:
281 print ('SIGINT: Port forwarding stopped cleanly')
281 print ('SIGINT: Port forwarding stopped cleanly')
282 sys.exit(0)
282 sys.exit(0)
283 except Exception as e:
283 except Exception as e:
284 print ("Port forwarding stopped uncleanly: %s"%e)
284 print ("Port forwarding stopped uncleanly: %s"%e)
285 sys.exit(255)
285 sys.exit(255)
286
286
287 if sys.platform == 'win32':
287 if sys.platform == 'win32':
288 ssh_tunnel = paramiko_tunnel
288 ssh_tunnel = paramiko_tunnel
289 else:
289 else:
290 ssh_tunnel = openssh_tunnel
290 ssh_tunnel = openssh_tunnel
291
291
292
292
293 __all__ = ['tunnel_connection', 'ssh_tunnel', 'openssh_tunnel', 'paramiko_tunnel', 'try_passwordless_ssh']
293 __all__ = ['tunnel_connection', 'ssh_tunnel', 'openssh_tunnel', 'paramiko_tunnel', 'try_passwordless_ssh']
294
294
295
295
@@ -1,24 +1,25 b''
1 """The IPython ZMQ-based parallel computing interface."""
1 """The IPython ZMQ-based parallel computing interface."""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2011 The IPython Development Team
3 # Copyright (C) 2011 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 import zmq
13 import zmq
14
14
15 if zmq.__version__ < '2.1.3':
15 if zmq.__version__ < '2.1.3':
16 raise ImportError("IPython.zmq.parallel requires pyzmq/0MQ >= 2.1.3, you appear to have %s"%zmq.__version__)
16 raise ImportError("IPython.parallel requires pyzmq/0MQ >= 2.1.3, you appear to have %s"%zmq.__version__)
17
17
18 from .asyncresult import *
18 from .asyncresult import *
19 from .client import Client
19 from .client import Client
20 from .dependency import *
20 from .dependency import *
21 from .remotefunction import *
21 from .remotefunction import *
22 from .view import *
22 from .view import *
23 from IPython.utils.pickleutil import Reference
23
24
24
25
1 NO CONTENT: file renamed from IPython/zmq/parallel/asyncresult.py to IPython/parallel/asyncresult.py
NO CONTENT: file renamed from IPython/zmq/parallel/asyncresult.py to IPython/parallel/asyncresult.py
@@ -1,1293 +1,1278 b''
1 """A semi-synchronous Client for the ZMQ cluster"""
1 """A semi-synchronous Client for the ZMQ cluster"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team
3 # Copyright (C) 2010 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 import os
13 import os
14 import json
14 import json
15 import time
15 import time
16 import warnings
16 import warnings
17 from datetime import datetime
17 from datetime import datetime
18 from getpass import getpass
18 from getpass import getpass
19 from pprint import pprint
19 from pprint import pprint
20
20
21 pjoin = os.path.join
21 pjoin = os.path.join
22
22
23 import zmq
23 import zmq
24 # from zmq.eventloop import ioloop, zmqstream
24 # from zmq.eventloop import ioloop, zmqstream
25
25
26 from IPython.utils.path import get_ipython_dir
26 from IPython.utils.path import get_ipython_dir
27 from IPython.utils.pickleutil import Reference
28 from IPython.utils.traitlets import (HasTraits, Int, Instance, CUnicode,
27 from IPython.utils.traitlets import (HasTraits, Int, Instance, CUnicode,
29 Dict, List, Bool, Str, Set)
28 Dict, List, Bool, Str, Set)
30 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
31 from IPython.external.ssh import tunnel
30 from IPython.external.ssh import tunnel
32
31
33 from . import error
32 from . import error
34 from . import util
33 from . import util
35 from . import streamsession as ss
34 from . import streamsession as ss
36 from .asyncresult import AsyncResult, AsyncMapResult, AsyncHubResult
35 from .asyncresult import AsyncResult, AsyncHubResult
37 from .clusterdir import ClusterDir, ClusterDirError
36 from .clusterdir import ClusterDir, ClusterDirError
38 from .dependency import Dependency, depend, require, dependent
39 from .remotefunction import remote, parallel, ParallelFunction, RemoteFunction
40 from .view import DirectView, LoadBalancedView
37 from .view import DirectView, LoadBalancedView
41
38
42 #--------------------------------------------------------------------------
39 #--------------------------------------------------------------------------
43 # Decorators for Client methods
40 # Decorators for Client methods
44 #--------------------------------------------------------------------------
41 #--------------------------------------------------------------------------
45
42
46 @decorator
43 @decorator
47 def spin_first(f, self, *args, **kwargs):
44 def spin_first(f, self, *args, **kwargs):
48 """Call spin() to sync state prior to calling the method."""
45 """Call spin() to sync state prior to calling the method."""
49 self.spin()
46 self.spin()
50 return f(self, *args, **kwargs)
47 return f(self, *args, **kwargs)
51
48
52 @decorator
49 @decorator
53 def default_block(f, self, *args, **kwargs):
50 def default_block(f, self, *args, **kwargs):
54 """Default to self.block; preserve self.block."""
51 """Default to self.block; preserve self.block."""
55 block = kwargs.get('block',None)
52 block = kwargs.get('block',None)
56 block = self.block if block is None else block
53 block = self.block if block is None else block
57 saveblock = self.block
54 saveblock = self.block
58 self.block = block
55 self.block = block
59 try:
56 try:
60 ret = f(self, *args, **kwargs)
57 ret = f(self, *args, **kwargs)
61 finally:
58 finally:
62 self.block = saveblock
59 self.block = saveblock
63 return ret
60 return ret
64
61
65
62
66 #--------------------------------------------------------------------------
63 #--------------------------------------------------------------------------
67 # Classes
64 # Classes
68 #--------------------------------------------------------------------------
65 #--------------------------------------------------------------------------
69
66
70 class Metadata(dict):
67 class Metadata(dict):
71 """Subclass of dict for initializing metadata values.
68 """Subclass of dict for initializing metadata values.
72
69
73 Attribute access works on keys.
70 Attribute access works on keys.
74
71
75 These objects have a strict set of keys - errors will raise if you try
72 These objects have a strict set of keys - errors will raise if you try
76 to add new keys.
73 to add new keys.
77 """
74 """
78 def __init__(self, *args, **kwargs):
75 def __init__(self, *args, **kwargs):
79 dict.__init__(self)
76 dict.__init__(self)
80 md = {'msg_id' : None,
77 md = {'msg_id' : None,
81 'submitted' : None,
78 'submitted' : None,
82 'started' : None,
79 'started' : None,
83 'completed' : None,
80 'completed' : None,
84 'received' : None,
81 'received' : None,
85 'engine_uuid' : None,
82 'engine_uuid' : None,
86 'engine_id' : None,
83 'engine_id' : None,
87 'follow' : None,
84 'follow' : None,
88 'after' : None,
85 'after' : None,
89 'status' : None,
86 'status' : None,
90
87
91 'pyin' : None,
88 'pyin' : None,
92 'pyout' : None,
89 'pyout' : None,
93 'pyerr' : None,
90 'pyerr' : None,
94 'stdout' : '',
91 'stdout' : '',
95 'stderr' : '',
92 'stderr' : '',
96 }
93 }
97 self.update(md)
94 self.update(md)
98 self.update(dict(*args, **kwargs))
95 self.update(dict(*args, **kwargs))
99
96
100 def __getattr__(self, key):
97 def __getattr__(self, key):
101 """getattr aliased to getitem"""
98 """getattr aliased to getitem"""
102 if key in self.iterkeys():
99 if key in self.iterkeys():
103 return self[key]
100 return self[key]
104 else:
101 else:
105 raise AttributeError(key)
102 raise AttributeError(key)
106
103
107 def __setattr__(self, key, value):
104 def __setattr__(self, key, value):
108 """setattr aliased to setitem, with strict"""
105 """setattr aliased to setitem, with strict"""
109 if key in self.iterkeys():
106 if key in self.iterkeys():
110 self[key] = value
107 self[key] = value
111 else:
108 else:
112 raise AttributeError(key)
109 raise AttributeError(key)
113
110
114 def __setitem__(self, key, value):
111 def __setitem__(self, key, value):
115 """strict static key enforcement"""
112 """strict static key enforcement"""
116 if key in self.iterkeys():
113 if key in self.iterkeys():
117 dict.__setitem__(self, key, value)
114 dict.__setitem__(self, key, value)
118 else:
115 else:
119 raise KeyError(key)
116 raise KeyError(key)
120
117
121
118
122 class Client(HasTraits):
119 class Client(HasTraits):
123 """A semi-synchronous client to the IPython ZMQ cluster
120 """A semi-synchronous client to the IPython ZMQ cluster
124
121
125 Parameters
122 Parameters
126 ----------
123 ----------
127
124
128 url_or_file : bytes; zmq url or path to ipcontroller-client.json
125 url_or_file : bytes; zmq url or path to ipcontroller-client.json
129 Connection information for the Hub's registration. If a json connector
126 Connection information for the Hub's registration. If a json connector
130 file is given, then likely no further configuration is necessary.
127 file is given, then likely no further configuration is necessary.
131 [Default: use profile]
128 [Default: use profile]
132 profile : bytes
129 profile : bytes
133 The name of the Cluster profile to be used to find connector information.
130 The name of the Cluster profile to be used to find connector information.
134 [Default: 'default']
131 [Default: 'default']
135 context : zmq.Context
132 context : zmq.Context
136 Pass an existing zmq.Context instance, otherwise the client will create its own.
133 Pass an existing zmq.Context instance, otherwise the client will create its own.
137 username : bytes
134 username : bytes
138 set username to be passed to the Session object
135 set username to be passed to the Session object
139 debug : bool
136 debug : bool
140 flag for lots of message printing for debug purposes
137 flag for lots of message printing for debug purposes
141
138
142 #-------------- ssh related args ----------------
139 #-------------- ssh related args ----------------
143 # These are args for configuring the ssh tunnel to be used
140 # These are args for configuring the ssh tunnel to be used
144 # credentials are used to forward connections over ssh to the Controller
141 # credentials are used to forward connections over ssh to the Controller
145 # Note that the ip given in `addr` needs to be relative to sshserver
142 # Note that the ip given in `addr` needs to be relative to sshserver
146 # The most basic case is to leave addr as pointing to localhost (127.0.0.1),
143 # The most basic case is to leave addr as pointing to localhost (127.0.0.1),
147 # and set sshserver as the same machine the Controller is on. However,
144 # and set sshserver as the same machine the Controller is on. However,
148 # the only requirement is that sshserver is able to see the Controller
145 # the only requirement is that sshserver is able to see the Controller
149 # (i.e. is within the same trusted network).
146 # (i.e. is within the same trusted network).
150
147
151 sshserver : str
148 sshserver : str
152 A string of the form passed to ssh, i.e. 'server.tld' or 'user@server.tld:port'
149 A string of the form passed to ssh, i.e. 'server.tld' or 'user@server.tld:port'
153 If keyfile or password is specified, and this is not, it will default to
150 If keyfile or password is specified, and this is not, it will default to
154 the ip given in addr.
151 the ip given in addr.
155 sshkey : str; path to public ssh key file
152 sshkey : str; path to public ssh key file
156 This specifies a key to be used in ssh login, default None.
153 This specifies a key to be used in ssh login, default None.
157 Regular default ssh keys will be used without specifying this argument.
154 Regular default ssh keys will be used without specifying this argument.
158 password : str
155 password : str
159 Your ssh password to sshserver. Note that if this is left None,
156 Your ssh password to sshserver. Note that if this is left None,
160 you will be prompted for it if passwordless key based login is unavailable.
157 you will be prompted for it if passwordless key based login is unavailable.
161 paramiko : bool
158 paramiko : bool
162 flag for whether to use paramiko instead of shell ssh for tunneling.
159 flag for whether to use paramiko instead of shell ssh for tunneling.
163 [default: True on win32, False else]
160 [default: True on win32, False else]
164
161
165 ------- exec authentication args -------
162 ------- exec authentication args -------
166 If even localhost is untrusted, you can have some protection against
163 If even localhost is untrusted, you can have some protection against
167 unauthorized execution by using a key. Messages are still sent
164 unauthorized execution by using a key. Messages are still sent
168 as cleartext, so if someone can snoop your loopback traffic this will
165 as cleartext, so if someone can snoop your loopback traffic this will
169 not help against malicious attacks.
166 not help against malicious attacks.
170
167
171 exec_key : str
168 exec_key : str
172 an authentication key or file containing a key
169 an authentication key or file containing a key
173 default: None
170 default: None
174
171
175
172
176 Attributes
173 Attributes
177 ----------
174 ----------
178
175
179 ids : list of int engine IDs
176 ids : list of int engine IDs
180 requesting the ids attribute always synchronizes
177 requesting the ids attribute always synchronizes
181 the registration state. To request ids without synchronization,
178 the registration state. To request ids without synchronization,
182 use semi-private _ids attributes.
179 use semi-private _ids attributes.
183
180
184 history : list of msg_ids
181 history : list of msg_ids
185 a list of msg_ids, keeping track of all the execution
182 a list of msg_ids, keeping track of all the execution
186 messages you have submitted in order.
183 messages you have submitted in order.
187
184
188 outstanding : set of msg_ids
185 outstanding : set of msg_ids
189 a set of msg_ids that have been submitted, but whose
186 a set of msg_ids that have been submitted, but whose
190 results have not yet been received.
187 results have not yet been received.
191
188
192 results : dict
189 results : dict
193 a dict of all our results, keyed by msg_id
190 a dict of all our results, keyed by msg_id
194
191
195 block : bool
192 block : bool
196 determines default behavior when block not specified
193 determines default behavior when block not specified
197 in execution methods
194 in execution methods
198
195
199 Methods
196 Methods
200 -------
197 -------
201
198
202 spin
199 spin
203 flushes incoming results and registration state changes
200 flushes incoming results and registration state changes
204 control methods spin, and requesting `ids` also ensures up to date
201 control methods spin, and requesting `ids` also ensures up to date
205
202
206 wait
203 wait
207 wait on one or more msg_ids
204 wait on one or more msg_ids
208
205
209 execution methods
206 execution methods
210 apply
207 apply
211 legacy: execute, run
208 legacy: execute, run
212
209
213 data movement
210 data movement
214 push, pull, scatter, gather
211 push, pull, scatter, gather
215
212
216 query methods
213 query methods
217 queue_status, get_result, purge, result_status
214 queue_status, get_result, purge, result_status
218
215
219 control methods
216 control methods
220 abort, shutdown
217 abort, shutdown
221
218
222 """
219 """
223
220
224
221
225 block = Bool(False)
222 block = Bool(False)
226 outstanding = Set()
223 outstanding = Set()
227 results = Instance('collections.defaultdict', (dict,))
224 results = Instance('collections.defaultdict', (dict,))
228 metadata = Instance('collections.defaultdict', (Metadata,))
225 metadata = Instance('collections.defaultdict', (Metadata,))
229 history = List()
226 history = List()
230 debug = Bool(False)
227 debug = Bool(False)
231 profile=CUnicode('default')
228 profile=CUnicode('default')
232
229
233 _outstanding_dict = Instance('collections.defaultdict', (set,))
230 _outstanding_dict = Instance('collections.defaultdict', (set,))
234 _ids = List()
231 _ids = List()
235 _connected=Bool(False)
232 _connected=Bool(False)
236 _ssh=Bool(False)
233 _ssh=Bool(False)
237 _context = Instance('zmq.Context')
234 _context = Instance('zmq.Context')
238 _config = Dict()
235 _config = Dict()
239 _engines=Instance(util.ReverseDict, (), {})
236 _engines=Instance(util.ReverseDict, (), {})
240 # _hub_socket=Instance('zmq.Socket')
237 # _hub_socket=Instance('zmq.Socket')
241 _query_socket=Instance('zmq.Socket')
238 _query_socket=Instance('zmq.Socket')
242 _control_socket=Instance('zmq.Socket')
239 _control_socket=Instance('zmq.Socket')
243 _iopub_socket=Instance('zmq.Socket')
240 _iopub_socket=Instance('zmq.Socket')
244 _notification_socket=Instance('zmq.Socket')
241 _notification_socket=Instance('zmq.Socket')
245 _mux_socket=Instance('zmq.Socket')
242 _mux_socket=Instance('zmq.Socket')
246 _task_socket=Instance('zmq.Socket')
243 _task_socket=Instance('zmq.Socket')
247 _task_scheme=Str()
244 _task_scheme=Str()
248 _closed = False
245 _closed = False
249 _ignored_control_replies=Int(0)
246 _ignored_control_replies=Int(0)
250 _ignored_hub_replies=Int(0)
247 _ignored_hub_replies=Int(0)
251
248
252 def __init__(self, url_or_file=None, profile='default', cluster_dir=None, ipython_dir=None,
249 def __init__(self, url_or_file=None, profile='default', cluster_dir=None, ipython_dir=None,
253 context=None, username=None, debug=False, exec_key=None,
250 context=None, username=None, debug=False, exec_key=None,
254 sshserver=None, sshkey=None, password=None, paramiko=None,
251 sshserver=None, sshkey=None, password=None, paramiko=None,
255 timeout=10
252 timeout=10
256 ):
253 ):
257 super(Client, self).__init__(debug=debug, profile=profile)
254 super(Client, self).__init__(debug=debug, profile=profile)
258 if context is None:
255 if context is None:
259 context = zmq.Context.instance()
256 context = zmq.Context.instance()
260 self._context = context
257 self._context = context
261
258
262
259
263 self._setup_cluster_dir(profile, cluster_dir, ipython_dir)
260 self._setup_cluster_dir(profile, cluster_dir, ipython_dir)
264 if self._cd is not None:
261 if self._cd is not None:
265 if url_or_file is None:
262 if url_or_file is None:
266 url_or_file = pjoin(self._cd.security_dir, 'ipcontroller-client.json')
263 url_or_file = pjoin(self._cd.security_dir, 'ipcontroller-client.json')
267 assert url_or_file is not None, "I can't find enough information to connect to a hub!"\
264 assert url_or_file is not None, "I can't find enough information to connect to a hub!"\
268 " Please specify at least one of url_or_file or profile."
265 " Please specify at least one of url_or_file or profile."
269
266
270 try:
267 try:
271 util.validate_url(url_or_file)
268 util.validate_url(url_or_file)
272 except AssertionError:
269 except AssertionError:
273 if not os.path.exists(url_or_file):
270 if not os.path.exists(url_or_file):
274 if self._cd:
271 if self._cd:
275 url_or_file = os.path.join(self._cd.security_dir, url_or_file)
272 url_or_file = os.path.join(self._cd.security_dir, url_or_file)
276 assert os.path.exists(url_or_file), "Not a valid connection file or url: %r"%url_or_file
273 assert os.path.exists(url_or_file), "Not a valid connection file or url: %r"%url_or_file
277 with open(url_or_file) as f:
274 with open(url_or_file) as f:
278 cfg = json.loads(f.read())
275 cfg = json.loads(f.read())
279 else:
276 else:
280 cfg = {'url':url_or_file}
277 cfg = {'url':url_or_file}
281
278
282 # sync defaults from args, json:
279 # sync defaults from args, json:
283 if sshserver:
280 if sshserver:
284 cfg['ssh'] = sshserver
281 cfg['ssh'] = sshserver
285 if exec_key:
282 if exec_key:
286 cfg['exec_key'] = exec_key
283 cfg['exec_key'] = exec_key
287 exec_key = cfg['exec_key']
284 exec_key = cfg['exec_key']
288 sshserver=cfg['ssh']
285 sshserver=cfg['ssh']
289 url = cfg['url']
286 url = cfg['url']
290 location = cfg.setdefault('location', None)
287 location = cfg.setdefault('location', None)
291 cfg['url'] = util.disambiguate_url(cfg['url'], location)
288 cfg['url'] = util.disambiguate_url(cfg['url'], location)
292 url = cfg['url']
289 url = cfg['url']
293
290
294 self._config = cfg
291 self._config = cfg
295
292
296 self._ssh = bool(sshserver or sshkey or password)
293 self._ssh = bool(sshserver or sshkey or password)
297 if self._ssh and sshserver is None:
294 if self._ssh and sshserver is None:
298 # default to ssh via localhost
295 # default to ssh via localhost
299 sshserver = url.split('://')[1].split(':')[0]
296 sshserver = url.split('://')[1].split(':')[0]
300 if self._ssh and password is None:
297 if self._ssh and password is None:
301 if tunnel.try_passwordless_ssh(sshserver, sshkey, paramiko):
298 if tunnel.try_passwordless_ssh(sshserver, sshkey, paramiko):
302 password=False
299 password=False
303 else:
300 else:
304 password = getpass("SSH Password for %s: "%sshserver)
301 password = getpass("SSH Password for %s: "%sshserver)
305 ssh_kwargs = dict(keyfile=sshkey, password=password, paramiko=paramiko)
302 ssh_kwargs = dict(keyfile=sshkey, password=password, paramiko=paramiko)
306 if exec_key is not None and os.path.isfile(exec_key):
303 if exec_key is not None and os.path.isfile(exec_key):
307 arg = 'keyfile'
304 arg = 'keyfile'
308 else:
305 else:
309 arg = 'key'
306 arg = 'key'
310 key_arg = {arg:exec_key}
307 key_arg = {arg:exec_key}
311 if username is None:
308 if username is None:
312 self.session = ss.StreamSession(**key_arg)
309 self.session = ss.StreamSession(**key_arg)
313 else:
310 else:
314 self.session = ss.StreamSession(username, **key_arg)
311 self.session = ss.StreamSession(username, **key_arg)
315 self._query_socket = self._context.socket(zmq.XREQ)
312 self._query_socket = self._context.socket(zmq.XREQ)
316 self._query_socket.setsockopt(zmq.IDENTITY, self.session.session)
313 self._query_socket.setsockopt(zmq.IDENTITY, self.session.session)
317 if self._ssh:
314 if self._ssh:
318 tunnel.tunnel_connection(self._query_socket, url, sshserver, **ssh_kwargs)
315 tunnel.tunnel_connection(self._query_socket, url, sshserver, **ssh_kwargs)
319 else:
316 else:
320 self._query_socket.connect(url)
317 self._query_socket.connect(url)
321
318
322 self.session.debug = self.debug
319 self.session.debug = self.debug
323
320
324 self._notification_handlers = {'registration_notification' : self._register_engine,
321 self._notification_handlers = {'registration_notification' : self._register_engine,
325 'unregistration_notification' : self._unregister_engine,
322 'unregistration_notification' : self._unregister_engine,
326 'shutdown_notification' : lambda msg: self.close(),
323 'shutdown_notification' : lambda msg: self.close(),
327 }
324 }
328 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
325 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
329 'apply_reply' : self._handle_apply_reply}
326 'apply_reply' : self._handle_apply_reply}
330 self._connect(sshserver, ssh_kwargs, timeout)
327 self._connect(sshserver, ssh_kwargs, timeout)
331
328
332 def __del__(self):
329 def __del__(self):
333 """cleanup sockets, but _not_ context."""
330 """cleanup sockets, but _not_ context."""
334 self.close()
331 self.close()
335
332
336 def _setup_cluster_dir(self, profile, cluster_dir, ipython_dir):
333 def _setup_cluster_dir(self, profile, cluster_dir, ipython_dir):
337 if ipython_dir is None:
334 if ipython_dir is None:
338 ipython_dir = get_ipython_dir()
335 ipython_dir = get_ipython_dir()
339 if cluster_dir is not None:
336 if cluster_dir is not None:
340 try:
337 try:
341 self._cd = ClusterDir.find_cluster_dir(cluster_dir)
338 self._cd = ClusterDir.find_cluster_dir(cluster_dir)
342 return
339 return
343 except ClusterDirError:
340 except ClusterDirError:
344 pass
341 pass
345 elif profile is not None:
342 elif profile is not None:
346 try:
343 try:
347 self._cd = ClusterDir.find_cluster_dir_by_profile(
344 self._cd = ClusterDir.find_cluster_dir_by_profile(
348 ipython_dir, profile)
345 ipython_dir, profile)
349 return
346 return
350 except ClusterDirError:
347 except ClusterDirError:
351 pass
348 pass
352 self._cd = None
349 self._cd = None
353
350
354 def _update_engines(self, engines):
351 def _update_engines(self, engines):
355 """Update our engines dict and _ids from a dict of the form: {id:uuid}."""
352 """Update our engines dict and _ids from a dict of the form: {id:uuid}."""
356 for k,v in engines.iteritems():
353 for k,v in engines.iteritems():
357 eid = int(k)
354 eid = int(k)
358 self._engines[eid] = bytes(v) # force not unicode
355 self._engines[eid] = bytes(v) # force not unicode
359 self._ids.append(eid)
356 self._ids.append(eid)
360 self._ids = sorted(self._ids)
357 self._ids = sorted(self._ids)
361 if sorted(self._engines.keys()) != range(len(self._engines)) and \
358 if sorted(self._engines.keys()) != range(len(self._engines)) and \
362 self._task_scheme == 'pure' and self._task_socket:
359 self._task_scheme == 'pure' and self._task_socket:
363 self._stop_scheduling_tasks()
360 self._stop_scheduling_tasks()
364
361
365 def _stop_scheduling_tasks(self):
362 def _stop_scheduling_tasks(self):
366 """Stop scheduling tasks because an engine has been unregistered
363 """Stop scheduling tasks because an engine has been unregistered
367 from a pure ZMQ scheduler.
364 from a pure ZMQ scheduler.
368 """
365 """
369 self._task_socket.close()
366 self._task_socket.close()
370 self._task_socket = None
367 self._task_socket = None
371 msg = "An engine has been unregistered, and we are using pure " +\
368 msg = "An engine has been unregistered, and we are using pure " +\
372 "ZMQ task scheduling. Task farming will be disabled."
369 "ZMQ task scheduling. Task farming will be disabled."
373 if self.outstanding:
370 if self.outstanding:
374 msg += " If you were running tasks when this happened, " +\
371 msg += " If you were running tasks when this happened, " +\
375 "some `outstanding` msg_ids may never resolve."
372 "some `outstanding` msg_ids may never resolve."
376 warnings.warn(msg, RuntimeWarning)
373 warnings.warn(msg, RuntimeWarning)
377
374
378 def _build_targets(self, targets):
375 def _build_targets(self, targets):
379 """Turn valid target IDs or 'all' into two lists:
376 """Turn valid target IDs or 'all' into two lists:
380 (int_ids, uuids).
377 (int_ids, uuids).
381 """
378 """
382 if targets is None:
379 if targets is None:
383 targets = self._ids
380 targets = self._ids
384 elif isinstance(targets, str):
381 elif isinstance(targets, str):
385 if targets.lower() == 'all':
382 if targets.lower() == 'all':
386 targets = self._ids
383 targets = self._ids
387 else:
384 else:
388 raise TypeError("%r not valid str target, must be 'all'"%(targets))
385 raise TypeError("%r not valid str target, must be 'all'"%(targets))
389 elif isinstance(targets, int):
386 elif isinstance(targets, int):
390 if targets < 0:
387 if targets < 0:
391 targets = self.ids[targets]
388 targets = self.ids[targets]
392 if targets not in self.ids:
389 if targets not in self.ids:
393 raise IndexError("No such engine: %i"%targets)
390 raise IndexError("No such engine: %i"%targets)
394 targets = [targets]
391 targets = [targets]
395
392
396 if isinstance(targets, slice):
393 if isinstance(targets, slice):
397 indices = range(len(self._ids))[targets]
394 indices = range(len(self._ids))[targets]
398 ids = self.ids
395 ids = self.ids
399 targets = [ ids[i] for i in indices ]
396 targets = [ ids[i] for i in indices ]
400
397
401 if not isinstance(targets, (tuple, list, xrange)):
398 if not isinstance(targets, (tuple, list, xrange)):
402 raise TypeError("targets by int/slice/collection of ints only, not %s"%(type(targets)))
399 raise TypeError("targets by int/slice/collection of ints only, not %s"%(type(targets)))
403
400
404 return [self._engines[t] for t in targets], list(targets)
401 return [self._engines[t] for t in targets], list(targets)
405
402
406 def _connect(self, sshserver, ssh_kwargs, timeout):
403 def _connect(self, sshserver, ssh_kwargs, timeout):
407 """setup all our socket connections to the cluster. This is called from
404 """setup all our socket connections to the cluster. This is called from
408 __init__."""
405 __init__."""
409
406
410 # Maybe allow reconnecting?
407 # Maybe allow reconnecting?
411 if self._connected:
408 if self._connected:
412 return
409 return
413 self._connected=True
410 self._connected=True
414
411
415 def connect_socket(s, url):
412 def connect_socket(s, url):
416 url = util.disambiguate_url(url, self._config['location'])
413 url = util.disambiguate_url(url, self._config['location'])
417 if self._ssh:
414 if self._ssh:
418 return tunnel.tunnel_connection(s, url, sshserver, **ssh_kwargs)
415 return tunnel.tunnel_connection(s, url, sshserver, **ssh_kwargs)
419 else:
416 else:
420 return s.connect(url)
417 return s.connect(url)
421
418
422 self.session.send(self._query_socket, 'connection_request')
419 self.session.send(self._query_socket, 'connection_request')
423 r,w,x = zmq.select([self._query_socket],[],[], timeout)
420 r,w,x = zmq.select([self._query_socket],[],[], timeout)
424 if not r:
421 if not r:
425 raise error.TimeoutError("Hub connection request timed out")
422 raise error.TimeoutError("Hub connection request timed out")
426 idents,msg = self.session.recv(self._query_socket,mode=0)
423 idents,msg = self.session.recv(self._query_socket,mode=0)
427 if self.debug:
424 if self.debug:
428 pprint(msg)
425 pprint(msg)
429 msg = ss.Message(msg)
426 msg = ss.Message(msg)
430 content = msg.content
427 content = msg.content
431 self._config['registration'] = dict(content)
428 self._config['registration'] = dict(content)
432 if content.status == 'ok':
429 if content.status == 'ok':
433 if content.mux:
430 if content.mux:
434 self._mux_socket = self._context.socket(zmq.XREQ)
431 self._mux_socket = self._context.socket(zmq.XREQ)
435 self._mux_socket.setsockopt(zmq.IDENTITY, self.session.session)
432 self._mux_socket.setsockopt(zmq.IDENTITY, self.session.session)
436 connect_socket(self._mux_socket, content.mux)
433 connect_socket(self._mux_socket, content.mux)
437 if content.task:
434 if content.task:
438 self._task_scheme, task_addr = content.task
435 self._task_scheme, task_addr = content.task
439 self._task_socket = self._context.socket(zmq.XREQ)
436 self._task_socket = self._context.socket(zmq.XREQ)
440 self._task_socket.setsockopt(zmq.IDENTITY, self.session.session)
437 self._task_socket.setsockopt(zmq.IDENTITY, self.session.session)
441 connect_socket(self._task_socket, task_addr)
438 connect_socket(self._task_socket, task_addr)
442 if content.notification:
439 if content.notification:
443 self._notification_socket = self._context.socket(zmq.SUB)
440 self._notification_socket = self._context.socket(zmq.SUB)
444 connect_socket(self._notification_socket, content.notification)
441 connect_socket(self._notification_socket, content.notification)
445 self._notification_socket.setsockopt(zmq.SUBSCRIBE, b'')
442 self._notification_socket.setsockopt(zmq.SUBSCRIBE, b'')
446 # if content.query:
443 # if content.query:
447 # self._query_socket = self._context.socket(zmq.XREQ)
444 # self._query_socket = self._context.socket(zmq.XREQ)
448 # self._query_socket.setsockopt(zmq.IDENTITY, self.session.session)
445 # self._query_socket.setsockopt(zmq.IDENTITY, self.session.session)
449 # connect_socket(self._query_socket, content.query)
446 # connect_socket(self._query_socket, content.query)
450 if content.control:
447 if content.control:
451 self._control_socket = self._context.socket(zmq.XREQ)
448 self._control_socket = self._context.socket(zmq.XREQ)
452 self._control_socket.setsockopt(zmq.IDENTITY, self.session.session)
449 self._control_socket.setsockopt(zmq.IDENTITY, self.session.session)
453 connect_socket(self._control_socket, content.control)
450 connect_socket(self._control_socket, content.control)
454 if content.iopub:
451 if content.iopub:
455 self._iopub_socket = self._context.socket(zmq.SUB)
452 self._iopub_socket = self._context.socket(zmq.SUB)
456 self._iopub_socket.setsockopt(zmq.SUBSCRIBE, b'')
453 self._iopub_socket.setsockopt(zmq.SUBSCRIBE, b'')
457 self._iopub_socket.setsockopt(zmq.IDENTITY, self.session.session)
454 self._iopub_socket.setsockopt(zmq.IDENTITY, self.session.session)
458 connect_socket(self._iopub_socket, content.iopub)
455 connect_socket(self._iopub_socket, content.iopub)
459 self._update_engines(dict(content.engines))
456 self._update_engines(dict(content.engines))
460 else:
457 else:
461 self._connected = False
458 self._connected = False
462 raise Exception("Failed to connect!")
459 raise Exception("Failed to connect!")
463
460
464 #--------------------------------------------------------------------------
461 #--------------------------------------------------------------------------
465 # handlers and callbacks for incoming messages
462 # handlers and callbacks for incoming messages
466 #--------------------------------------------------------------------------
463 #--------------------------------------------------------------------------
467
464
468 def _unwrap_exception(self, content):
465 def _unwrap_exception(self, content):
469 """unwrap exception, and remap engine_id to int."""
466 """unwrap exception, and remap engine_id to int."""
470 e = error.unwrap_exception(content)
467 e = error.unwrap_exception(content)
471 # print e.traceback
468 # print e.traceback
472 if e.engine_info:
469 if e.engine_info:
473 e_uuid = e.engine_info['engine_uuid']
470 e_uuid = e.engine_info['engine_uuid']
474 eid = self._engines[e_uuid]
471 eid = self._engines[e_uuid]
475 e.engine_info['engine_id'] = eid
472 e.engine_info['engine_id'] = eid
476 return e
473 return e
477
474
478 def _extract_metadata(self, header, parent, content):
475 def _extract_metadata(self, header, parent, content):
479 md = {'msg_id' : parent['msg_id'],
476 md = {'msg_id' : parent['msg_id'],
480 'received' : datetime.now(),
477 'received' : datetime.now(),
481 'engine_uuid' : header.get('engine', None),
478 'engine_uuid' : header.get('engine', None),
482 'follow' : parent.get('follow', []),
479 'follow' : parent.get('follow', []),
483 'after' : parent.get('after', []),
480 'after' : parent.get('after', []),
484 'status' : content['status'],
481 'status' : content['status'],
485 }
482 }
486
483
487 if md['engine_uuid'] is not None:
484 if md['engine_uuid'] is not None:
488 md['engine_id'] = self._engines.get(md['engine_uuid'], None)
485 md['engine_id'] = self._engines.get(md['engine_uuid'], None)
489
486
490 if 'date' in parent:
487 if 'date' in parent:
491 md['submitted'] = datetime.strptime(parent['date'], util.ISO8601)
488 md['submitted'] = datetime.strptime(parent['date'], util.ISO8601)
492 if 'started' in header:
489 if 'started' in header:
493 md['started'] = datetime.strptime(header['started'], util.ISO8601)
490 md['started'] = datetime.strptime(header['started'], util.ISO8601)
494 if 'date' in header:
491 if 'date' in header:
495 md['completed'] = datetime.strptime(header['date'], util.ISO8601)
492 md['completed'] = datetime.strptime(header['date'], util.ISO8601)
496 return md
493 return md
497
494
498 def _register_engine(self, msg):
495 def _register_engine(self, msg):
499 """Register a new engine, and update our connection info."""
496 """Register a new engine, and update our connection info."""
500 content = msg['content']
497 content = msg['content']
501 eid = content['id']
498 eid = content['id']
502 d = {eid : content['queue']}
499 d = {eid : content['queue']}
503 self._update_engines(d)
500 self._update_engines(d)
504
501
505 def _unregister_engine(self, msg):
502 def _unregister_engine(self, msg):
506 """Unregister an engine that has died."""
503 """Unregister an engine that has died."""
507 content = msg['content']
504 content = msg['content']
508 eid = int(content['id'])
505 eid = int(content['id'])
509 if eid in self._ids:
506 if eid in self._ids:
510 self._ids.remove(eid)
507 self._ids.remove(eid)
511 uuid = self._engines.pop(eid)
508 uuid = self._engines.pop(eid)
512
509
513 self._handle_stranded_msgs(eid, uuid)
510 self._handle_stranded_msgs(eid, uuid)
514
511
515 if self._task_socket and self._task_scheme == 'pure':
512 if self._task_socket and self._task_scheme == 'pure':
516 self._stop_scheduling_tasks()
513 self._stop_scheduling_tasks()
517
514
518 def _handle_stranded_msgs(self, eid, uuid):
515 def _handle_stranded_msgs(self, eid, uuid):
519 """Handle messages known to be on an engine when the engine unregisters.
516 """Handle messages known to be on an engine when the engine unregisters.
520
517
521 It is possible that this will fire prematurely - that is, an engine will
518 It is possible that this will fire prematurely - that is, an engine will
522 go down after completing a result, and the client will be notified
519 go down after completing a result, and the client will be notified
523 of the unregistration and later receive the successful result.
520 of the unregistration and later receive the successful result.
524 """
521 """
525
522
526 outstanding = self._outstanding_dict[uuid]
523 outstanding = self._outstanding_dict[uuid]
527
524
528 for msg_id in list(outstanding):
525 for msg_id in list(outstanding):
529 if msg_id in self.results:
526 if msg_id in self.results:
530 # we already
527 # we already
531 continue
528 continue
532 try:
529 try:
533 raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id))
530 raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id))
534 except:
531 except:
535 content = error.wrap_exception()
532 content = error.wrap_exception()
536 # build a fake message:
533 # build a fake message:
537 parent = {}
534 parent = {}
538 header = {}
535 header = {}
539 parent['msg_id'] = msg_id
536 parent['msg_id'] = msg_id
540 header['engine'] = uuid
537 header['engine'] = uuid
541 header['date'] = datetime.now().strftime(util.ISO8601)
538 header['date'] = datetime.now().strftime(util.ISO8601)
542 msg = dict(parent_header=parent, header=header, content=content)
539 msg = dict(parent_header=parent, header=header, content=content)
543 self._handle_apply_reply(msg)
540 self._handle_apply_reply(msg)
544
541
545 def _handle_execute_reply(self, msg):
542 def _handle_execute_reply(self, msg):
546 """Save the reply to an execute_request into our results.
543 """Save the reply to an execute_request into our results.
547
544
548 execute messages are never actually used. apply is used instead.
545 execute messages are never actually used. apply is used instead.
549 """
546 """
550
547
551 parent = msg['parent_header']
548 parent = msg['parent_header']
552 msg_id = parent['msg_id']
549 msg_id = parent['msg_id']
553 if msg_id not in self.outstanding:
550 if msg_id not in self.outstanding:
554 if msg_id in self.history:
551 if msg_id in self.history:
555 print ("got stale result: %s"%msg_id)
552 print ("got stale result: %s"%msg_id)
556 else:
553 else:
557 print ("got unknown result: %s"%msg_id)
554 print ("got unknown result: %s"%msg_id)
558 else:
555 else:
559 self.outstanding.remove(msg_id)
556 self.outstanding.remove(msg_id)
560 self.results[msg_id] = self._unwrap_exception(msg['content'])
557 self.results[msg_id] = self._unwrap_exception(msg['content'])
561
558
562 def _handle_apply_reply(self, msg):
559 def _handle_apply_reply(self, msg):
563 """Save the reply to an apply_request into our results."""
560 """Save the reply to an apply_request into our results."""
564 parent = msg['parent_header']
561 parent = msg['parent_header']
565 msg_id = parent['msg_id']
562 msg_id = parent['msg_id']
566 if msg_id not in self.outstanding:
563 if msg_id not in self.outstanding:
567 if msg_id in self.history:
564 if msg_id in self.history:
568 print ("got stale result: %s"%msg_id)
565 print ("got stale result: %s"%msg_id)
569 print self.results[msg_id]
566 print self.results[msg_id]
570 print msg
567 print msg
571 else:
568 else:
572 print ("got unknown result: %s"%msg_id)
569 print ("got unknown result: %s"%msg_id)
573 else:
570 else:
574 self.outstanding.remove(msg_id)
571 self.outstanding.remove(msg_id)
575 content = msg['content']
572 content = msg['content']
576 header = msg['header']
573 header = msg['header']
577
574
578 # construct metadata:
575 # construct metadata:
579 md = self.metadata[msg_id]
576 md = self.metadata[msg_id]
580 md.update(self._extract_metadata(header, parent, content))
577 md.update(self._extract_metadata(header, parent, content))
581 # is this redundant?
578 # is this redundant?
582 self.metadata[msg_id] = md
579 self.metadata[msg_id] = md
583
580
584 e_outstanding = self._outstanding_dict[md['engine_uuid']]
581 e_outstanding = self._outstanding_dict[md['engine_uuid']]
585 if msg_id in e_outstanding:
582 if msg_id in e_outstanding:
586 e_outstanding.remove(msg_id)
583 e_outstanding.remove(msg_id)
587
584
588 # construct result:
585 # construct result:
589 if content['status'] == 'ok':
586 if content['status'] == 'ok':
590 self.results[msg_id] = util.unserialize_object(msg['buffers'])[0]
587 self.results[msg_id] = util.unserialize_object(msg['buffers'])[0]
591 elif content['status'] == 'aborted':
588 elif content['status'] == 'aborted':
592 self.results[msg_id] = error.TaskAborted(msg_id)
589 self.results[msg_id] = error.TaskAborted(msg_id)
593 elif content['status'] == 'resubmitted':
590 elif content['status'] == 'resubmitted':
594 # TODO: handle resubmission
591 # TODO: handle resubmission
595 pass
592 pass
596 else:
593 else:
597 self.results[msg_id] = self._unwrap_exception(content)
594 self.results[msg_id] = self._unwrap_exception(content)
598
595
599 def _flush_notifications(self):
596 def _flush_notifications(self):
600 """Flush notifications of engine registrations waiting
597 """Flush notifications of engine registrations waiting
601 in ZMQ queue."""
598 in ZMQ queue."""
602 msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
599 msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
603 while msg is not None:
600 while msg is not None:
604 if self.debug:
601 if self.debug:
605 pprint(msg)
602 pprint(msg)
606 msg = msg[-1]
603 msg = msg[-1]
607 msg_type = msg['msg_type']
604 msg_type = msg['msg_type']
608 handler = self._notification_handlers.get(msg_type, None)
605 handler = self._notification_handlers.get(msg_type, None)
609 if handler is None:
606 if handler is None:
610 raise Exception("Unhandled message type: %s"%msg.msg_type)
607 raise Exception("Unhandled message type: %s"%msg.msg_type)
611 else:
608 else:
612 handler(msg)
609 handler(msg)
613 msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
610 msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
614
611
615 def _flush_results(self, sock):
612 def _flush_results(self, sock):
616 """Flush task or queue results waiting in ZMQ queue."""
613 """Flush task or queue results waiting in ZMQ queue."""
617 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
614 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
618 while msg is not None:
615 while msg is not None:
619 if self.debug:
616 if self.debug:
620 pprint(msg)
617 pprint(msg)
621 msg = msg[-1]
618 msg = msg[-1]
622 msg_type = msg['msg_type']
619 msg_type = msg['msg_type']
623 handler = self._queue_handlers.get(msg_type, None)
620 handler = self._queue_handlers.get(msg_type, None)
624 if handler is None:
621 if handler is None:
625 raise Exception("Unhandled message type: %s"%msg.msg_type)
622 raise Exception("Unhandled message type: %s"%msg.msg_type)
626 else:
623 else:
627 handler(msg)
624 handler(msg)
628 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
625 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
629
626
630 def _flush_control(self, sock):
627 def _flush_control(self, sock):
631 """Flush replies from the control channel waiting
628 """Flush replies from the control channel waiting
632 in the ZMQ queue.
629 in the ZMQ queue.
633
630
634 Currently: ignore them."""
631 Currently: ignore them."""
635 if self._ignored_control_replies <= 0:
632 if self._ignored_control_replies <= 0:
636 return
633 return
637 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
634 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
638 while msg is not None:
635 while msg is not None:
639 self._ignored_control_replies -= 1
636 self._ignored_control_replies -= 1
640 if self.debug:
637 if self.debug:
641 pprint(msg)
638 pprint(msg)
642 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
639 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
643
640
644 def _flush_ignored_control(self):
641 def _flush_ignored_control(self):
645 """flush ignored control replies"""
642 """flush ignored control replies"""
646 while self._ignored_control_replies > 0:
643 while self._ignored_control_replies > 0:
647 self.session.recv(self._control_socket)
644 self.session.recv(self._control_socket)
648 self._ignored_control_replies -= 1
645 self._ignored_control_replies -= 1
649
646
650 def _flush_ignored_hub_replies(self):
647 def _flush_ignored_hub_replies(self):
651 msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
648 msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
652 while msg is not None:
649 while msg is not None:
653 msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
650 msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
654
651
655 def _flush_iopub(self, sock):
652 def _flush_iopub(self, sock):
656 """Flush replies from the iopub channel waiting
653 """Flush replies from the iopub channel waiting
657 in the ZMQ queue.
654 in the ZMQ queue.
658 """
655 """
659 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
656 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
660 while msg is not None:
657 while msg is not None:
661 if self.debug:
658 if self.debug:
662 pprint(msg)
659 pprint(msg)
663 msg = msg[-1]
660 msg = msg[-1]
664 parent = msg['parent_header']
661 parent = msg['parent_header']
665 msg_id = parent['msg_id']
662 msg_id = parent['msg_id']
666 content = msg['content']
663 content = msg['content']
667 header = msg['header']
664 header = msg['header']
668 msg_type = msg['msg_type']
665 msg_type = msg['msg_type']
669
666
670 # init metadata:
667 # init metadata:
671 md = self.metadata[msg_id]
668 md = self.metadata[msg_id]
672
669
673 if msg_type == 'stream':
670 if msg_type == 'stream':
674 name = content['name']
671 name = content['name']
675 s = md[name] or ''
672 s = md[name] or ''
676 md[name] = s + content['data']
673 md[name] = s + content['data']
677 elif msg_type == 'pyerr':
674 elif msg_type == 'pyerr':
678 md.update({'pyerr' : self._unwrap_exception(content)})
675 md.update({'pyerr' : self._unwrap_exception(content)})
679 else:
676 else:
680 md.update({msg_type : content['data']})
677 md.update({msg_type : content['data']})
681
678
682 # reduntant?
679 # reduntant?
683 self.metadata[msg_id] = md
680 self.metadata[msg_id] = md
684
681
685 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
682 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
686
683
687 #--------------------------------------------------------------------------
684 #--------------------------------------------------------------------------
688 # len, getitem
685 # len, getitem
689 #--------------------------------------------------------------------------
686 #--------------------------------------------------------------------------
690
687
691 def __len__(self):
688 def __len__(self):
692 """len(client) returns # of engines."""
689 """len(client) returns # of engines."""
693 return len(self.ids)
690 return len(self.ids)
694
691
695 def __getitem__(self, key):
692 def __getitem__(self, key):
696 """index access returns DirectView multiplexer objects
693 """index access returns DirectView multiplexer objects
697
694
698 Must be int, slice, or list/tuple/xrange of ints"""
695 Must be int, slice, or list/tuple/xrange of ints"""
699 if not isinstance(key, (int, slice, tuple, list, xrange)):
696 if not isinstance(key, (int, slice, tuple, list, xrange)):
700 raise TypeError("key by int/slice/iterable of ints only, not %s"%(type(key)))
697 raise TypeError("key by int/slice/iterable of ints only, not %s"%(type(key)))
701 else:
698 else:
702 return self.direct_view(key)
699 return self.direct_view(key)
703
700
704 #--------------------------------------------------------------------------
701 #--------------------------------------------------------------------------
705 # Begin public methods
702 # Begin public methods
706 #--------------------------------------------------------------------------
703 #--------------------------------------------------------------------------
707
704
708 @property
705 @property
709 def ids(self):
706 def ids(self):
710 """Always up-to-date ids property."""
707 """Always up-to-date ids property."""
711 self._flush_notifications()
708 self._flush_notifications()
712 # always copy:
709 # always copy:
713 return list(self._ids)
710 return list(self._ids)
714
711
715 def close(self):
712 def close(self):
716 if self._closed:
713 if self._closed:
717 return
714 return
718 snames = filter(lambda n: n.endswith('socket'), dir(self))
715 snames = filter(lambda n: n.endswith('socket'), dir(self))
719 for socket in map(lambda name: getattr(self, name), snames):
716 for socket in map(lambda name: getattr(self, name), snames):
720 if isinstance(socket, zmq.Socket) and not socket.closed:
717 if isinstance(socket, zmq.Socket) and not socket.closed:
721 socket.close()
718 socket.close()
722 self._closed = True
719 self._closed = True
723
720
724 def spin(self):
721 def spin(self):
725 """Flush any registration notifications and execution results
722 """Flush any registration notifications and execution results
726 waiting in the ZMQ queue.
723 waiting in the ZMQ queue.
727 """
724 """
728 if self._notification_socket:
725 if self._notification_socket:
729 self._flush_notifications()
726 self._flush_notifications()
730 if self._mux_socket:
727 if self._mux_socket:
731 self._flush_results(self._mux_socket)
728 self._flush_results(self._mux_socket)
732 if self._task_socket:
729 if self._task_socket:
733 self._flush_results(self._task_socket)
730 self._flush_results(self._task_socket)
734 if self._control_socket:
731 if self._control_socket:
735 self._flush_control(self._control_socket)
732 self._flush_control(self._control_socket)
736 if self._iopub_socket:
733 if self._iopub_socket:
737 self._flush_iopub(self._iopub_socket)
734 self._flush_iopub(self._iopub_socket)
738 if self._query_socket:
735 if self._query_socket:
739 self._flush_ignored_hub_replies()
736 self._flush_ignored_hub_replies()
740
737
741 def wait(self, jobs=None, timeout=-1):
738 def wait(self, jobs=None, timeout=-1):
742 """waits on one or more `jobs`, for up to `timeout` seconds.
739 """waits on one or more `jobs`, for up to `timeout` seconds.
743
740
744 Parameters
741 Parameters
745 ----------
742 ----------
746
743
747 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
744 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
748 ints are indices to self.history
745 ints are indices to self.history
749 strs are msg_ids
746 strs are msg_ids
750 default: wait on all outstanding messages
747 default: wait on all outstanding messages
751 timeout : float
748 timeout : float
752 a time in seconds, after which to give up.
749 a time in seconds, after which to give up.
753 default is -1, which means no timeout
750 default is -1, which means no timeout
754
751
755 Returns
752 Returns
756 -------
753 -------
757
754
758 True : when all msg_ids are done
755 True : when all msg_ids are done
759 False : timeout reached, some msg_ids still outstanding
756 False : timeout reached, some msg_ids still outstanding
760 """
757 """
761 tic = time.time()
758 tic = time.time()
762 if jobs is None:
759 if jobs is None:
763 theids = self.outstanding
760 theids = self.outstanding
764 else:
761 else:
765 if isinstance(jobs, (int, str, AsyncResult)):
762 if isinstance(jobs, (int, str, AsyncResult)):
766 jobs = [jobs]
763 jobs = [jobs]
767 theids = set()
764 theids = set()
768 for job in jobs:
765 for job in jobs:
769 if isinstance(job, int):
766 if isinstance(job, int):
770 # index access
767 # index access
771 job = self.history[job]
768 job = self.history[job]
772 elif isinstance(job, AsyncResult):
769 elif isinstance(job, AsyncResult):
773 map(theids.add, job.msg_ids)
770 map(theids.add, job.msg_ids)
774 continue
771 continue
775 theids.add(job)
772 theids.add(job)
776 if not theids.intersection(self.outstanding):
773 if not theids.intersection(self.outstanding):
777 return True
774 return True
778 self.spin()
775 self.spin()
779 while theids.intersection(self.outstanding):
776 while theids.intersection(self.outstanding):
780 if timeout >= 0 and ( time.time()-tic ) > timeout:
777 if timeout >= 0 and ( time.time()-tic ) > timeout:
781 break
778 break
782 time.sleep(1e-3)
779 time.sleep(1e-3)
783 self.spin()
780 self.spin()
784 return len(theids.intersection(self.outstanding)) == 0
781 return len(theids.intersection(self.outstanding)) == 0
785
782
786 #--------------------------------------------------------------------------
783 #--------------------------------------------------------------------------
787 # Control methods
784 # Control methods
788 #--------------------------------------------------------------------------
785 #--------------------------------------------------------------------------
789
786
790 @spin_first
787 @spin_first
791 @default_block
788 @default_block
792 def clear(self, targets=None, block=None):
789 def clear(self, targets=None, block=None):
793 """Clear the namespace in target(s)."""
790 """Clear the namespace in target(s)."""
794 targets = self._build_targets(targets)[0]
791 targets = self._build_targets(targets)[0]
795 for t in targets:
792 for t in targets:
796 self.session.send(self._control_socket, 'clear_request', content={}, ident=t)
793 self.session.send(self._control_socket, 'clear_request', content={}, ident=t)
797 error = False
794 error = False
798 if self.block:
795 if self.block:
799 self._flush_ignored_control()
796 self._flush_ignored_control()
800 for i in range(len(targets)):
797 for i in range(len(targets)):
801 idents,msg = self.session.recv(self._control_socket,0)
798 idents,msg = self.session.recv(self._control_socket,0)
802 if self.debug:
799 if self.debug:
803 pprint(msg)
800 pprint(msg)
804 if msg['content']['status'] != 'ok':
801 if msg['content']['status'] != 'ok':
805 error = self._unwrap_exception(msg['content'])
802 error = self._unwrap_exception(msg['content'])
806 else:
803 else:
807 self._ignored_control_replies += len(targets)
804 self._ignored_control_replies += len(targets)
808 if error:
805 if error:
809 raise error
806 raise error
810
807
811
808
812 @spin_first
809 @spin_first
813 @default_block
810 @default_block
814 def abort(self, jobs=None, targets=None, block=None):
811 def abort(self, jobs=None, targets=None, block=None):
815 """Abort specific jobs from the execution queues of target(s).
812 """Abort specific jobs from the execution queues of target(s).
816
813
817 This is a mechanism to prevent jobs that have already been submitted
814 This is a mechanism to prevent jobs that have already been submitted
818 from executing.
815 from executing.
819
816
820 Parameters
817 Parameters
821 ----------
818 ----------
822
819
823 jobs : msg_id, list of msg_ids, or AsyncResult
820 jobs : msg_id, list of msg_ids, or AsyncResult
824 The jobs to be aborted
821 The jobs to be aborted
825
822
826
823
827 """
824 """
828 targets = self._build_targets(targets)[0]
825 targets = self._build_targets(targets)[0]
829 msg_ids = []
826 msg_ids = []
830 if isinstance(jobs, (basestring,AsyncResult)):
827 if isinstance(jobs, (basestring,AsyncResult)):
831 jobs = [jobs]
828 jobs = [jobs]
832 bad_ids = filter(lambda obj: not isinstance(obj, (basestring, AsyncResult)), jobs)
829 bad_ids = filter(lambda obj: not isinstance(obj, (basestring, AsyncResult)), jobs)
833 if bad_ids:
830 if bad_ids:
834 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
831 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
835 for j in jobs:
832 for j in jobs:
836 if isinstance(j, AsyncResult):
833 if isinstance(j, AsyncResult):
837 msg_ids.extend(j.msg_ids)
834 msg_ids.extend(j.msg_ids)
838 else:
835 else:
839 msg_ids.append(j)
836 msg_ids.append(j)
840 content = dict(msg_ids=msg_ids)
837 content = dict(msg_ids=msg_ids)
841 for t in targets:
838 for t in targets:
842 self.session.send(self._control_socket, 'abort_request',
839 self.session.send(self._control_socket, 'abort_request',
843 content=content, ident=t)
840 content=content, ident=t)
844 error = False
841 error = False
845 if self.block:
842 if self.block:
846 self._flush_ignored_control()
843 self._flush_ignored_control()
847 for i in range(len(targets)):
844 for i in range(len(targets)):
848 idents,msg = self.session.recv(self._control_socket,0)
845 idents,msg = self.session.recv(self._control_socket,0)
849 if self.debug:
846 if self.debug:
850 pprint(msg)
847 pprint(msg)
851 if msg['content']['status'] != 'ok':
848 if msg['content']['status'] != 'ok':
852 error = self._unwrap_exception(msg['content'])
849 error = self._unwrap_exception(msg['content'])
853 else:
850 else:
854 self._ignored_control_replies += len(targets)
851 self._ignored_control_replies += len(targets)
855 if error:
852 if error:
856 raise error
853 raise error
857
854
858 @spin_first
855 @spin_first
859 @default_block
856 @default_block
860 def shutdown(self, targets=None, restart=False, hub=False, block=None):
857 def shutdown(self, targets=None, restart=False, hub=False, block=None):
861 """Terminates one or more engine processes, optionally including the hub."""
858 """Terminates one or more engine processes, optionally including the hub."""
862 if hub:
859 if hub:
863 targets = 'all'
860 targets = 'all'
864 targets = self._build_targets(targets)[0]
861 targets = self._build_targets(targets)[0]
865 for t in targets:
862 for t in targets:
866 self.session.send(self._control_socket, 'shutdown_request',
863 self.session.send(self._control_socket, 'shutdown_request',
867 content={'restart':restart},ident=t)
864 content={'restart':restart},ident=t)
868 error = False
865 error = False
869 if block or hub:
866 if block or hub:
870 self._flush_ignored_control()
867 self._flush_ignored_control()
871 for i in range(len(targets)):
868 for i in range(len(targets)):
872 idents,msg = self.session.recv(self._control_socket, 0)
869 idents,msg = self.session.recv(self._control_socket, 0)
873 if self.debug:
870 if self.debug:
874 pprint(msg)
871 pprint(msg)
875 if msg['content']['status'] != 'ok':
872 if msg['content']['status'] != 'ok':
876 error = self._unwrap_exception(msg['content'])
873 error = self._unwrap_exception(msg['content'])
877 else:
874 else:
878 self._ignored_control_replies += len(targets)
875 self._ignored_control_replies += len(targets)
879
876
880 if hub:
877 if hub:
881 time.sleep(0.25)
878 time.sleep(0.25)
882 self.session.send(self._query_socket, 'shutdown_request')
879 self.session.send(self._query_socket, 'shutdown_request')
883 idents,msg = self.session.recv(self._query_socket, 0)
880 idents,msg = self.session.recv(self._query_socket, 0)
884 if self.debug:
881 if self.debug:
885 pprint(msg)
882 pprint(msg)
886 if msg['content']['status'] != 'ok':
883 if msg['content']['status'] != 'ok':
887 error = self._unwrap_exception(msg['content'])
884 error = self._unwrap_exception(msg['content'])
888
885
889 if error:
886 if error:
890 raise error
887 raise error
891
888
892 #--------------------------------------------------------------------------
889 #--------------------------------------------------------------------------
893 # Execution methods
890 # Execution methods
894 #--------------------------------------------------------------------------
891 #--------------------------------------------------------------------------
895
892
896 @default_block
893 @default_block
897 def _execute(self, code, targets='all', block=None):
894 def _execute(self, code, targets='all', block=None):
898 """Executes `code` on `targets` in blocking or nonblocking manner.
895 """Executes `code` on `targets` in blocking or nonblocking manner.
899
896
900 ``execute`` is always `bound` (affects engine namespace)
897 ``execute`` is always `bound` (affects engine namespace)
901
898
902 Parameters
899 Parameters
903 ----------
900 ----------
904
901
905 code : str
902 code : str
906 the code string to be executed
903 the code string to be executed
907 targets : int/str/list of ints/strs
904 targets : int/str/list of ints/strs
908 the engines on which to execute
905 the engines on which to execute
909 default : all
906 default : all
910 block : bool
907 block : bool
911 whether or not to wait until done to return
908 whether or not to wait until done to return
912 default: self.block
909 default: self.block
913 """
910 """
914 return self[targets].execute(code, block=block)
911 return self[targets].execute(code, block=block)
915
912
916 def _maybe_raise(self, result):
913 def _maybe_raise(self, result):
917 """wrapper for maybe raising an exception if apply failed."""
914 """wrapper for maybe raising an exception if apply failed."""
918 if isinstance(result, error.RemoteError):
915 if isinstance(result, error.RemoteError):
919 raise result
916 raise result
920
917
921 return result
918 return result
922
919
923 def send_apply_message(self, socket, f, args=None, kwargs=None, subheader=None, track=False,
920 def send_apply_message(self, socket, f, args=None, kwargs=None, subheader=None, track=False,
924 ident=None):
921 ident=None):
925 """construct and send an apply message via a socket.
922 """construct and send an apply message via a socket.
926
923
927 This is the principal method with which all engine execution is performed by views.
924 This is the principal method with which all engine execution is performed by views.
928 """
925 """
929
926
930 assert not self._closed, "cannot use me anymore, I'm closed!"
927 assert not self._closed, "cannot use me anymore, I'm closed!"
931 # defaults:
928 # defaults:
932 args = args if args is not None else []
929 args = args if args is not None else []
933 kwargs = kwargs if kwargs is not None else {}
930 kwargs = kwargs if kwargs is not None else {}
934 subheader = subheader if subheader is not None else {}
931 subheader = subheader if subheader is not None else {}
935
932
936 # validate arguments
933 # validate arguments
937 if not callable(f):
934 if not callable(f):
938 raise TypeError("f must be callable, not %s"%type(f))
935 raise TypeError("f must be callable, not %s"%type(f))
939 if not isinstance(args, (tuple, list)):
936 if not isinstance(args, (tuple, list)):
940 raise TypeError("args must be tuple or list, not %s"%type(args))
937 raise TypeError("args must be tuple or list, not %s"%type(args))
941 if not isinstance(kwargs, dict):
938 if not isinstance(kwargs, dict):
942 raise TypeError("kwargs must be dict, not %s"%type(kwargs))
939 raise TypeError("kwargs must be dict, not %s"%type(kwargs))
943 if not isinstance(subheader, dict):
940 if not isinstance(subheader, dict):
944 raise TypeError("subheader must be dict, not %s"%type(subheader))
941 raise TypeError("subheader must be dict, not %s"%type(subheader))
945
942
946 if not self._ids:
943 if not self._ids:
947 # flush notification socket if no engines yet
944 # flush notification socket if no engines yet
948 any_ids = self.ids
945 any_ids = self.ids
949 if not any_ids:
946 if not any_ids:
950 raise error.NoEnginesRegistered("Can't execute without any connected engines.")
947 raise error.NoEnginesRegistered("Can't execute without any connected engines.")
951 # enforce types of f,args,kwargs
948 # enforce types of f,args,kwargs
952
949
953 bufs = util.pack_apply_message(f,args,kwargs)
950 bufs = util.pack_apply_message(f,args,kwargs)
954
951
955 msg = self.session.send(socket, "apply_request", buffers=bufs, ident=ident,
952 msg = self.session.send(socket, "apply_request", buffers=bufs, ident=ident,
956 subheader=subheader, track=track)
953 subheader=subheader, track=track)
957
954
958 msg_id = msg['msg_id']
955 msg_id = msg['msg_id']
959 self.outstanding.add(msg_id)
956 self.outstanding.add(msg_id)
960 if ident:
957 if ident:
961 # possibly routed to a specific engine
958 # possibly routed to a specific engine
962 if isinstance(ident, list):
959 if isinstance(ident, list):
963 ident = ident[-1]
960 ident = ident[-1]
964 if ident in self._engines.values():
961 if ident in self._engines.values():
965 # save for later, in case of engine death
962 # save for later, in case of engine death
966 self._outstanding_dict[ident].add(msg_id)
963 self._outstanding_dict[ident].add(msg_id)
967 self.history.append(msg_id)
964 self.history.append(msg_id)
968 self.metadata[msg_id]['submitted'] = datetime.now()
965 self.metadata[msg_id]['submitted'] = datetime.now()
969
966
970 return msg
967 return msg
971
968
972 #--------------------------------------------------------------------------
969 #--------------------------------------------------------------------------
973 # construct a View object
970 # construct a View object
974 #--------------------------------------------------------------------------
971 #--------------------------------------------------------------------------
975
972
976 def load_balanced_view(self, targets=None):
973 def load_balanced_view(self, targets=None):
977 """construct a DirectView object.
974 """construct a DirectView object.
978
975
979 If no arguments are specified, create a LoadBalancedView
976 If no arguments are specified, create a LoadBalancedView
980 using all engines.
977 using all engines.
981
978
982 Parameters
979 Parameters
983 ----------
980 ----------
984
981
985 targets: list,slice,int,etc. [default: use all engines]
982 targets: list,slice,int,etc. [default: use all engines]
986 The subset of engines across which to load-balance
983 The subset of engines across which to load-balance
987 """
984 """
988 if targets is None:
985 if targets is not None:
989 targets = self._build_targets(targets)[1]
986 targets = self._build_targets(targets)[1]
990 return LoadBalancedView(client=self, socket=self._task_socket, targets=targets)
987 return LoadBalancedView(client=self, socket=self._task_socket, targets=targets)
991
988
992 def direct_view(self, targets='all'):
989 def direct_view(self, targets='all'):
993 """construct a DirectView object.
990 """construct a DirectView object.
994
991
995 If no targets are specified, create a DirectView
992 If no targets are specified, create a DirectView
996 using all engines.
993 using all engines.
997
994
998 Parameters
995 Parameters
999 ----------
996 ----------
1000
997
1001 targets: list,slice,int,etc. [default: use all engines]
998 targets: list,slice,int,etc. [default: use all engines]
1002 The engines to use for the View
999 The engines to use for the View
1003 """
1000 """
1004 single = isinstance(targets, int)
1001 single = isinstance(targets, int)
1005 targets = self._build_targets(targets)[1]
1002 targets = self._build_targets(targets)[1]
1006 if single:
1003 if single:
1007 targets = targets[0]
1004 targets = targets[0]
1008 return DirectView(client=self, socket=self._mux_socket, targets=targets)
1005 return DirectView(client=self, socket=self._mux_socket, targets=targets)
1009
1006
1010 #--------------------------------------------------------------------------
1007 #--------------------------------------------------------------------------
1011 # Data movement (TO BE REMOVED)
1008 # Data movement (TO BE REMOVED)
1012 #--------------------------------------------------------------------------
1009 #--------------------------------------------------------------------------
1013
1010
1014 @default_block
1011 @default_block
1015 def _push(self, ns, targets='all', block=None, track=False):
1012 def _push(self, ns, targets='all', block=None, track=False):
1016 """Push the contents of `ns` into the namespace on `target`"""
1013 """Push the contents of `ns` into the namespace on `target`"""
1017 if not isinstance(ns, dict):
1014 if not isinstance(ns, dict):
1018 raise TypeError("Must be a dict, not %s"%type(ns))
1015 raise TypeError("Must be a dict, not %s"%type(ns))
1019 result = self.apply(util._push, kwargs=ns, targets=targets, block=block, bound=True, balanced=False, track=track)
1016 result = self.apply(util._push, kwargs=ns, targets=targets, block=block, bound=True, balanced=False, track=track)
1020 if not block:
1017 if not block:
1021 return result
1018 return result
1022
1019
1023 @default_block
1020 @default_block
1024 def _pull(self, keys, targets='all', block=None):
1021 def _pull(self, keys, targets='all', block=None):
1025 """Pull objects from `target`'s namespace by `keys`"""
1022 """Pull objects from `target`'s namespace by `keys`"""
1026 if isinstance(keys, basestring):
1023 if isinstance(keys, basestring):
1027 pass
1024 pass
1028 elif isinstance(keys, (list,tuple,set)):
1025 elif isinstance(keys, (list,tuple,set)):
1029 for key in keys:
1026 for key in keys:
1030 if not isinstance(key, basestring):
1027 if not isinstance(key, basestring):
1031 raise TypeError("keys must be str, not type %r"%type(key))
1028 raise TypeError("keys must be str, not type %r"%type(key))
1032 else:
1029 else:
1033 raise TypeError("keys must be strs, not %r"%keys)
1030 raise TypeError("keys must be strs, not %r"%keys)
1034 result = self.apply(util._pull, (keys,), targets=targets, block=block, bound=True, balanced=False)
1031 result = self.apply(util._pull, (keys,), targets=targets, block=block, bound=True, balanced=False)
1035 return result
1032 return result
1036
1033
1037 #--------------------------------------------------------------------------
1034 #--------------------------------------------------------------------------
1038 # Query methods
1035 # Query methods
1039 #--------------------------------------------------------------------------
1036 #--------------------------------------------------------------------------
1040
1037
1041 @spin_first
1038 @spin_first
1042 @default_block
1039 @default_block
1043 def get_result(self, indices_or_msg_ids=None, block=None):
1040 def get_result(self, indices_or_msg_ids=None, block=None):
1044 """Retrieve a result by msg_id or history index, wrapped in an AsyncResult object.
1041 """Retrieve a result by msg_id or history index, wrapped in an AsyncResult object.
1045
1042
1046 If the client already has the results, no request to the Hub will be made.
1043 If the client already has the results, no request to the Hub will be made.
1047
1044
1048 This is a convenient way to construct AsyncResult objects, which are wrappers
1045 This is a convenient way to construct AsyncResult objects, which are wrappers
1049 that include metadata about execution, and allow for awaiting results that
1046 that include metadata about execution, and allow for awaiting results that
1050 were not submitted by this Client.
1047 were not submitted by this Client.
1051
1048
1052 It can also be a convenient way to retrieve the metadata associated with
1049 It can also be a convenient way to retrieve the metadata associated with
1053 blocking execution, since it always retrieves
1050 blocking execution, since it always retrieves
1054
1051
1055 Examples
1052 Examples
1056 --------
1053 --------
1057 ::
1054 ::
1058
1055
1059 In [10]: r = client.apply()
1056 In [10]: r = client.apply()
1060
1057
1061 Parameters
1058 Parameters
1062 ----------
1059 ----------
1063
1060
1064 indices_or_msg_ids : integer history index, str msg_id, or list of either
1061 indices_or_msg_ids : integer history index, str msg_id, or list of either
1065 The indices or msg_ids of indices to be retrieved
1062 The indices or msg_ids of indices to be retrieved
1066
1063
1067 block : bool
1064 block : bool
1068 Whether to wait for the result to be done
1065 Whether to wait for the result to be done
1069
1066
1070 Returns
1067 Returns
1071 -------
1068 -------
1072
1069
1073 AsyncResult
1070 AsyncResult
1074 A single AsyncResult object will always be returned.
1071 A single AsyncResult object will always be returned.
1075
1072
1076 AsyncHubResult
1073 AsyncHubResult
1077 A subclass of AsyncResult that retrieves results from the Hub
1074 A subclass of AsyncResult that retrieves results from the Hub
1078
1075
1079 """
1076 """
1080 if indices_or_msg_ids is None:
1077 if indices_or_msg_ids is None:
1081 indices_or_msg_ids = -1
1078 indices_or_msg_ids = -1
1082
1079
1083 if not isinstance(indices_or_msg_ids, (list,tuple)):
1080 if not isinstance(indices_or_msg_ids, (list,tuple)):
1084 indices_or_msg_ids = [indices_or_msg_ids]
1081 indices_or_msg_ids = [indices_or_msg_ids]
1085
1082
1086 theids = []
1083 theids = []
1087 for id in indices_or_msg_ids:
1084 for id in indices_or_msg_ids:
1088 if isinstance(id, int):
1085 if isinstance(id, int):
1089 id = self.history[id]
1086 id = self.history[id]
1090 if not isinstance(id, str):
1087 if not isinstance(id, str):
1091 raise TypeError("indices must be str or int, not %r"%id)
1088 raise TypeError("indices must be str or int, not %r"%id)
1092 theids.append(id)
1089 theids.append(id)
1093
1090
1094 local_ids = filter(lambda msg_id: msg_id in self.history or msg_id in self.results, theids)
1091 local_ids = filter(lambda msg_id: msg_id in self.history or msg_id in self.results, theids)
1095 remote_ids = filter(lambda msg_id: msg_id not in local_ids, theids)
1092 remote_ids = filter(lambda msg_id: msg_id not in local_ids, theids)
1096
1093
1097 if remote_ids:
1094 if remote_ids:
1098 ar = AsyncHubResult(self, msg_ids=theids)
1095 ar = AsyncHubResult(self, msg_ids=theids)
1099 else:
1096 else:
1100 ar = AsyncResult(self, msg_ids=theids)
1097 ar = AsyncResult(self, msg_ids=theids)
1101
1098
1102 if block:
1099 if block:
1103 ar.wait()
1100 ar.wait()
1104
1101
1105 return ar
1102 return ar
1106
1103
1107 @spin_first
1104 @spin_first
1108 def result_status(self, msg_ids, status_only=True):
1105 def result_status(self, msg_ids, status_only=True):
1109 """Check on the status of the result(s) of the apply request with `msg_ids`.
1106 """Check on the status of the result(s) of the apply request with `msg_ids`.
1110
1107
1111 If status_only is False, then the actual results will be retrieved, else
1108 If status_only is False, then the actual results will be retrieved, else
1112 only the status of the results will be checked.
1109 only the status of the results will be checked.
1113
1110
1114 Parameters
1111 Parameters
1115 ----------
1112 ----------
1116
1113
1117 msg_ids : list of msg_ids
1114 msg_ids : list of msg_ids
1118 if int:
1115 if int:
1119 Passed as index to self.history for convenience.
1116 Passed as index to self.history for convenience.
1120 status_only : bool (default: True)
1117 status_only : bool (default: True)
1121 if False:
1118 if False:
1122 Retrieve the actual results of completed tasks.
1119 Retrieve the actual results of completed tasks.
1123
1120
1124 Returns
1121 Returns
1125 -------
1122 -------
1126
1123
1127 results : dict
1124 results : dict
1128 There will always be the keys 'pending' and 'completed', which will
1125 There will always be the keys 'pending' and 'completed', which will
1129 be lists of msg_ids that are incomplete or complete. If `status_only`
1126 be lists of msg_ids that are incomplete or complete. If `status_only`
1130 is False, then completed results will be keyed by their `msg_id`.
1127 is False, then completed results will be keyed by their `msg_id`.
1131 """
1128 """
1132 if not isinstance(msg_ids, (list,tuple)):
1129 if not isinstance(msg_ids, (list,tuple)):
1133 msg_ids = [msg_ids]
1130 msg_ids = [msg_ids]
1134
1131
1135 theids = []
1132 theids = []
1136 for msg_id in msg_ids:
1133 for msg_id in msg_ids:
1137 if isinstance(msg_id, int):
1134 if isinstance(msg_id, int):
1138 msg_id = self.history[msg_id]
1135 msg_id = self.history[msg_id]
1139 if not isinstance(msg_id, basestring):
1136 if not isinstance(msg_id, basestring):
1140 raise TypeError("msg_ids must be str, not %r"%msg_id)
1137 raise TypeError("msg_ids must be str, not %r"%msg_id)
1141 theids.append(msg_id)
1138 theids.append(msg_id)
1142
1139
1143 completed = []
1140 completed = []
1144 local_results = {}
1141 local_results = {}
1145
1142
1146 # comment this block out to temporarily disable local shortcut:
1143 # comment this block out to temporarily disable local shortcut:
1147 for msg_id in theids:
1144 for msg_id in theids:
1148 if msg_id in self.results:
1145 if msg_id in self.results:
1149 completed.append(msg_id)
1146 completed.append(msg_id)
1150 local_results[msg_id] = self.results[msg_id]
1147 local_results[msg_id] = self.results[msg_id]
1151 theids.remove(msg_id)
1148 theids.remove(msg_id)
1152
1149
1153 if theids: # some not locally cached
1150 if theids: # some not locally cached
1154 content = dict(msg_ids=theids, status_only=status_only)
1151 content = dict(msg_ids=theids, status_only=status_only)
1155 msg = self.session.send(self._query_socket, "result_request", content=content)
1152 msg = self.session.send(self._query_socket, "result_request", content=content)
1156 zmq.select([self._query_socket], [], [])
1153 zmq.select([self._query_socket], [], [])
1157 idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK)
1154 idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK)
1158 if self.debug:
1155 if self.debug:
1159 pprint(msg)
1156 pprint(msg)
1160 content = msg['content']
1157 content = msg['content']
1161 if content['status'] != 'ok':
1158 if content['status'] != 'ok':
1162 raise self._unwrap_exception(content)
1159 raise self._unwrap_exception(content)
1163 buffers = msg['buffers']
1160 buffers = msg['buffers']
1164 else:
1161 else:
1165 content = dict(completed=[],pending=[])
1162 content = dict(completed=[],pending=[])
1166
1163
1167 content['completed'].extend(completed)
1164 content['completed'].extend(completed)
1168
1165
1169 if status_only:
1166 if status_only:
1170 return content
1167 return content
1171
1168
1172 failures = []
1169 failures = []
1173 # load cached results into result:
1170 # load cached results into result:
1174 content.update(local_results)
1171 content.update(local_results)
1175 # update cache with results:
1172 # update cache with results:
1176 for msg_id in sorted(theids):
1173 for msg_id in sorted(theids):
1177 if msg_id in content['completed']:
1174 if msg_id in content['completed']:
1178 rec = content[msg_id]
1175 rec = content[msg_id]
1179 parent = rec['header']
1176 parent = rec['header']
1180 header = rec['result_header']
1177 header = rec['result_header']
1181 rcontent = rec['result_content']
1178 rcontent = rec['result_content']
1182 iodict = rec['io']
1179 iodict = rec['io']
1183 if isinstance(rcontent, str):
1180 if isinstance(rcontent, str):
1184 rcontent = self.session.unpack(rcontent)
1181 rcontent = self.session.unpack(rcontent)
1185
1182
1186 md = self.metadata[msg_id]
1183 md = self.metadata[msg_id]
1187 md.update(self._extract_metadata(header, parent, rcontent))
1184 md.update(self._extract_metadata(header, parent, rcontent))
1188 md.update(iodict)
1185 md.update(iodict)
1189
1186
1190 if rcontent['status'] == 'ok':
1187 if rcontent['status'] == 'ok':
1191 res,buffers = util.unserialize_object(buffers)
1188 res,buffers = util.unserialize_object(buffers)
1192 else:
1189 else:
1193 print rcontent
1190 print rcontent
1194 res = self._unwrap_exception(rcontent)
1191 res = self._unwrap_exception(rcontent)
1195 failures.append(res)
1192 failures.append(res)
1196
1193
1197 self.results[msg_id] = res
1194 self.results[msg_id] = res
1198 content[msg_id] = res
1195 content[msg_id] = res
1199
1196
1200 if len(theids) == 1 and failures:
1197 if len(theids) == 1 and failures:
1201 raise failures[0]
1198 raise failures[0]
1202
1199
1203 error.collect_exceptions(failures, "result_status")
1200 error.collect_exceptions(failures, "result_status")
1204 return content
1201 return content
1205
1202
1206 @spin_first
1203 @spin_first
1207 def queue_status(self, targets='all', verbose=False):
1204 def queue_status(self, targets='all', verbose=False):
1208 """Fetch the status of engine queues.
1205 """Fetch the status of engine queues.
1209
1206
1210 Parameters
1207 Parameters
1211 ----------
1208 ----------
1212
1209
1213 targets : int/str/list of ints/strs
1210 targets : int/str/list of ints/strs
1214 the engines whose states are to be queried.
1211 the engines whose states are to be queried.
1215 default : all
1212 default : all
1216 verbose : bool
1213 verbose : bool
1217 Whether to return lengths only, or lists of ids for each element
1214 Whether to return lengths only, or lists of ids for each element
1218 """
1215 """
1219 engine_ids = self._build_targets(targets)[1]
1216 engine_ids = self._build_targets(targets)[1]
1220 content = dict(targets=engine_ids, verbose=verbose)
1217 content = dict(targets=engine_ids, verbose=verbose)
1221 self.session.send(self._query_socket, "queue_request", content=content)
1218 self.session.send(self._query_socket, "queue_request", content=content)
1222 idents,msg = self.session.recv(self._query_socket, 0)
1219 idents,msg = self.session.recv(self._query_socket, 0)
1223 if self.debug:
1220 if self.debug:
1224 pprint(msg)
1221 pprint(msg)
1225 content = msg['content']
1222 content = msg['content']
1226 status = content.pop('status')
1223 status = content.pop('status')
1227 if status != 'ok':
1224 if status != 'ok':
1228 raise self._unwrap_exception(content)
1225 raise self._unwrap_exception(content)
1229 content = util.rekey(content)
1226 content = util.rekey(content)
1230 if isinstance(targets, int):
1227 if isinstance(targets, int):
1231 return content[targets]
1228 return content[targets]
1232 else:
1229 else:
1233 return content
1230 return content
1234
1231
1235 @spin_first
1232 @spin_first
1236 def purge_results(self, jobs=[], targets=[]):
1233 def purge_results(self, jobs=[], targets=[]):
1237 """Tell the Hub to forget results.
1234 """Tell the Hub to forget results.
1238
1235
1239 Individual results can be purged by msg_id, or the entire
1236 Individual results can be purged by msg_id, or the entire
1240 history of specific targets can be purged.
1237 history of specific targets can be purged.
1241
1238
1242 Parameters
1239 Parameters
1243 ----------
1240 ----------
1244
1241
1245 jobs : str or list of str or AsyncResult objects
1242 jobs : str or list of str or AsyncResult objects
1246 the msg_ids whose results should be forgotten.
1243 the msg_ids whose results should be forgotten.
1247 targets : int/str/list of ints/strs
1244 targets : int/str/list of ints/strs
1248 The targets, by uuid or int_id, whose entire history is to be purged.
1245 The targets, by uuid or int_id, whose entire history is to be purged.
1249 Use `targets='all'` to scrub everything from the Hub's memory.
1246 Use `targets='all'` to scrub everything from the Hub's memory.
1250
1247
1251 default : None
1248 default : None
1252 """
1249 """
1253 if not targets and not jobs:
1250 if not targets and not jobs:
1254 raise ValueError("Must specify at least one of `targets` and `jobs`")
1251 raise ValueError("Must specify at least one of `targets` and `jobs`")
1255 if targets:
1252 if targets:
1256 targets = self._build_targets(targets)[1]
1253 targets = self._build_targets(targets)[1]
1257
1254
1258 # construct msg_ids from jobs
1255 # construct msg_ids from jobs
1259 msg_ids = []
1256 msg_ids = []
1260 if isinstance(jobs, (basestring,AsyncResult)):
1257 if isinstance(jobs, (basestring,AsyncResult)):
1261 jobs = [jobs]
1258 jobs = [jobs]
1262 bad_ids = filter(lambda obj: not isinstance(obj, (basestring, AsyncResult)), jobs)
1259 bad_ids = filter(lambda obj: not isinstance(obj, (basestring, AsyncResult)), jobs)
1263 if bad_ids:
1260 if bad_ids:
1264 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
1261 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
1265 for j in jobs:
1262 for j in jobs:
1266 if isinstance(j, AsyncResult):
1263 if isinstance(j, AsyncResult):
1267 msg_ids.extend(j.msg_ids)
1264 msg_ids.extend(j.msg_ids)
1268 else:
1265 else:
1269 msg_ids.append(j)
1266 msg_ids.append(j)
1270
1267
1271 content = dict(targets=targets, msg_ids=msg_ids)
1268 content = dict(targets=targets, msg_ids=msg_ids)
1272 self.session.send(self._query_socket, "purge_request", content=content)
1269 self.session.send(self._query_socket, "purge_request", content=content)
1273 idents, msg = self.session.recv(self._query_socket, 0)
1270 idents, msg = self.session.recv(self._query_socket, 0)
1274 if self.debug:
1271 if self.debug:
1275 pprint(msg)
1272 pprint(msg)
1276 content = msg['content']
1273 content = msg['content']
1277 if content['status'] != 'ok':
1274 if content['status'] != 'ok':
1278 raise self._unwrap_exception(content)
1275 raise self._unwrap_exception(content)
1279
1276
1280
1277
1281 __all__ = [ 'Client',
1278 __all__ = [ 'Client' ]
1282 'depend',
1283 'require',
1284 'remote',
1285 'parallel',
1286 'RemoteFunction',
1287 'ParallelFunction',
1288 'DirectView',
1289 'LoadBalancedView',
1290 'AsyncResult',
1291 'AsyncMapResult',
1292 'Reference'
1293 ]
1 NO CONTENT: file renamed from IPython/zmq/parallel/clusterdir.py to IPython/parallel/clusterdir.py
NO CONTENT: file renamed from IPython/zmq/parallel/clusterdir.py to IPython/parallel/clusterdir.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/controller.py to IPython/parallel/controller.py
NO CONTENT: file renamed from IPython/zmq/parallel/controller.py to IPython/parallel/controller.py
@@ -1,196 +1,196 b''
1 """Dependency utilities"""
1 """Dependency utilities"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team
3 # Copyright (C) 2010-2011 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 from types import ModuleType
9 from types import ModuleType
10
10
11 from .asyncresult import AsyncResult
11 from .asyncresult import AsyncResult
12 from .error import UnmetDependency
12 from .error import UnmetDependency
13 from .util import interactive
13 from .util import interactive
14
14
15 class depend(object):
15 class depend(object):
16 """Dependency decorator, for use with tasks.
16 """Dependency decorator, for use with tasks.
17
17
18 `@depend` lets you define a function for engine dependencies
18 `@depend` lets you define a function for engine dependencies
19 just like you use `apply` for tasks.
19 just like you use `apply` for tasks.
20
20
21
21
22 Examples
22 Examples
23 --------
23 --------
24 ::
24 ::
25
25
26 @depend(df, a,b, c=5)
26 @depend(df, a,b, c=5)
27 def f(m,n,p)
27 def f(m,n,p)
28
28
29 view.apply(f, 1,2,3)
29 view.apply(f, 1,2,3)
30
30
31 will call df(a,b,c=5) on the engine, and if it returns False or
31 will call df(a,b,c=5) on the engine, and if it returns False or
32 raises an UnmetDependency error, then the task will not be run
32 raises an UnmetDependency error, then the task will not be run
33 and another engine will be tried.
33 and another engine will be tried.
34 """
34 """
35 def __init__(self, f, *args, **kwargs):
35 def __init__(self, f, *args, **kwargs):
36 self.f = f
36 self.f = f
37 self.args = args
37 self.args = args
38 self.kwargs = kwargs
38 self.kwargs = kwargs
39
39
40 def __call__(self, f):
40 def __call__(self, f):
41 return dependent(f, self.f, *self.args, **self.kwargs)
41 return dependent(f, self.f, *self.args, **self.kwargs)
42
42
43 class dependent(object):
43 class dependent(object):
44 """A function that depends on another function.
44 """A function that depends on another function.
45 This is an object to prevent the closure used
45 This is an object to prevent the closure used
46 in traditional decorators, which are not picklable.
46 in traditional decorators, which are not picklable.
47 """
47 """
48
48
49 def __init__(self, f, df, *dargs, **dkwargs):
49 def __init__(self, f, df, *dargs, **dkwargs):
50 self.f = f
50 self.f = f
51 self.func_name = getattr(f, '__name__', 'f')
51 self.func_name = getattr(f, '__name__', 'f')
52 self.df = df
52 self.df = df
53 self.dargs = dargs
53 self.dargs = dargs
54 self.dkwargs = dkwargs
54 self.dkwargs = dkwargs
55
55
56 def __call__(self, *args, **kwargs):
56 def __call__(self, *args, **kwargs):
57 # if hasattr(self.f, 'func_globals') and hasattr(self.df, 'func_globals'):
57 # if hasattr(self.f, 'func_globals') and hasattr(self.df, 'func_globals'):
58 # self.df.func_globals = self.f.func_globals
58 # self.df.func_globals = self.f.func_globals
59 if self.df(*self.dargs, **self.dkwargs) is False:
59 if self.df(*self.dargs, **self.dkwargs) is False:
60 raise UnmetDependency()
60 raise UnmetDependency()
61 return self.f(*args, **kwargs)
61 return self.f(*args, **kwargs)
62
62
63 @property
63 @property
64 def __name__(self):
64 def __name__(self):
65 return self.func_name
65 return self.func_name
66
66
67 @interactive
67 @interactive
68 def _require(*names):
68 def _require(*names):
69 """Helper for @require decorator."""
69 """Helper for @require decorator."""
70 from IPython.zmq.parallel.error import UnmetDependency
70 from IPython.parallel.error import UnmetDependency
71 user_ns = globals()
71 user_ns = globals()
72 for name in names:
72 for name in names:
73 if name in user_ns:
73 if name in user_ns:
74 continue
74 continue
75 try:
75 try:
76 exec 'import %s'%name in user_ns
76 exec 'import %s'%name in user_ns
77 except ImportError:
77 except ImportError:
78 raise UnmetDependency(name)
78 raise UnmetDependency(name)
79 return True
79 return True
80
80
81 def require(*mods):
81 def require(*mods):
82 """Simple decorator for requiring names to be importable.
82 """Simple decorator for requiring names to be importable.
83
83
84 Examples
84 Examples
85 --------
85 --------
86
86
87 In [1]: @require('numpy')
87 In [1]: @require('numpy')
88 ...: def norm(a):
88 ...: def norm(a):
89 ...: import numpy
89 ...: import numpy
90 ...: return numpy.linalg.norm(a,2)
90 ...: return numpy.linalg.norm(a,2)
91 """
91 """
92 names = []
92 names = []
93 for mod in mods:
93 for mod in mods:
94 if isinstance(mod, ModuleType):
94 if isinstance(mod, ModuleType):
95 mod = mod.__name__
95 mod = mod.__name__
96
96
97 if isinstance(mod, basestring):
97 if isinstance(mod, basestring):
98 names.append(mod)
98 names.append(mod)
99 else:
99 else:
100 raise TypeError("names must be modules or module names, not %s"%type(mod))
100 raise TypeError("names must be modules or module names, not %s"%type(mod))
101
101
102 return depend(_require, *names)
102 return depend(_require, *names)
103
103
104 class Dependency(set):
104 class Dependency(set):
105 """An object for representing a set of msg_id dependencies.
105 """An object for representing a set of msg_id dependencies.
106
106
107 Subclassed from set().
107 Subclassed from set().
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 dependencies: list/set of msg_ids or AsyncResult objects or output of Dependency.as_dict()
111 dependencies: list/set of msg_ids or AsyncResult objects or output of Dependency.as_dict()
112 The msg_ids to depend on
112 The msg_ids to depend on
113 all : bool [default True]
113 all : bool [default True]
114 Whether the dependency should be considered met when *all* depending tasks have completed
114 Whether the dependency should be considered met when *all* depending tasks have completed
115 or only when *any* have been completed.
115 or only when *any* have been completed.
116 success : bool [default True]
116 success : bool [default True]
117 Whether to consider successes as fulfilling dependencies.
117 Whether to consider successes as fulfilling dependencies.
118 failure : bool [default False]
118 failure : bool [default False]
119 Whether to consider failures as fulfilling dependencies.
119 Whether to consider failures as fulfilling dependencies.
120
120
121 If `all=success=True` and `failure=False`, then the task will fail with an ImpossibleDependency
121 If `all=success=True` and `failure=False`, then the task will fail with an ImpossibleDependency
122 as soon as the first depended-upon task fails.
122 as soon as the first depended-upon task fails.
123 """
123 """
124
124
125 all=True
125 all=True
126 success=True
126 success=True
127 failure=True
127 failure=True
128
128
129 def __init__(self, dependencies=[], all=True, success=True, failure=False):
129 def __init__(self, dependencies=[], all=True, success=True, failure=False):
130 if isinstance(dependencies, dict):
130 if isinstance(dependencies, dict):
131 # load from dict
131 # load from dict
132 all = dependencies.get('all', True)
132 all = dependencies.get('all', True)
133 success = dependencies.get('success', success)
133 success = dependencies.get('success', success)
134 failure = dependencies.get('failure', failure)
134 failure = dependencies.get('failure', failure)
135 dependencies = dependencies.get('dependencies', [])
135 dependencies = dependencies.get('dependencies', [])
136 ids = []
136 ids = []
137
137
138 # extract ids from various sources:
138 # extract ids from various sources:
139 if isinstance(dependencies, (basestring, AsyncResult)):
139 if isinstance(dependencies, (basestring, AsyncResult)):
140 dependencies = [dependencies]
140 dependencies = [dependencies]
141 for d in dependencies:
141 for d in dependencies:
142 if isinstance(d, basestring):
142 if isinstance(d, basestring):
143 ids.append(d)
143 ids.append(d)
144 elif isinstance(d, AsyncResult):
144 elif isinstance(d, AsyncResult):
145 ids.extend(d.msg_ids)
145 ids.extend(d.msg_ids)
146 else:
146 else:
147 raise TypeError("invalid dependency type: %r"%type(d))
147 raise TypeError("invalid dependency type: %r"%type(d))
148
148
149 set.__init__(self, ids)
149 set.__init__(self, ids)
150 self.all = all
150 self.all = all
151 if not (success or failure):
151 if not (success or failure):
152 raise ValueError("Must depend on at least one of successes or failures!")
152 raise ValueError("Must depend on at least one of successes or failures!")
153 self.success=success
153 self.success=success
154 self.failure = failure
154 self.failure = failure
155
155
156 def check(self, completed, failed=None):
156 def check(self, completed, failed=None):
157 """check whether our dependencies have been met."""
157 """check whether our dependencies have been met."""
158 if len(self) == 0:
158 if len(self) == 0:
159 return True
159 return True
160 against = set()
160 against = set()
161 if self.success:
161 if self.success:
162 against = completed
162 against = completed
163 if failed is not None and self.failure:
163 if failed is not None and self.failure:
164 against = against.union(failed)
164 against = against.union(failed)
165 if self.all:
165 if self.all:
166 return self.issubset(against)
166 return self.issubset(against)
167 else:
167 else:
168 return not self.isdisjoint(against)
168 return not self.isdisjoint(against)
169
169
170 def unreachable(self, completed, failed=None):
170 def unreachable(self, completed, failed=None):
171 """return whether this dependency has become impossible."""
171 """return whether this dependency has become impossible."""
172 if len(self) == 0:
172 if len(self) == 0:
173 return False
173 return False
174 against = set()
174 against = set()
175 if not self.success:
175 if not self.success:
176 against = completed
176 against = completed
177 if failed is not None and not self.failure:
177 if failed is not None and not self.failure:
178 against = against.union(failed)
178 against = against.union(failed)
179 if self.all:
179 if self.all:
180 return not self.isdisjoint(against)
180 return not self.isdisjoint(against)
181 else:
181 else:
182 return self.issubset(against)
182 return self.issubset(against)
183
183
184
184
185 def as_dict(self):
185 def as_dict(self):
186 """Represent this dependency as a dict. For json compatibility."""
186 """Represent this dependency as a dict. For json compatibility."""
187 return dict(
187 return dict(
188 dependencies=list(self),
188 dependencies=list(self),
189 all=self.all,
189 all=self.all,
190 success=self.success,
190 success=self.success,
191 failure=self.failure
191 failure=self.failure
192 )
192 )
193
193
194
194
195 __all__ = ['depend', 'require', 'dependent', 'Dependency']
195 __all__ = ['depend', 'require', 'dependent', 'Dependency']
196
196
1 NO CONTENT: file renamed from IPython/zmq/parallel/dictdb.py to IPython/parallel/dictdb.py
NO CONTENT: file renamed from IPython/zmq/parallel/dictdb.py to IPython/parallel/dictdb.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/engine.py to IPython/parallel/engine.py
NO CONTENT: file renamed from IPython/zmq/parallel/engine.py to IPython/parallel/engine.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/entry_point.py to IPython/parallel/entry_point.py
NO CONTENT: file renamed from IPython/zmq/parallel/entry_point.py to IPython/parallel/entry_point.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/error.py to IPython/parallel/error.py
NO CONTENT: file renamed from IPython/zmq/parallel/error.py to IPython/parallel/error.py
@@ -1,152 +1,152 b''
1 """Base config factories."""
1 """Base config factories."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2008-2009 The IPython Development Team
4 # Copyright (C) 2008-2009 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14
14
15 import logging
15 import logging
16 import os
16 import os
17 import uuid
17 import uuid
18
18
19 from zmq.eventloop.ioloop import IOLoop
19 from zmq.eventloop.ioloop import IOLoop
20
20
21 from IPython.config.configurable import Configurable
21 from IPython.config.configurable import Configurable
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23 from IPython.utils.traitlets import Str,Int,Instance, CUnicode, CStr
23 from IPython.utils.traitlets import Str,Int,Instance, CUnicode, CStr
24
24
25 import IPython.zmq.parallel.streamsession as ss
25 import IPython.parallel.streamsession as ss
26 from IPython.zmq.parallel.entry_point import select_random_ports
26 from IPython.parallel.entry_point import select_random_ports
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 class LoggingFactory(Configurable):
31 class LoggingFactory(Configurable):
32 """A most basic class, that has a `log` (type:`Logger`) attribute, set via a `logname` Trait."""
32 """A most basic class, that has a `log` (type:`Logger`) attribute, set via a `logname` Trait."""
33 log = Instance('logging.Logger', ('ZMQ', logging.WARN))
33 log = Instance('logging.Logger', ('ZMQ', logging.WARN))
34 logname = CUnicode('ZMQ')
34 logname = CUnicode('ZMQ')
35 def _logname_changed(self, name, old, new):
35 def _logname_changed(self, name, old, new):
36 self.log = logging.getLogger(new)
36 self.log = logging.getLogger(new)
37
37
38
38
39 class SessionFactory(LoggingFactory):
39 class SessionFactory(LoggingFactory):
40 """The Base factory from which every factory in IPython.zmq.parallel inherits"""
40 """The Base factory from which every factory in IPython.parallel inherits"""
41
41
42 packer = Str('',config=True)
42 packer = Str('',config=True)
43 unpacker = Str('',config=True)
43 unpacker = Str('',config=True)
44 ident = CStr('',config=True)
44 ident = CStr('',config=True)
45 def _ident_default(self):
45 def _ident_default(self):
46 return str(uuid.uuid4())
46 return str(uuid.uuid4())
47 username = CUnicode(os.environ.get('USER','username'),config=True)
47 username = CUnicode(os.environ.get('USER','username'),config=True)
48 exec_key = CUnicode('',config=True)
48 exec_key = CUnicode('',config=True)
49 # not configurable:
49 # not configurable:
50 context = Instance('zmq.Context', (), {})
50 context = Instance('zmq.Context', (), {})
51 session = Instance('IPython.zmq.parallel.streamsession.StreamSession')
51 session = Instance('IPython.parallel.streamsession.StreamSession')
52 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
52 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
53 def _loop_default(self):
53 def _loop_default(self):
54 return IOLoop.instance()
54 return IOLoop.instance()
55
55
56
56
57 def __init__(self, **kwargs):
57 def __init__(self, **kwargs):
58 super(SessionFactory, self).__init__(**kwargs)
58 super(SessionFactory, self).__init__(**kwargs)
59 exec_key = self.exec_key or None
59 exec_key = self.exec_key or None
60 # set the packers:
60 # set the packers:
61 if not self.packer:
61 if not self.packer:
62 packer_f = unpacker_f = None
62 packer_f = unpacker_f = None
63 elif self.packer.lower() == 'json':
63 elif self.packer.lower() == 'json':
64 packer_f = ss.json_packer
64 packer_f = ss.json_packer
65 unpacker_f = ss.json_unpacker
65 unpacker_f = ss.json_unpacker
66 elif self.packer.lower() == 'pickle':
66 elif self.packer.lower() == 'pickle':
67 packer_f = ss.pickle_packer
67 packer_f = ss.pickle_packer
68 unpacker_f = ss.pickle_unpacker
68 unpacker_f = ss.pickle_unpacker
69 else:
69 else:
70 packer_f = import_item(self.packer)
70 packer_f = import_item(self.packer)
71 unpacker_f = import_item(self.unpacker)
71 unpacker_f = import_item(self.unpacker)
72
72
73 # construct the session
73 # construct the session
74 self.session = ss.StreamSession(self.username, self.ident, packer=packer_f, unpacker=unpacker_f, key=exec_key)
74 self.session = ss.StreamSession(self.username, self.ident, packer=packer_f, unpacker=unpacker_f, key=exec_key)
75
75
76
76
77 class RegistrationFactory(SessionFactory):
77 class RegistrationFactory(SessionFactory):
78 """The Base Configurable for objects that involve registration."""
78 """The Base Configurable for objects that involve registration."""
79
79
80 url = Str('', config=True) # url takes precedence over ip,regport,transport
80 url = Str('', config=True) # url takes precedence over ip,regport,transport
81 transport = Str('tcp', config=True)
81 transport = Str('tcp', config=True)
82 ip = Str('127.0.0.1', config=True)
82 ip = Str('127.0.0.1', config=True)
83 regport = Instance(int, config=True)
83 regport = Instance(int, config=True)
84 def _regport_default(self):
84 def _regport_default(self):
85 # return 10101
85 # return 10101
86 return select_random_ports(1)[0]
86 return select_random_ports(1)[0]
87
87
88 def __init__(self, **kwargs):
88 def __init__(self, **kwargs):
89 super(RegistrationFactory, self).__init__(**kwargs)
89 super(RegistrationFactory, self).__init__(**kwargs)
90 self._propagate_url()
90 self._propagate_url()
91 self._rebuild_url()
91 self._rebuild_url()
92 self.on_trait_change(self._propagate_url, 'url')
92 self.on_trait_change(self._propagate_url, 'url')
93 self.on_trait_change(self._rebuild_url, 'ip')
93 self.on_trait_change(self._rebuild_url, 'ip')
94 self.on_trait_change(self._rebuild_url, 'transport')
94 self.on_trait_change(self._rebuild_url, 'transport')
95 self.on_trait_change(self._rebuild_url, 'regport')
95 self.on_trait_change(self._rebuild_url, 'regport')
96
96
97 def _rebuild_url(self):
97 def _rebuild_url(self):
98 self.url = "%s://%s:%i"%(self.transport, self.ip, self.regport)
98 self.url = "%s://%s:%i"%(self.transport, self.ip, self.regport)
99
99
100 def _propagate_url(self):
100 def _propagate_url(self):
101 """Ensure self.url contains full transport://interface:port"""
101 """Ensure self.url contains full transport://interface:port"""
102 if self.url:
102 if self.url:
103 iface = self.url.split('://',1)
103 iface = self.url.split('://',1)
104 if len(iface) == 2:
104 if len(iface) == 2:
105 self.transport,iface = iface
105 self.transport,iface = iface
106 iface = iface.split(':')
106 iface = iface.split(':')
107 self.ip = iface[0]
107 self.ip = iface[0]
108 if iface[1]:
108 if iface[1]:
109 self.regport = int(iface[1])
109 self.regport = int(iface[1])
110
110
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112 # argparse argument extenders
112 # argparse argument extenders
113 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
114
114
115
115
116 def add_session_arguments(parser):
116 def add_session_arguments(parser):
117 paa = parser.add_argument
117 paa = parser.add_argument
118 paa('--ident',
118 paa('--ident',
119 type=str, dest='SessionFactory.ident',
119 type=str, dest='SessionFactory.ident',
120 help='set the ZMQ and session identity [default: random uuid]',
120 help='set the ZMQ and session identity [default: random uuid]',
121 metavar='identity')
121 metavar='identity')
122 # paa('--execkey',
122 # paa('--execkey',
123 # type=str, dest='SessionFactory.exec_key',
123 # type=str, dest='SessionFactory.exec_key',
124 # help='path to a file containing an execution key.',
124 # help='path to a file containing an execution key.',
125 # metavar='execkey')
125 # metavar='execkey')
126 paa('--packer',
126 paa('--packer',
127 type=str, dest='SessionFactory.packer',
127 type=str, dest='SessionFactory.packer',
128 help='method to serialize messages: {json,pickle} [default: json]',
128 help='method to serialize messages: {json,pickle} [default: json]',
129 metavar='packer')
129 metavar='packer')
130 paa('--unpacker',
130 paa('--unpacker',
131 type=str, dest='SessionFactory.unpacker',
131 type=str, dest='SessionFactory.unpacker',
132 help='inverse function of `packer`. Only necessary when using something other than json|pickle',
132 help='inverse function of `packer`. Only necessary when using something other than json|pickle',
133 metavar='packer')
133 metavar='packer')
134
134
135 def add_registration_arguments(parser):
135 def add_registration_arguments(parser):
136 paa = parser.add_argument
136 paa = parser.add_argument
137 paa('--ip',
137 paa('--ip',
138 type=str, dest='RegistrationFactory.ip',
138 type=str, dest='RegistrationFactory.ip',
139 help="The IP used for registration [default: localhost]",
139 help="The IP used for registration [default: localhost]",
140 metavar='ip')
140 metavar='ip')
141 paa('--transport',
141 paa('--transport',
142 type=str, dest='RegistrationFactory.transport',
142 type=str, dest='RegistrationFactory.transport',
143 help="The ZeroMQ transport used for registration [default: tcp]",
143 help="The ZeroMQ transport used for registration [default: tcp]",
144 metavar='transport')
144 metavar='transport')
145 paa('--url',
145 paa('--url',
146 type=str, dest='RegistrationFactory.url',
146 type=str, dest='RegistrationFactory.url',
147 help='set transport,ip,regport in one go, e.g. tcp://127.0.0.1:10101',
147 help='set transport,ip,regport in one go, e.g. tcp://127.0.0.1:10101',
148 metavar='url')
148 metavar='url')
149 paa('--regport',
149 paa('--regport',
150 type=int, dest='RegistrationFactory.regport',
150 type=int, dest='RegistrationFactory.regport',
151 help="The port used for registration [default: 10101]",
151 help="The port used for registration [default: 10101]",
152 metavar='ip')
152 metavar='ip')
1 NO CONTENT: file renamed from IPython/zmq/parallel/heartmonitor.py to IPython/parallel/heartmonitor.py
NO CONTENT: file renamed from IPython/zmq/parallel/heartmonitor.py to IPython/parallel/heartmonitor.py
@@ -1,1035 +1,1035 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """The IPython Controller Hub with 0MQ
2 """The IPython Controller Hub with 0MQ
3 This is the master object that handles connections from engines and clients,
3 This is the master object that handles connections from engines and clients,
4 and monitors traffic through the various queues.
4 and monitors traffic through the various queues.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010 The IPython Development Team
7 # Copyright (C) 2010 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 import sys
18 import sys
19 import time
19 import time
20 from datetime import datetime
20 from datetime import datetime
21
21
22 import zmq
22 import zmq
23 from zmq.eventloop import ioloop
23 from zmq.eventloop import ioloop
24 from zmq.eventloop.zmqstream import ZMQStream
24 from zmq.eventloop.zmqstream import ZMQStream
25
25
26 # internal:
26 # internal:
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils.traitlets import HasTraits, Instance, Int, CStr, Str, Dict, Set, List, Bool
28 from IPython.utils.traitlets import HasTraits, Instance, Int, CStr, Str, Dict, Set, List, Bool
29
29
30 from .entry_point import select_random_ports
30 from .entry_point import select_random_ports
31 from .factory import RegistrationFactory, LoggingFactory
31 from .factory import RegistrationFactory, LoggingFactory
32
32
33 from . import error
33 from . import error
34 from .heartmonitor import HeartMonitor
34 from .heartmonitor import HeartMonitor
35 from .util import validate_url_container, ISO8601
35 from .util import validate_url_container, ISO8601
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Code
38 # Code
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 def _passer(*args, **kwargs):
41 def _passer(*args, **kwargs):
42 return
42 return
43
43
44 def _printer(*args, **kwargs):
44 def _printer(*args, **kwargs):
45 print (args)
45 print (args)
46 print (kwargs)
46 print (kwargs)
47
47
48 def init_record(msg):
48 def init_record(msg):
49 """Initialize a TaskRecord based on a request."""
49 """Initialize a TaskRecord based on a request."""
50 header = msg['header']
50 header = msg['header']
51 return {
51 return {
52 'msg_id' : header['msg_id'],
52 'msg_id' : header['msg_id'],
53 'header' : header,
53 'header' : header,
54 'content': msg['content'],
54 'content': msg['content'],
55 'buffers': msg['buffers'],
55 'buffers': msg['buffers'],
56 'submitted': datetime.strptime(header['date'], ISO8601),
56 'submitted': datetime.strptime(header['date'], ISO8601),
57 'client_uuid' : None,
57 'client_uuid' : None,
58 'engine_uuid' : None,
58 'engine_uuid' : None,
59 'started': None,
59 'started': None,
60 'completed': None,
60 'completed': None,
61 'resubmitted': None,
61 'resubmitted': None,
62 'result_header' : None,
62 'result_header' : None,
63 'result_content' : None,
63 'result_content' : None,
64 'result_buffers' : None,
64 'result_buffers' : None,
65 'queue' : None,
65 'queue' : None,
66 'pyin' : None,
66 'pyin' : None,
67 'pyout': None,
67 'pyout': None,
68 'pyerr': None,
68 'pyerr': None,
69 'stdout': '',
69 'stdout': '',
70 'stderr': '',
70 'stderr': '',
71 }
71 }
72
72
73
73
74 class EngineConnector(HasTraits):
74 class EngineConnector(HasTraits):
75 """A simple object for accessing the various zmq connections of an object.
75 """A simple object for accessing the various zmq connections of an object.
76 Attributes are:
76 Attributes are:
77 id (int): engine ID
77 id (int): engine ID
78 uuid (str): uuid (unused?)
78 uuid (str): uuid (unused?)
79 queue (str): identity of queue's XREQ socket
79 queue (str): identity of queue's XREQ socket
80 registration (str): identity of registration XREQ socket
80 registration (str): identity of registration XREQ socket
81 heartbeat (str): identity of heartbeat XREQ socket
81 heartbeat (str): identity of heartbeat XREQ socket
82 """
82 """
83 id=Int(0)
83 id=Int(0)
84 queue=Str()
84 queue=Str()
85 control=Str()
85 control=Str()
86 registration=Str()
86 registration=Str()
87 heartbeat=Str()
87 heartbeat=Str()
88 pending=Set()
88 pending=Set()
89
89
90 class HubFactory(RegistrationFactory):
90 class HubFactory(RegistrationFactory):
91 """The Configurable for setting up a Hub."""
91 """The Configurable for setting up a Hub."""
92
92
93 # name of a scheduler scheme
93 # name of a scheduler scheme
94 scheme = Str('leastload', config=True)
94 scheme = Str('leastload', config=True)
95
95
96 # port-pairs for monitoredqueues:
96 # port-pairs for monitoredqueues:
97 hb = Instance(list, config=True)
97 hb = Instance(list, config=True)
98 def _hb_default(self):
98 def _hb_default(self):
99 return select_random_ports(2)
99 return select_random_ports(2)
100
100
101 mux = Instance(list, config=True)
101 mux = Instance(list, config=True)
102 def _mux_default(self):
102 def _mux_default(self):
103 return select_random_ports(2)
103 return select_random_ports(2)
104
104
105 task = Instance(list, config=True)
105 task = Instance(list, config=True)
106 def _task_default(self):
106 def _task_default(self):
107 return select_random_ports(2)
107 return select_random_ports(2)
108
108
109 control = Instance(list, config=True)
109 control = Instance(list, config=True)
110 def _control_default(self):
110 def _control_default(self):
111 return select_random_ports(2)
111 return select_random_ports(2)
112
112
113 iopub = Instance(list, config=True)
113 iopub = Instance(list, config=True)
114 def _iopub_default(self):
114 def _iopub_default(self):
115 return select_random_ports(2)
115 return select_random_ports(2)
116
116
117 # single ports:
117 # single ports:
118 mon_port = Instance(int, config=True)
118 mon_port = Instance(int, config=True)
119 def _mon_port_default(self):
119 def _mon_port_default(self):
120 return select_random_ports(1)[0]
120 return select_random_ports(1)[0]
121
121
122 notifier_port = Instance(int, config=True)
122 notifier_port = Instance(int, config=True)
123 def _notifier_port_default(self):
123 def _notifier_port_default(self):
124 return select_random_ports(1)[0]
124 return select_random_ports(1)[0]
125
125
126 ping = Int(1000, config=True) # ping frequency
126 ping = Int(1000, config=True) # ping frequency
127
127
128 engine_ip = CStr('127.0.0.1', config=True)
128 engine_ip = CStr('127.0.0.1', config=True)
129 engine_transport = CStr('tcp', config=True)
129 engine_transport = CStr('tcp', config=True)
130
130
131 client_ip = CStr('127.0.0.1', config=True)
131 client_ip = CStr('127.0.0.1', config=True)
132 client_transport = CStr('tcp', config=True)
132 client_transport = CStr('tcp', config=True)
133
133
134 monitor_ip = CStr('127.0.0.1', config=True)
134 monitor_ip = CStr('127.0.0.1', config=True)
135 monitor_transport = CStr('tcp', config=True)
135 monitor_transport = CStr('tcp', config=True)
136
136
137 monitor_url = CStr('')
137 monitor_url = CStr('')
138
138
139 db_class = CStr('IPython.zmq.parallel.dictdb.DictDB', config=True)
139 db_class = CStr('IPython.parallel.dictdb.DictDB', config=True)
140
140
141 # not configurable
141 # not configurable
142 db = Instance('IPython.zmq.parallel.dictdb.BaseDB')
142 db = Instance('IPython.parallel.dictdb.BaseDB')
143 heartmonitor = Instance('IPython.zmq.parallel.heartmonitor.HeartMonitor')
143 heartmonitor = Instance('IPython.parallel.heartmonitor.HeartMonitor')
144 subconstructors = List()
144 subconstructors = List()
145 _constructed = Bool(False)
145 _constructed = Bool(False)
146
146
147 def _ip_changed(self, name, old, new):
147 def _ip_changed(self, name, old, new):
148 self.engine_ip = new
148 self.engine_ip = new
149 self.client_ip = new
149 self.client_ip = new
150 self.monitor_ip = new
150 self.monitor_ip = new
151 self._update_monitor_url()
151 self._update_monitor_url()
152
152
153 def _update_monitor_url(self):
153 def _update_monitor_url(self):
154 self.monitor_url = "%s://%s:%i"%(self.monitor_transport, self.monitor_ip, self.mon_port)
154 self.monitor_url = "%s://%s:%i"%(self.monitor_transport, self.monitor_ip, self.mon_port)
155
155
156 def _transport_changed(self, name, old, new):
156 def _transport_changed(self, name, old, new):
157 self.engine_transport = new
157 self.engine_transport = new
158 self.client_transport = new
158 self.client_transport = new
159 self.monitor_transport = new
159 self.monitor_transport = new
160 self._update_monitor_url()
160 self._update_monitor_url()
161
161
162 def __init__(self, **kwargs):
162 def __init__(self, **kwargs):
163 super(HubFactory, self).__init__(**kwargs)
163 super(HubFactory, self).__init__(**kwargs)
164 self._update_monitor_url()
164 self._update_monitor_url()
165 # self.on_trait_change(self._sync_ips, 'ip')
165 # self.on_trait_change(self._sync_ips, 'ip')
166 # self.on_trait_change(self._sync_transports, 'transport')
166 # self.on_trait_change(self._sync_transports, 'transport')
167 self.subconstructors.append(self.construct_hub)
167 self.subconstructors.append(self.construct_hub)
168
168
169
169
170 def construct(self):
170 def construct(self):
171 assert not self._constructed, "already constructed!"
171 assert not self._constructed, "already constructed!"
172
172
173 for subc in self.subconstructors:
173 for subc in self.subconstructors:
174 subc()
174 subc()
175
175
176 self._constructed = True
176 self._constructed = True
177
177
178
178
179 def start(self):
179 def start(self):
180 assert self._constructed, "must be constructed by self.construct() first!"
180 assert self._constructed, "must be constructed by self.construct() first!"
181 self.heartmonitor.start()
181 self.heartmonitor.start()
182 self.log.info("Heartmonitor started")
182 self.log.info("Heartmonitor started")
183
183
184 def construct_hub(self):
184 def construct_hub(self):
185 """construct"""
185 """construct"""
186 client_iface = "%s://%s:"%(self.client_transport, self.client_ip) + "%i"
186 client_iface = "%s://%s:"%(self.client_transport, self.client_ip) + "%i"
187 engine_iface = "%s://%s:"%(self.engine_transport, self.engine_ip) + "%i"
187 engine_iface = "%s://%s:"%(self.engine_transport, self.engine_ip) + "%i"
188
188
189 ctx = self.context
189 ctx = self.context
190 loop = self.loop
190 loop = self.loop
191
191
192 # Registrar socket
192 # Registrar socket
193 q = ZMQStream(ctx.socket(zmq.XREP), loop)
193 q = ZMQStream(ctx.socket(zmq.XREP), loop)
194 q.bind(client_iface % self.regport)
194 q.bind(client_iface % self.regport)
195 self.log.info("Hub listening on %s for registration."%(client_iface%self.regport))
195 self.log.info("Hub listening on %s for registration."%(client_iface%self.regport))
196 if self.client_ip != self.engine_ip:
196 if self.client_ip != self.engine_ip:
197 q.bind(engine_iface % self.regport)
197 q.bind(engine_iface % self.regport)
198 self.log.info("Hub listening on %s for registration."%(engine_iface%self.regport))
198 self.log.info("Hub listening on %s for registration."%(engine_iface%self.regport))
199
199
200 ### Engine connections ###
200 ### Engine connections ###
201
201
202 # heartbeat
202 # heartbeat
203 hpub = ctx.socket(zmq.PUB)
203 hpub = ctx.socket(zmq.PUB)
204 hpub.bind(engine_iface % self.hb[0])
204 hpub.bind(engine_iface % self.hb[0])
205 hrep = ctx.socket(zmq.XREP)
205 hrep = ctx.socket(zmq.XREP)
206 hrep.bind(engine_iface % self.hb[1])
206 hrep.bind(engine_iface % self.hb[1])
207 self.heartmonitor = HeartMonitor(loop=loop, pingstream=ZMQStream(hpub,loop), pongstream=ZMQStream(hrep,loop),
207 self.heartmonitor = HeartMonitor(loop=loop, pingstream=ZMQStream(hpub,loop), pongstream=ZMQStream(hrep,loop),
208 period=self.ping, logname=self.log.name)
208 period=self.ping, logname=self.log.name)
209
209
210 ### Client connections ###
210 ### Client connections ###
211 # Notifier socket
211 # Notifier socket
212 n = ZMQStream(ctx.socket(zmq.PUB), loop)
212 n = ZMQStream(ctx.socket(zmq.PUB), loop)
213 n.bind(client_iface%self.notifier_port)
213 n.bind(client_iface%self.notifier_port)
214
214
215 ### build and launch the queues ###
215 ### build and launch the queues ###
216
216
217 # monitor socket
217 # monitor socket
218 sub = ctx.socket(zmq.SUB)
218 sub = ctx.socket(zmq.SUB)
219 sub.setsockopt(zmq.SUBSCRIBE, "")
219 sub.setsockopt(zmq.SUBSCRIBE, "")
220 sub.bind(self.monitor_url)
220 sub.bind(self.monitor_url)
221 sub.bind('inproc://monitor')
221 sub.bind('inproc://monitor')
222 sub = ZMQStream(sub, loop)
222 sub = ZMQStream(sub, loop)
223
223
224 # connect the db
224 # connect the db
225 self.log.info('Hub using DB backend: %r'%(self.db_class.split()[-1]))
225 self.log.info('Hub using DB backend: %r'%(self.db_class.split()[-1]))
226 # cdir = self.config.Global.cluster_dir
226 # cdir = self.config.Global.cluster_dir
227 self.db = import_item(self.db_class)(session=self.session.session, config=self.config)
227 self.db = import_item(self.db_class)(session=self.session.session, config=self.config)
228 time.sleep(.25)
228 time.sleep(.25)
229
229
230 # build connection dicts
230 # build connection dicts
231 self.engine_info = {
231 self.engine_info = {
232 'control' : engine_iface%self.control[1],
232 'control' : engine_iface%self.control[1],
233 'mux': engine_iface%self.mux[1],
233 'mux': engine_iface%self.mux[1],
234 'heartbeat': (engine_iface%self.hb[0], engine_iface%self.hb[1]),
234 'heartbeat': (engine_iface%self.hb[0], engine_iface%self.hb[1]),
235 'task' : engine_iface%self.task[1],
235 'task' : engine_iface%self.task[1],
236 'iopub' : engine_iface%self.iopub[1],
236 'iopub' : engine_iface%self.iopub[1],
237 # 'monitor' : engine_iface%self.mon_port,
237 # 'monitor' : engine_iface%self.mon_port,
238 }
238 }
239
239
240 self.client_info = {
240 self.client_info = {
241 'control' : client_iface%self.control[0],
241 'control' : client_iface%self.control[0],
242 'mux': client_iface%self.mux[0],
242 'mux': client_iface%self.mux[0],
243 'task' : (self.scheme, client_iface%self.task[0]),
243 'task' : (self.scheme, client_iface%self.task[0]),
244 'iopub' : client_iface%self.iopub[0],
244 'iopub' : client_iface%self.iopub[0],
245 'notification': client_iface%self.notifier_port
245 'notification': client_iface%self.notifier_port
246 }
246 }
247 self.log.debug("Hub engine addrs: %s"%self.engine_info)
247 self.log.debug("Hub engine addrs: %s"%self.engine_info)
248 self.log.debug("Hub client addrs: %s"%self.client_info)
248 self.log.debug("Hub client addrs: %s"%self.client_info)
249 self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor,
249 self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor,
250 query=q, notifier=n, db=self.db,
250 query=q, notifier=n, db=self.db,
251 engine_info=self.engine_info, client_info=self.client_info,
251 engine_info=self.engine_info, client_info=self.client_info,
252 logname=self.log.name)
252 logname=self.log.name)
253
253
254
254
255 class Hub(LoggingFactory):
255 class Hub(LoggingFactory):
256 """The IPython Controller Hub with 0MQ connections
256 """The IPython Controller Hub with 0MQ connections
257
257
258 Parameters
258 Parameters
259 ==========
259 ==========
260 loop: zmq IOLoop instance
260 loop: zmq IOLoop instance
261 session: StreamSession object
261 session: StreamSession object
262 <removed> context: zmq context for creating new connections (?)
262 <removed> context: zmq context for creating new connections (?)
263 queue: ZMQStream for monitoring the command queue (SUB)
263 queue: ZMQStream for monitoring the command queue (SUB)
264 query: ZMQStream for engine registration and client queries requests (XREP)
264 query: ZMQStream for engine registration and client queries requests (XREP)
265 heartbeat: HeartMonitor object checking the pulse of the engines
265 heartbeat: HeartMonitor object checking the pulse of the engines
266 notifier: ZMQStream for broadcasting engine registration changes (PUB)
266 notifier: ZMQStream for broadcasting engine registration changes (PUB)
267 db: connection to db for out of memory logging of commands
267 db: connection to db for out of memory logging of commands
268 NotImplemented
268 NotImplemented
269 engine_info: dict of zmq connection information for engines to connect
269 engine_info: dict of zmq connection information for engines to connect
270 to the queues.
270 to the queues.
271 client_info: dict of zmq connection information for engines to connect
271 client_info: dict of zmq connection information for engines to connect
272 to the queues.
272 to the queues.
273 """
273 """
274 # internal data structures:
274 # internal data structures:
275 ids=Set() # engine IDs
275 ids=Set() # engine IDs
276 keytable=Dict()
276 keytable=Dict()
277 by_ident=Dict()
277 by_ident=Dict()
278 engines=Dict()
278 engines=Dict()
279 clients=Dict()
279 clients=Dict()
280 hearts=Dict()
280 hearts=Dict()
281 pending=Set()
281 pending=Set()
282 queues=Dict() # pending msg_ids keyed by engine_id
282 queues=Dict() # pending msg_ids keyed by engine_id
283 tasks=Dict() # pending msg_ids submitted as tasks, keyed by client_id
283 tasks=Dict() # pending msg_ids submitted as tasks, keyed by client_id
284 completed=Dict() # completed msg_ids keyed by engine_id
284 completed=Dict() # completed msg_ids keyed by engine_id
285 all_completed=Set() # completed msg_ids keyed by engine_id
285 all_completed=Set() # completed msg_ids keyed by engine_id
286 # mia=None
286 # mia=None
287 incoming_registrations=Dict()
287 incoming_registrations=Dict()
288 registration_timeout=Int()
288 registration_timeout=Int()
289 _idcounter=Int(0)
289 _idcounter=Int(0)
290
290
291 # objects from constructor:
291 # objects from constructor:
292 loop=Instance(ioloop.IOLoop)
292 loop=Instance(ioloop.IOLoop)
293 query=Instance(ZMQStream)
293 query=Instance(ZMQStream)
294 monitor=Instance(ZMQStream)
294 monitor=Instance(ZMQStream)
295 heartmonitor=Instance(HeartMonitor)
295 heartmonitor=Instance(HeartMonitor)
296 notifier=Instance(ZMQStream)
296 notifier=Instance(ZMQStream)
297 db=Instance(object)
297 db=Instance(object)
298 client_info=Dict()
298 client_info=Dict()
299 engine_info=Dict()
299 engine_info=Dict()
300
300
301
301
302 def __init__(self, **kwargs):
302 def __init__(self, **kwargs):
303 """
303 """
304 # universal:
304 # universal:
305 loop: IOLoop for creating future connections
305 loop: IOLoop for creating future connections
306 session: streamsession for sending serialized data
306 session: streamsession for sending serialized data
307 # engine:
307 # engine:
308 queue: ZMQStream for monitoring queue messages
308 queue: ZMQStream for monitoring queue messages
309 query: ZMQStream for engine+client registration and client requests
309 query: ZMQStream for engine+client registration and client requests
310 heartbeat: HeartMonitor object for tracking engines
310 heartbeat: HeartMonitor object for tracking engines
311 # extra:
311 # extra:
312 db: ZMQStream for db connection (NotImplemented)
312 db: ZMQStream for db connection (NotImplemented)
313 engine_info: zmq address/protocol dict for engine connections
313 engine_info: zmq address/protocol dict for engine connections
314 client_info: zmq address/protocol dict for client connections
314 client_info: zmq address/protocol dict for client connections
315 """
315 """
316
316
317 super(Hub, self).__init__(**kwargs)
317 super(Hub, self).__init__(**kwargs)
318 self.registration_timeout = max(5000, 2*self.heartmonitor.period)
318 self.registration_timeout = max(5000, 2*self.heartmonitor.period)
319
319
320 # validate connection dicts:
320 # validate connection dicts:
321 for k,v in self.client_info.iteritems():
321 for k,v in self.client_info.iteritems():
322 if k == 'task':
322 if k == 'task':
323 validate_url_container(v[1])
323 validate_url_container(v[1])
324 else:
324 else:
325 validate_url_container(v)
325 validate_url_container(v)
326 # validate_url_container(self.client_info)
326 # validate_url_container(self.client_info)
327 validate_url_container(self.engine_info)
327 validate_url_container(self.engine_info)
328
328
329 # register our callbacks
329 # register our callbacks
330 self.query.on_recv(self.dispatch_query)
330 self.query.on_recv(self.dispatch_query)
331 self.monitor.on_recv(self.dispatch_monitor_traffic)
331 self.monitor.on_recv(self.dispatch_monitor_traffic)
332
332
333 self.heartmonitor.add_heart_failure_handler(self.handle_heart_failure)
333 self.heartmonitor.add_heart_failure_handler(self.handle_heart_failure)
334 self.heartmonitor.add_new_heart_handler(self.handle_new_heart)
334 self.heartmonitor.add_new_heart_handler(self.handle_new_heart)
335
335
336 self.monitor_handlers = { 'in' : self.save_queue_request,
336 self.monitor_handlers = { 'in' : self.save_queue_request,
337 'out': self.save_queue_result,
337 'out': self.save_queue_result,
338 'intask': self.save_task_request,
338 'intask': self.save_task_request,
339 'outtask': self.save_task_result,
339 'outtask': self.save_task_result,
340 'tracktask': self.save_task_destination,
340 'tracktask': self.save_task_destination,
341 'incontrol': _passer,
341 'incontrol': _passer,
342 'outcontrol': _passer,
342 'outcontrol': _passer,
343 'iopub': self.save_iopub_message,
343 'iopub': self.save_iopub_message,
344 }
344 }
345
345
346 self.query_handlers = {'queue_request': self.queue_status,
346 self.query_handlers = {'queue_request': self.queue_status,
347 'result_request': self.get_results,
347 'result_request': self.get_results,
348 'purge_request': self.purge_results,
348 'purge_request': self.purge_results,
349 'load_request': self.check_load,
349 'load_request': self.check_load,
350 'resubmit_request': self.resubmit_task,
350 'resubmit_request': self.resubmit_task,
351 'shutdown_request': self.shutdown_request,
351 'shutdown_request': self.shutdown_request,
352 'registration_request' : self.register_engine,
352 'registration_request' : self.register_engine,
353 'unregistration_request' : self.unregister_engine,
353 'unregistration_request' : self.unregister_engine,
354 'connection_request': self.connection_request,
354 'connection_request': self.connection_request,
355 }
355 }
356
356
357 self.log.info("hub::created hub")
357 self.log.info("hub::created hub")
358
358
359 @property
359 @property
360 def _next_id(self):
360 def _next_id(self):
361 """gemerate a new ID.
361 """gemerate a new ID.
362
362
363 No longer reuse old ids, just count from 0."""
363 No longer reuse old ids, just count from 0."""
364 newid = self._idcounter
364 newid = self._idcounter
365 self._idcounter += 1
365 self._idcounter += 1
366 return newid
366 return newid
367 # newid = 0
367 # newid = 0
368 # incoming = [id[0] for id in self.incoming_registrations.itervalues()]
368 # incoming = [id[0] for id in self.incoming_registrations.itervalues()]
369 # # print newid, self.ids, self.incoming_registrations
369 # # print newid, self.ids, self.incoming_registrations
370 # while newid in self.ids or newid in incoming:
370 # while newid in self.ids or newid in incoming:
371 # newid += 1
371 # newid += 1
372 # return newid
372 # return newid
373
373
374 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
375 # message validation
375 # message validation
376 #-----------------------------------------------------------------------------
376 #-----------------------------------------------------------------------------
377
377
378 def _validate_targets(self, targets):
378 def _validate_targets(self, targets):
379 """turn any valid targets argument into a list of integer ids"""
379 """turn any valid targets argument into a list of integer ids"""
380 if targets is None:
380 if targets is None:
381 # default to all
381 # default to all
382 targets = self.ids
382 targets = self.ids
383
383
384 if isinstance(targets, (int,str,unicode)):
384 if isinstance(targets, (int,str,unicode)):
385 # only one target specified
385 # only one target specified
386 targets = [targets]
386 targets = [targets]
387 _targets = []
387 _targets = []
388 for t in targets:
388 for t in targets:
389 # map raw identities to ids
389 # map raw identities to ids
390 if isinstance(t, (str,unicode)):
390 if isinstance(t, (str,unicode)):
391 t = self.by_ident.get(t, t)
391 t = self.by_ident.get(t, t)
392 _targets.append(t)
392 _targets.append(t)
393 targets = _targets
393 targets = _targets
394 bad_targets = [ t for t in targets if t not in self.ids ]
394 bad_targets = [ t for t in targets if t not in self.ids ]
395 if bad_targets:
395 if bad_targets:
396 raise IndexError("No Such Engine: %r"%bad_targets)
396 raise IndexError("No Such Engine: %r"%bad_targets)
397 if not targets:
397 if not targets:
398 raise IndexError("No Engines Registered")
398 raise IndexError("No Engines Registered")
399 return targets
399 return targets
400
400
401 #-----------------------------------------------------------------------------
401 #-----------------------------------------------------------------------------
402 # dispatch methods (1 per stream)
402 # dispatch methods (1 per stream)
403 #-----------------------------------------------------------------------------
403 #-----------------------------------------------------------------------------
404
404
405 # def dispatch_registration_request(self, msg):
405 # def dispatch_registration_request(self, msg):
406 # """"""
406 # """"""
407 # self.log.debug("registration::dispatch_register_request(%s)"%msg)
407 # self.log.debug("registration::dispatch_register_request(%s)"%msg)
408 # idents,msg = self.session.feed_identities(msg)
408 # idents,msg = self.session.feed_identities(msg)
409 # if not idents:
409 # if not idents:
410 # self.log.error("Bad Query Message: %s"%msg, exc_info=True)
410 # self.log.error("Bad Query Message: %s"%msg, exc_info=True)
411 # return
411 # return
412 # try:
412 # try:
413 # msg = self.session.unpack_message(msg,content=True)
413 # msg = self.session.unpack_message(msg,content=True)
414 # except:
414 # except:
415 # self.log.error("registration::got bad registration message: %s"%msg, exc_info=True)
415 # self.log.error("registration::got bad registration message: %s"%msg, exc_info=True)
416 # return
416 # return
417 #
417 #
418 # msg_type = msg['msg_type']
418 # msg_type = msg['msg_type']
419 # content = msg['content']
419 # content = msg['content']
420 #
420 #
421 # handler = self.query_handlers.get(msg_type, None)
421 # handler = self.query_handlers.get(msg_type, None)
422 # if handler is None:
422 # if handler is None:
423 # self.log.error("registration::got bad registration message: %s"%msg)
423 # self.log.error("registration::got bad registration message: %s"%msg)
424 # else:
424 # else:
425 # handler(idents, msg)
425 # handler(idents, msg)
426
426
427 def dispatch_monitor_traffic(self, msg):
427 def dispatch_monitor_traffic(self, msg):
428 """all ME and Task queue messages come through here, as well as
428 """all ME and Task queue messages come through here, as well as
429 IOPub traffic."""
429 IOPub traffic."""
430 self.log.debug("monitor traffic: %s"%msg[:2])
430 self.log.debug("monitor traffic: %s"%msg[:2])
431 switch = msg[0]
431 switch = msg[0]
432 idents, msg = self.session.feed_identities(msg[1:])
432 idents, msg = self.session.feed_identities(msg[1:])
433 if not idents:
433 if not idents:
434 self.log.error("Bad Monitor Message: %s"%msg)
434 self.log.error("Bad Monitor Message: %s"%msg)
435 return
435 return
436 handler = self.monitor_handlers.get(switch, None)
436 handler = self.monitor_handlers.get(switch, None)
437 if handler is not None:
437 if handler is not None:
438 handler(idents, msg)
438 handler(idents, msg)
439 else:
439 else:
440 self.log.error("Invalid monitor topic: %s"%switch)
440 self.log.error("Invalid monitor topic: %s"%switch)
441
441
442
442
443 def dispatch_query(self, msg):
443 def dispatch_query(self, msg):
444 """Route registration requests and queries from clients."""
444 """Route registration requests and queries from clients."""
445 idents, msg = self.session.feed_identities(msg)
445 idents, msg = self.session.feed_identities(msg)
446 if not idents:
446 if not idents:
447 self.log.error("Bad Query Message: %s"%msg)
447 self.log.error("Bad Query Message: %s"%msg)
448 return
448 return
449 client_id = idents[0]
449 client_id = idents[0]
450 try:
450 try:
451 msg = self.session.unpack_message(msg, content=True)
451 msg = self.session.unpack_message(msg, content=True)
452 except:
452 except:
453 content = error.wrap_exception()
453 content = error.wrap_exception()
454 self.log.error("Bad Query Message: %s"%msg, exc_info=True)
454 self.log.error("Bad Query Message: %s"%msg, exc_info=True)
455 self.session.send(self.query, "hub_error", ident=client_id,
455 self.session.send(self.query, "hub_error", ident=client_id,
456 content=content)
456 content=content)
457 return
457 return
458
458
459 # print client_id, header, parent, content
459 # print client_id, header, parent, content
460 #switch on message type:
460 #switch on message type:
461 msg_type = msg['msg_type']
461 msg_type = msg['msg_type']
462 self.log.info("client::client %s requested %s"%(client_id, msg_type))
462 self.log.info("client::client %s requested %s"%(client_id, msg_type))
463 handler = self.query_handlers.get(msg_type, None)
463 handler = self.query_handlers.get(msg_type, None)
464 try:
464 try:
465 assert handler is not None, "Bad Message Type: %s"%msg_type
465 assert handler is not None, "Bad Message Type: %s"%msg_type
466 except:
466 except:
467 content = error.wrap_exception()
467 content = error.wrap_exception()
468 self.log.error("Bad Message Type: %s"%msg_type, exc_info=True)
468 self.log.error("Bad Message Type: %s"%msg_type, exc_info=True)
469 self.session.send(self.query, "hub_error", ident=client_id,
469 self.session.send(self.query, "hub_error", ident=client_id,
470 content=content)
470 content=content)
471 return
471 return
472 else:
472 else:
473 handler(idents, msg)
473 handler(idents, msg)
474
474
475 def dispatch_db(self, msg):
475 def dispatch_db(self, msg):
476 """"""
476 """"""
477 raise NotImplementedError
477 raise NotImplementedError
478
478
479 #---------------------------------------------------------------------------
479 #---------------------------------------------------------------------------
480 # handler methods (1 per event)
480 # handler methods (1 per event)
481 #---------------------------------------------------------------------------
481 #---------------------------------------------------------------------------
482
482
483 #----------------------- Heartbeat --------------------------------------
483 #----------------------- Heartbeat --------------------------------------
484
484
485 def handle_new_heart(self, heart):
485 def handle_new_heart(self, heart):
486 """handler to attach to heartbeater.
486 """handler to attach to heartbeater.
487 Called when a new heart starts to beat.
487 Called when a new heart starts to beat.
488 Triggers completion of registration."""
488 Triggers completion of registration."""
489 self.log.debug("heartbeat::handle_new_heart(%r)"%heart)
489 self.log.debug("heartbeat::handle_new_heart(%r)"%heart)
490 if heart not in self.incoming_registrations:
490 if heart not in self.incoming_registrations:
491 self.log.info("heartbeat::ignoring new heart: %r"%heart)
491 self.log.info("heartbeat::ignoring new heart: %r"%heart)
492 else:
492 else:
493 self.finish_registration(heart)
493 self.finish_registration(heart)
494
494
495
495
496 def handle_heart_failure(self, heart):
496 def handle_heart_failure(self, heart):
497 """handler to attach to heartbeater.
497 """handler to attach to heartbeater.
498 called when a previously registered heart fails to respond to beat request.
498 called when a previously registered heart fails to respond to beat request.
499 triggers unregistration"""
499 triggers unregistration"""
500 self.log.debug("heartbeat::handle_heart_failure(%r)"%heart)
500 self.log.debug("heartbeat::handle_heart_failure(%r)"%heart)
501 eid = self.hearts.get(heart, None)
501 eid = self.hearts.get(heart, None)
502 queue = self.engines[eid].queue
502 queue = self.engines[eid].queue
503 if eid is None:
503 if eid is None:
504 self.log.info("heartbeat::ignoring heart failure %r"%heart)
504 self.log.info("heartbeat::ignoring heart failure %r"%heart)
505 else:
505 else:
506 self.unregister_engine(heart, dict(content=dict(id=eid, queue=queue)))
506 self.unregister_engine(heart, dict(content=dict(id=eid, queue=queue)))
507
507
508 #----------------------- MUX Queue Traffic ------------------------------
508 #----------------------- MUX Queue Traffic ------------------------------
509
509
510 def save_queue_request(self, idents, msg):
510 def save_queue_request(self, idents, msg):
511 if len(idents) < 2:
511 if len(idents) < 2:
512 self.log.error("invalid identity prefix: %s"%idents)
512 self.log.error("invalid identity prefix: %s"%idents)
513 return
513 return
514 queue_id, client_id = idents[:2]
514 queue_id, client_id = idents[:2]
515 try:
515 try:
516 msg = self.session.unpack_message(msg, content=False)
516 msg = self.session.unpack_message(msg, content=False)
517 except:
517 except:
518 self.log.error("queue::client %r sent invalid message to %r: %s"%(client_id, queue_id, msg), exc_info=True)
518 self.log.error("queue::client %r sent invalid message to %r: %s"%(client_id, queue_id, msg), exc_info=True)
519 return
519 return
520
520
521 eid = self.by_ident.get(queue_id, None)
521 eid = self.by_ident.get(queue_id, None)
522 if eid is None:
522 if eid is None:
523 self.log.error("queue::target %r not registered"%queue_id)
523 self.log.error("queue::target %r not registered"%queue_id)
524 self.log.debug("queue:: valid are: %s"%(self.by_ident.keys()))
524 self.log.debug("queue:: valid are: %s"%(self.by_ident.keys()))
525 return
525 return
526
526
527 header = msg['header']
527 header = msg['header']
528 msg_id = header['msg_id']
528 msg_id = header['msg_id']
529 record = init_record(msg)
529 record = init_record(msg)
530 record['engine_uuid'] = queue_id
530 record['engine_uuid'] = queue_id
531 record['client_uuid'] = client_id
531 record['client_uuid'] = client_id
532 record['queue'] = 'mux'
532 record['queue'] = 'mux'
533
533
534 self.pending.add(msg_id)
534 self.pending.add(msg_id)
535 self.queues[eid].append(msg_id)
535 self.queues[eid].append(msg_id)
536 self.db.add_record(msg_id, record)
536 self.db.add_record(msg_id, record)
537
537
538 def save_queue_result(self, idents, msg):
538 def save_queue_result(self, idents, msg):
539 if len(idents) < 2:
539 if len(idents) < 2:
540 self.log.error("invalid identity prefix: %s"%idents)
540 self.log.error("invalid identity prefix: %s"%idents)
541 return
541 return
542
542
543 client_id, queue_id = idents[:2]
543 client_id, queue_id = idents[:2]
544 try:
544 try:
545 msg = self.session.unpack_message(msg, content=False)
545 msg = self.session.unpack_message(msg, content=False)
546 except:
546 except:
547 self.log.error("queue::engine %r sent invalid message to %r: %s"%(
547 self.log.error("queue::engine %r sent invalid message to %r: %s"%(
548 queue_id,client_id, msg), exc_info=True)
548 queue_id,client_id, msg), exc_info=True)
549 return
549 return
550
550
551 eid = self.by_ident.get(queue_id, None)
551 eid = self.by_ident.get(queue_id, None)
552 if eid is None:
552 if eid is None:
553 self.log.error("queue::unknown engine %r is sending a reply: "%queue_id)
553 self.log.error("queue::unknown engine %r is sending a reply: "%queue_id)
554 self.log.debug("queue:: %s"%msg[2:])
554 self.log.debug("queue:: %s"%msg[2:])
555 return
555 return
556
556
557 parent = msg['parent_header']
557 parent = msg['parent_header']
558 if not parent:
558 if not parent:
559 return
559 return
560 msg_id = parent['msg_id']
560 msg_id = parent['msg_id']
561 if msg_id in self.pending:
561 if msg_id in self.pending:
562 self.pending.remove(msg_id)
562 self.pending.remove(msg_id)
563 self.all_completed.add(msg_id)
563 self.all_completed.add(msg_id)
564 self.queues[eid].remove(msg_id)
564 self.queues[eid].remove(msg_id)
565 self.completed[eid].append(msg_id)
565 self.completed[eid].append(msg_id)
566 elif msg_id not in self.all_completed:
566 elif msg_id not in self.all_completed:
567 # it could be a result from a dead engine that died before delivering the
567 # it could be a result from a dead engine that died before delivering the
568 # result
568 # result
569 self.log.warn("queue:: unknown msg finished %s"%msg_id)
569 self.log.warn("queue:: unknown msg finished %s"%msg_id)
570 return
570 return
571 # update record anyway, because the unregistration could have been premature
571 # update record anyway, because the unregistration could have been premature
572 rheader = msg['header']
572 rheader = msg['header']
573 completed = datetime.strptime(rheader['date'], ISO8601)
573 completed = datetime.strptime(rheader['date'], ISO8601)
574 started = rheader.get('started', None)
574 started = rheader.get('started', None)
575 if started is not None:
575 if started is not None:
576 started = datetime.strptime(started, ISO8601)
576 started = datetime.strptime(started, ISO8601)
577 result = {
577 result = {
578 'result_header' : rheader,
578 'result_header' : rheader,
579 'result_content': msg['content'],
579 'result_content': msg['content'],
580 'started' : started,
580 'started' : started,
581 'completed' : completed
581 'completed' : completed
582 }
582 }
583
583
584 result['result_buffers'] = msg['buffers']
584 result['result_buffers'] = msg['buffers']
585 self.db.update_record(msg_id, result)
585 self.db.update_record(msg_id, result)
586
586
587
587
588 #--------------------- Task Queue Traffic ------------------------------
588 #--------------------- Task Queue Traffic ------------------------------
589
589
590 def save_task_request(self, idents, msg):
590 def save_task_request(self, idents, msg):
591 """Save the submission of a task."""
591 """Save the submission of a task."""
592 client_id = idents[0]
592 client_id = idents[0]
593
593
594 try:
594 try:
595 msg = self.session.unpack_message(msg, content=False)
595 msg = self.session.unpack_message(msg, content=False)
596 except:
596 except:
597 self.log.error("task::client %r sent invalid task message: %s"%(
597 self.log.error("task::client %r sent invalid task message: %s"%(
598 client_id, msg), exc_info=True)
598 client_id, msg), exc_info=True)
599 return
599 return
600 record = init_record(msg)
600 record = init_record(msg)
601
601
602 record['client_uuid'] = client_id
602 record['client_uuid'] = client_id
603 record['queue'] = 'task'
603 record['queue'] = 'task'
604 header = msg['header']
604 header = msg['header']
605 msg_id = header['msg_id']
605 msg_id = header['msg_id']
606 self.pending.add(msg_id)
606 self.pending.add(msg_id)
607 self.db.add_record(msg_id, record)
607 self.db.add_record(msg_id, record)
608
608
609 def save_task_result(self, idents, msg):
609 def save_task_result(self, idents, msg):
610 """save the result of a completed task."""
610 """save the result of a completed task."""
611 client_id = idents[0]
611 client_id = idents[0]
612 try:
612 try:
613 msg = self.session.unpack_message(msg, content=False)
613 msg = self.session.unpack_message(msg, content=False)
614 except:
614 except:
615 self.log.error("task::invalid task result message send to %r: %s"%(
615 self.log.error("task::invalid task result message send to %r: %s"%(
616 client_id, msg), exc_info=True)
616 client_id, msg), exc_info=True)
617 raise
617 raise
618 return
618 return
619
619
620 parent = msg['parent_header']
620 parent = msg['parent_header']
621 if not parent:
621 if not parent:
622 # print msg
622 # print msg
623 self.log.warn("Task %r had no parent!"%msg)
623 self.log.warn("Task %r had no parent!"%msg)
624 return
624 return
625 msg_id = parent['msg_id']
625 msg_id = parent['msg_id']
626
626
627 header = msg['header']
627 header = msg['header']
628 engine_uuid = header.get('engine', None)
628 engine_uuid = header.get('engine', None)
629 eid = self.by_ident.get(engine_uuid, None)
629 eid = self.by_ident.get(engine_uuid, None)
630
630
631 if msg_id in self.pending:
631 if msg_id in self.pending:
632 self.pending.remove(msg_id)
632 self.pending.remove(msg_id)
633 self.all_completed.add(msg_id)
633 self.all_completed.add(msg_id)
634 if eid is not None:
634 if eid is not None:
635 self.completed[eid].append(msg_id)
635 self.completed[eid].append(msg_id)
636 if msg_id in self.tasks[eid]:
636 if msg_id in self.tasks[eid]:
637 self.tasks[eid].remove(msg_id)
637 self.tasks[eid].remove(msg_id)
638 completed = datetime.strptime(header['date'], ISO8601)
638 completed = datetime.strptime(header['date'], ISO8601)
639 started = header.get('started', None)
639 started = header.get('started', None)
640 if started is not None:
640 if started is not None:
641 started = datetime.strptime(started, ISO8601)
641 started = datetime.strptime(started, ISO8601)
642 result = {
642 result = {
643 'result_header' : header,
643 'result_header' : header,
644 'result_content': msg['content'],
644 'result_content': msg['content'],
645 'started' : started,
645 'started' : started,
646 'completed' : completed,
646 'completed' : completed,
647 'engine_uuid': engine_uuid
647 'engine_uuid': engine_uuid
648 }
648 }
649
649
650 result['result_buffers'] = msg['buffers']
650 result['result_buffers'] = msg['buffers']
651 self.db.update_record(msg_id, result)
651 self.db.update_record(msg_id, result)
652
652
653 else:
653 else:
654 self.log.debug("task::unknown task %s finished"%msg_id)
654 self.log.debug("task::unknown task %s finished"%msg_id)
655
655
656 def save_task_destination(self, idents, msg):
656 def save_task_destination(self, idents, msg):
657 try:
657 try:
658 msg = self.session.unpack_message(msg, content=True)
658 msg = self.session.unpack_message(msg, content=True)
659 except:
659 except:
660 self.log.error("task::invalid task tracking message", exc_info=True)
660 self.log.error("task::invalid task tracking message", exc_info=True)
661 return
661 return
662 content = msg['content']
662 content = msg['content']
663 # print (content)
663 # print (content)
664 msg_id = content['msg_id']
664 msg_id = content['msg_id']
665 engine_uuid = content['engine_id']
665 engine_uuid = content['engine_id']
666 eid = self.by_ident[engine_uuid]
666 eid = self.by_ident[engine_uuid]
667
667
668 self.log.info("task::task %s arrived on %s"%(msg_id, eid))
668 self.log.info("task::task %s arrived on %s"%(msg_id, eid))
669 # if msg_id in self.mia:
669 # if msg_id in self.mia:
670 # self.mia.remove(msg_id)
670 # self.mia.remove(msg_id)
671 # else:
671 # else:
672 # self.log.debug("task::task %s not listed as MIA?!"%(msg_id))
672 # self.log.debug("task::task %s not listed as MIA?!"%(msg_id))
673
673
674 self.tasks[eid].append(msg_id)
674 self.tasks[eid].append(msg_id)
675 # self.pending[msg_id][1].update(received=datetime.now(),engine=(eid,engine_uuid))
675 # self.pending[msg_id][1].update(received=datetime.now(),engine=(eid,engine_uuid))
676 self.db.update_record(msg_id, dict(engine_uuid=engine_uuid))
676 self.db.update_record(msg_id, dict(engine_uuid=engine_uuid))
677
677
678 def mia_task_request(self, idents, msg):
678 def mia_task_request(self, idents, msg):
679 raise NotImplementedError
679 raise NotImplementedError
680 client_id = idents[0]
680 client_id = idents[0]
681 # content = dict(mia=self.mia,status='ok')
681 # content = dict(mia=self.mia,status='ok')
682 # self.session.send('mia_reply', content=content, idents=client_id)
682 # self.session.send('mia_reply', content=content, idents=client_id)
683
683
684
684
685 #--------------------- IOPub Traffic ------------------------------
685 #--------------------- IOPub Traffic ------------------------------
686
686
687 def save_iopub_message(self, topics, msg):
687 def save_iopub_message(self, topics, msg):
688 """save an iopub message into the db"""
688 """save an iopub message into the db"""
689 # print (topics)
689 # print (topics)
690 try:
690 try:
691 msg = self.session.unpack_message(msg, content=True)
691 msg = self.session.unpack_message(msg, content=True)
692 except:
692 except:
693 self.log.error("iopub::invalid IOPub message", exc_info=True)
693 self.log.error("iopub::invalid IOPub message", exc_info=True)
694 return
694 return
695
695
696 parent = msg['parent_header']
696 parent = msg['parent_header']
697 if not parent:
697 if not parent:
698 self.log.error("iopub::invalid IOPub message: %s"%msg)
698 self.log.error("iopub::invalid IOPub message: %s"%msg)
699 return
699 return
700 msg_id = parent['msg_id']
700 msg_id = parent['msg_id']
701 msg_type = msg['msg_type']
701 msg_type = msg['msg_type']
702 content = msg['content']
702 content = msg['content']
703
703
704 # ensure msg_id is in db
704 # ensure msg_id is in db
705 try:
705 try:
706 rec = self.db.get_record(msg_id)
706 rec = self.db.get_record(msg_id)
707 except:
707 except:
708 self.log.error("iopub::IOPub message has invalid parent", exc_info=True)
708 self.log.error("iopub::IOPub message has invalid parent", exc_info=True)
709 return
709 return
710 # stream
710 # stream
711 d = {}
711 d = {}
712 if msg_type == 'stream':
712 if msg_type == 'stream':
713 name = content['name']
713 name = content['name']
714 s = rec[name] or ''
714 s = rec[name] or ''
715 d[name] = s + content['data']
715 d[name] = s + content['data']
716
716
717 elif msg_type == 'pyerr':
717 elif msg_type == 'pyerr':
718 d['pyerr'] = content
718 d['pyerr'] = content
719 else:
719 else:
720 d[msg_type] = content['data']
720 d[msg_type] = content['data']
721
721
722 self.db.update_record(msg_id, d)
722 self.db.update_record(msg_id, d)
723
723
724
724
725
725
726 #-------------------------------------------------------------------------
726 #-------------------------------------------------------------------------
727 # Registration requests
727 # Registration requests
728 #-------------------------------------------------------------------------
728 #-------------------------------------------------------------------------
729
729
730 def connection_request(self, client_id, msg):
730 def connection_request(self, client_id, msg):
731 """Reply with connection addresses for clients."""
731 """Reply with connection addresses for clients."""
732 self.log.info("client::client %s connected"%client_id)
732 self.log.info("client::client %s connected"%client_id)
733 content = dict(status='ok')
733 content = dict(status='ok')
734 content.update(self.client_info)
734 content.update(self.client_info)
735 jsonable = {}
735 jsonable = {}
736 for k,v in self.keytable.iteritems():
736 for k,v in self.keytable.iteritems():
737 jsonable[str(k)] = v
737 jsonable[str(k)] = v
738 content['engines'] = jsonable
738 content['engines'] = jsonable
739 self.session.send(self.query, 'connection_reply', content, parent=msg, ident=client_id)
739 self.session.send(self.query, 'connection_reply', content, parent=msg, ident=client_id)
740
740
741 def register_engine(self, reg, msg):
741 def register_engine(self, reg, msg):
742 """Register a new engine."""
742 """Register a new engine."""
743 content = msg['content']
743 content = msg['content']
744 try:
744 try:
745 queue = content['queue']
745 queue = content['queue']
746 except KeyError:
746 except KeyError:
747 self.log.error("registration::queue not specified", exc_info=True)
747 self.log.error("registration::queue not specified", exc_info=True)
748 return
748 return
749 heart = content.get('heartbeat', None)
749 heart = content.get('heartbeat', None)
750 """register a new engine, and create the socket(s) necessary"""
750 """register a new engine, and create the socket(s) necessary"""
751 eid = self._next_id
751 eid = self._next_id
752 # print (eid, queue, reg, heart)
752 # print (eid, queue, reg, heart)
753
753
754 self.log.debug("registration::register_engine(%i, %r, %r, %r)"%(eid, queue, reg, heart))
754 self.log.debug("registration::register_engine(%i, %r, %r, %r)"%(eid, queue, reg, heart))
755
755
756 content = dict(id=eid,status='ok')
756 content = dict(id=eid,status='ok')
757 content.update(self.engine_info)
757 content.update(self.engine_info)
758 # check if requesting available IDs:
758 # check if requesting available IDs:
759 if queue in self.by_ident:
759 if queue in self.by_ident:
760 try:
760 try:
761 raise KeyError("queue_id %r in use"%queue)
761 raise KeyError("queue_id %r in use"%queue)
762 except:
762 except:
763 content = error.wrap_exception()
763 content = error.wrap_exception()
764 self.log.error("queue_id %r in use"%queue, exc_info=True)
764 self.log.error("queue_id %r in use"%queue, exc_info=True)
765 elif heart in self.hearts: # need to check unique hearts?
765 elif heart in self.hearts: # need to check unique hearts?
766 try:
766 try:
767 raise KeyError("heart_id %r in use"%heart)
767 raise KeyError("heart_id %r in use"%heart)
768 except:
768 except:
769 self.log.error("heart_id %r in use"%heart, exc_info=True)
769 self.log.error("heart_id %r in use"%heart, exc_info=True)
770 content = error.wrap_exception()
770 content = error.wrap_exception()
771 else:
771 else:
772 for h, pack in self.incoming_registrations.iteritems():
772 for h, pack in self.incoming_registrations.iteritems():
773 if heart == h:
773 if heart == h:
774 try:
774 try:
775 raise KeyError("heart_id %r in use"%heart)
775 raise KeyError("heart_id %r in use"%heart)
776 except:
776 except:
777 self.log.error("heart_id %r in use"%heart, exc_info=True)
777 self.log.error("heart_id %r in use"%heart, exc_info=True)
778 content = error.wrap_exception()
778 content = error.wrap_exception()
779 break
779 break
780 elif queue == pack[1]:
780 elif queue == pack[1]:
781 try:
781 try:
782 raise KeyError("queue_id %r in use"%queue)
782 raise KeyError("queue_id %r in use"%queue)
783 except:
783 except:
784 self.log.error("queue_id %r in use"%queue, exc_info=True)
784 self.log.error("queue_id %r in use"%queue, exc_info=True)
785 content = error.wrap_exception()
785 content = error.wrap_exception()
786 break
786 break
787
787
788 msg = self.session.send(self.query, "registration_reply",
788 msg = self.session.send(self.query, "registration_reply",
789 content=content,
789 content=content,
790 ident=reg)
790 ident=reg)
791
791
792 if content['status'] == 'ok':
792 if content['status'] == 'ok':
793 if heart in self.heartmonitor.hearts:
793 if heart in self.heartmonitor.hearts:
794 # already beating
794 # already beating
795 self.incoming_registrations[heart] = (eid,queue,reg[0],None)
795 self.incoming_registrations[heart] = (eid,queue,reg[0],None)
796 self.finish_registration(heart)
796 self.finish_registration(heart)
797 else:
797 else:
798 purge = lambda : self._purge_stalled_registration(heart)
798 purge = lambda : self._purge_stalled_registration(heart)
799 dc = ioloop.DelayedCallback(purge, self.registration_timeout, self.loop)
799 dc = ioloop.DelayedCallback(purge, self.registration_timeout, self.loop)
800 dc.start()
800 dc.start()
801 self.incoming_registrations[heart] = (eid,queue,reg[0],dc)
801 self.incoming_registrations[heart] = (eid,queue,reg[0],dc)
802 else:
802 else:
803 self.log.error("registration::registration %i failed: %s"%(eid, content['evalue']))
803 self.log.error("registration::registration %i failed: %s"%(eid, content['evalue']))
804 return eid
804 return eid
805
805
806 def unregister_engine(self, ident, msg):
806 def unregister_engine(self, ident, msg):
807 """Unregister an engine that explicitly requested to leave."""
807 """Unregister an engine that explicitly requested to leave."""
808 try:
808 try:
809 eid = msg['content']['id']
809 eid = msg['content']['id']
810 except:
810 except:
811 self.log.error("registration::bad engine id for unregistration: %s"%ident, exc_info=True)
811 self.log.error("registration::bad engine id for unregistration: %s"%ident, exc_info=True)
812 return
812 return
813 self.log.info("registration::unregister_engine(%s)"%eid)
813 self.log.info("registration::unregister_engine(%s)"%eid)
814 # print (eid)
814 # print (eid)
815 content=dict(id=eid, queue=self.engines[eid].queue)
815 content=dict(id=eid, queue=self.engines[eid].queue)
816 self.ids.remove(eid)
816 self.ids.remove(eid)
817 uuid = self.keytable.pop(eid)
817 uuid = self.keytable.pop(eid)
818 ec = self.engines.pop(eid)
818 ec = self.engines.pop(eid)
819 self.hearts.pop(ec.heartbeat)
819 self.hearts.pop(ec.heartbeat)
820 self.by_ident.pop(ec.queue)
820 self.by_ident.pop(ec.queue)
821 self.completed.pop(eid)
821 self.completed.pop(eid)
822 self._handle_stranded_msgs(eid, uuid)
822 self._handle_stranded_msgs(eid, uuid)
823 ############## TODO: HANDLE IT ################
823 ############## TODO: HANDLE IT ################
824
824
825 if self.notifier:
825 if self.notifier:
826 self.session.send(self.notifier, "unregistration_notification", content=content)
826 self.session.send(self.notifier, "unregistration_notification", content=content)
827
827
828 def _handle_stranded_msgs(self, eid, uuid):
828 def _handle_stranded_msgs(self, eid, uuid):
829 """Handle messages known to be on an engine when the engine unregisters.
829 """Handle messages known to be on an engine when the engine unregisters.
830
830
831 It is possible that this will fire prematurely - that is, an engine will
831 It is possible that this will fire prematurely - that is, an engine will
832 go down after completing a result, and the client will be notified
832 go down after completing a result, and the client will be notified
833 that the result failed and later receive the actual result.
833 that the result failed and later receive the actual result.
834 """
834 """
835
835
836 outstanding = self.queues.pop(eid)
836 outstanding = self.queues.pop(eid)
837
837
838 for msg_id in outstanding:
838 for msg_id in outstanding:
839 self.pending.remove(msg_id)
839 self.pending.remove(msg_id)
840 self.all_completed.add(msg_id)
840 self.all_completed.add(msg_id)
841 try:
841 try:
842 raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id))
842 raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id))
843 except:
843 except:
844 content = error.wrap_exception()
844 content = error.wrap_exception()
845 # build a fake header:
845 # build a fake header:
846 header = {}
846 header = {}
847 header['engine'] = uuid
847 header['engine'] = uuid
848 header['date'] = datetime.now().strftime(ISO8601)
848 header['date'] = datetime.now().strftime(ISO8601)
849 rec = dict(result_content=content, result_header=header, result_buffers=[])
849 rec = dict(result_content=content, result_header=header, result_buffers=[])
850 rec['completed'] = header['date']
850 rec['completed'] = header['date']
851 rec['engine_uuid'] = uuid
851 rec['engine_uuid'] = uuid
852 self.db.update_record(msg_id, rec)
852 self.db.update_record(msg_id, rec)
853
853
854 def finish_registration(self, heart):
854 def finish_registration(self, heart):
855 """Second half of engine registration, called after our HeartMonitor
855 """Second half of engine registration, called after our HeartMonitor
856 has received a beat from the Engine's Heart."""
856 has received a beat from the Engine's Heart."""
857 try:
857 try:
858 (eid,queue,reg,purge) = self.incoming_registrations.pop(heart)
858 (eid,queue,reg,purge) = self.incoming_registrations.pop(heart)
859 except KeyError:
859 except KeyError:
860 self.log.error("registration::tried to finish nonexistant registration", exc_info=True)
860 self.log.error("registration::tried to finish nonexistant registration", exc_info=True)
861 return
861 return
862 self.log.info("registration::finished registering engine %i:%r"%(eid,queue))
862 self.log.info("registration::finished registering engine %i:%r"%(eid,queue))
863 if purge is not None:
863 if purge is not None:
864 purge.stop()
864 purge.stop()
865 control = queue
865 control = queue
866 self.ids.add(eid)
866 self.ids.add(eid)
867 self.keytable[eid] = queue
867 self.keytable[eid] = queue
868 self.engines[eid] = EngineConnector(id=eid, queue=queue, registration=reg,
868 self.engines[eid] = EngineConnector(id=eid, queue=queue, registration=reg,
869 control=control, heartbeat=heart)
869 control=control, heartbeat=heart)
870 self.by_ident[queue] = eid
870 self.by_ident[queue] = eid
871 self.queues[eid] = list()
871 self.queues[eid] = list()
872 self.tasks[eid] = list()
872 self.tasks[eid] = list()
873 self.completed[eid] = list()
873 self.completed[eid] = list()
874 self.hearts[heart] = eid
874 self.hearts[heart] = eid
875 content = dict(id=eid, queue=self.engines[eid].queue)
875 content = dict(id=eid, queue=self.engines[eid].queue)
876 if self.notifier:
876 if self.notifier:
877 self.session.send(self.notifier, "registration_notification", content=content)
877 self.session.send(self.notifier, "registration_notification", content=content)
878 self.log.info("engine::Engine Connected: %i"%eid)
878 self.log.info("engine::Engine Connected: %i"%eid)
879
879
880 def _purge_stalled_registration(self, heart):
880 def _purge_stalled_registration(self, heart):
881 if heart in self.incoming_registrations:
881 if heart in self.incoming_registrations:
882 eid = self.incoming_registrations.pop(heart)[0]
882 eid = self.incoming_registrations.pop(heart)[0]
883 self.log.info("registration::purging stalled registration: %i"%eid)
883 self.log.info("registration::purging stalled registration: %i"%eid)
884 else:
884 else:
885 pass
885 pass
886
886
887 #-------------------------------------------------------------------------
887 #-------------------------------------------------------------------------
888 # Client Requests
888 # Client Requests
889 #-------------------------------------------------------------------------
889 #-------------------------------------------------------------------------
890
890
891 def shutdown_request(self, client_id, msg):
891 def shutdown_request(self, client_id, msg):
892 """handle shutdown request."""
892 """handle shutdown request."""
893 self.session.send(self.query, 'shutdown_reply', content={'status': 'ok'}, ident=client_id)
893 self.session.send(self.query, 'shutdown_reply', content={'status': 'ok'}, ident=client_id)
894 # also notify other clients of shutdown
894 # also notify other clients of shutdown
895 self.session.send(self.notifier, 'shutdown_notice', content={'status': 'ok'})
895 self.session.send(self.notifier, 'shutdown_notice', content={'status': 'ok'})
896 dc = ioloop.DelayedCallback(lambda : self._shutdown(), 1000, self.loop)
896 dc = ioloop.DelayedCallback(lambda : self._shutdown(), 1000, self.loop)
897 dc.start()
897 dc.start()
898
898
899 def _shutdown(self):
899 def _shutdown(self):
900 self.log.info("hub::hub shutting down.")
900 self.log.info("hub::hub shutting down.")
901 time.sleep(0.1)
901 time.sleep(0.1)
902 sys.exit(0)
902 sys.exit(0)
903
903
904
904
905 def check_load(self, client_id, msg):
905 def check_load(self, client_id, msg):
906 content = msg['content']
906 content = msg['content']
907 try:
907 try:
908 targets = content['targets']
908 targets = content['targets']
909 targets = self._validate_targets(targets)
909 targets = self._validate_targets(targets)
910 except:
910 except:
911 content = error.wrap_exception()
911 content = error.wrap_exception()
912 self.session.send(self.query, "hub_error",
912 self.session.send(self.query, "hub_error",
913 content=content, ident=client_id)
913 content=content, ident=client_id)
914 return
914 return
915
915
916 content = dict(status='ok')
916 content = dict(status='ok')
917 # loads = {}
917 # loads = {}
918 for t in targets:
918 for t in targets:
919 content[bytes(t)] = len(self.queues[t])+len(self.tasks[t])
919 content[bytes(t)] = len(self.queues[t])+len(self.tasks[t])
920 self.session.send(self.query, "load_reply", content=content, ident=client_id)
920 self.session.send(self.query, "load_reply", content=content, ident=client_id)
921
921
922
922
923 def queue_status(self, client_id, msg):
923 def queue_status(self, client_id, msg):
924 """Return the Queue status of one or more targets.
924 """Return the Queue status of one or more targets.
925 if verbose: return the msg_ids
925 if verbose: return the msg_ids
926 else: return len of each type.
926 else: return len of each type.
927 keys: queue (pending MUX jobs)
927 keys: queue (pending MUX jobs)
928 tasks (pending Task jobs)
928 tasks (pending Task jobs)
929 completed (finished jobs from both queues)"""
929 completed (finished jobs from both queues)"""
930 content = msg['content']
930 content = msg['content']
931 targets = content['targets']
931 targets = content['targets']
932 try:
932 try:
933 targets = self._validate_targets(targets)
933 targets = self._validate_targets(targets)
934 except:
934 except:
935 content = error.wrap_exception()
935 content = error.wrap_exception()
936 self.session.send(self.query, "hub_error",
936 self.session.send(self.query, "hub_error",
937 content=content, ident=client_id)
937 content=content, ident=client_id)
938 return
938 return
939 verbose = content.get('verbose', False)
939 verbose = content.get('verbose', False)
940 content = dict(status='ok')
940 content = dict(status='ok')
941 for t in targets:
941 for t in targets:
942 queue = self.queues[t]
942 queue = self.queues[t]
943 completed = self.completed[t]
943 completed = self.completed[t]
944 tasks = self.tasks[t]
944 tasks = self.tasks[t]
945 if not verbose:
945 if not verbose:
946 queue = len(queue)
946 queue = len(queue)
947 completed = len(completed)
947 completed = len(completed)
948 tasks = len(tasks)
948 tasks = len(tasks)
949 content[bytes(t)] = {'queue': queue, 'completed': completed , 'tasks': tasks}
949 content[bytes(t)] = {'queue': queue, 'completed': completed , 'tasks': tasks}
950 # pending
950 # pending
951 self.session.send(self.query, "queue_reply", content=content, ident=client_id)
951 self.session.send(self.query, "queue_reply", content=content, ident=client_id)
952
952
953 def purge_results(self, client_id, msg):
953 def purge_results(self, client_id, msg):
954 """Purge results from memory. This method is more valuable before we move
954 """Purge results from memory. This method is more valuable before we move
955 to a DB based message storage mechanism."""
955 to a DB based message storage mechanism."""
956 content = msg['content']
956 content = msg['content']
957 msg_ids = content.get('msg_ids', [])
957 msg_ids = content.get('msg_ids', [])
958 reply = dict(status='ok')
958 reply = dict(status='ok')
959 if msg_ids == 'all':
959 if msg_ids == 'all':
960 self.db.drop_matching_records(dict(completed={'$ne':None}))
960 self.db.drop_matching_records(dict(completed={'$ne':None}))
961 else:
961 else:
962 for msg_id in msg_ids:
962 for msg_id in msg_ids:
963 if msg_id in self.all_completed:
963 if msg_id in self.all_completed:
964 self.db.drop_record(msg_id)
964 self.db.drop_record(msg_id)
965 else:
965 else:
966 if msg_id in self.pending:
966 if msg_id in self.pending:
967 try:
967 try:
968 raise IndexError("msg pending: %r"%msg_id)
968 raise IndexError("msg pending: %r"%msg_id)
969 except:
969 except:
970 reply = error.wrap_exception()
970 reply = error.wrap_exception()
971 else:
971 else:
972 try:
972 try:
973 raise IndexError("No such msg: %r"%msg_id)
973 raise IndexError("No such msg: %r"%msg_id)
974 except:
974 except:
975 reply = error.wrap_exception()
975 reply = error.wrap_exception()
976 break
976 break
977 eids = content.get('engine_ids', [])
977 eids = content.get('engine_ids', [])
978 for eid in eids:
978 for eid in eids:
979 if eid not in self.engines:
979 if eid not in self.engines:
980 try:
980 try:
981 raise IndexError("No such engine: %i"%eid)
981 raise IndexError("No such engine: %i"%eid)
982 except:
982 except:
983 reply = error.wrap_exception()
983 reply = error.wrap_exception()
984 break
984 break
985 msg_ids = self.completed.pop(eid)
985 msg_ids = self.completed.pop(eid)
986 uid = self.engines[eid].queue
986 uid = self.engines[eid].queue
987 self.db.drop_matching_records(dict(engine_uuid=uid, completed={'$ne':None}))
987 self.db.drop_matching_records(dict(engine_uuid=uid, completed={'$ne':None}))
988
988
989 self.session.send(self.query, 'purge_reply', content=reply, ident=client_id)
989 self.session.send(self.query, 'purge_reply', content=reply, ident=client_id)
990
990
991 def resubmit_task(self, client_id, msg, buffers):
991 def resubmit_task(self, client_id, msg, buffers):
992 """Resubmit a task."""
992 """Resubmit a task."""
993 raise NotImplementedError
993 raise NotImplementedError
994
994
995 def get_results(self, client_id, msg):
995 def get_results(self, client_id, msg):
996 """Get the result of 1 or more messages."""
996 """Get the result of 1 or more messages."""
997 content = msg['content']
997 content = msg['content']
998 msg_ids = sorted(set(content['msg_ids']))
998 msg_ids = sorted(set(content['msg_ids']))
999 statusonly = content.get('status_only', False)
999 statusonly = content.get('status_only', False)
1000 pending = []
1000 pending = []
1001 completed = []
1001 completed = []
1002 content = dict(status='ok')
1002 content = dict(status='ok')
1003 content['pending'] = pending
1003 content['pending'] = pending
1004 content['completed'] = completed
1004 content['completed'] = completed
1005 buffers = []
1005 buffers = []
1006 if not statusonly:
1006 if not statusonly:
1007 content['results'] = {}
1007 content['results'] = {}
1008 records = self.db.find_records(dict(msg_id={'$in':msg_ids}))
1008 records = self.db.find_records(dict(msg_id={'$in':msg_ids}))
1009 for msg_id in msg_ids:
1009 for msg_id in msg_ids:
1010 if msg_id in self.pending:
1010 if msg_id in self.pending:
1011 pending.append(msg_id)
1011 pending.append(msg_id)
1012 elif msg_id in self.all_completed:
1012 elif msg_id in self.all_completed:
1013 completed.append(msg_id)
1013 completed.append(msg_id)
1014 if not statusonly:
1014 if not statusonly:
1015 rec = records[msg_id]
1015 rec = records[msg_id]
1016 io_dict = {}
1016 io_dict = {}
1017 for key in 'pyin pyout pyerr stdout stderr'.split():
1017 for key in 'pyin pyout pyerr stdout stderr'.split():
1018 io_dict[key] = rec[key]
1018 io_dict[key] = rec[key]
1019 content[msg_id] = { 'result_content': rec['result_content'],
1019 content[msg_id] = { 'result_content': rec['result_content'],
1020 'header': rec['header'],
1020 'header': rec['header'],
1021 'result_header' : rec['result_header'],
1021 'result_header' : rec['result_header'],
1022 'io' : io_dict,
1022 'io' : io_dict,
1023 }
1023 }
1024 if rec['result_buffers']:
1024 if rec['result_buffers']:
1025 buffers.extend(map(str, rec['result_buffers']))
1025 buffers.extend(map(str, rec['result_buffers']))
1026 else:
1026 else:
1027 try:
1027 try:
1028 raise KeyError('No such message: '+msg_id)
1028 raise KeyError('No such message: '+msg_id)
1029 except:
1029 except:
1030 content = error.wrap_exception()
1030 content = error.wrap_exception()
1031 break
1031 break
1032 self.session.send(self.query, "result_reply", content=content,
1032 self.session.send(self.query, "result_reply", content=content,
1033 parent=msg, ident=client_id,
1033 parent=msg, ident=client_id,
1034 buffers=buffers)
1034 buffers=buffers)
1035
1035
@@ -1,97 +1,97 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Old ipcluster script. Possibly to be removed."""
2 """Old ipcluster script. Possibly to be removed."""
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010-2011 The IPython Development Team
4 # Copyright (C) 2010-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 from __future__ import print_function
9 from __future__ import print_function
10
10
11 import os
11 import os
12 import sys
12 import sys
13 import time
13 import time
14 from subprocess import Popen, PIPE
14 from subprocess import Popen, PIPE
15
15
16 from IPython.external.argparse import ArgumentParser, SUPPRESS
16 from IPython.external.argparse import ArgumentParser, SUPPRESS
17
17
18 def _filter_arg(flag, args):
18 def _filter_arg(flag, args):
19 filtered = []
19 filtered = []
20 if flag in args:
20 if flag in args:
21 filtered.append(flag)
21 filtered.append(flag)
22 idx = args.index(flag)
22 idx = args.index(flag)
23 if len(args) > idx+1:
23 if len(args) > idx+1:
24 if not args[idx+1].startswith('-'):
24 if not args[idx+1].startswith('-'):
25 filtered.append(args[idx+1])
25 filtered.append(args[idx+1])
26 return filtered
26 return filtered
27
27
28 def filter_args(flags, args=sys.argv[1:]):
28 def filter_args(flags, args=sys.argv[1:]):
29 filtered = []
29 filtered = []
30 for flag in flags:
30 for flag in flags:
31 if isinstance(flag, (list,tuple)):
31 if isinstance(flag, (list,tuple)):
32 for f in flag:
32 for f in flag:
33 filtered.extend(_filter_arg(f, args))
33 filtered.extend(_filter_arg(f, args))
34 else:
34 else:
35 filtered.extend(_filter_arg(flag, args))
35 filtered.extend(_filter_arg(flag, args))
36 return filtered
36 return filtered
37
37
38 def _strip_arg(flag, args):
38 def _strip_arg(flag, args):
39 while flag in args:
39 while flag in args:
40 idx = args.index(flag)
40 idx = args.index(flag)
41 args.pop(idx)
41 args.pop(idx)
42 if len(args) > idx:
42 if len(args) > idx:
43 if not args[idx].startswith('-'):
43 if not args[idx].startswith('-'):
44 args.pop(idx)
44 args.pop(idx)
45
45
46 def strip_args(flags, args=sys.argv[1:]):
46 def strip_args(flags, args=sys.argv[1:]):
47 args = list(args)
47 args = list(args)
48 for flag in flags:
48 for flag in flags:
49 if isinstance(flag, (list,tuple)):
49 if isinstance(flag, (list,tuple)):
50 for f in flag:
50 for f in flag:
51 _strip_arg(f, args)
51 _strip_arg(f, args)
52 else:
52 else:
53 _strip_arg(flag, args)
53 _strip_arg(flag, args)
54 return args
54 return args
55
55
56
56
57 def launch_process(mod, args):
57 def launch_process(mod, args):
58 """Launch a controller or engine in a subprocess."""
58 """Launch a controller or engine in a subprocess."""
59 code = "from IPython.zmq.parallel.%s import launch_new_instance;launch_new_instance()"%mod
59 code = "from IPython.parallel.%s import launch_new_instance;launch_new_instance()"%mod
60 arguments = [ sys.executable, '-c', code ] + args
60 arguments = [ sys.executable, '-c', code ] + args
61 blackholew = file(os.devnull, 'w')
61 blackholew = file(os.devnull, 'w')
62 blackholer = file(os.devnull, 'r')
62 blackholer = file(os.devnull, 'r')
63
63
64 proc = Popen(arguments, stdin=blackholer, stdout=blackholew, stderr=PIPE)
64 proc = Popen(arguments, stdin=blackholer, stdout=blackholew, stderr=PIPE)
65 return proc
65 return proc
66
66
67 def main():
67 def main():
68 parser = ArgumentParser(argument_default=SUPPRESS)
68 parser = ArgumentParser(argument_default=SUPPRESS)
69 parser.add_argument('--n', '-n', type=int, default=1,
69 parser.add_argument('--n', '-n', type=int, default=1,
70 help="The number of engines to start.")
70 help="The number of engines to start.")
71 ns,args = parser.parse_known_args()
71 ns,args = parser.parse_known_args()
72 n = ns.n
72 n = ns.n
73
73
74 controller = launch_process('ipcontrollerapp', args)
74 controller = launch_process('ipcontrollerapp', args)
75 for i in range(10):
75 for i in range(10):
76 time.sleep(.1)
76 time.sleep(.1)
77 if controller.poll() is not None:
77 if controller.poll() is not None:
78 print("Controller failed to launch:")
78 print("Controller failed to launch:")
79 print (controller.stderr.read())
79 print (controller.stderr.read())
80 sys.exit(255)
80 sys.exit(255)
81
81
82 print("Launched Controller")
82 print("Launched Controller")
83 engines = [ launch_process('ipengineapp', args+['--ident', 'engine-%i'%i]) for i in range(n) ]
83 engines = [ launch_process('ipengineapp', args+['--ident', 'engine-%i'%i]) for i in range(n) ]
84 print("%i Engines started"%n)
84 print("%i Engines started"%n)
85
85
86 def wait_quietly(p):
86 def wait_quietly(p):
87 try:
87 try:
88 p.wait()
88 p.wait()
89 except KeyboardInterrupt:
89 except KeyboardInterrupt:
90 pass
90 pass
91
91
92 wait_quietly(controller)
92 wait_quietly(controller)
93 map(wait_quietly, engines)
93 map(wait_quietly, engines)
94 print ("Engines cleaned up.")
94 print ("Engines cleaned up.")
95
95
96 if __name__ == '__main__':
96 if __name__ == '__main__':
97 main() No newline at end of file
97 main()
@@ -1,592 +1,592 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The ipcluster application.
4 The ipcluster application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import errno
18 import errno
19 import logging
19 import logging
20 import os
20 import os
21 import re
21 import re
22 import signal
22 import signal
23
23
24 import zmq
24 import zmq
25 from zmq.eventloop import ioloop
25 from zmq.eventloop import ioloop
26
26
27 from IPython.external.argparse import ArgumentParser, SUPPRESS
27 from IPython.external.argparse import ArgumentParser, SUPPRESS
28 from IPython.utils.importstring import import_item
28 from IPython.utils.importstring import import_item
29 from IPython.zmq.parallel.clusterdir import (
29 from IPython.parallel.clusterdir import (
30 ApplicationWithClusterDir, ClusterDirConfigLoader,
30 ApplicationWithClusterDir, ClusterDirConfigLoader,
31 ClusterDirError, PIDFileError
31 ClusterDirError, PIDFileError
32 )
32 )
33
33
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Module level variables
36 # Module level variables
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39
39
40 default_config_file_name = u'ipclusterz_config.py'
40 default_config_file_name = u'ipclusterz_config.py'
41
41
42
42
43 _description = """\
43 _description = """\
44 Start an IPython cluster for parallel computing.\n\n
44 Start an IPython cluster for parallel computing.\n\n
45
45
46 An IPython cluster consists of 1 controller and 1 or more engines.
46 An IPython cluster consists of 1 controller and 1 or more engines.
47 This command automates the startup of these processes using a wide
47 This command automates the startup of these processes using a wide
48 range of startup methods (SSH, local processes, PBS, mpiexec,
48 range of startup methods (SSH, local processes, PBS, mpiexec,
49 Windows HPC Server 2008). To start a cluster with 4 engines on your
49 Windows HPC Server 2008). To start a cluster with 4 engines on your
50 local host simply do 'ipclusterz start -n 4'. For more complex usage
50 local host simply do 'ipclusterz start -n 4'. For more complex usage
51 you will typically do 'ipclusterz create -p mycluster', then edit
51 you will typically do 'ipclusterz create -p mycluster', then edit
52 configuration files, followed by 'ipclusterz start -p mycluster -n 4'.
52 configuration files, followed by 'ipclusterz start -p mycluster -n 4'.
53 """
53 """
54
54
55
55
56 # Exit codes for ipcluster
56 # Exit codes for ipcluster
57
57
58 # This will be the exit code if the ipcluster appears to be running because
58 # This will be the exit code if the ipcluster appears to be running because
59 # a .pid file exists
59 # a .pid file exists
60 ALREADY_STARTED = 10
60 ALREADY_STARTED = 10
61
61
62
62
63 # This will be the exit code if ipcluster stop is run, but there is not .pid
63 # This will be the exit code if ipcluster stop is run, but there is not .pid
64 # file to be found.
64 # file to be found.
65 ALREADY_STOPPED = 11
65 ALREADY_STOPPED = 11
66
66
67 # This will be the exit code if ipcluster engines is run, but there is not .pid
67 # This will be the exit code if ipcluster engines is run, but there is not .pid
68 # file to be found.
68 # file to be found.
69 NO_CLUSTER = 12
69 NO_CLUSTER = 12
70
70
71
71
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 # Command line options
73 # Command line options
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75
75
76
76
77 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
77 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
78
78
79 def _add_arguments(self):
79 def _add_arguments(self):
80 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
80 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
81 # its defaults on self.parser. Instead, we will put those on
81 # its defaults on self.parser. Instead, we will put those on
82 # default options on our subparsers.
82 # default options on our subparsers.
83
83
84 # This has all the common options that all subcommands use
84 # This has all the common options that all subcommands use
85 parent_parser1 = ArgumentParser(
85 parent_parser1 = ArgumentParser(
86 add_help=False,
86 add_help=False,
87 argument_default=SUPPRESS
87 argument_default=SUPPRESS
88 )
88 )
89 self._add_ipython_dir(parent_parser1)
89 self._add_ipython_dir(parent_parser1)
90 self._add_log_level(parent_parser1)
90 self._add_log_level(parent_parser1)
91
91
92 # This has all the common options that other subcommands use
92 # This has all the common options that other subcommands use
93 parent_parser2 = ArgumentParser(
93 parent_parser2 = ArgumentParser(
94 add_help=False,
94 add_help=False,
95 argument_default=SUPPRESS
95 argument_default=SUPPRESS
96 )
96 )
97 self._add_cluster_profile(parent_parser2)
97 self._add_cluster_profile(parent_parser2)
98 self._add_cluster_dir(parent_parser2)
98 self._add_cluster_dir(parent_parser2)
99 self._add_work_dir(parent_parser2)
99 self._add_work_dir(parent_parser2)
100 paa = parent_parser2.add_argument
100 paa = parent_parser2.add_argument
101 paa('--log-to-file',
101 paa('--log-to-file',
102 action='store_true', dest='Global.log_to_file',
102 action='store_true', dest='Global.log_to_file',
103 help='Log to a file in the log directory (default is stdout)')
103 help='Log to a file in the log directory (default is stdout)')
104
104
105 # Create the object used to create the subparsers.
105 # Create the object used to create the subparsers.
106 subparsers = self.parser.add_subparsers(
106 subparsers = self.parser.add_subparsers(
107 dest='Global.subcommand',
107 dest='Global.subcommand',
108 title='ipcluster subcommands',
108 title='ipcluster subcommands',
109 description=
109 description=
110 """ipcluster has a variety of subcommands. The general way of
110 """ipcluster has a variety of subcommands. The general way of
111 running ipcluster is 'ipclusterz <cmd> [options]'. To get help
111 running ipcluster is 'ipclusterz <cmd> [options]'. To get help
112 on a particular subcommand do 'ipclusterz <cmd> -h'."""
112 on a particular subcommand do 'ipclusterz <cmd> -h'."""
113 # help="For more help, type 'ipclusterz <cmd> -h'",
113 # help="For more help, type 'ipclusterz <cmd> -h'",
114 )
114 )
115
115
116 # The "list" subcommand parser
116 # The "list" subcommand parser
117 parser_list = subparsers.add_parser(
117 parser_list = subparsers.add_parser(
118 'list',
118 'list',
119 parents=[parent_parser1],
119 parents=[parent_parser1],
120 argument_default=SUPPRESS,
120 argument_default=SUPPRESS,
121 help="List all clusters in cwd and ipython_dir.",
121 help="List all clusters in cwd and ipython_dir.",
122 description=
122 description=
123 """List all available clusters, by cluster directory, that can
123 """List all available clusters, by cluster directory, that can
124 be found in the current working directly or in the ipython
124 be found in the current working directly or in the ipython
125 directory. Cluster directories are named using the convention
125 directory. Cluster directories are named using the convention
126 'clusterz_<profile>'."""
126 'clusterz_<profile>'."""
127 )
127 )
128
128
129 # The "create" subcommand parser
129 # The "create" subcommand parser
130 parser_create = subparsers.add_parser(
130 parser_create = subparsers.add_parser(
131 'create',
131 'create',
132 parents=[parent_parser1, parent_parser2],
132 parents=[parent_parser1, parent_parser2],
133 argument_default=SUPPRESS,
133 argument_default=SUPPRESS,
134 help="Create a new cluster directory.",
134 help="Create a new cluster directory.",
135 description=
135 description=
136 """Create an ipython cluster directory by its profile name or
136 """Create an ipython cluster directory by its profile name or
137 cluster directory path. Cluster directories contain
137 cluster directory path. Cluster directories contain
138 configuration, log and security related files and are named
138 configuration, log and security related files and are named
139 using the convention 'clusterz_<profile>'. By default they are
139 using the convention 'clusterz_<profile>'. By default they are
140 located in your ipython directory. Once created, you will
140 located in your ipython directory. Once created, you will
141 probably need to edit the configuration files in the cluster
141 probably need to edit the configuration files in the cluster
142 directory to configure your cluster. Most users will create a
142 directory to configure your cluster. Most users will create a
143 cluster directory by profile name,
143 cluster directory by profile name,
144 'ipclusterz create -p mycluster', which will put the directory
144 'ipclusterz create -p mycluster', which will put the directory
145 in '<ipython_dir>/clusterz_mycluster'.
145 in '<ipython_dir>/clusterz_mycluster'.
146 """
146 """
147 )
147 )
148 paa = parser_create.add_argument
148 paa = parser_create.add_argument
149 paa('--reset-config',
149 paa('--reset-config',
150 dest='Global.reset_config', action='store_true',
150 dest='Global.reset_config', action='store_true',
151 help=
151 help=
152 """Recopy the default config files to the cluster directory.
152 """Recopy the default config files to the cluster directory.
153 You will loose any modifications you have made to these files.""")
153 You will loose any modifications you have made to these files.""")
154
154
155 # The "start" subcommand parser
155 # The "start" subcommand parser
156 parser_start = subparsers.add_parser(
156 parser_start = subparsers.add_parser(
157 'start',
157 'start',
158 parents=[parent_parser1, parent_parser2],
158 parents=[parent_parser1, parent_parser2],
159 argument_default=SUPPRESS,
159 argument_default=SUPPRESS,
160 help="Start a cluster.",
160 help="Start a cluster.",
161 description=
161 description=
162 """Start an ipython cluster by its profile name or cluster
162 """Start an ipython cluster by its profile name or cluster
163 directory. Cluster directories contain configuration, log and
163 directory. Cluster directories contain configuration, log and
164 security related files and are named using the convention
164 security related files and are named using the convention
165 'clusterz_<profile>' and should be creating using the 'start'
165 'clusterz_<profile>' and should be creating using the 'start'
166 subcommand of 'ipcluster'. If your cluster directory is in
166 subcommand of 'ipcluster'. If your cluster directory is in
167 the cwd or the ipython directory, you can simply refer to it
167 the cwd or the ipython directory, you can simply refer to it
168 using its profile name, 'ipclusterz start -n 4 -p <profile>`,
168 using its profile name, 'ipclusterz start -n 4 -p <profile>`,
169 otherwise use the '--cluster-dir' option.
169 otherwise use the '--cluster-dir' option.
170 """
170 """
171 )
171 )
172
172
173 paa = parser_start.add_argument
173 paa = parser_start.add_argument
174 paa('-n', '--number',
174 paa('-n', '--number',
175 type=int, dest='Global.n',
175 type=int, dest='Global.n',
176 help='The number of engines to start.',
176 help='The number of engines to start.',
177 metavar='Global.n')
177 metavar='Global.n')
178 paa('--clean-logs',
178 paa('--clean-logs',
179 dest='Global.clean_logs', action='store_true',
179 dest='Global.clean_logs', action='store_true',
180 help='Delete old log flies before starting.')
180 help='Delete old log flies before starting.')
181 paa('--no-clean-logs',
181 paa('--no-clean-logs',
182 dest='Global.clean_logs', action='store_false',
182 dest='Global.clean_logs', action='store_false',
183 help="Don't delete old log flies before starting.")
183 help="Don't delete old log flies before starting.")
184 paa('--daemon',
184 paa('--daemon',
185 dest='Global.daemonize', action='store_true',
185 dest='Global.daemonize', action='store_true',
186 help='Daemonize the ipcluster program. This implies --log-to-file')
186 help='Daemonize the ipcluster program. This implies --log-to-file')
187 paa('--no-daemon',
187 paa('--no-daemon',
188 dest='Global.daemonize', action='store_false',
188 dest='Global.daemonize', action='store_false',
189 help="Dont't daemonize the ipcluster program.")
189 help="Dont't daemonize the ipcluster program.")
190 paa('--delay',
190 paa('--delay',
191 type=float, dest='Global.delay',
191 type=float, dest='Global.delay',
192 help="Specify the delay (in seconds) between starting the controller and starting the engine(s).")
192 help="Specify the delay (in seconds) between starting the controller and starting the engine(s).")
193
193
194 # The "stop" subcommand parser
194 # The "stop" subcommand parser
195 parser_stop = subparsers.add_parser(
195 parser_stop = subparsers.add_parser(
196 'stop',
196 'stop',
197 parents=[parent_parser1, parent_parser2],
197 parents=[parent_parser1, parent_parser2],
198 argument_default=SUPPRESS,
198 argument_default=SUPPRESS,
199 help="Stop a running cluster.",
199 help="Stop a running cluster.",
200 description=
200 description=
201 """Stop a running ipython cluster by its profile name or cluster
201 """Stop a running ipython cluster by its profile name or cluster
202 directory. Cluster directories are named using the convention
202 directory. Cluster directories are named using the convention
203 'clusterz_<profile>'. If your cluster directory is in
203 'clusterz_<profile>'. If your cluster directory is in
204 the cwd or the ipython directory, you can simply refer to it
204 the cwd or the ipython directory, you can simply refer to it
205 using its profile name, 'ipclusterz stop -p <profile>`, otherwise
205 using its profile name, 'ipclusterz stop -p <profile>`, otherwise
206 use the '--cluster-dir' option.
206 use the '--cluster-dir' option.
207 """
207 """
208 )
208 )
209 paa = parser_stop.add_argument
209 paa = parser_stop.add_argument
210 paa('--signal',
210 paa('--signal',
211 dest='Global.signal', type=int,
211 dest='Global.signal', type=int,
212 help="The signal number to use in stopping the cluster (default=2).",
212 help="The signal number to use in stopping the cluster (default=2).",
213 metavar="Global.signal")
213 metavar="Global.signal")
214
214
215 # the "engines" subcommand parser
215 # the "engines" subcommand parser
216 parser_engines = subparsers.add_parser(
216 parser_engines = subparsers.add_parser(
217 'engines',
217 'engines',
218 parents=[parent_parser1, parent_parser2],
218 parents=[parent_parser1, parent_parser2],
219 argument_default=SUPPRESS,
219 argument_default=SUPPRESS,
220 help="Attach some engines to an existing controller or cluster.",
220 help="Attach some engines to an existing controller or cluster.",
221 description=
221 description=
222 """Start one or more engines to connect to an existing Cluster
222 """Start one or more engines to connect to an existing Cluster
223 by profile name or cluster directory.
223 by profile name or cluster directory.
224 Cluster directories contain configuration, log and
224 Cluster directories contain configuration, log and
225 security related files and are named using the convention
225 security related files and are named using the convention
226 'clusterz_<profile>' and should be creating using the 'start'
226 'clusterz_<profile>' and should be creating using the 'start'
227 subcommand of 'ipcluster'. If your cluster directory is in
227 subcommand of 'ipcluster'. If your cluster directory is in
228 the cwd or the ipython directory, you can simply refer to it
228 the cwd or the ipython directory, you can simply refer to it
229 using its profile name, 'ipclusterz engines -n 4 -p <profile>`,
229 using its profile name, 'ipclusterz engines -n 4 -p <profile>`,
230 otherwise use the '--cluster-dir' option.
230 otherwise use the '--cluster-dir' option.
231 """
231 """
232 )
232 )
233 paa = parser_engines.add_argument
233 paa = parser_engines.add_argument
234 paa('-n', '--number',
234 paa('-n', '--number',
235 type=int, dest='Global.n',
235 type=int, dest='Global.n',
236 help='The number of engines to start.',
236 help='The number of engines to start.',
237 metavar='Global.n')
237 metavar='Global.n')
238 paa('--daemon',
238 paa('--daemon',
239 dest='Global.daemonize', action='store_true',
239 dest='Global.daemonize', action='store_true',
240 help='Daemonize the ipcluster program. This implies --log-to-file')
240 help='Daemonize the ipcluster program. This implies --log-to-file')
241 paa('--no-daemon',
241 paa('--no-daemon',
242 dest='Global.daemonize', action='store_false',
242 dest='Global.daemonize', action='store_false',
243 help="Dont't daemonize the ipcluster program.")
243 help="Dont't daemonize the ipcluster program.")
244
244
245 #-----------------------------------------------------------------------------
245 #-----------------------------------------------------------------------------
246 # Main application
246 # Main application
247 #-----------------------------------------------------------------------------
247 #-----------------------------------------------------------------------------
248
248
249
249
250 class IPClusterApp(ApplicationWithClusterDir):
250 class IPClusterApp(ApplicationWithClusterDir):
251
251
252 name = u'ipclusterz'
252 name = u'ipclusterz'
253 description = _description
253 description = _description
254 usage = None
254 usage = None
255 command_line_loader = IPClusterAppConfigLoader
255 command_line_loader = IPClusterAppConfigLoader
256 default_config_file_name = default_config_file_name
256 default_config_file_name = default_config_file_name
257 default_log_level = logging.INFO
257 default_log_level = logging.INFO
258 auto_create_cluster_dir = False
258 auto_create_cluster_dir = False
259
259
260 def create_default_config(self):
260 def create_default_config(self):
261 super(IPClusterApp, self).create_default_config()
261 super(IPClusterApp, self).create_default_config()
262 self.default_config.Global.controller_launcher = \
262 self.default_config.Global.controller_launcher = \
263 'IPython.zmq.parallel.launcher.LocalControllerLauncher'
263 'IPython.parallel.launcher.LocalControllerLauncher'
264 self.default_config.Global.engine_launcher = \
264 self.default_config.Global.engine_launcher = \
265 'IPython.zmq.parallel.launcher.LocalEngineSetLauncher'
265 'IPython.parallel.launcher.LocalEngineSetLauncher'
266 self.default_config.Global.n = 2
266 self.default_config.Global.n = 2
267 self.default_config.Global.delay = 2
267 self.default_config.Global.delay = 2
268 self.default_config.Global.reset_config = False
268 self.default_config.Global.reset_config = False
269 self.default_config.Global.clean_logs = True
269 self.default_config.Global.clean_logs = True
270 self.default_config.Global.signal = signal.SIGINT
270 self.default_config.Global.signal = signal.SIGINT
271 self.default_config.Global.daemonize = False
271 self.default_config.Global.daemonize = False
272
272
273 def find_resources(self):
273 def find_resources(self):
274 subcommand = self.command_line_config.Global.subcommand
274 subcommand = self.command_line_config.Global.subcommand
275 if subcommand=='list':
275 if subcommand=='list':
276 self.list_cluster_dirs()
276 self.list_cluster_dirs()
277 # Exit immediately because there is nothing left to do.
277 # Exit immediately because there is nothing left to do.
278 self.exit()
278 self.exit()
279 elif subcommand=='create':
279 elif subcommand=='create':
280 self.auto_create_cluster_dir = True
280 self.auto_create_cluster_dir = True
281 super(IPClusterApp, self).find_resources()
281 super(IPClusterApp, self).find_resources()
282 elif subcommand=='start' or subcommand=='stop':
282 elif subcommand=='start' or subcommand=='stop':
283 self.auto_create_cluster_dir = True
283 self.auto_create_cluster_dir = True
284 try:
284 try:
285 super(IPClusterApp, self).find_resources()
285 super(IPClusterApp, self).find_resources()
286 except ClusterDirError:
286 except ClusterDirError:
287 raise ClusterDirError(
287 raise ClusterDirError(
288 "Could not find a cluster directory. A cluster dir must "
288 "Could not find a cluster directory. A cluster dir must "
289 "be created before running 'ipclusterz start'. Do "
289 "be created before running 'ipclusterz start'. Do "
290 "'ipclusterz create -h' or 'ipclusterz list -h' for more "
290 "'ipclusterz create -h' or 'ipclusterz list -h' for more "
291 "information about creating and listing cluster dirs."
291 "information about creating and listing cluster dirs."
292 )
292 )
293 elif subcommand=='engines':
293 elif subcommand=='engines':
294 self.auto_create_cluster_dir = False
294 self.auto_create_cluster_dir = False
295 try:
295 try:
296 super(IPClusterApp, self).find_resources()
296 super(IPClusterApp, self).find_resources()
297 except ClusterDirError:
297 except ClusterDirError:
298 raise ClusterDirError(
298 raise ClusterDirError(
299 "Could not find a cluster directory. A cluster dir must "
299 "Could not find a cluster directory. A cluster dir must "
300 "be created before running 'ipclusterz start'. Do "
300 "be created before running 'ipclusterz start'. Do "
301 "'ipclusterz create -h' or 'ipclusterz list -h' for more "
301 "'ipclusterz create -h' or 'ipclusterz list -h' for more "
302 "information about creating and listing cluster dirs."
302 "information about creating and listing cluster dirs."
303 )
303 )
304
304
305 def list_cluster_dirs(self):
305 def list_cluster_dirs(self):
306 # Find the search paths
306 # Find the search paths
307 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
307 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
308 if cluster_dir_paths:
308 if cluster_dir_paths:
309 cluster_dir_paths = cluster_dir_paths.split(':')
309 cluster_dir_paths = cluster_dir_paths.split(':')
310 else:
310 else:
311 cluster_dir_paths = []
311 cluster_dir_paths = []
312 try:
312 try:
313 ipython_dir = self.command_line_config.Global.ipython_dir
313 ipython_dir = self.command_line_config.Global.ipython_dir
314 except AttributeError:
314 except AttributeError:
315 ipython_dir = self.default_config.Global.ipython_dir
315 ipython_dir = self.default_config.Global.ipython_dir
316 paths = [os.getcwd(), ipython_dir] + \
316 paths = [os.getcwd(), ipython_dir] + \
317 cluster_dir_paths
317 cluster_dir_paths
318 paths = list(set(paths))
318 paths = list(set(paths))
319
319
320 self.log.info('Searching for cluster dirs in paths: %r' % paths)
320 self.log.info('Searching for cluster dirs in paths: %r' % paths)
321 for path in paths:
321 for path in paths:
322 files = os.listdir(path)
322 files = os.listdir(path)
323 for f in files:
323 for f in files:
324 full_path = os.path.join(path, f)
324 full_path = os.path.join(path, f)
325 if os.path.isdir(full_path) and f.startswith('clusterz_'):
325 if os.path.isdir(full_path) and f.startswith('clusterz_'):
326 profile = full_path.split('_')[-1]
326 profile = full_path.split('_')[-1]
327 start_cmd = 'ipclusterz start -p %s -n 4' % profile
327 start_cmd = 'ipclusterz start -p %s -n 4' % profile
328 print start_cmd + " ==> " + full_path
328 print start_cmd + " ==> " + full_path
329
329
330 def pre_construct(self):
330 def pre_construct(self):
331 # IPClusterApp.pre_construct() is where we cd to the working directory.
331 # IPClusterApp.pre_construct() is where we cd to the working directory.
332 super(IPClusterApp, self).pre_construct()
332 super(IPClusterApp, self).pre_construct()
333 config = self.master_config
333 config = self.master_config
334 try:
334 try:
335 daemon = config.Global.daemonize
335 daemon = config.Global.daemonize
336 if daemon:
336 if daemon:
337 config.Global.log_to_file = True
337 config.Global.log_to_file = True
338 except AttributeError:
338 except AttributeError:
339 pass
339 pass
340
340
341 def construct(self):
341 def construct(self):
342 config = self.master_config
342 config = self.master_config
343 subcmd = config.Global.subcommand
343 subcmd = config.Global.subcommand
344 reset = config.Global.reset_config
344 reset = config.Global.reset_config
345 if subcmd == 'list':
345 if subcmd == 'list':
346 return
346 return
347 if subcmd == 'create':
347 if subcmd == 'create':
348 self.log.info('Copying default config files to cluster directory '
348 self.log.info('Copying default config files to cluster directory '
349 '[overwrite=%r]' % (reset,))
349 '[overwrite=%r]' % (reset,))
350 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
350 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
351 if subcmd =='start':
351 if subcmd =='start':
352 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
352 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
353 self.start_logging()
353 self.start_logging()
354 self.loop = ioloop.IOLoop.instance()
354 self.loop = ioloop.IOLoop.instance()
355 # reactor.callWhenRunning(self.start_launchers)
355 # reactor.callWhenRunning(self.start_launchers)
356 dc = ioloop.DelayedCallback(self.start_launchers, 0, self.loop)
356 dc = ioloop.DelayedCallback(self.start_launchers, 0, self.loop)
357 dc.start()
357 dc.start()
358 if subcmd == 'engines':
358 if subcmd == 'engines':
359 self.start_logging()
359 self.start_logging()
360 self.loop = ioloop.IOLoop.instance()
360 self.loop = ioloop.IOLoop.instance()
361 # reactor.callWhenRunning(self.start_launchers)
361 # reactor.callWhenRunning(self.start_launchers)
362 engine_only = lambda : self.start_launchers(controller=False)
362 engine_only = lambda : self.start_launchers(controller=False)
363 dc = ioloop.DelayedCallback(engine_only, 0, self.loop)
363 dc = ioloop.DelayedCallback(engine_only, 0, self.loop)
364 dc.start()
364 dc.start()
365
365
366 def start_launchers(self, controller=True):
366 def start_launchers(self, controller=True):
367 config = self.master_config
367 config = self.master_config
368
368
369 # Create the launchers. In both bases, we set the work_dir of
369 # Create the launchers. In both bases, we set the work_dir of
370 # the launcher to the cluster_dir. This is where the launcher's
370 # the launcher to the cluster_dir. This is where the launcher's
371 # subprocesses will be launched. It is not where the controller
371 # subprocesses will be launched. It is not where the controller
372 # and engine will be launched.
372 # and engine will be launched.
373 if controller:
373 if controller:
374 cl_class = import_item(config.Global.controller_launcher)
374 cl_class = import_item(config.Global.controller_launcher)
375 self.controller_launcher = cl_class(
375 self.controller_launcher = cl_class(
376 work_dir=self.cluster_dir, config=config,
376 work_dir=self.cluster_dir, config=config,
377 logname=self.log.name
377 logname=self.log.name
378 )
378 )
379 # Setup the observing of stopping. If the controller dies, shut
379 # Setup the observing of stopping. If the controller dies, shut
380 # everything down as that will be completely fatal for the engines.
380 # everything down as that will be completely fatal for the engines.
381 self.controller_launcher.on_stop(self.stop_launchers)
381 self.controller_launcher.on_stop(self.stop_launchers)
382 # But, we don't monitor the stopping of engines. An engine dying
382 # But, we don't monitor the stopping of engines. An engine dying
383 # is just fine and in principle a user could start a new engine.
383 # is just fine and in principle a user could start a new engine.
384 # Also, if we did monitor engine stopping, it is difficult to
384 # Also, if we did monitor engine stopping, it is difficult to
385 # know what to do when only some engines die. Currently, the
385 # know what to do when only some engines die. Currently, the
386 # observing of engine stopping is inconsistent. Some launchers
386 # observing of engine stopping is inconsistent. Some launchers
387 # might trigger on a single engine stopping, other wait until
387 # might trigger on a single engine stopping, other wait until
388 # all stop. TODO: think more about how to handle this.
388 # all stop. TODO: think more about how to handle this.
389 else:
389 else:
390 self.controller_launcher = None
390 self.controller_launcher = None
391
391
392 el_class = import_item(config.Global.engine_launcher)
392 el_class = import_item(config.Global.engine_launcher)
393 self.engine_launcher = el_class(
393 self.engine_launcher = el_class(
394 work_dir=self.cluster_dir, config=config, logname=self.log.name
394 work_dir=self.cluster_dir, config=config, logname=self.log.name
395 )
395 )
396
396
397 # Setup signals
397 # Setup signals
398 signal.signal(signal.SIGINT, self.sigint_handler)
398 signal.signal(signal.SIGINT, self.sigint_handler)
399
399
400 # Start the controller and engines
400 # Start the controller and engines
401 self._stopping = False # Make sure stop_launchers is not called 2x.
401 self._stopping = False # Make sure stop_launchers is not called 2x.
402 if controller:
402 if controller:
403 self.start_controller()
403 self.start_controller()
404 dc = ioloop.DelayedCallback(self.start_engines, 1000*config.Global.delay*controller, self.loop)
404 dc = ioloop.DelayedCallback(self.start_engines, 1000*config.Global.delay*controller, self.loop)
405 dc.start()
405 dc.start()
406 self.startup_message()
406 self.startup_message()
407
407
408 def startup_message(self, r=None):
408 def startup_message(self, r=None):
409 self.log.info("IPython cluster: started")
409 self.log.info("IPython cluster: started")
410 return r
410 return r
411
411
412 def start_controller(self, r=None):
412 def start_controller(self, r=None):
413 # self.log.info("In start_controller")
413 # self.log.info("In start_controller")
414 config = self.master_config
414 config = self.master_config
415 d = self.controller_launcher.start(
415 d = self.controller_launcher.start(
416 cluster_dir=config.Global.cluster_dir
416 cluster_dir=config.Global.cluster_dir
417 )
417 )
418 return d
418 return d
419
419
420 def start_engines(self, r=None):
420 def start_engines(self, r=None):
421 # self.log.info("In start_engines")
421 # self.log.info("In start_engines")
422 config = self.master_config
422 config = self.master_config
423
423
424 d = self.engine_launcher.start(
424 d = self.engine_launcher.start(
425 config.Global.n,
425 config.Global.n,
426 cluster_dir=config.Global.cluster_dir
426 cluster_dir=config.Global.cluster_dir
427 )
427 )
428 return d
428 return d
429
429
430 def stop_controller(self, r=None):
430 def stop_controller(self, r=None):
431 # self.log.info("In stop_controller")
431 # self.log.info("In stop_controller")
432 if self.controller_launcher and self.controller_launcher.running:
432 if self.controller_launcher and self.controller_launcher.running:
433 return self.controller_launcher.stop()
433 return self.controller_launcher.stop()
434
434
435 def stop_engines(self, r=None):
435 def stop_engines(self, r=None):
436 # self.log.info("In stop_engines")
436 # self.log.info("In stop_engines")
437 if self.engine_launcher.running:
437 if self.engine_launcher.running:
438 d = self.engine_launcher.stop()
438 d = self.engine_launcher.stop()
439 # d.addErrback(self.log_err)
439 # d.addErrback(self.log_err)
440 return d
440 return d
441 else:
441 else:
442 return None
442 return None
443
443
444 def log_err(self, f):
444 def log_err(self, f):
445 self.log.error(f.getTraceback())
445 self.log.error(f.getTraceback())
446 return None
446 return None
447
447
448 def stop_launchers(self, r=None):
448 def stop_launchers(self, r=None):
449 if not self._stopping:
449 if not self._stopping:
450 self._stopping = True
450 self._stopping = True
451 # if isinstance(r, failure.Failure):
451 # if isinstance(r, failure.Failure):
452 # self.log.error('Unexpected error in ipcluster:')
452 # self.log.error('Unexpected error in ipcluster:')
453 # self.log.info(r.getTraceback())
453 # self.log.info(r.getTraceback())
454 self.log.error("IPython cluster: stopping")
454 self.log.error("IPython cluster: stopping")
455 # These return deferreds. We are not doing anything with them
455 # These return deferreds. We are not doing anything with them
456 # but we are holding refs to them as a reminder that they
456 # but we are holding refs to them as a reminder that they
457 # do return deferreds.
457 # do return deferreds.
458 d1 = self.stop_engines()
458 d1 = self.stop_engines()
459 d2 = self.stop_controller()
459 d2 = self.stop_controller()
460 # Wait a few seconds to let things shut down.
460 # Wait a few seconds to let things shut down.
461 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
461 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
462 dc.start()
462 dc.start()
463 # reactor.callLater(4.0, reactor.stop)
463 # reactor.callLater(4.0, reactor.stop)
464
464
465 def sigint_handler(self, signum, frame):
465 def sigint_handler(self, signum, frame):
466 self.stop_launchers()
466 self.stop_launchers()
467
467
468 def start_logging(self):
468 def start_logging(self):
469 # Remove old log files of the controller and engine
469 # Remove old log files of the controller and engine
470 if self.master_config.Global.clean_logs:
470 if self.master_config.Global.clean_logs:
471 log_dir = self.master_config.Global.log_dir
471 log_dir = self.master_config.Global.log_dir
472 for f in os.listdir(log_dir):
472 for f in os.listdir(log_dir):
473 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
473 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
474 os.remove(os.path.join(log_dir, f))
474 os.remove(os.path.join(log_dir, f))
475 # This will remove old log files for ipcluster itself
475 # This will remove old log files for ipcluster itself
476 super(IPClusterApp, self).start_logging()
476 super(IPClusterApp, self).start_logging()
477
477
478 def start_app(self):
478 def start_app(self):
479 """Start the application, depending on what subcommand is used."""
479 """Start the application, depending on what subcommand is used."""
480 subcmd = self.master_config.Global.subcommand
480 subcmd = self.master_config.Global.subcommand
481 if subcmd=='create' or subcmd=='list':
481 if subcmd=='create' or subcmd=='list':
482 return
482 return
483 elif subcmd=='start':
483 elif subcmd=='start':
484 self.start_app_start()
484 self.start_app_start()
485 elif subcmd=='stop':
485 elif subcmd=='stop':
486 self.start_app_stop()
486 self.start_app_stop()
487 elif subcmd=='engines':
487 elif subcmd=='engines':
488 self.start_app_engines()
488 self.start_app_engines()
489
489
490 def start_app_start(self):
490 def start_app_start(self):
491 """Start the app for the start subcommand."""
491 """Start the app for the start subcommand."""
492 config = self.master_config
492 config = self.master_config
493 # First see if the cluster is already running
493 # First see if the cluster is already running
494 try:
494 try:
495 pid = self.get_pid_from_file()
495 pid = self.get_pid_from_file()
496 except PIDFileError:
496 except PIDFileError:
497 pass
497 pass
498 else:
498 else:
499 self.log.critical(
499 self.log.critical(
500 'Cluster is already running with [pid=%s]. '
500 'Cluster is already running with [pid=%s]. '
501 'use "ipclusterz stop" to stop the cluster.' % pid
501 'use "ipclusterz stop" to stop the cluster.' % pid
502 )
502 )
503 # Here I exit with a unusual exit status that other processes
503 # Here I exit with a unusual exit status that other processes
504 # can watch for to learn how I existed.
504 # can watch for to learn how I existed.
505 self.exit(ALREADY_STARTED)
505 self.exit(ALREADY_STARTED)
506
506
507 # Now log and daemonize
507 # Now log and daemonize
508 self.log.info(
508 self.log.info(
509 'Starting ipclusterz with [daemon=%r]' % config.Global.daemonize
509 'Starting ipclusterz with [daemon=%r]' % config.Global.daemonize
510 )
510 )
511 # TODO: Get daemonize working on Windows or as a Windows Server.
511 # TODO: Get daemonize working on Windows or as a Windows Server.
512 if config.Global.daemonize:
512 if config.Global.daemonize:
513 if os.name=='posix':
513 if os.name=='posix':
514 from twisted.scripts._twistd_unix import daemonize
514 from twisted.scripts._twistd_unix import daemonize
515 daemonize()
515 daemonize()
516
516
517 # Now write the new pid file AFTER our new forked pid is active.
517 # Now write the new pid file AFTER our new forked pid is active.
518 self.write_pid_file()
518 self.write_pid_file()
519 try:
519 try:
520 self.loop.start()
520 self.loop.start()
521 except KeyboardInterrupt:
521 except KeyboardInterrupt:
522 pass
522 pass
523 except zmq.ZMQError as e:
523 except zmq.ZMQError as e:
524 if e.errno == errno.EINTR:
524 if e.errno == errno.EINTR:
525 pass
525 pass
526 else:
526 else:
527 raise
527 raise
528 self.remove_pid_file()
528 self.remove_pid_file()
529
529
530 def start_app_engines(self):
530 def start_app_engines(self):
531 """Start the app for the start subcommand."""
531 """Start the app for the start subcommand."""
532 config = self.master_config
532 config = self.master_config
533 # First see if the cluster is already running
533 # First see if the cluster is already running
534
534
535 # Now log and daemonize
535 # Now log and daemonize
536 self.log.info(
536 self.log.info(
537 'Starting engines with [daemon=%r]' % config.Global.daemonize
537 'Starting engines with [daemon=%r]' % config.Global.daemonize
538 )
538 )
539 # TODO: Get daemonize working on Windows or as a Windows Server.
539 # TODO: Get daemonize working on Windows or as a Windows Server.
540 if config.Global.daemonize:
540 if config.Global.daemonize:
541 if os.name=='posix':
541 if os.name=='posix':
542 from twisted.scripts._twistd_unix import daemonize
542 from twisted.scripts._twistd_unix import daemonize
543 daemonize()
543 daemonize()
544
544
545 # Now write the new pid file AFTER our new forked pid is active.
545 # Now write the new pid file AFTER our new forked pid is active.
546 # self.write_pid_file()
546 # self.write_pid_file()
547 try:
547 try:
548 self.loop.start()
548 self.loop.start()
549 except KeyboardInterrupt:
549 except KeyboardInterrupt:
550 pass
550 pass
551 except zmq.ZMQError as e:
551 except zmq.ZMQError as e:
552 if e.errno == errno.EINTR:
552 if e.errno == errno.EINTR:
553 pass
553 pass
554 else:
554 else:
555 raise
555 raise
556 # self.remove_pid_file()
556 # self.remove_pid_file()
557
557
558 def start_app_stop(self):
558 def start_app_stop(self):
559 """Start the app for the stop subcommand."""
559 """Start the app for the stop subcommand."""
560 config = self.master_config
560 config = self.master_config
561 try:
561 try:
562 pid = self.get_pid_from_file()
562 pid = self.get_pid_from_file()
563 except PIDFileError:
563 except PIDFileError:
564 self.log.critical(
564 self.log.critical(
565 'Problem reading pid file, cluster is probably not running.'
565 'Problem reading pid file, cluster is probably not running.'
566 )
566 )
567 # Here I exit with a unusual exit status that other processes
567 # Here I exit with a unusual exit status that other processes
568 # can watch for to learn how I existed.
568 # can watch for to learn how I existed.
569 self.exit(ALREADY_STOPPED)
569 self.exit(ALREADY_STOPPED)
570 else:
570 else:
571 if os.name=='posix':
571 if os.name=='posix':
572 sig = config.Global.signal
572 sig = config.Global.signal
573 self.log.info(
573 self.log.info(
574 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
574 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
575 )
575 )
576 os.kill(pid, sig)
576 os.kill(pid, sig)
577 elif os.name=='nt':
577 elif os.name=='nt':
578 # As of right now, we don't support daemonize on Windows, so
578 # As of right now, we don't support daemonize on Windows, so
579 # stop will not do anything. Minimally, it should clean up the
579 # stop will not do anything. Minimally, it should clean up the
580 # old .pid files.
580 # old .pid files.
581 self.remove_pid_file()
581 self.remove_pid_file()
582
582
583
583
584 def launch_new_instance():
584 def launch_new_instance():
585 """Create and run the IPython cluster."""
585 """Create and run the IPython cluster."""
586 app = IPClusterApp()
586 app = IPClusterApp()
587 app.start()
587 app.start()
588
588
589
589
590 if __name__ == '__main__':
590 if __name__ == '__main__':
591 launch_new_instance()
591 launch_new_instance()
592
592
@@ -1,431 +1,431 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application.
4 The IPython controller application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import with_statement
18 from __future__ import with_statement
19
19
20 import copy
20 import copy
21 import os
21 import os
22 import logging
22 import logging
23 import socket
23 import socket
24 import stat
24 import stat
25 import sys
25 import sys
26 import uuid
26 import uuid
27
27
28 import zmq
28 import zmq
29 from zmq.log.handlers import PUBHandler
29 from zmq.log.handlers import PUBHandler
30 from zmq.utils import jsonapi as json
30 from zmq.utils import jsonapi as json
31
31
32 from IPython.config.loader import Config
32 from IPython.config.loader import Config
33 from IPython.zmq.parallel import factory
33 from IPython.parallel import factory
34 from IPython.zmq.parallel.controller import ControllerFactory
34 from IPython.parallel.controller import ControllerFactory
35 from IPython.zmq.parallel.clusterdir import (
35 from IPython.parallel.clusterdir import (
36 ApplicationWithClusterDir,
36 ApplicationWithClusterDir,
37 ClusterDirConfigLoader
37 ClusterDirConfigLoader
38 )
38 )
39 from IPython.zmq.parallel.util import disambiguate_ip_address, split_url
39 from IPython.parallel.util import disambiguate_ip_address, split_url
40 # from IPython.kernel.fcutil import FCServiceFactory, FURLError
40 # from IPython.kernel.fcutil import FCServiceFactory, FURLError
41 from IPython.utils.traitlets import Instance, Unicode
41 from IPython.utils.traitlets import Instance, Unicode
42
42
43
43
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Module level variables
46 # Module level variables
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49
49
50 #: The default config file name for this application
50 #: The default config file name for this application
51 default_config_file_name = u'ipcontrollerz_config.py'
51 default_config_file_name = u'ipcontrollerz_config.py'
52
52
53
53
54 _description = """Start the IPython controller for parallel computing.
54 _description = """Start the IPython controller for parallel computing.
55
55
56 The IPython controller provides a gateway between the IPython engines and
56 The IPython controller provides a gateway between the IPython engines and
57 clients. The controller needs to be started before the engines and can be
57 clients. The controller needs to be started before the engines and can be
58 configured using command line options or using a cluster directory. Cluster
58 configured using command line options or using a cluster directory. Cluster
59 directories contain config, log and security files and are usually located in
59 directories contain config, log and security files and are usually located in
60 your ipython directory and named as "clusterz_<profile>". See the --profile
60 your ipython directory and named as "clusterz_<profile>". See the --profile
61 and --cluster-dir options for details.
61 and --cluster-dir options for details.
62 """
62 """
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Default interfaces
65 # Default interfaces
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 # The default client interfaces for FCClientServiceFactory.interfaces
68 # The default client interfaces for FCClientServiceFactory.interfaces
69 default_client_interfaces = Config()
69 default_client_interfaces = Config()
70 default_client_interfaces.Default.url_file = 'ipcontroller-client.url'
70 default_client_interfaces.Default.url_file = 'ipcontroller-client.url'
71
71
72 # Make this a dict we can pass to Config.__init__ for the default
72 # Make this a dict we can pass to Config.__init__ for the default
73 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
73 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
74
74
75
75
76
76
77 # The default engine interfaces for FCEngineServiceFactory.interfaces
77 # The default engine interfaces for FCEngineServiceFactory.interfaces
78 default_engine_interfaces = Config()
78 default_engine_interfaces = Config()
79 default_engine_interfaces.Default.url_file = u'ipcontroller-engine.url'
79 default_engine_interfaces.Default.url_file = u'ipcontroller-engine.url'
80
80
81 # Make this a dict we can pass to Config.__init__ for the default
81 # Make this a dict we can pass to Config.__init__ for the default
82 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
82 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
83
83
84
84
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86 # Service factories
86 # Service factories
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88
88
89 #
89 #
90 # class FCClientServiceFactory(FCServiceFactory):
90 # class FCClientServiceFactory(FCServiceFactory):
91 # """A Foolscap implementation of the client services."""
91 # """A Foolscap implementation of the client services."""
92 #
92 #
93 # cert_file = Unicode(u'ipcontroller-client.pem', config=True)
93 # cert_file = Unicode(u'ipcontroller-client.pem', config=True)
94 # interfaces = Instance(klass=Config, kw=default_client_interfaces,
94 # interfaces = Instance(klass=Config, kw=default_client_interfaces,
95 # allow_none=False, config=True)
95 # allow_none=False, config=True)
96 #
96 #
97 #
97 #
98 # class FCEngineServiceFactory(FCServiceFactory):
98 # class FCEngineServiceFactory(FCServiceFactory):
99 # """A Foolscap implementation of the engine services."""
99 # """A Foolscap implementation of the engine services."""
100 #
100 #
101 # cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
101 # cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
102 # interfaces = Instance(klass=dict, kw=default_engine_interfaces,
102 # interfaces = Instance(klass=dict, kw=default_engine_interfaces,
103 # allow_none=False, config=True)
103 # allow_none=False, config=True)
104 #
104 #
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Command line options
107 # Command line options
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110
110
111 class IPControllerAppConfigLoader(ClusterDirConfigLoader):
111 class IPControllerAppConfigLoader(ClusterDirConfigLoader):
112
112
113 def _add_arguments(self):
113 def _add_arguments(self):
114 super(IPControllerAppConfigLoader, self)._add_arguments()
114 super(IPControllerAppConfigLoader, self)._add_arguments()
115 paa = self.parser.add_argument
115 paa = self.parser.add_argument
116
116
117 ## Hub Config:
117 ## Hub Config:
118 paa('--mongodb',
118 paa('--mongodb',
119 dest='HubFactory.db_class', action='store_const',
119 dest='HubFactory.db_class', action='store_const',
120 const='IPython.zmq.parallel.mongodb.MongoDB',
120 const='IPython.parallel.mongodb.MongoDB',
121 help='Use MongoDB for task storage [default: in-memory]')
121 help='Use MongoDB for task storage [default: in-memory]')
122 paa('--sqlite',
122 paa('--sqlite',
123 dest='HubFactory.db_class', action='store_const',
123 dest='HubFactory.db_class', action='store_const',
124 const='IPython.zmq.parallel.sqlitedb.SQLiteDB',
124 const='IPython.parallel.sqlitedb.SQLiteDB',
125 help='Use SQLite3 for DB task storage [default: in-memory]')
125 help='Use SQLite3 for DB task storage [default: in-memory]')
126 paa('--hb',
126 paa('--hb',
127 type=int, dest='HubFactory.hb', nargs=2,
127 type=int, dest='HubFactory.hb', nargs=2,
128 help='The (2) ports the Hub\'s Heartmonitor will use for the heartbeat '
128 help='The (2) ports the Hub\'s Heartmonitor will use for the heartbeat '
129 'connections [default: random]',
129 'connections [default: random]',
130 metavar='Hub.hb_ports')
130 metavar='Hub.hb_ports')
131 paa('--ping',
131 paa('--ping',
132 type=int, dest='HubFactory.ping',
132 type=int, dest='HubFactory.ping',
133 help='The frequency at which the Hub pings the engines for heartbeats '
133 help='The frequency at which the Hub pings the engines for heartbeats '
134 ' (in ms) [default: 100]',
134 ' (in ms) [default: 100]',
135 metavar='Hub.ping')
135 metavar='Hub.ping')
136
136
137 # Client config
137 # Client config
138 paa('--client-ip',
138 paa('--client-ip',
139 type=str, dest='HubFactory.client_ip',
139 type=str, dest='HubFactory.client_ip',
140 help='The IP address or hostname the Hub will listen on for '
140 help='The IP address or hostname the Hub will listen on for '
141 'client connections. Both engine-ip and client-ip can be set simultaneously '
141 'client connections. Both engine-ip and client-ip can be set simultaneously '
142 'via --ip [default: loopback]',
142 'via --ip [default: loopback]',
143 metavar='Hub.client_ip')
143 metavar='Hub.client_ip')
144 paa('--client-transport',
144 paa('--client-transport',
145 type=str, dest='HubFactory.client_transport',
145 type=str, dest='HubFactory.client_transport',
146 help='The ZeroMQ transport the Hub will use for '
146 help='The ZeroMQ transport the Hub will use for '
147 'client connections. Both engine-transport and client-transport can be set simultaneously '
147 'client connections. Both engine-transport and client-transport can be set simultaneously '
148 'via --transport [default: tcp]',
148 'via --transport [default: tcp]',
149 metavar='Hub.client_transport')
149 metavar='Hub.client_transport')
150 paa('--query',
150 paa('--query',
151 type=int, dest='HubFactory.query_port',
151 type=int, dest='HubFactory.query_port',
152 help='The port on which the Hub XREP socket will listen for result queries from clients [default: random]',
152 help='The port on which the Hub XREP socket will listen for result queries from clients [default: random]',
153 metavar='Hub.query_port')
153 metavar='Hub.query_port')
154 paa('--notifier',
154 paa('--notifier',
155 type=int, dest='HubFactory.notifier_port',
155 type=int, dest='HubFactory.notifier_port',
156 help='The port on which the Hub PUB socket will listen for notification connections [default: random]',
156 help='The port on which the Hub PUB socket will listen for notification connections [default: random]',
157 metavar='Hub.notifier_port')
157 metavar='Hub.notifier_port')
158
158
159 # Engine config
159 # Engine config
160 paa('--engine-ip',
160 paa('--engine-ip',
161 type=str, dest='HubFactory.engine_ip',
161 type=str, dest='HubFactory.engine_ip',
162 help='The IP address or hostname the Hub will listen on for '
162 help='The IP address or hostname the Hub will listen on for '
163 'engine connections. This applies to the Hub and its schedulers'
163 'engine connections. This applies to the Hub and its schedulers'
164 'engine-ip and client-ip can be set simultaneously '
164 'engine-ip and client-ip can be set simultaneously '
165 'via --ip [default: loopback]',
165 'via --ip [default: loopback]',
166 metavar='Hub.engine_ip')
166 metavar='Hub.engine_ip')
167 paa('--engine-transport',
167 paa('--engine-transport',
168 type=str, dest='HubFactory.engine_transport',
168 type=str, dest='HubFactory.engine_transport',
169 help='The ZeroMQ transport the Hub will use for '
169 help='The ZeroMQ transport the Hub will use for '
170 'client connections. Both engine-transport and client-transport can be set simultaneously '
170 'client connections. Both engine-transport and client-transport can be set simultaneously '
171 'via --transport [default: tcp]',
171 'via --transport [default: tcp]',
172 metavar='Hub.engine_transport')
172 metavar='Hub.engine_transport')
173
173
174 # Scheduler config
174 # Scheduler config
175 paa('--mux',
175 paa('--mux',
176 type=int, dest='ControllerFactory.mux', nargs=2,
176 type=int, dest='ControllerFactory.mux', nargs=2,
177 help='The (2) ports the MUX scheduler will listen on for client,engine '
177 help='The (2) ports the MUX scheduler will listen on for client,engine '
178 'connections, respectively [default: random]',
178 'connections, respectively [default: random]',
179 metavar='Scheduler.mux_ports')
179 metavar='Scheduler.mux_ports')
180 paa('--task',
180 paa('--task',
181 type=int, dest='ControllerFactory.task', nargs=2,
181 type=int, dest='ControllerFactory.task', nargs=2,
182 help='The (2) ports the Task scheduler will listen on for client,engine '
182 help='The (2) ports the Task scheduler will listen on for client,engine '
183 'connections, respectively [default: random]',
183 'connections, respectively [default: random]',
184 metavar='Scheduler.task_ports')
184 metavar='Scheduler.task_ports')
185 paa('--control',
185 paa('--control',
186 type=int, dest='ControllerFactory.control', nargs=2,
186 type=int, dest='ControllerFactory.control', nargs=2,
187 help='The (2) ports the Control scheduler will listen on for client,engine '
187 help='The (2) ports the Control scheduler will listen on for client,engine '
188 'connections, respectively [default: random]',
188 'connections, respectively [default: random]',
189 metavar='Scheduler.control_ports')
189 metavar='Scheduler.control_ports')
190 paa('--iopub',
190 paa('--iopub',
191 type=int, dest='ControllerFactory.iopub', nargs=2,
191 type=int, dest='ControllerFactory.iopub', nargs=2,
192 help='The (2) ports the IOPub scheduler will listen on for client,engine '
192 help='The (2) ports the IOPub scheduler will listen on for client,engine '
193 'connections, respectively [default: random]',
193 'connections, respectively [default: random]',
194 metavar='Scheduler.iopub_ports')
194 metavar='Scheduler.iopub_ports')
195
195
196 paa('--scheme',
196 paa('--scheme',
197 type=str, dest='HubFactory.scheme',
197 type=str, dest='HubFactory.scheme',
198 choices = ['pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'],
198 choices = ['pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'],
199 help='select the task scheduler scheme [default: Python LRU]',
199 help='select the task scheduler scheme [default: Python LRU]',
200 metavar='Scheduler.scheme')
200 metavar='Scheduler.scheme')
201 paa('--usethreads',
201 paa('--usethreads',
202 dest='ControllerFactory.usethreads', action="store_true",
202 dest='ControllerFactory.usethreads', action="store_true",
203 help='Use threads instead of processes for the schedulers',
203 help='Use threads instead of processes for the schedulers',
204 )
204 )
205 paa('--hwm',
205 paa('--hwm',
206 dest='ControllerFactory.hwm', type=int,
206 dest='ControllerFactory.hwm', type=int,
207 help='specify the High Water Mark (HWM) for the downstream '
207 help='specify the High Water Mark (HWM) for the downstream '
208 'socket in the pure ZMQ scheduler. This is the maximum number '
208 'socket in the pure ZMQ scheduler. This is the maximum number '
209 'of allowed outstanding tasks on each engine.',
209 'of allowed outstanding tasks on each engine.',
210 )
210 )
211
211
212 ## Global config
212 ## Global config
213 paa('--log-to-file',
213 paa('--log-to-file',
214 action='store_true', dest='Global.log_to_file',
214 action='store_true', dest='Global.log_to_file',
215 help='Log to a file in the log directory (default is stdout)')
215 help='Log to a file in the log directory (default is stdout)')
216 paa('--log-url',
216 paa('--log-url',
217 type=str, dest='Global.log_url',
217 type=str, dest='Global.log_url',
218 help='Broadcast logs to an iploggerz process [default: disabled]')
218 help='Broadcast logs to an iploggerz process [default: disabled]')
219 paa('-r','--reuse-files',
219 paa('-r','--reuse-files',
220 action='store_true', dest='Global.reuse_files',
220 action='store_true', dest='Global.reuse_files',
221 help='Try to reuse existing json connection files.')
221 help='Try to reuse existing json connection files.')
222 paa('--no-secure',
222 paa('--no-secure',
223 action='store_false', dest='Global.secure',
223 action='store_false', dest='Global.secure',
224 help='Turn off execution keys (default).')
224 help='Turn off execution keys (default).')
225 paa('--secure',
225 paa('--secure',
226 action='store_true', dest='Global.secure',
226 action='store_true', dest='Global.secure',
227 help='Turn on execution keys.')
227 help='Turn on execution keys.')
228 paa('--execkey',
228 paa('--execkey',
229 type=str, dest='Global.exec_key',
229 type=str, dest='Global.exec_key',
230 help='path to a file containing an execution key.',
230 help='path to a file containing an execution key.',
231 metavar='keyfile')
231 metavar='keyfile')
232 paa('--ssh',
232 paa('--ssh',
233 type=str, dest='Global.sshserver',
233 type=str, dest='Global.sshserver',
234 help='ssh url for clients to use when connecting to the Controller '
234 help='ssh url for clients to use when connecting to the Controller '
235 'processes. It should be of the form: [user@]server[:port]. The '
235 'processes. It should be of the form: [user@]server[:port]. The '
236 'Controller\'s listening addresses must be accessible from the ssh server',
236 'Controller\'s listening addresses must be accessible from the ssh server',
237 metavar='Global.sshserver')
237 metavar='Global.sshserver')
238 paa('--location',
238 paa('--location',
239 type=str, dest='Global.location',
239 type=str, dest='Global.location',
240 help="The external IP or domain name of this machine, used for disambiguating "
240 help="The external IP or domain name of this machine, used for disambiguating "
241 "engine and client connections.",
241 "engine and client connections.",
242 metavar='Global.location')
242 metavar='Global.location')
243 factory.add_session_arguments(self.parser)
243 factory.add_session_arguments(self.parser)
244 factory.add_registration_arguments(self.parser)
244 factory.add_registration_arguments(self.parser)
245
245
246
246
247 #-----------------------------------------------------------------------------
247 #-----------------------------------------------------------------------------
248 # The main application
248 # The main application
249 #-----------------------------------------------------------------------------
249 #-----------------------------------------------------------------------------
250
250
251
251
252 class IPControllerApp(ApplicationWithClusterDir):
252 class IPControllerApp(ApplicationWithClusterDir):
253
253
254 name = u'ipcontrollerz'
254 name = u'ipcontrollerz'
255 description = _description
255 description = _description
256 command_line_loader = IPControllerAppConfigLoader
256 command_line_loader = IPControllerAppConfigLoader
257 default_config_file_name = default_config_file_name
257 default_config_file_name = default_config_file_name
258 auto_create_cluster_dir = True
258 auto_create_cluster_dir = True
259
259
260
260
261 def create_default_config(self):
261 def create_default_config(self):
262 super(IPControllerApp, self).create_default_config()
262 super(IPControllerApp, self).create_default_config()
263 # Don't set defaults for Global.secure or Global.reuse_furls
263 # Don't set defaults for Global.secure or Global.reuse_furls
264 # as those are set in a component.
264 # as those are set in a component.
265 self.default_config.Global.import_statements = []
265 self.default_config.Global.import_statements = []
266 self.default_config.Global.clean_logs = True
266 self.default_config.Global.clean_logs = True
267 self.default_config.Global.secure = True
267 self.default_config.Global.secure = True
268 self.default_config.Global.reuse_files = False
268 self.default_config.Global.reuse_files = False
269 self.default_config.Global.exec_key = "exec_key.key"
269 self.default_config.Global.exec_key = "exec_key.key"
270 self.default_config.Global.sshserver = None
270 self.default_config.Global.sshserver = None
271 self.default_config.Global.location = None
271 self.default_config.Global.location = None
272
272
273 def pre_construct(self):
273 def pre_construct(self):
274 super(IPControllerApp, self).pre_construct()
274 super(IPControllerApp, self).pre_construct()
275 c = self.master_config
275 c = self.master_config
276 # The defaults for these are set in FCClientServiceFactory and
276 # The defaults for these are set in FCClientServiceFactory and
277 # FCEngineServiceFactory, so we only set them here if the global
277 # FCEngineServiceFactory, so we only set them here if the global
278 # options have be set to override the class level defaults.
278 # options have be set to override the class level defaults.
279
279
280 # if hasattr(c.Global, 'reuse_furls'):
280 # if hasattr(c.Global, 'reuse_furls'):
281 # c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
281 # c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
282 # c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
282 # c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
283 # del c.Global.reuse_furls
283 # del c.Global.reuse_furls
284 # if hasattr(c.Global, 'secure'):
284 # if hasattr(c.Global, 'secure'):
285 # c.FCClientServiceFactory.secure = c.Global.secure
285 # c.FCClientServiceFactory.secure = c.Global.secure
286 # c.FCEngineServiceFactory.secure = c.Global.secure
286 # c.FCEngineServiceFactory.secure = c.Global.secure
287 # del c.Global.secure
287 # del c.Global.secure
288
288
289 def save_connection_dict(self, fname, cdict):
289 def save_connection_dict(self, fname, cdict):
290 """save a connection dict to json file."""
290 """save a connection dict to json file."""
291 c = self.master_config
291 c = self.master_config
292 url = cdict['url']
292 url = cdict['url']
293 location = cdict['location']
293 location = cdict['location']
294 if not location:
294 if not location:
295 try:
295 try:
296 proto,ip,port = split_url(url)
296 proto,ip,port = split_url(url)
297 except AssertionError:
297 except AssertionError:
298 pass
298 pass
299 else:
299 else:
300 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
300 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
301 cdict['location'] = location
301 cdict['location'] = location
302 fname = os.path.join(c.Global.security_dir, fname)
302 fname = os.path.join(c.Global.security_dir, fname)
303 with open(fname, 'w') as f:
303 with open(fname, 'w') as f:
304 f.write(json.dumps(cdict, indent=2))
304 f.write(json.dumps(cdict, indent=2))
305 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
305 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
306
306
307 def load_config_from_json(self):
307 def load_config_from_json(self):
308 """load config from existing json connector files."""
308 """load config from existing json connector files."""
309 c = self.master_config
309 c = self.master_config
310 # load from engine config
310 # load from engine config
311 with open(os.path.join(c.Global.security_dir, 'ipcontroller-engine.json')) as f:
311 with open(os.path.join(c.Global.security_dir, 'ipcontroller-engine.json')) as f:
312 cfg = json.loads(f.read())
312 cfg = json.loads(f.read())
313 key = c.SessionFactory.exec_key = cfg['exec_key']
313 key = c.SessionFactory.exec_key = cfg['exec_key']
314 xport,addr = cfg['url'].split('://')
314 xport,addr = cfg['url'].split('://')
315 c.HubFactory.engine_transport = xport
315 c.HubFactory.engine_transport = xport
316 ip,ports = addr.split(':')
316 ip,ports = addr.split(':')
317 c.HubFactory.engine_ip = ip
317 c.HubFactory.engine_ip = ip
318 c.HubFactory.regport = int(ports)
318 c.HubFactory.regport = int(ports)
319 c.Global.location = cfg['location']
319 c.Global.location = cfg['location']
320
320
321 # load client config
321 # load client config
322 with open(os.path.join(c.Global.security_dir, 'ipcontroller-client.json')) as f:
322 with open(os.path.join(c.Global.security_dir, 'ipcontroller-client.json')) as f:
323 cfg = json.loads(f.read())
323 cfg = json.loads(f.read())
324 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
324 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
325 xport,addr = cfg['url'].split('://')
325 xport,addr = cfg['url'].split('://')
326 c.HubFactory.client_transport = xport
326 c.HubFactory.client_transport = xport
327 ip,ports = addr.split(':')
327 ip,ports = addr.split(':')
328 c.HubFactory.client_ip = ip
328 c.HubFactory.client_ip = ip
329 c.Global.sshserver = cfg['ssh']
329 c.Global.sshserver = cfg['ssh']
330 assert int(ports) == c.HubFactory.regport, "regport mismatch"
330 assert int(ports) == c.HubFactory.regport, "regport mismatch"
331
331
332 def construct(self):
332 def construct(self):
333 # This is the working dir by now.
333 # This is the working dir by now.
334 sys.path.insert(0, '')
334 sys.path.insert(0, '')
335 c = self.master_config
335 c = self.master_config
336
336
337 self.import_statements()
337 self.import_statements()
338 reusing = c.Global.reuse_files
338 reusing = c.Global.reuse_files
339 if reusing:
339 if reusing:
340 try:
340 try:
341 self.load_config_from_json()
341 self.load_config_from_json()
342 except (AssertionError,IOError):
342 except (AssertionError,IOError):
343 reusing=False
343 reusing=False
344 # check again, because reusing may have failed:
344 # check again, because reusing may have failed:
345 if reusing:
345 if reusing:
346 pass
346 pass
347 elif c.Global.secure:
347 elif c.Global.secure:
348 keyfile = os.path.join(c.Global.security_dir, c.Global.exec_key)
348 keyfile = os.path.join(c.Global.security_dir, c.Global.exec_key)
349 key = str(uuid.uuid4())
349 key = str(uuid.uuid4())
350 with open(keyfile, 'w') as f:
350 with open(keyfile, 'w') as f:
351 f.write(key)
351 f.write(key)
352 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
352 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
353 c.SessionFactory.exec_key = key
353 c.SessionFactory.exec_key = key
354 else:
354 else:
355 c.SessionFactory.exec_key = ''
355 c.SessionFactory.exec_key = ''
356 key = None
356 key = None
357
357
358 try:
358 try:
359 self.factory = ControllerFactory(config=c, logname=self.log.name)
359 self.factory = ControllerFactory(config=c, logname=self.log.name)
360 self.start_logging()
360 self.start_logging()
361 self.factory.construct()
361 self.factory.construct()
362 except:
362 except:
363 self.log.error("Couldn't construct the Controller", exc_info=True)
363 self.log.error("Couldn't construct the Controller", exc_info=True)
364 self.exit(1)
364 self.exit(1)
365
365
366 if not reusing:
366 if not reusing:
367 # save to new json config files
367 # save to new json config files
368 f = self.factory
368 f = self.factory
369 cdict = {'exec_key' : key,
369 cdict = {'exec_key' : key,
370 'ssh' : c.Global.sshserver,
370 'ssh' : c.Global.sshserver,
371 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
371 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
372 'location' : c.Global.location
372 'location' : c.Global.location
373 }
373 }
374 self.save_connection_dict('ipcontroller-client.json', cdict)
374 self.save_connection_dict('ipcontroller-client.json', cdict)
375 edict = cdict
375 edict = cdict
376 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
376 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
377 self.save_connection_dict('ipcontroller-engine.json', edict)
377 self.save_connection_dict('ipcontroller-engine.json', edict)
378
378
379
379
380 def save_urls(self):
380 def save_urls(self):
381 """save the registration urls to files."""
381 """save the registration urls to files."""
382 c = self.master_config
382 c = self.master_config
383
383
384 sec_dir = c.Global.security_dir
384 sec_dir = c.Global.security_dir
385 cf = self.factory
385 cf = self.factory
386
386
387 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
387 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
388 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
388 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
389
389
390 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
390 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
391 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
391 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
392
392
393
393
394 def import_statements(self):
394 def import_statements(self):
395 statements = self.master_config.Global.import_statements
395 statements = self.master_config.Global.import_statements
396 for s in statements:
396 for s in statements:
397 try:
397 try:
398 self.log.msg("Executing statement: '%s'" % s)
398 self.log.msg("Executing statement: '%s'" % s)
399 exec s in globals(), locals()
399 exec s in globals(), locals()
400 except:
400 except:
401 self.log.msg("Error running statement: %s" % s)
401 self.log.msg("Error running statement: %s" % s)
402
402
403 def start_logging(self):
403 def start_logging(self):
404 super(IPControllerApp, self).start_logging()
404 super(IPControllerApp, self).start_logging()
405 if self.master_config.Global.log_url:
405 if self.master_config.Global.log_url:
406 context = self.factory.context
406 context = self.factory.context
407 lsock = context.socket(zmq.PUB)
407 lsock = context.socket(zmq.PUB)
408 lsock.connect(self.master_config.Global.log_url)
408 lsock.connect(self.master_config.Global.log_url)
409 handler = PUBHandler(lsock)
409 handler = PUBHandler(lsock)
410 handler.root_topic = 'controller'
410 handler.root_topic = 'controller'
411 handler.setLevel(self.log_level)
411 handler.setLevel(self.log_level)
412 self.log.addHandler(handler)
412 self.log.addHandler(handler)
413 #
413 #
414 def start_app(self):
414 def start_app(self):
415 # Start the subprocesses:
415 # Start the subprocesses:
416 self.factory.start()
416 self.factory.start()
417 self.write_pid_file(overwrite=True)
417 self.write_pid_file(overwrite=True)
418 try:
418 try:
419 self.factory.loop.start()
419 self.factory.loop.start()
420 except KeyboardInterrupt:
420 except KeyboardInterrupt:
421 self.log.critical("Interrupted, Exiting...\n")
421 self.log.critical("Interrupted, Exiting...\n")
422
422
423
423
424 def launch_new_instance():
424 def launch_new_instance():
425 """Create and run the IPython controller"""
425 """Create and run the IPython controller"""
426 app = IPControllerApp()
426 app = IPControllerApp()
427 app.start()
427 app.start()
428
428
429
429
430 if __name__ == '__main__':
430 if __name__ == '__main__':
431 launch_new_instance()
431 launch_new_instance()
@@ -1,294 +1,294 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython engine application
4 The IPython engine application
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import json
18 import json
19 import os
19 import os
20 import sys
20 import sys
21
21
22 import zmq
22 import zmq
23 from zmq.eventloop import ioloop
23 from zmq.eventloop import ioloop
24
24
25 from IPython.zmq.parallel.clusterdir import (
25 from IPython.parallel.clusterdir import (
26 ApplicationWithClusterDir,
26 ApplicationWithClusterDir,
27 ClusterDirConfigLoader
27 ClusterDirConfigLoader
28 )
28 )
29 from IPython.zmq.log import EnginePUBHandler
29 from IPython.zmq.log import EnginePUBHandler
30
30
31 from IPython.zmq.parallel import factory
31 from IPython.parallel import factory
32 from IPython.zmq.parallel.engine import EngineFactory
32 from IPython.parallel.engine import EngineFactory
33 from IPython.zmq.parallel.streamkernel import Kernel
33 from IPython.parallel.streamkernel import Kernel
34 from IPython.zmq.parallel.util import disambiguate_url
34 from IPython.parallel.util import disambiguate_url
35 from IPython.utils.importstring import import_item
35 from IPython.utils.importstring import import_item
36
36
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Module level variables
39 # Module level variables
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 #: The default config file name for this application
42 #: The default config file name for this application
43 default_config_file_name = u'ipenginez_config.py'
43 default_config_file_name = u'ipenginez_config.py'
44
44
45
45
46 mpi4py_init = """from mpi4py import MPI as mpi
46 mpi4py_init = """from mpi4py import MPI as mpi
47 mpi.size = mpi.COMM_WORLD.Get_size()
47 mpi.size = mpi.COMM_WORLD.Get_size()
48 mpi.rank = mpi.COMM_WORLD.Get_rank()
48 mpi.rank = mpi.COMM_WORLD.Get_rank()
49 """
49 """
50
50
51
51
52 pytrilinos_init = """from PyTrilinos import Epetra
52 pytrilinos_init = """from PyTrilinos import Epetra
53 class SimpleStruct:
53 class SimpleStruct:
54 pass
54 pass
55 mpi = SimpleStruct()
55 mpi = SimpleStruct()
56 mpi.rank = 0
56 mpi.rank = 0
57 mpi.size = 0
57 mpi.size = 0
58 """
58 """
59
59
60
60
61 _description = """Start an IPython engine for parallel computing.\n\n
61 _description = """Start an IPython engine for parallel computing.\n\n
62
62
63 IPython engines run in parallel and perform computations on behalf of a client
63 IPython engines run in parallel and perform computations on behalf of a client
64 and controller. A controller needs to be started before the engines. The
64 and controller. A controller needs to be started before the engines. The
65 engine can be configured using command line options or using a cluster
65 engine can be configured using command line options or using a cluster
66 directory. Cluster directories contain config, log and security files and are
66 directory. Cluster directories contain config, log and security files and are
67 usually located in your ipython directory and named as "clusterz_<profile>".
67 usually located in your ipython directory and named as "clusterz_<profile>".
68 See the --profile and --cluster-dir options for details.
68 See the --profile and --cluster-dir options for details.
69 """
69 """
70
70
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 # Command line options
72 # Command line options
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74
74
75
75
76 class IPEngineAppConfigLoader(ClusterDirConfigLoader):
76 class IPEngineAppConfigLoader(ClusterDirConfigLoader):
77
77
78 def _add_arguments(self):
78 def _add_arguments(self):
79 super(IPEngineAppConfigLoader, self)._add_arguments()
79 super(IPEngineAppConfigLoader, self)._add_arguments()
80 paa = self.parser.add_argument
80 paa = self.parser.add_argument
81 # Controller config
81 # Controller config
82 paa('--file', '-f',
82 paa('--file', '-f',
83 type=unicode, dest='Global.url_file',
83 type=unicode, dest='Global.url_file',
84 help='The full location of the file containing the connection information fo '
84 help='The full location of the file containing the connection information fo '
85 'controller. If this is not given, the file must be in the '
85 'controller. If this is not given, the file must be in the '
86 'security directory of the cluster directory. This location is '
86 'security directory of the cluster directory. This location is '
87 'resolved using the --profile and --app-dir options.',
87 'resolved using the --profile and --app-dir options.',
88 metavar='Global.url_file')
88 metavar='Global.url_file')
89 # MPI
89 # MPI
90 paa('--mpi',
90 paa('--mpi',
91 type=str, dest='MPI.use',
91 type=str, dest='MPI.use',
92 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
92 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
93 metavar='MPI.use')
93 metavar='MPI.use')
94 # Global config
94 # Global config
95 paa('--log-to-file',
95 paa('--log-to-file',
96 action='store_true', dest='Global.log_to_file',
96 action='store_true', dest='Global.log_to_file',
97 help='Log to a file in the log directory (default is stdout)')
97 help='Log to a file in the log directory (default is stdout)')
98 paa('--log-url',
98 paa('--log-url',
99 dest='Global.log_url',
99 dest='Global.log_url',
100 help="url of ZMQ logger, as started with iploggerz")
100 help="url of ZMQ logger, as started with iploggerz")
101 # paa('--execkey',
101 # paa('--execkey',
102 # type=str, dest='Global.exec_key',
102 # type=str, dest='Global.exec_key',
103 # help='path to a file containing an execution key.',
103 # help='path to a file containing an execution key.',
104 # metavar='keyfile')
104 # metavar='keyfile')
105 # paa('--no-secure',
105 # paa('--no-secure',
106 # action='store_false', dest='Global.secure',
106 # action='store_false', dest='Global.secure',
107 # help='Turn off execution keys.')
107 # help='Turn off execution keys.')
108 # paa('--secure',
108 # paa('--secure',
109 # action='store_true', dest='Global.secure',
109 # action='store_true', dest='Global.secure',
110 # help='Turn on execution keys (default).')
110 # help='Turn on execution keys (default).')
111 # init command
111 # init command
112 paa('-c',
112 paa('-c',
113 type=str, dest='Global.extra_exec_lines',
113 type=str, dest='Global.extra_exec_lines',
114 help='specify a command to be run at startup')
114 help='specify a command to be run at startup')
115
115
116 factory.add_session_arguments(self.parser)
116 factory.add_session_arguments(self.parser)
117 factory.add_registration_arguments(self.parser)
117 factory.add_registration_arguments(self.parser)
118
118
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # Main application
121 # Main application
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123
123
124
124
125 class IPEngineApp(ApplicationWithClusterDir):
125 class IPEngineApp(ApplicationWithClusterDir):
126
126
127 name = u'ipenginez'
127 name = u'ipenginez'
128 description = _description
128 description = _description
129 command_line_loader = IPEngineAppConfigLoader
129 command_line_loader = IPEngineAppConfigLoader
130 default_config_file_name = default_config_file_name
130 default_config_file_name = default_config_file_name
131 auto_create_cluster_dir = True
131 auto_create_cluster_dir = True
132
132
133 def create_default_config(self):
133 def create_default_config(self):
134 super(IPEngineApp, self).create_default_config()
134 super(IPEngineApp, self).create_default_config()
135
135
136 # The engine should not clean logs as we don't want to remove the
136 # The engine should not clean logs as we don't want to remove the
137 # active log files of other running engines.
137 # active log files of other running engines.
138 self.default_config.Global.clean_logs = False
138 self.default_config.Global.clean_logs = False
139 self.default_config.Global.secure = True
139 self.default_config.Global.secure = True
140
140
141 # Global config attributes
141 # Global config attributes
142 self.default_config.Global.exec_lines = []
142 self.default_config.Global.exec_lines = []
143 self.default_config.Global.extra_exec_lines = ''
143 self.default_config.Global.extra_exec_lines = ''
144
144
145 # Configuration related to the controller
145 # Configuration related to the controller
146 # This must match the filename (path not included) that the controller
146 # This must match the filename (path not included) that the controller
147 # used for the FURL file.
147 # used for the FURL file.
148 self.default_config.Global.url_file = u''
148 self.default_config.Global.url_file = u''
149 self.default_config.Global.url_file_name = u'ipcontroller-engine.json'
149 self.default_config.Global.url_file_name = u'ipcontroller-engine.json'
150 # If given, this is the actual location of the controller's FURL file.
150 # If given, this is the actual location of the controller's FURL file.
151 # If not, this is computed using the profile, app_dir and furl_file_name
151 # If not, this is computed using the profile, app_dir and furl_file_name
152 # self.default_config.Global.key_file_name = u'exec_key.key'
152 # self.default_config.Global.key_file_name = u'exec_key.key'
153 # self.default_config.Global.key_file = u''
153 # self.default_config.Global.key_file = u''
154
154
155 # MPI related config attributes
155 # MPI related config attributes
156 self.default_config.MPI.use = ''
156 self.default_config.MPI.use = ''
157 self.default_config.MPI.mpi4py = mpi4py_init
157 self.default_config.MPI.mpi4py = mpi4py_init
158 self.default_config.MPI.pytrilinos = pytrilinos_init
158 self.default_config.MPI.pytrilinos = pytrilinos_init
159
159
160 def post_load_command_line_config(self):
160 def post_load_command_line_config(self):
161 pass
161 pass
162
162
163 def pre_construct(self):
163 def pre_construct(self):
164 super(IPEngineApp, self).pre_construct()
164 super(IPEngineApp, self).pre_construct()
165 # self.find_cont_url_file()
165 # self.find_cont_url_file()
166 self.find_url_file()
166 self.find_url_file()
167 if self.master_config.Global.extra_exec_lines:
167 if self.master_config.Global.extra_exec_lines:
168 self.master_config.Global.exec_lines.append(self.master_config.Global.extra_exec_lines)
168 self.master_config.Global.exec_lines.append(self.master_config.Global.extra_exec_lines)
169
169
170 # def find_key_file(self):
170 # def find_key_file(self):
171 # """Set the key file.
171 # """Set the key file.
172 #
172 #
173 # Here we don't try to actually see if it exists for is valid as that
173 # Here we don't try to actually see if it exists for is valid as that
174 # is hadled by the connection logic.
174 # is hadled by the connection logic.
175 # """
175 # """
176 # config = self.master_config
176 # config = self.master_config
177 # # Find the actual controller key file
177 # # Find the actual controller key file
178 # if not config.Global.key_file:
178 # if not config.Global.key_file:
179 # try_this = os.path.join(
179 # try_this = os.path.join(
180 # config.Global.cluster_dir,
180 # config.Global.cluster_dir,
181 # config.Global.security_dir,
181 # config.Global.security_dir,
182 # config.Global.key_file_name
182 # config.Global.key_file_name
183 # )
183 # )
184 # config.Global.key_file = try_this
184 # config.Global.key_file = try_this
185
185
186 def find_url_file(self):
186 def find_url_file(self):
187 """Set the key file.
187 """Set the key file.
188
188
189 Here we don't try to actually see if it exists for is valid as that
189 Here we don't try to actually see if it exists for is valid as that
190 is hadled by the connection logic.
190 is hadled by the connection logic.
191 """
191 """
192 config = self.master_config
192 config = self.master_config
193 # Find the actual controller key file
193 # Find the actual controller key file
194 if not config.Global.url_file:
194 if not config.Global.url_file:
195 try_this = os.path.join(
195 try_this = os.path.join(
196 config.Global.cluster_dir,
196 config.Global.cluster_dir,
197 config.Global.security_dir,
197 config.Global.security_dir,
198 config.Global.url_file_name
198 config.Global.url_file_name
199 )
199 )
200 config.Global.url_file = try_this
200 config.Global.url_file = try_this
201
201
202 def construct(self):
202 def construct(self):
203 # This is the working dir by now.
203 # This is the working dir by now.
204 sys.path.insert(0, '')
204 sys.path.insert(0, '')
205 config = self.master_config
205 config = self.master_config
206 # if os.path.exists(config.Global.key_file) and config.Global.secure:
206 # if os.path.exists(config.Global.key_file) and config.Global.secure:
207 # config.SessionFactory.exec_key = config.Global.key_file
207 # config.SessionFactory.exec_key = config.Global.key_file
208 if os.path.exists(config.Global.url_file):
208 if os.path.exists(config.Global.url_file):
209 with open(config.Global.url_file) as f:
209 with open(config.Global.url_file) as f:
210 d = json.loads(f.read())
210 d = json.loads(f.read())
211 for k,v in d.iteritems():
211 for k,v in d.iteritems():
212 if isinstance(v, unicode):
212 if isinstance(v, unicode):
213 d[k] = v.encode()
213 d[k] = v.encode()
214 if d['exec_key']:
214 if d['exec_key']:
215 config.SessionFactory.exec_key = d['exec_key']
215 config.SessionFactory.exec_key = d['exec_key']
216 d['url'] = disambiguate_url(d['url'], d['location'])
216 d['url'] = disambiguate_url(d['url'], d['location'])
217 config.RegistrationFactory.url=d['url']
217 config.RegistrationFactory.url=d['url']
218 config.EngineFactory.location = d['location']
218 config.EngineFactory.location = d['location']
219
219
220
220
221
221
222 config.Kernel.exec_lines = config.Global.exec_lines
222 config.Kernel.exec_lines = config.Global.exec_lines
223
223
224 self.start_mpi()
224 self.start_mpi()
225
225
226 # Create the underlying shell class and EngineService
226 # Create the underlying shell class and EngineService
227 # shell_class = import_item(self.master_config.Global.shell_class)
227 # shell_class = import_item(self.master_config.Global.shell_class)
228 try:
228 try:
229 self.engine = EngineFactory(config=config, logname=self.log.name)
229 self.engine = EngineFactory(config=config, logname=self.log.name)
230 except:
230 except:
231 self.log.error("Couldn't start the Engine", exc_info=True)
231 self.log.error("Couldn't start the Engine", exc_info=True)
232 self.exit(1)
232 self.exit(1)
233
233
234 self.start_logging()
234 self.start_logging()
235
235
236 # Create the service hierarchy
236 # Create the service hierarchy
237 # self.main_service = service.MultiService()
237 # self.main_service = service.MultiService()
238 # self.engine_service.setServiceParent(self.main_service)
238 # self.engine_service.setServiceParent(self.main_service)
239 # self.tub_service = Tub()
239 # self.tub_service = Tub()
240 # self.tub_service.setServiceParent(self.main_service)
240 # self.tub_service.setServiceParent(self.main_service)
241 # # This needs to be called before the connection is initiated
241 # # This needs to be called before the connection is initiated
242 # self.main_service.startService()
242 # self.main_service.startService()
243
243
244 # This initiates the connection to the controller and calls
244 # This initiates the connection to the controller and calls
245 # register_engine to tell the controller we are ready to do work
245 # register_engine to tell the controller we are ready to do work
246 # self.engine_connector = EngineConnector(self.tub_service)
246 # self.engine_connector = EngineConnector(self.tub_service)
247
247
248 # self.log.info("Using furl file: %s" % self.master_config.Global.furl_file)
248 # self.log.info("Using furl file: %s" % self.master_config.Global.furl_file)
249
249
250 # reactor.callWhenRunning(self.call_connect)
250 # reactor.callWhenRunning(self.call_connect)
251
251
252
252
253 def start_logging(self):
253 def start_logging(self):
254 super(IPEngineApp, self).start_logging()
254 super(IPEngineApp, self).start_logging()
255 if self.master_config.Global.log_url:
255 if self.master_config.Global.log_url:
256 context = self.engine.context
256 context = self.engine.context
257 lsock = context.socket(zmq.PUB)
257 lsock = context.socket(zmq.PUB)
258 lsock.connect(self.master_config.Global.log_url)
258 lsock.connect(self.master_config.Global.log_url)
259 handler = EnginePUBHandler(self.engine, lsock)
259 handler = EnginePUBHandler(self.engine, lsock)
260 handler.setLevel(self.log_level)
260 handler.setLevel(self.log_level)
261 self.log.addHandler(handler)
261 self.log.addHandler(handler)
262
262
263 def start_mpi(self):
263 def start_mpi(self):
264 global mpi
264 global mpi
265 mpikey = self.master_config.MPI.use
265 mpikey = self.master_config.MPI.use
266 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
266 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
267 if mpi_import_statement is not None:
267 if mpi_import_statement is not None:
268 try:
268 try:
269 self.log.info("Initializing MPI:")
269 self.log.info("Initializing MPI:")
270 self.log.info(mpi_import_statement)
270 self.log.info(mpi_import_statement)
271 exec mpi_import_statement in globals()
271 exec mpi_import_statement in globals()
272 except:
272 except:
273 mpi = None
273 mpi = None
274 else:
274 else:
275 mpi = None
275 mpi = None
276
276
277
277
278 def start_app(self):
278 def start_app(self):
279 self.engine.start()
279 self.engine.start()
280 try:
280 try:
281 self.engine.loop.start()
281 self.engine.loop.start()
282 except KeyboardInterrupt:
282 except KeyboardInterrupt:
283 self.log.critical("Engine Interrupted, shutting down...\n")
283 self.log.critical("Engine Interrupted, shutting down...\n")
284
284
285
285
286 def launch_new_instance():
286 def launch_new_instance():
287 """Create and run the IPython controller"""
287 """Create and run the IPython controller"""
288 app = IPEngineApp()
288 app = IPEngineApp()
289 app.start()
289 app.start()
290
290
291
291
292 if __name__ == '__main__':
292 if __name__ == '__main__':
293 launch_new_instance()
293 launch_new_instance()
294
294
@@ -1,132 +1,132 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A simple IPython logger application
4 A simple IPython logger application
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2011 The IPython Development Team
8 # Copyright (C) 2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import os
18 import os
19 import sys
19 import sys
20
20
21 import zmq
21 import zmq
22
22
23 from IPython.zmq.parallel.clusterdir import (
23 from IPython.parallel.clusterdir import (
24 ApplicationWithClusterDir,
24 ApplicationWithClusterDir,
25 ClusterDirConfigLoader
25 ClusterDirConfigLoader
26 )
26 )
27 from .logwatcher import LogWatcher
27 from .logwatcher import LogWatcher
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Module level variables
30 # Module level variables
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 #: The default config file name for this application
33 #: The default config file name for this application
34 default_config_file_name = u'iplogger_config.py'
34 default_config_file_name = u'iplogger_config.py'
35
35
36 _description = """Start an IPython logger for parallel computing.\n\n
36 _description = """Start an IPython logger for parallel computing.\n\n
37
37
38 IPython controllers and engines (and your own processes) can broadcast log messages
38 IPython controllers and engines (and your own processes) can broadcast log messages
39 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
39 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
40 logger can be configured using command line options or using a cluster
40 logger can be configured using command line options or using a cluster
41 directory. Cluster directories contain config, log and security files and are
41 directory. Cluster directories contain config, log and security files and are
42 usually located in your ipython directory and named as "clusterz_<profile>".
42 usually located in your ipython directory and named as "clusterz_<profile>".
43 See the --profile and --cluster-dir options for details.
43 See the --profile and --cluster-dir options for details.
44 """
44 """
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Command line options
47 # Command line options
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50
50
51 class IPLoggerAppConfigLoader(ClusterDirConfigLoader):
51 class IPLoggerAppConfigLoader(ClusterDirConfigLoader):
52
52
53 def _add_arguments(self):
53 def _add_arguments(self):
54 super(IPLoggerAppConfigLoader, self)._add_arguments()
54 super(IPLoggerAppConfigLoader, self)._add_arguments()
55 paa = self.parser.add_argument
55 paa = self.parser.add_argument
56 # Controller config
56 # Controller config
57 paa('--url',
57 paa('--url',
58 type=str, dest='LogWatcher.url',
58 type=str, dest='LogWatcher.url',
59 help='The url the LogWatcher will listen on',
59 help='The url the LogWatcher will listen on',
60 )
60 )
61 # MPI
61 # MPI
62 paa('--topics',
62 paa('--topics',
63 type=str, dest='LogWatcher.topics', nargs='+',
63 type=str, dest='LogWatcher.topics', nargs='+',
64 help='What topics to subscribe to',
64 help='What topics to subscribe to',
65 metavar='topics')
65 metavar='topics')
66 # Global config
66 # Global config
67 paa('--log-to-file',
67 paa('--log-to-file',
68 action='store_true', dest='Global.log_to_file',
68 action='store_true', dest='Global.log_to_file',
69 help='Log to a file in the log directory (default is stdout)')
69 help='Log to a file in the log directory (default is stdout)')
70
70
71
71
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 # Main application
73 # Main application
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75
75
76
76
77 class IPLoggerApp(ApplicationWithClusterDir):
77 class IPLoggerApp(ApplicationWithClusterDir):
78
78
79 name = u'iploggerz'
79 name = u'iploggerz'
80 description = _description
80 description = _description
81 command_line_loader = IPLoggerAppConfigLoader
81 command_line_loader = IPLoggerAppConfigLoader
82 default_config_file_name = default_config_file_name
82 default_config_file_name = default_config_file_name
83 auto_create_cluster_dir = True
83 auto_create_cluster_dir = True
84
84
85 def create_default_config(self):
85 def create_default_config(self):
86 super(IPLoggerApp, self).create_default_config()
86 super(IPLoggerApp, self).create_default_config()
87
87
88 # The engine should not clean logs as we don't want to remove the
88 # The engine should not clean logs as we don't want to remove the
89 # active log files of other running engines.
89 # active log files of other running engines.
90 self.default_config.Global.clean_logs = False
90 self.default_config.Global.clean_logs = False
91
91
92 # If given, this is the actual location of the logger's URL file.
92 # If given, this is the actual location of the logger's URL file.
93 # If not, this is computed using the profile, app_dir and furl_file_name
93 # If not, this is computed using the profile, app_dir and furl_file_name
94 self.default_config.Global.url_file_name = u'iplogger.url'
94 self.default_config.Global.url_file_name = u'iplogger.url'
95 self.default_config.Global.url_file = u''
95 self.default_config.Global.url_file = u''
96
96
97 def post_load_command_line_config(self):
97 def post_load_command_line_config(self):
98 pass
98 pass
99
99
100 def pre_construct(self):
100 def pre_construct(self):
101 super(IPLoggerApp, self).pre_construct()
101 super(IPLoggerApp, self).pre_construct()
102
102
103 def construct(self):
103 def construct(self):
104 # This is the working dir by now.
104 # This is the working dir by now.
105 sys.path.insert(0, '')
105 sys.path.insert(0, '')
106
106
107 self.start_logging()
107 self.start_logging()
108
108
109 try:
109 try:
110 self.watcher = LogWatcher(config=self.master_config, logname=self.log.name)
110 self.watcher = LogWatcher(config=self.master_config, logname=self.log.name)
111 except:
111 except:
112 self.log.error("Couldn't start the LogWatcher", exc_info=True)
112 self.log.error("Couldn't start the LogWatcher", exc_info=True)
113 self.exit(1)
113 self.exit(1)
114
114
115
115
116 def start_app(self):
116 def start_app(self):
117 try:
117 try:
118 self.watcher.start()
118 self.watcher.start()
119 self.watcher.loop.start()
119 self.watcher.loop.start()
120 except KeyboardInterrupt:
120 except KeyboardInterrupt:
121 self.log.critical("Logging Interrupted, shutting down...\n")
121 self.log.critical("Logging Interrupted, shutting down...\n")
122
122
123
123
124 def launch_new_instance():
124 def launch_new_instance():
125 """Create and run the IPython LogWatcher"""
125 """Create and run the IPython LogWatcher"""
126 app = IPLoggerApp()
126 app = IPLoggerApp()
127 app.start()
127 app.start()
128
128
129
129
130 if __name__ == '__main__':
130 if __name__ == '__main__':
131 launch_new_instance()
131 launch_new_instance()
132
132
1 NO CONTENT: file renamed from IPython/zmq/parallel/kernelstarter.py to IPython/parallel/kernelstarter.py
NO CONTENT: file renamed from IPython/zmq/parallel/kernelstarter.py to IPython/parallel/kernelstarter.py
@@ -1,971 +1,971 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Facilities for launching IPython processes asynchronously.
4 Facilities for launching IPython processes asynchronously.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import copy
18 import copy
19 import logging
19 import logging
20 import os
20 import os
21 import re
21 import re
22 import stat
22 import stat
23
23
24 from signal import SIGINT, SIGTERM
24 from signal import SIGINT, SIGTERM
25 try:
25 try:
26 from signal import SIGKILL
26 from signal import SIGKILL
27 except ImportError:
27 except ImportError:
28 SIGKILL=SIGTERM
28 SIGKILL=SIGTERM
29
29
30 from subprocess import Popen, PIPE, STDOUT
30 from subprocess import Popen, PIPE, STDOUT
31 try:
31 try:
32 from subprocess import check_output
32 from subprocess import check_output
33 except ImportError:
33 except ImportError:
34 # pre-2.7, define check_output with Popen
34 # pre-2.7, define check_output with Popen
35 def check_output(*args, **kwargs):
35 def check_output(*args, **kwargs):
36 kwargs.update(dict(stdout=PIPE))
36 kwargs.update(dict(stdout=PIPE))
37 p = Popen(*args, **kwargs)
37 p = Popen(*args, **kwargs)
38 out,err = p.communicate()
38 out,err = p.communicate()
39 return out
39 return out
40
40
41 from zmq.eventloop import ioloop
41 from zmq.eventloop import ioloop
42
42
43 from IPython.external import Itpl
43 from IPython.external import Itpl
44 # from IPython.config.configurable import Configurable
44 # from IPython.config.configurable import Configurable
45 from IPython.utils.traitlets import Any, Str, Int, List, Unicode, Dict, Instance, CUnicode
45 from IPython.utils.traitlets import Any, Str, Int, List, Unicode, Dict, Instance, CUnicode
46 from IPython.utils.path import get_ipython_module_path
46 from IPython.utils.path import get_ipython_module_path
47 from IPython.utils.process import find_cmd, pycmd2argv, FindCmdError
47 from IPython.utils.process import find_cmd, pycmd2argv, FindCmdError
48
48
49 from .factory import LoggingFactory
49 from .factory import LoggingFactory
50
50
51 # load winhpcjob only on Windows
51 # load winhpcjob only on Windows
52 try:
52 try:
53 from .winhpcjob import (
53 from .winhpcjob import (
54 IPControllerTask, IPEngineTask,
54 IPControllerTask, IPEngineTask,
55 IPControllerJob, IPEngineSetJob
55 IPControllerJob, IPEngineSetJob
56 )
56 )
57 except ImportError:
57 except ImportError:
58 pass
58 pass
59
59
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Paths to the kernel apps
62 # Paths to the kernel apps
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65
65
66 ipclusterz_cmd_argv = pycmd2argv(get_ipython_module_path(
66 ipclusterz_cmd_argv = pycmd2argv(get_ipython_module_path(
67 'IPython.zmq.parallel.ipclusterapp'
67 'IPython.parallel.ipclusterapp'
68 ))
68 ))
69
69
70 ipenginez_cmd_argv = pycmd2argv(get_ipython_module_path(
70 ipenginez_cmd_argv = pycmd2argv(get_ipython_module_path(
71 'IPython.zmq.parallel.ipengineapp'
71 'IPython.parallel.ipengineapp'
72 ))
72 ))
73
73
74 ipcontrollerz_cmd_argv = pycmd2argv(get_ipython_module_path(
74 ipcontrollerz_cmd_argv = pycmd2argv(get_ipython_module_path(
75 'IPython.zmq.parallel.ipcontrollerapp'
75 'IPython.parallel.ipcontrollerapp'
76 ))
76 ))
77
77
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79 # Base launchers and errors
79 # Base launchers and errors
80 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
81
81
82
82
83 class LauncherError(Exception):
83 class LauncherError(Exception):
84 pass
84 pass
85
85
86
86
87 class ProcessStateError(LauncherError):
87 class ProcessStateError(LauncherError):
88 pass
88 pass
89
89
90
90
91 class UnknownStatus(LauncherError):
91 class UnknownStatus(LauncherError):
92 pass
92 pass
93
93
94
94
95 class BaseLauncher(LoggingFactory):
95 class BaseLauncher(LoggingFactory):
96 """An asbtraction for starting, stopping and signaling a process."""
96 """An asbtraction for starting, stopping and signaling a process."""
97
97
98 # In all of the launchers, the work_dir is where child processes will be
98 # In all of the launchers, the work_dir is where child processes will be
99 # run. This will usually be the cluster_dir, but may not be. any work_dir
99 # run. This will usually be the cluster_dir, but may not be. any work_dir
100 # passed into the __init__ method will override the config value.
100 # passed into the __init__ method will override the config value.
101 # This should not be used to set the work_dir for the actual engine
101 # This should not be used to set the work_dir for the actual engine
102 # and controller. Instead, use their own config files or the
102 # and controller. Instead, use their own config files or the
103 # controller_args, engine_args attributes of the launchers to add
103 # controller_args, engine_args attributes of the launchers to add
104 # the --work-dir option.
104 # the --work-dir option.
105 work_dir = Unicode(u'.')
105 work_dir = Unicode(u'.')
106 loop = Instance('zmq.eventloop.ioloop.IOLoop')
106 loop = Instance('zmq.eventloop.ioloop.IOLoop')
107
107
108 start_data = Any()
108 start_data = Any()
109 stop_data = Any()
109 stop_data = Any()
110
110
111 def _loop_default(self):
111 def _loop_default(self):
112 return ioloop.IOLoop.instance()
112 return ioloop.IOLoop.instance()
113
113
114 def __init__(self, work_dir=u'.', config=None, **kwargs):
114 def __init__(self, work_dir=u'.', config=None, **kwargs):
115 super(BaseLauncher, self).__init__(work_dir=work_dir, config=config, **kwargs)
115 super(BaseLauncher, self).__init__(work_dir=work_dir, config=config, **kwargs)
116 self.state = 'before' # can be before, running, after
116 self.state = 'before' # can be before, running, after
117 self.stop_callbacks = []
117 self.stop_callbacks = []
118 self.start_data = None
118 self.start_data = None
119 self.stop_data = None
119 self.stop_data = None
120
120
121 @property
121 @property
122 def args(self):
122 def args(self):
123 """A list of cmd and args that will be used to start the process.
123 """A list of cmd and args that will be used to start the process.
124
124
125 This is what is passed to :func:`spawnProcess` and the first element
125 This is what is passed to :func:`spawnProcess` and the first element
126 will be the process name.
126 will be the process name.
127 """
127 """
128 return self.find_args()
128 return self.find_args()
129
129
130 def find_args(self):
130 def find_args(self):
131 """The ``.args`` property calls this to find the args list.
131 """The ``.args`` property calls this to find the args list.
132
132
133 Subcommand should implement this to construct the cmd and args.
133 Subcommand should implement this to construct the cmd and args.
134 """
134 """
135 raise NotImplementedError('find_args must be implemented in a subclass')
135 raise NotImplementedError('find_args must be implemented in a subclass')
136
136
137 @property
137 @property
138 def arg_str(self):
138 def arg_str(self):
139 """The string form of the program arguments."""
139 """The string form of the program arguments."""
140 return ' '.join(self.args)
140 return ' '.join(self.args)
141
141
142 @property
142 @property
143 def running(self):
143 def running(self):
144 """Am I running."""
144 """Am I running."""
145 if self.state == 'running':
145 if self.state == 'running':
146 return True
146 return True
147 else:
147 else:
148 return False
148 return False
149
149
150 def start(self):
150 def start(self):
151 """Start the process.
151 """Start the process.
152
152
153 This must return a deferred that fires with information about the
153 This must return a deferred that fires with information about the
154 process starting (like a pid, job id, etc.).
154 process starting (like a pid, job id, etc.).
155 """
155 """
156 raise NotImplementedError('start must be implemented in a subclass')
156 raise NotImplementedError('start must be implemented in a subclass')
157
157
158 def stop(self):
158 def stop(self):
159 """Stop the process and notify observers of stopping.
159 """Stop the process and notify observers of stopping.
160
160
161 This must return a deferred that fires with information about the
161 This must return a deferred that fires with information about the
162 processing stopping, like errors that occur while the process is
162 processing stopping, like errors that occur while the process is
163 attempting to be shut down. This deferred won't fire when the process
163 attempting to be shut down. This deferred won't fire when the process
164 actually stops. To observe the actual process stopping, see
164 actually stops. To observe the actual process stopping, see
165 :func:`observe_stop`.
165 :func:`observe_stop`.
166 """
166 """
167 raise NotImplementedError('stop must be implemented in a subclass')
167 raise NotImplementedError('stop must be implemented in a subclass')
168
168
169 def on_stop(self, f):
169 def on_stop(self, f):
170 """Get a deferred that will fire when the process stops.
170 """Get a deferred that will fire when the process stops.
171
171
172 The deferred will fire with data that contains information about
172 The deferred will fire with data that contains information about
173 the exit status of the process.
173 the exit status of the process.
174 """
174 """
175 if self.state=='after':
175 if self.state=='after':
176 return f(self.stop_data)
176 return f(self.stop_data)
177 else:
177 else:
178 self.stop_callbacks.append(f)
178 self.stop_callbacks.append(f)
179
179
180 def notify_start(self, data):
180 def notify_start(self, data):
181 """Call this to trigger startup actions.
181 """Call this to trigger startup actions.
182
182
183 This logs the process startup and sets the state to 'running'. It is
183 This logs the process startup and sets the state to 'running'. It is
184 a pass-through so it can be used as a callback.
184 a pass-through so it can be used as a callback.
185 """
185 """
186
186
187 self.log.info('Process %r started: %r' % (self.args[0], data))
187 self.log.info('Process %r started: %r' % (self.args[0], data))
188 self.start_data = data
188 self.start_data = data
189 self.state = 'running'
189 self.state = 'running'
190 return data
190 return data
191
191
192 def notify_stop(self, data):
192 def notify_stop(self, data):
193 """Call this to trigger process stop actions.
193 """Call this to trigger process stop actions.
194
194
195 This logs the process stopping and sets the state to 'after'. Call
195 This logs the process stopping and sets the state to 'after'. Call
196 this to trigger all the deferreds from :func:`observe_stop`."""
196 this to trigger all the deferreds from :func:`observe_stop`."""
197
197
198 self.log.info('Process %r stopped: %r' % (self.args[0], data))
198 self.log.info('Process %r stopped: %r' % (self.args[0], data))
199 self.stop_data = data
199 self.stop_data = data
200 self.state = 'after'
200 self.state = 'after'
201 for i in range(len(self.stop_callbacks)):
201 for i in range(len(self.stop_callbacks)):
202 d = self.stop_callbacks.pop()
202 d = self.stop_callbacks.pop()
203 d(data)
203 d(data)
204 return data
204 return data
205
205
206 def signal(self, sig):
206 def signal(self, sig):
207 """Signal the process.
207 """Signal the process.
208
208
209 Return a semi-meaningless deferred after signaling the process.
209 Return a semi-meaningless deferred after signaling the process.
210
210
211 Parameters
211 Parameters
212 ----------
212 ----------
213 sig : str or int
213 sig : str or int
214 'KILL', 'INT', etc., or any signal number
214 'KILL', 'INT', etc., or any signal number
215 """
215 """
216 raise NotImplementedError('signal must be implemented in a subclass')
216 raise NotImplementedError('signal must be implemented in a subclass')
217
217
218
218
219 #-----------------------------------------------------------------------------
219 #-----------------------------------------------------------------------------
220 # Local process launchers
220 # Local process launchers
221 #-----------------------------------------------------------------------------
221 #-----------------------------------------------------------------------------
222
222
223
223
224 class LocalProcessLauncher(BaseLauncher):
224 class LocalProcessLauncher(BaseLauncher):
225 """Start and stop an external process in an asynchronous manner.
225 """Start and stop an external process in an asynchronous manner.
226
226
227 This will launch the external process with a working directory of
227 This will launch the external process with a working directory of
228 ``self.work_dir``.
228 ``self.work_dir``.
229 """
229 """
230
230
231 # This is used to to construct self.args, which is passed to
231 # This is used to to construct self.args, which is passed to
232 # spawnProcess.
232 # spawnProcess.
233 cmd_and_args = List([])
233 cmd_and_args = List([])
234 poll_frequency = Int(100) # in ms
234 poll_frequency = Int(100) # in ms
235
235
236 def __init__(self, work_dir=u'.', config=None, **kwargs):
236 def __init__(self, work_dir=u'.', config=None, **kwargs):
237 super(LocalProcessLauncher, self).__init__(
237 super(LocalProcessLauncher, self).__init__(
238 work_dir=work_dir, config=config, **kwargs
238 work_dir=work_dir, config=config, **kwargs
239 )
239 )
240 self.process = None
240 self.process = None
241 self.start_deferred = None
241 self.start_deferred = None
242 self.poller = None
242 self.poller = None
243
243
244 def find_args(self):
244 def find_args(self):
245 return self.cmd_and_args
245 return self.cmd_and_args
246
246
247 def start(self):
247 def start(self):
248 if self.state == 'before':
248 if self.state == 'before':
249 self.process = Popen(self.args,
249 self.process = Popen(self.args,
250 stdout=PIPE,stderr=PIPE,stdin=PIPE,
250 stdout=PIPE,stderr=PIPE,stdin=PIPE,
251 env=os.environ,
251 env=os.environ,
252 cwd=self.work_dir
252 cwd=self.work_dir
253 )
253 )
254
254
255 self.loop.add_handler(self.process.stdout.fileno(), self.handle_stdout, self.loop.READ)
255 self.loop.add_handler(self.process.stdout.fileno(), self.handle_stdout, self.loop.READ)
256 self.loop.add_handler(self.process.stderr.fileno(), self.handle_stderr, self.loop.READ)
256 self.loop.add_handler(self.process.stderr.fileno(), self.handle_stderr, self.loop.READ)
257 self.poller = ioloop.PeriodicCallback(self.poll, self.poll_frequency, self.loop)
257 self.poller = ioloop.PeriodicCallback(self.poll, self.poll_frequency, self.loop)
258 self.poller.start()
258 self.poller.start()
259 self.notify_start(self.process.pid)
259 self.notify_start(self.process.pid)
260 else:
260 else:
261 s = 'The process was already started and has state: %r' % self.state
261 s = 'The process was already started and has state: %r' % self.state
262 raise ProcessStateError(s)
262 raise ProcessStateError(s)
263
263
264 def stop(self):
264 def stop(self):
265 return self.interrupt_then_kill()
265 return self.interrupt_then_kill()
266
266
267 def signal(self, sig):
267 def signal(self, sig):
268 if self.state == 'running':
268 if self.state == 'running':
269 self.process.send_signal(sig)
269 self.process.send_signal(sig)
270
270
271 def interrupt_then_kill(self, delay=2.0):
271 def interrupt_then_kill(self, delay=2.0):
272 """Send INT, wait a delay and then send KILL."""
272 """Send INT, wait a delay and then send KILL."""
273 self.signal(SIGINT)
273 self.signal(SIGINT)
274 self.killer = ioloop.DelayedCallback(lambda : self.signal(SIGKILL), delay*1000, self.loop)
274 self.killer = ioloop.DelayedCallback(lambda : self.signal(SIGKILL), delay*1000, self.loop)
275 self.killer.start()
275 self.killer.start()
276
276
277 # callbacks, etc:
277 # callbacks, etc:
278
278
279 def handle_stdout(self, fd, events):
279 def handle_stdout(self, fd, events):
280 line = self.process.stdout.readline()
280 line = self.process.stdout.readline()
281 # a stopped process will be readable but return empty strings
281 # a stopped process will be readable but return empty strings
282 if line:
282 if line:
283 self.log.info(line[:-1])
283 self.log.info(line[:-1])
284 else:
284 else:
285 self.poll()
285 self.poll()
286
286
287 def handle_stderr(self, fd, events):
287 def handle_stderr(self, fd, events):
288 line = self.process.stderr.readline()
288 line = self.process.stderr.readline()
289 # a stopped process will be readable but return empty strings
289 # a stopped process will be readable but return empty strings
290 if line:
290 if line:
291 self.log.error(line[:-1])
291 self.log.error(line[:-1])
292 else:
292 else:
293 self.poll()
293 self.poll()
294
294
295 def poll(self):
295 def poll(self):
296 status = self.process.poll()
296 status = self.process.poll()
297 if status is not None:
297 if status is not None:
298 self.poller.stop()
298 self.poller.stop()
299 self.loop.remove_handler(self.process.stdout.fileno())
299 self.loop.remove_handler(self.process.stdout.fileno())
300 self.loop.remove_handler(self.process.stderr.fileno())
300 self.loop.remove_handler(self.process.stderr.fileno())
301 self.notify_stop(dict(exit_code=status, pid=self.process.pid))
301 self.notify_stop(dict(exit_code=status, pid=self.process.pid))
302 return status
302 return status
303
303
304 class LocalControllerLauncher(LocalProcessLauncher):
304 class LocalControllerLauncher(LocalProcessLauncher):
305 """Launch a controller as a regular external process."""
305 """Launch a controller as a regular external process."""
306
306
307 controller_cmd = List(ipcontrollerz_cmd_argv, config=True)
307 controller_cmd = List(ipcontrollerz_cmd_argv, config=True)
308 # Command line arguments to ipcontroller.
308 # Command line arguments to ipcontroller.
309 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
309 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
310
310
311 def find_args(self):
311 def find_args(self):
312 return self.controller_cmd + self.controller_args
312 return self.controller_cmd + self.controller_args
313
313
314 def start(self, cluster_dir):
314 def start(self, cluster_dir):
315 """Start the controller by cluster_dir."""
315 """Start the controller by cluster_dir."""
316 self.controller_args.extend(['--cluster-dir', cluster_dir])
316 self.controller_args.extend(['--cluster-dir', cluster_dir])
317 self.cluster_dir = unicode(cluster_dir)
317 self.cluster_dir = unicode(cluster_dir)
318 self.log.info("Starting LocalControllerLauncher: %r" % self.args)
318 self.log.info("Starting LocalControllerLauncher: %r" % self.args)
319 return super(LocalControllerLauncher, self).start()
319 return super(LocalControllerLauncher, self).start()
320
320
321
321
322 class LocalEngineLauncher(LocalProcessLauncher):
322 class LocalEngineLauncher(LocalProcessLauncher):
323 """Launch a single engine as a regular externall process."""
323 """Launch a single engine as a regular externall process."""
324
324
325 engine_cmd = List(ipenginez_cmd_argv, config=True)
325 engine_cmd = List(ipenginez_cmd_argv, config=True)
326 # Command line arguments for ipengine.
326 # Command line arguments for ipengine.
327 engine_args = List(
327 engine_args = List(
328 ['--log-to-file','--log-level', str(logging.INFO)], config=True
328 ['--log-to-file','--log-level', str(logging.INFO)], config=True
329 )
329 )
330
330
331 def find_args(self):
331 def find_args(self):
332 return self.engine_cmd + self.engine_args
332 return self.engine_cmd + self.engine_args
333
333
334 def start(self, cluster_dir):
334 def start(self, cluster_dir):
335 """Start the engine by cluster_dir."""
335 """Start the engine by cluster_dir."""
336 self.engine_args.extend(['--cluster-dir', cluster_dir])
336 self.engine_args.extend(['--cluster-dir', cluster_dir])
337 self.cluster_dir = unicode(cluster_dir)
337 self.cluster_dir = unicode(cluster_dir)
338 return super(LocalEngineLauncher, self).start()
338 return super(LocalEngineLauncher, self).start()
339
339
340
340
341 class LocalEngineSetLauncher(BaseLauncher):
341 class LocalEngineSetLauncher(BaseLauncher):
342 """Launch a set of engines as regular external processes."""
342 """Launch a set of engines as regular external processes."""
343
343
344 # Command line arguments for ipengine.
344 # Command line arguments for ipengine.
345 engine_args = List(
345 engine_args = List(
346 ['--log-to-file','--log-level', str(logging.INFO)], config=True
346 ['--log-to-file','--log-level', str(logging.INFO)], config=True
347 )
347 )
348 # launcher class
348 # launcher class
349 launcher_class = LocalEngineLauncher
349 launcher_class = LocalEngineLauncher
350
350
351 launchers = Dict()
351 launchers = Dict()
352 stop_data = Dict()
352 stop_data = Dict()
353
353
354 def __init__(self, work_dir=u'.', config=None, **kwargs):
354 def __init__(self, work_dir=u'.', config=None, **kwargs):
355 super(LocalEngineSetLauncher, self).__init__(
355 super(LocalEngineSetLauncher, self).__init__(
356 work_dir=work_dir, config=config, **kwargs
356 work_dir=work_dir, config=config, **kwargs
357 )
357 )
358 self.stop_data = {}
358 self.stop_data = {}
359
359
360 def start(self, n, cluster_dir):
360 def start(self, n, cluster_dir):
361 """Start n engines by profile or cluster_dir."""
361 """Start n engines by profile or cluster_dir."""
362 self.cluster_dir = unicode(cluster_dir)
362 self.cluster_dir = unicode(cluster_dir)
363 dlist = []
363 dlist = []
364 for i in range(n):
364 for i in range(n):
365 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
365 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
366 # Copy the engine args over to each engine launcher.
366 # Copy the engine args over to each engine launcher.
367 el.engine_args = copy.deepcopy(self.engine_args)
367 el.engine_args = copy.deepcopy(self.engine_args)
368 el.on_stop(self._notice_engine_stopped)
368 el.on_stop(self._notice_engine_stopped)
369 d = el.start(cluster_dir)
369 d = el.start(cluster_dir)
370 if i==0:
370 if i==0:
371 self.log.info("Starting LocalEngineSetLauncher: %r" % el.args)
371 self.log.info("Starting LocalEngineSetLauncher: %r" % el.args)
372 self.launchers[i] = el
372 self.launchers[i] = el
373 dlist.append(d)
373 dlist.append(d)
374 self.notify_start(dlist)
374 self.notify_start(dlist)
375 # The consumeErrors here could be dangerous
375 # The consumeErrors here could be dangerous
376 # dfinal = gatherBoth(dlist, consumeErrors=True)
376 # dfinal = gatherBoth(dlist, consumeErrors=True)
377 # dfinal.addCallback(self.notify_start)
377 # dfinal.addCallback(self.notify_start)
378 return dlist
378 return dlist
379
379
380 def find_args(self):
380 def find_args(self):
381 return ['engine set']
381 return ['engine set']
382
382
383 def signal(self, sig):
383 def signal(self, sig):
384 dlist = []
384 dlist = []
385 for el in self.launchers.itervalues():
385 for el in self.launchers.itervalues():
386 d = el.signal(sig)
386 d = el.signal(sig)
387 dlist.append(d)
387 dlist.append(d)
388 # dfinal = gatherBoth(dlist, consumeErrors=True)
388 # dfinal = gatherBoth(dlist, consumeErrors=True)
389 return dlist
389 return dlist
390
390
391 def interrupt_then_kill(self, delay=1.0):
391 def interrupt_then_kill(self, delay=1.0):
392 dlist = []
392 dlist = []
393 for el in self.launchers.itervalues():
393 for el in self.launchers.itervalues():
394 d = el.interrupt_then_kill(delay)
394 d = el.interrupt_then_kill(delay)
395 dlist.append(d)
395 dlist.append(d)
396 # dfinal = gatherBoth(dlist, consumeErrors=True)
396 # dfinal = gatherBoth(dlist, consumeErrors=True)
397 return dlist
397 return dlist
398
398
399 def stop(self):
399 def stop(self):
400 return self.interrupt_then_kill()
400 return self.interrupt_then_kill()
401
401
402 def _notice_engine_stopped(self, data):
402 def _notice_engine_stopped(self, data):
403 pid = data['pid']
403 pid = data['pid']
404 for idx,el in self.launchers.iteritems():
404 for idx,el in self.launchers.iteritems():
405 if el.process.pid == pid:
405 if el.process.pid == pid:
406 break
406 break
407 self.launchers.pop(idx)
407 self.launchers.pop(idx)
408 self.stop_data[idx] = data
408 self.stop_data[idx] = data
409 if not self.launchers:
409 if not self.launchers:
410 self.notify_stop(self.stop_data)
410 self.notify_stop(self.stop_data)
411
411
412
412
413 #-----------------------------------------------------------------------------
413 #-----------------------------------------------------------------------------
414 # MPIExec launchers
414 # MPIExec launchers
415 #-----------------------------------------------------------------------------
415 #-----------------------------------------------------------------------------
416
416
417
417
418 class MPIExecLauncher(LocalProcessLauncher):
418 class MPIExecLauncher(LocalProcessLauncher):
419 """Launch an external process using mpiexec."""
419 """Launch an external process using mpiexec."""
420
420
421 # The mpiexec command to use in starting the process.
421 # The mpiexec command to use in starting the process.
422 mpi_cmd = List(['mpiexec'], config=True)
422 mpi_cmd = List(['mpiexec'], config=True)
423 # The command line arguments to pass to mpiexec.
423 # The command line arguments to pass to mpiexec.
424 mpi_args = List([], config=True)
424 mpi_args = List([], config=True)
425 # The program to start using mpiexec.
425 # The program to start using mpiexec.
426 program = List(['date'], config=True)
426 program = List(['date'], config=True)
427 # The command line argument to the program.
427 # The command line argument to the program.
428 program_args = List([], config=True)
428 program_args = List([], config=True)
429 # The number of instances of the program to start.
429 # The number of instances of the program to start.
430 n = Int(1, config=True)
430 n = Int(1, config=True)
431
431
432 def find_args(self):
432 def find_args(self):
433 """Build self.args using all the fields."""
433 """Build self.args using all the fields."""
434 return self.mpi_cmd + ['-n', str(self.n)] + self.mpi_args + \
434 return self.mpi_cmd + ['-n', str(self.n)] + self.mpi_args + \
435 self.program + self.program_args
435 self.program + self.program_args
436
436
437 def start(self, n):
437 def start(self, n):
438 """Start n instances of the program using mpiexec."""
438 """Start n instances of the program using mpiexec."""
439 self.n = n
439 self.n = n
440 return super(MPIExecLauncher, self).start()
440 return super(MPIExecLauncher, self).start()
441
441
442
442
443 class MPIExecControllerLauncher(MPIExecLauncher):
443 class MPIExecControllerLauncher(MPIExecLauncher):
444 """Launch a controller using mpiexec."""
444 """Launch a controller using mpiexec."""
445
445
446 controller_cmd = List(ipcontrollerz_cmd_argv, config=True)
446 controller_cmd = List(ipcontrollerz_cmd_argv, config=True)
447 # Command line arguments to ipcontroller.
447 # Command line arguments to ipcontroller.
448 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
448 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
449 n = Int(1, config=False)
449 n = Int(1, config=False)
450
450
451 def start(self, cluster_dir):
451 def start(self, cluster_dir):
452 """Start the controller by cluster_dir."""
452 """Start the controller by cluster_dir."""
453 self.controller_args.extend(['--cluster-dir', cluster_dir])
453 self.controller_args.extend(['--cluster-dir', cluster_dir])
454 self.cluster_dir = unicode(cluster_dir)
454 self.cluster_dir = unicode(cluster_dir)
455 self.log.info("Starting MPIExecControllerLauncher: %r" % self.args)
455 self.log.info("Starting MPIExecControllerLauncher: %r" % self.args)
456 return super(MPIExecControllerLauncher, self).start(1)
456 return super(MPIExecControllerLauncher, self).start(1)
457
457
458 def find_args(self):
458 def find_args(self):
459 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
459 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
460 self.controller_cmd + self.controller_args
460 self.controller_cmd + self.controller_args
461
461
462
462
463 class MPIExecEngineSetLauncher(MPIExecLauncher):
463 class MPIExecEngineSetLauncher(MPIExecLauncher):
464
464
465 program = List(ipenginez_cmd_argv, config=True)
465 program = List(ipenginez_cmd_argv, config=True)
466 # Command line arguments for ipengine.
466 # Command line arguments for ipengine.
467 program_args = List(
467 program_args = List(
468 ['--log-to-file','--log-level', str(logging.INFO)], config=True
468 ['--log-to-file','--log-level', str(logging.INFO)], config=True
469 )
469 )
470 n = Int(1, config=True)
470 n = Int(1, config=True)
471
471
472 def start(self, n, cluster_dir):
472 def start(self, n, cluster_dir):
473 """Start n engines by profile or cluster_dir."""
473 """Start n engines by profile or cluster_dir."""
474 self.program_args.extend(['--cluster-dir', cluster_dir])
474 self.program_args.extend(['--cluster-dir', cluster_dir])
475 self.cluster_dir = unicode(cluster_dir)
475 self.cluster_dir = unicode(cluster_dir)
476 self.n = n
476 self.n = n
477 self.log.info('Starting MPIExecEngineSetLauncher: %r' % self.args)
477 self.log.info('Starting MPIExecEngineSetLauncher: %r' % self.args)
478 return super(MPIExecEngineSetLauncher, self).start(n)
478 return super(MPIExecEngineSetLauncher, self).start(n)
479
479
480 #-----------------------------------------------------------------------------
480 #-----------------------------------------------------------------------------
481 # SSH launchers
481 # SSH launchers
482 #-----------------------------------------------------------------------------
482 #-----------------------------------------------------------------------------
483
483
484 # TODO: Get SSH Launcher working again.
484 # TODO: Get SSH Launcher working again.
485
485
486 class SSHLauncher(LocalProcessLauncher):
486 class SSHLauncher(LocalProcessLauncher):
487 """A minimal launcher for ssh.
487 """A minimal launcher for ssh.
488
488
489 To be useful this will probably have to be extended to use the ``sshx``
489 To be useful this will probably have to be extended to use the ``sshx``
490 idea for environment variables. There could be other things this needs
490 idea for environment variables. There could be other things this needs
491 as well.
491 as well.
492 """
492 """
493
493
494 ssh_cmd = List(['ssh'], config=True)
494 ssh_cmd = List(['ssh'], config=True)
495 ssh_args = List(['-tt'], config=True)
495 ssh_args = List(['-tt'], config=True)
496 program = List(['date'], config=True)
496 program = List(['date'], config=True)
497 program_args = List([], config=True)
497 program_args = List([], config=True)
498 hostname = CUnicode('', config=True)
498 hostname = CUnicode('', config=True)
499 user = CUnicode('', config=True)
499 user = CUnicode('', config=True)
500 location = CUnicode('')
500 location = CUnicode('')
501
501
502 def _hostname_changed(self, name, old, new):
502 def _hostname_changed(self, name, old, new):
503 if self.user:
503 if self.user:
504 self.location = u'%s@%s' % (self.user, new)
504 self.location = u'%s@%s' % (self.user, new)
505 else:
505 else:
506 self.location = new
506 self.location = new
507
507
508 def _user_changed(self, name, old, new):
508 def _user_changed(self, name, old, new):
509 self.location = u'%s@%s' % (new, self.hostname)
509 self.location = u'%s@%s' % (new, self.hostname)
510
510
511 def find_args(self):
511 def find_args(self):
512 return self.ssh_cmd + self.ssh_args + [self.location] + \
512 return self.ssh_cmd + self.ssh_args + [self.location] + \
513 self.program + self.program_args
513 self.program + self.program_args
514
514
515 def start(self, cluster_dir, hostname=None, user=None):
515 def start(self, cluster_dir, hostname=None, user=None):
516 self.cluster_dir = unicode(cluster_dir)
516 self.cluster_dir = unicode(cluster_dir)
517 if hostname is not None:
517 if hostname is not None:
518 self.hostname = hostname
518 self.hostname = hostname
519 if user is not None:
519 if user is not None:
520 self.user = user
520 self.user = user
521
521
522 return super(SSHLauncher, self).start()
522 return super(SSHLauncher, self).start()
523
523
524 def signal(self, sig):
524 def signal(self, sig):
525 if self.state == 'running':
525 if self.state == 'running':
526 # send escaped ssh connection-closer
526 # send escaped ssh connection-closer
527 self.process.stdin.write('~.')
527 self.process.stdin.write('~.')
528 self.process.stdin.flush()
528 self.process.stdin.flush()
529
529
530
530
531
531
532 class SSHControllerLauncher(SSHLauncher):
532 class SSHControllerLauncher(SSHLauncher):
533
533
534 program = List(ipcontrollerz_cmd_argv, config=True)
534 program = List(ipcontrollerz_cmd_argv, config=True)
535 # Command line arguments to ipcontroller.
535 # Command line arguments to ipcontroller.
536 program_args = List(['-r', '--log-to-file','--log-level', str(logging.INFO)], config=True)
536 program_args = List(['-r', '--log-to-file','--log-level', str(logging.INFO)], config=True)
537
537
538
538
539 class SSHEngineLauncher(SSHLauncher):
539 class SSHEngineLauncher(SSHLauncher):
540 program = List(ipenginez_cmd_argv, config=True)
540 program = List(ipenginez_cmd_argv, config=True)
541 # Command line arguments for ipengine.
541 # Command line arguments for ipengine.
542 program_args = List(
542 program_args = List(
543 ['--log-to-file','--log-level', str(logging.INFO)], config=True
543 ['--log-to-file','--log-level', str(logging.INFO)], config=True
544 )
544 )
545
545
546 class SSHEngineSetLauncher(LocalEngineSetLauncher):
546 class SSHEngineSetLauncher(LocalEngineSetLauncher):
547 launcher_class = SSHEngineLauncher
547 launcher_class = SSHEngineLauncher
548 engines = Dict(config=True)
548 engines = Dict(config=True)
549
549
550 def start(self, n, cluster_dir):
550 def start(self, n, cluster_dir):
551 """Start engines by profile or cluster_dir.
551 """Start engines by profile or cluster_dir.
552 `n` is ignored, and the `engines` config property is used instead.
552 `n` is ignored, and the `engines` config property is used instead.
553 """
553 """
554
554
555 self.cluster_dir = unicode(cluster_dir)
555 self.cluster_dir = unicode(cluster_dir)
556 dlist = []
556 dlist = []
557 for host, n in self.engines.iteritems():
557 for host, n in self.engines.iteritems():
558 if isinstance(n, (tuple, list)):
558 if isinstance(n, (tuple, list)):
559 n, args = n
559 n, args = n
560 else:
560 else:
561 args = copy.deepcopy(self.engine_args)
561 args = copy.deepcopy(self.engine_args)
562
562
563 if '@' in host:
563 if '@' in host:
564 user,host = host.split('@',1)
564 user,host = host.split('@',1)
565 else:
565 else:
566 user=None
566 user=None
567 for i in range(n):
567 for i in range(n):
568 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
568 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
569
569
570 # Copy the engine args over to each engine launcher.
570 # Copy the engine args over to each engine launcher.
571 i
571 i
572 el.program_args = args
572 el.program_args = args
573 el.on_stop(self._notice_engine_stopped)
573 el.on_stop(self._notice_engine_stopped)
574 d = el.start(cluster_dir, user=user, hostname=host)
574 d = el.start(cluster_dir, user=user, hostname=host)
575 if i==0:
575 if i==0:
576 self.log.info("Starting SSHEngineSetLauncher: %r" % el.args)
576 self.log.info("Starting SSHEngineSetLauncher: %r" % el.args)
577 self.launchers[host+str(i)] = el
577 self.launchers[host+str(i)] = el
578 dlist.append(d)
578 dlist.append(d)
579 self.notify_start(dlist)
579 self.notify_start(dlist)
580 return dlist
580 return dlist
581
581
582
582
583
583
584 #-----------------------------------------------------------------------------
584 #-----------------------------------------------------------------------------
585 # Windows HPC Server 2008 scheduler launchers
585 # Windows HPC Server 2008 scheduler launchers
586 #-----------------------------------------------------------------------------
586 #-----------------------------------------------------------------------------
587
587
588
588
589 # This is only used on Windows.
589 # This is only used on Windows.
590 def find_job_cmd():
590 def find_job_cmd():
591 if os.name=='nt':
591 if os.name=='nt':
592 try:
592 try:
593 return find_cmd('job')
593 return find_cmd('job')
594 except FindCmdError:
594 except FindCmdError:
595 return 'job'
595 return 'job'
596 else:
596 else:
597 return 'job'
597 return 'job'
598
598
599
599
600 class WindowsHPCLauncher(BaseLauncher):
600 class WindowsHPCLauncher(BaseLauncher):
601
601
602 # A regular expression used to get the job id from the output of the
602 # A regular expression used to get the job id from the output of the
603 # submit_command.
603 # submit_command.
604 job_id_regexp = Str(r'\d+', config=True)
604 job_id_regexp = Str(r'\d+', config=True)
605 # The filename of the instantiated job script.
605 # The filename of the instantiated job script.
606 job_file_name = CUnicode(u'ipython_job.xml', config=True)
606 job_file_name = CUnicode(u'ipython_job.xml', config=True)
607 # The full path to the instantiated job script. This gets made dynamically
607 # The full path to the instantiated job script. This gets made dynamically
608 # by combining the work_dir with the job_file_name.
608 # by combining the work_dir with the job_file_name.
609 job_file = CUnicode(u'')
609 job_file = CUnicode(u'')
610 # The hostname of the scheduler to submit the job to
610 # The hostname of the scheduler to submit the job to
611 scheduler = CUnicode('', config=True)
611 scheduler = CUnicode('', config=True)
612 job_cmd = CUnicode(find_job_cmd(), config=True)
612 job_cmd = CUnicode(find_job_cmd(), config=True)
613
613
614 def __init__(self, work_dir=u'.', config=None, **kwargs):
614 def __init__(self, work_dir=u'.', config=None, **kwargs):
615 super(WindowsHPCLauncher, self).__init__(
615 super(WindowsHPCLauncher, self).__init__(
616 work_dir=work_dir, config=config, **kwargs
616 work_dir=work_dir, config=config, **kwargs
617 )
617 )
618
618
619 @property
619 @property
620 def job_file(self):
620 def job_file(self):
621 return os.path.join(self.work_dir, self.job_file_name)
621 return os.path.join(self.work_dir, self.job_file_name)
622
622
623 def write_job_file(self, n):
623 def write_job_file(self, n):
624 raise NotImplementedError("Implement write_job_file in a subclass.")
624 raise NotImplementedError("Implement write_job_file in a subclass.")
625
625
626 def find_args(self):
626 def find_args(self):
627 return [u'job.exe']
627 return [u'job.exe']
628
628
629 def parse_job_id(self, output):
629 def parse_job_id(self, output):
630 """Take the output of the submit command and return the job id."""
630 """Take the output of the submit command and return the job id."""
631 m = re.search(self.job_id_regexp, output)
631 m = re.search(self.job_id_regexp, output)
632 if m is not None:
632 if m is not None:
633 job_id = m.group()
633 job_id = m.group()
634 else:
634 else:
635 raise LauncherError("Job id couldn't be determined: %s" % output)
635 raise LauncherError("Job id couldn't be determined: %s" % output)
636 self.job_id = job_id
636 self.job_id = job_id
637 self.log.info('Job started with job id: %r' % job_id)
637 self.log.info('Job started with job id: %r' % job_id)
638 return job_id
638 return job_id
639
639
640 def start(self, n):
640 def start(self, n):
641 """Start n copies of the process using the Win HPC job scheduler."""
641 """Start n copies of the process using the Win HPC job scheduler."""
642 self.write_job_file(n)
642 self.write_job_file(n)
643 args = [
643 args = [
644 'submit',
644 'submit',
645 '/jobfile:%s' % self.job_file,
645 '/jobfile:%s' % self.job_file,
646 '/scheduler:%s' % self.scheduler
646 '/scheduler:%s' % self.scheduler
647 ]
647 ]
648 self.log.info("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
648 self.log.info("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
649 # Twisted will raise DeprecationWarnings if we try to pass unicode to this
649 # Twisted will raise DeprecationWarnings if we try to pass unicode to this
650 output = check_output([self.job_cmd]+args,
650 output = check_output([self.job_cmd]+args,
651 env=os.environ,
651 env=os.environ,
652 cwd=self.work_dir,
652 cwd=self.work_dir,
653 stderr=STDOUT
653 stderr=STDOUT
654 )
654 )
655 job_id = self.parse_job_id(output)
655 job_id = self.parse_job_id(output)
656 self.notify_start(job_id)
656 self.notify_start(job_id)
657 return job_id
657 return job_id
658
658
659 def stop(self):
659 def stop(self):
660 args = [
660 args = [
661 'cancel',
661 'cancel',
662 self.job_id,
662 self.job_id,
663 '/scheduler:%s' % self.scheduler
663 '/scheduler:%s' % self.scheduler
664 ]
664 ]
665 self.log.info("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
665 self.log.info("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
666 try:
666 try:
667 output = check_output([self.job_cmd]+args,
667 output = check_output([self.job_cmd]+args,
668 env=os.environ,
668 env=os.environ,
669 cwd=self.work_dir,
669 cwd=self.work_dir,
670 stderr=STDOUT
670 stderr=STDOUT
671 )
671 )
672 except:
672 except:
673 output = 'The job already appears to be stoppped: %r' % self.job_id
673 output = 'The job already appears to be stoppped: %r' % self.job_id
674 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
674 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
675 return output
675 return output
676
676
677
677
678 class WindowsHPCControllerLauncher(WindowsHPCLauncher):
678 class WindowsHPCControllerLauncher(WindowsHPCLauncher):
679
679
680 job_file_name = CUnicode(u'ipcontroller_job.xml', config=True)
680 job_file_name = CUnicode(u'ipcontroller_job.xml', config=True)
681 extra_args = List([], config=False)
681 extra_args = List([], config=False)
682
682
683 def write_job_file(self, n):
683 def write_job_file(self, n):
684 job = IPControllerJob(config=self.config)
684 job = IPControllerJob(config=self.config)
685
685
686 t = IPControllerTask(config=self.config)
686 t = IPControllerTask(config=self.config)
687 # The tasks work directory is *not* the actual work directory of
687 # The tasks work directory is *not* the actual work directory of
688 # the controller. It is used as the base path for the stdout/stderr
688 # the controller. It is used as the base path for the stdout/stderr
689 # files that the scheduler redirects to.
689 # files that the scheduler redirects to.
690 t.work_directory = self.cluster_dir
690 t.work_directory = self.cluster_dir
691 # Add the --cluster-dir and from self.start().
691 # Add the --cluster-dir and from self.start().
692 t.controller_args.extend(self.extra_args)
692 t.controller_args.extend(self.extra_args)
693 job.add_task(t)
693 job.add_task(t)
694
694
695 self.log.info("Writing job description file: %s" % self.job_file)
695 self.log.info("Writing job description file: %s" % self.job_file)
696 job.write(self.job_file)
696 job.write(self.job_file)
697
697
698 @property
698 @property
699 def job_file(self):
699 def job_file(self):
700 return os.path.join(self.cluster_dir, self.job_file_name)
700 return os.path.join(self.cluster_dir, self.job_file_name)
701
701
702 def start(self, cluster_dir):
702 def start(self, cluster_dir):
703 """Start the controller by cluster_dir."""
703 """Start the controller by cluster_dir."""
704 self.extra_args = ['--cluster-dir', cluster_dir]
704 self.extra_args = ['--cluster-dir', cluster_dir]
705 self.cluster_dir = unicode(cluster_dir)
705 self.cluster_dir = unicode(cluster_dir)
706 return super(WindowsHPCControllerLauncher, self).start(1)
706 return super(WindowsHPCControllerLauncher, self).start(1)
707
707
708
708
709 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):
709 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):
710
710
711 job_file_name = CUnicode(u'ipengineset_job.xml', config=True)
711 job_file_name = CUnicode(u'ipengineset_job.xml', config=True)
712 extra_args = List([], config=False)
712 extra_args = List([], config=False)
713
713
714 def write_job_file(self, n):
714 def write_job_file(self, n):
715 job = IPEngineSetJob(config=self.config)
715 job = IPEngineSetJob(config=self.config)
716
716
717 for i in range(n):
717 for i in range(n):
718 t = IPEngineTask(config=self.config)
718 t = IPEngineTask(config=self.config)
719 # The tasks work directory is *not* the actual work directory of
719 # The tasks work directory is *not* the actual work directory of
720 # the engine. It is used as the base path for the stdout/stderr
720 # the engine. It is used as the base path for the stdout/stderr
721 # files that the scheduler redirects to.
721 # files that the scheduler redirects to.
722 t.work_directory = self.cluster_dir
722 t.work_directory = self.cluster_dir
723 # Add the --cluster-dir and from self.start().
723 # Add the --cluster-dir and from self.start().
724 t.engine_args.extend(self.extra_args)
724 t.engine_args.extend(self.extra_args)
725 job.add_task(t)
725 job.add_task(t)
726
726
727 self.log.info("Writing job description file: %s" % self.job_file)
727 self.log.info("Writing job description file: %s" % self.job_file)
728 job.write(self.job_file)
728 job.write(self.job_file)
729
729
730 @property
730 @property
731 def job_file(self):
731 def job_file(self):
732 return os.path.join(self.cluster_dir, self.job_file_name)
732 return os.path.join(self.cluster_dir, self.job_file_name)
733
733
734 def start(self, n, cluster_dir):
734 def start(self, n, cluster_dir):
735 """Start the controller by cluster_dir."""
735 """Start the controller by cluster_dir."""
736 self.extra_args = ['--cluster-dir', cluster_dir]
736 self.extra_args = ['--cluster-dir', cluster_dir]
737 self.cluster_dir = unicode(cluster_dir)
737 self.cluster_dir = unicode(cluster_dir)
738 return super(WindowsHPCEngineSetLauncher, self).start(n)
738 return super(WindowsHPCEngineSetLauncher, self).start(n)
739
739
740
740
741 #-----------------------------------------------------------------------------
741 #-----------------------------------------------------------------------------
742 # Batch (PBS) system launchers
742 # Batch (PBS) system launchers
743 #-----------------------------------------------------------------------------
743 #-----------------------------------------------------------------------------
744
744
745 class BatchSystemLauncher(BaseLauncher):
745 class BatchSystemLauncher(BaseLauncher):
746 """Launch an external process using a batch system.
746 """Launch an external process using a batch system.
747
747
748 This class is designed to work with UNIX batch systems like PBS, LSF,
748 This class is designed to work with UNIX batch systems like PBS, LSF,
749 GridEngine, etc. The overall model is that there are different commands
749 GridEngine, etc. The overall model is that there are different commands
750 like qsub, qdel, etc. that handle the starting and stopping of the process.
750 like qsub, qdel, etc. that handle the starting and stopping of the process.
751
751
752 This class also has the notion of a batch script. The ``batch_template``
752 This class also has the notion of a batch script. The ``batch_template``
753 attribute can be set to a string that is a template for the batch script.
753 attribute can be set to a string that is a template for the batch script.
754 This template is instantiated using Itpl. Thus the template can use
754 This template is instantiated using Itpl. Thus the template can use
755 ${n} fot the number of instances. Subclasses can add additional variables
755 ${n} fot the number of instances. Subclasses can add additional variables
756 to the template dict.
756 to the template dict.
757 """
757 """
758
758
759 # Subclasses must fill these in. See PBSEngineSet
759 # Subclasses must fill these in. See PBSEngineSet
760 # The name of the command line program used to submit jobs.
760 # The name of the command line program used to submit jobs.
761 submit_command = List([''], config=True)
761 submit_command = List([''], config=True)
762 # The name of the command line program used to delete jobs.
762 # The name of the command line program used to delete jobs.
763 delete_command = List([''], config=True)
763 delete_command = List([''], config=True)
764 # A regular expression used to get the job id from the output of the
764 # A regular expression used to get the job id from the output of the
765 # submit_command.
765 # submit_command.
766 job_id_regexp = CUnicode('', config=True)
766 job_id_regexp = CUnicode('', config=True)
767 # The string that is the batch script template itself.
767 # The string that is the batch script template itself.
768 batch_template = CUnicode('', config=True)
768 batch_template = CUnicode('', config=True)
769 # The file that contains the batch template
769 # The file that contains the batch template
770 batch_template_file = CUnicode(u'', config=True)
770 batch_template_file = CUnicode(u'', config=True)
771 # The filename of the instantiated batch script.
771 # The filename of the instantiated batch script.
772 batch_file_name = CUnicode(u'batch_script', config=True)
772 batch_file_name = CUnicode(u'batch_script', config=True)
773 # The PBS Queue
773 # The PBS Queue
774 queue = CUnicode(u'', config=True)
774 queue = CUnicode(u'', config=True)
775
775
776 # not configurable, override in subclasses
776 # not configurable, override in subclasses
777 # PBS Job Array regex
777 # PBS Job Array regex
778 job_array_regexp = CUnicode('')
778 job_array_regexp = CUnicode('')
779 job_array_template = CUnicode('')
779 job_array_template = CUnicode('')
780 # PBS Queue regex
780 # PBS Queue regex
781 queue_regexp = CUnicode('')
781 queue_regexp = CUnicode('')
782 queue_template = CUnicode('')
782 queue_template = CUnicode('')
783 # The default batch template, override in subclasses
783 # The default batch template, override in subclasses
784 default_template = CUnicode('')
784 default_template = CUnicode('')
785 # The full path to the instantiated batch script.
785 # The full path to the instantiated batch script.
786 batch_file = CUnicode(u'')
786 batch_file = CUnicode(u'')
787 # the format dict used with batch_template:
787 # the format dict used with batch_template:
788 context = Dict()
788 context = Dict()
789
789
790
790
791 def find_args(self):
791 def find_args(self):
792 return self.submit_command + [self.batch_file]
792 return self.submit_command + [self.batch_file]
793
793
794 def __init__(self, work_dir=u'.', config=None, **kwargs):
794 def __init__(self, work_dir=u'.', config=None, **kwargs):
795 super(BatchSystemLauncher, self).__init__(
795 super(BatchSystemLauncher, self).__init__(
796 work_dir=work_dir, config=config, **kwargs
796 work_dir=work_dir, config=config, **kwargs
797 )
797 )
798 self.batch_file = os.path.join(self.work_dir, self.batch_file_name)
798 self.batch_file = os.path.join(self.work_dir, self.batch_file_name)
799
799
800 def parse_job_id(self, output):
800 def parse_job_id(self, output):
801 """Take the output of the submit command and return the job id."""
801 """Take the output of the submit command and return the job id."""
802 m = re.search(self.job_id_regexp, output)
802 m = re.search(self.job_id_regexp, output)
803 if m is not None:
803 if m is not None:
804 job_id = m.group()
804 job_id = m.group()
805 else:
805 else:
806 raise LauncherError("Job id couldn't be determined: %s" % output)
806 raise LauncherError("Job id couldn't be determined: %s" % output)
807 self.job_id = job_id
807 self.job_id = job_id
808 self.log.info('Job submitted with job id: %r' % job_id)
808 self.log.info('Job submitted with job id: %r' % job_id)
809 return job_id
809 return job_id
810
810
811 def write_batch_script(self, n):
811 def write_batch_script(self, n):
812 """Instantiate and write the batch script to the work_dir."""
812 """Instantiate and write the batch script to the work_dir."""
813 self.context['n'] = n
813 self.context['n'] = n
814 self.context['queue'] = self.queue
814 self.context['queue'] = self.queue
815 print self.context
815 print self.context
816 # first priority is batch_template if set
816 # first priority is batch_template if set
817 if self.batch_template_file and not self.batch_template:
817 if self.batch_template_file and not self.batch_template:
818 # second priority is batch_template_file
818 # second priority is batch_template_file
819 with open(self.batch_template_file) as f:
819 with open(self.batch_template_file) as f:
820 self.batch_template = f.read()
820 self.batch_template = f.read()
821 if not self.batch_template:
821 if not self.batch_template:
822 # third (last) priority is default_template
822 # third (last) priority is default_template
823 self.batch_template = self.default_template
823 self.batch_template = self.default_template
824
824
825 regex = re.compile(self.job_array_regexp)
825 regex = re.compile(self.job_array_regexp)
826 # print regex.search(self.batch_template)
826 # print regex.search(self.batch_template)
827 if not regex.search(self.batch_template):
827 if not regex.search(self.batch_template):
828 self.log.info("adding job array settings to batch script")
828 self.log.info("adding job array settings to batch script")
829 firstline, rest = self.batch_template.split('\n',1)
829 firstline, rest = self.batch_template.split('\n',1)
830 self.batch_template = u'\n'.join([firstline, self.job_array_template, rest])
830 self.batch_template = u'\n'.join([firstline, self.job_array_template, rest])
831
831
832 regex = re.compile(self.queue_regexp)
832 regex = re.compile(self.queue_regexp)
833 # print regex.search(self.batch_template)
833 # print regex.search(self.batch_template)
834 if self.queue and not regex.search(self.batch_template):
834 if self.queue and not regex.search(self.batch_template):
835 self.log.info("adding PBS queue settings to batch script")
835 self.log.info("adding PBS queue settings to batch script")
836 firstline, rest = self.batch_template.split('\n',1)
836 firstline, rest = self.batch_template.split('\n',1)
837 self.batch_template = u'\n'.join([firstline, self.queue_template, rest])
837 self.batch_template = u'\n'.join([firstline, self.queue_template, rest])
838
838
839 script_as_string = Itpl.itplns(self.batch_template, self.context)
839 script_as_string = Itpl.itplns(self.batch_template, self.context)
840 self.log.info('Writing instantiated batch script: %s' % self.batch_file)
840 self.log.info('Writing instantiated batch script: %s' % self.batch_file)
841
841
842 with open(self.batch_file, 'w') as f:
842 with open(self.batch_file, 'w') as f:
843 f.write(script_as_string)
843 f.write(script_as_string)
844 os.chmod(self.batch_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
844 os.chmod(self.batch_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
845
845
846 def start(self, n, cluster_dir):
846 def start(self, n, cluster_dir):
847 """Start n copies of the process using a batch system."""
847 """Start n copies of the process using a batch system."""
848 # Here we save profile and cluster_dir in the context so they
848 # Here we save profile and cluster_dir in the context so they
849 # can be used in the batch script template as ${profile} and
849 # can be used in the batch script template as ${profile} and
850 # ${cluster_dir}
850 # ${cluster_dir}
851 self.context['cluster_dir'] = cluster_dir
851 self.context['cluster_dir'] = cluster_dir
852 self.cluster_dir = unicode(cluster_dir)
852 self.cluster_dir = unicode(cluster_dir)
853 self.write_batch_script(n)
853 self.write_batch_script(n)
854 output = check_output(self.args, env=os.environ)
854 output = check_output(self.args, env=os.environ)
855
855
856 job_id = self.parse_job_id(output)
856 job_id = self.parse_job_id(output)
857 self.notify_start(job_id)
857 self.notify_start(job_id)
858 return job_id
858 return job_id
859
859
860 def stop(self):
860 def stop(self):
861 output = check_output(self.delete_command+[self.job_id], env=os.environ)
861 output = check_output(self.delete_command+[self.job_id], env=os.environ)
862 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
862 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
863 return output
863 return output
864
864
865
865
866 class PBSLauncher(BatchSystemLauncher):
866 class PBSLauncher(BatchSystemLauncher):
867 """A BatchSystemLauncher subclass for PBS."""
867 """A BatchSystemLauncher subclass for PBS."""
868
868
869 submit_command = List(['qsub'], config=True)
869 submit_command = List(['qsub'], config=True)
870 delete_command = List(['qdel'], config=True)
870 delete_command = List(['qdel'], config=True)
871 job_id_regexp = CUnicode(r'\d+', config=True)
871 job_id_regexp = CUnicode(r'\d+', config=True)
872
872
873 batch_file = CUnicode(u'')
873 batch_file = CUnicode(u'')
874 job_array_regexp = CUnicode('#PBS\W+-t\W+[\w\d\-\$]+')
874 job_array_regexp = CUnicode('#PBS\W+-t\W+[\w\d\-\$]+')
875 job_array_template = CUnicode('#PBS -t 1-$n')
875 job_array_template = CUnicode('#PBS -t 1-$n')
876 queue_regexp = CUnicode('#PBS\W+-q\W+\$?\w+')
876 queue_regexp = CUnicode('#PBS\W+-q\W+\$?\w+')
877 queue_template = CUnicode('#PBS -q $queue')
877 queue_template = CUnicode('#PBS -q $queue')
878
878
879
879
880 class PBSControllerLauncher(PBSLauncher):
880 class PBSControllerLauncher(PBSLauncher):
881 """Launch a controller using PBS."""
881 """Launch a controller using PBS."""
882
882
883 batch_file_name = CUnicode(u'pbs_controller', config=True)
883 batch_file_name = CUnicode(u'pbs_controller', config=True)
884 default_template= CUnicode("""#!/bin/sh
884 default_template= CUnicode("""#!/bin/sh
885 #PBS -V
885 #PBS -V
886 #PBS -N ipcontrollerz
886 #PBS -N ipcontrollerz
887 %s --log-to-file --cluster-dir $cluster_dir
887 %s --log-to-file --cluster-dir $cluster_dir
888 """%(' '.join(ipcontrollerz_cmd_argv)))
888 """%(' '.join(ipcontrollerz_cmd_argv)))
889
889
890 def start(self, cluster_dir):
890 def start(self, cluster_dir):
891 """Start the controller by profile or cluster_dir."""
891 """Start the controller by profile or cluster_dir."""
892 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
892 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
893 return super(PBSControllerLauncher, self).start(1, cluster_dir)
893 return super(PBSControllerLauncher, self).start(1, cluster_dir)
894
894
895
895
896 class PBSEngineSetLauncher(PBSLauncher):
896 class PBSEngineSetLauncher(PBSLauncher):
897 """Launch Engines using PBS"""
897 """Launch Engines using PBS"""
898 batch_file_name = CUnicode(u'pbs_engines', config=True)
898 batch_file_name = CUnicode(u'pbs_engines', config=True)
899 default_template= CUnicode(u"""#!/bin/sh
899 default_template= CUnicode(u"""#!/bin/sh
900 #PBS -V
900 #PBS -V
901 #PBS -N ipenginez
901 #PBS -N ipenginez
902 %s --cluster-dir $cluster_dir
902 %s --cluster-dir $cluster_dir
903 """%(' '.join(ipenginez_cmd_argv)))
903 """%(' '.join(ipenginez_cmd_argv)))
904
904
905 def start(self, n, cluster_dir):
905 def start(self, n, cluster_dir):
906 """Start n engines by profile or cluster_dir."""
906 """Start n engines by profile or cluster_dir."""
907 self.log.info('Starting %n engines with PBSEngineSetLauncher: %r' % (n, self.args))
907 self.log.info('Starting %n engines with PBSEngineSetLauncher: %r' % (n, self.args))
908 return super(PBSEngineSetLauncher, self).start(n, cluster_dir)
908 return super(PBSEngineSetLauncher, self).start(n, cluster_dir)
909
909
910 #SGE is very similar to PBS
910 #SGE is very similar to PBS
911
911
912 class SGELauncher(PBSLauncher):
912 class SGELauncher(PBSLauncher):
913 """Sun GridEngine is a PBS clone with slightly different syntax"""
913 """Sun GridEngine is a PBS clone with slightly different syntax"""
914 job_array_regexp = CUnicode('#$$\W+-t\W+[\w\d\-\$]+')
914 job_array_regexp = CUnicode('#$$\W+-t\W+[\w\d\-\$]+')
915 job_array_template = CUnicode('#$$ -t 1-$n')
915 job_array_template = CUnicode('#$$ -t 1-$n')
916 queue_regexp = CUnicode('#$$\W+-q\W+\$?\w+')
916 queue_regexp = CUnicode('#$$\W+-q\W+\$?\w+')
917 queue_template = CUnicode('#$$ -q $queue')
917 queue_template = CUnicode('#$$ -q $queue')
918
918
919 class SGEControllerLauncher(SGELauncher):
919 class SGEControllerLauncher(SGELauncher):
920 """Launch a controller using SGE."""
920 """Launch a controller using SGE."""
921
921
922 batch_file_name = CUnicode(u'sge_controller', config=True)
922 batch_file_name = CUnicode(u'sge_controller', config=True)
923 default_template= CUnicode(u"""#$$ -V
923 default_template= CUnicode(u"""#$$ -V
924 #$$ -S /bin/sh
924 #$$ -S /bin/sh
925 #$$ -N ipcontrollerz
925 #$$ -N ipcontrollerz
926 %s --log-to-file --cluster-dir $cluster_dir
926 %s --log-to-file --cluster-dir $cluster_dir
927 """%(' '.join(ipcontrollerz_cmd_argv)))
927 """%(' '.join(ipcontrollerz_cmd_argv)))
928
928
929 def start(self, cluster_dir):
929 def start(self, cluster_dir):
930 """Start the controller by profile or cluster_dir."""
930 """Start the controller by profile or cluster_dir."""
931 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
931 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
932 return super(PBSControllerLauncher, self).start(1, cluster_dir)
932 return super(PBSControllerLauncher, self).start(1, cluster_dir)
933
933
934 class SGEEngineSetLauncher(SGELauncher):
934 class SGEEngineSetLauncher(SGELauncher):
935 """Launch Engines with SGE"""
935 """Launch Engines with SGE"""
936 batch_file_name = CUnicode(u'sge_engines', config=True)
936 batch_file_name = CUnicode(u'sge_engines', config=True)
937 default_template = CUnicode("""#$$ -V
937 default_template = CUnicode("""#$$ -V
938 #$$ -S /bin/sh
938 #$$ -S /bin/sh
939 #$$ -N ipenginez
939 #$$ -N ipenginez
940 %s --cluster-dir $cluster_dir
940 %s --cluster-dir $cluster_dir
941 """%(' '.join(ipenginez_cmd_argv)))
941 """%(' '.join(ipenginez_cmd_argv)))
942
942
943 def start(self, n, cluster_dir):
943 def start(self, n, cluster_dir):
944 """Start n engines by profile or cluster_dir."""
944 """Start n engines by profile or cluster_dir."""
945 self.log.info('Starting %n engines with SGEEngineSetLauncher: %r' % (n, self.args))
945 self.log.info('Starting %n engines with SGEEngineSetLauncher: %r' % (n, self.args))
946 return super(SGEEngineSetLauncher, self).start(n, cluster_dir)
946 return super(SGEEngineSetLauncher, self).start(n, cluster_dir)
947
947
948
948
949 #-----------------------------------------------------------------------------
949 #-----------------------------------------------------------------------------
950 # A launcher for ipcluster itself!
950 # A launcher for ipcluster itself!
951 #-----------------------------------------------------------------------------
951 #-----------------------------------------------------------------------------
952
952
953
953
954 class IPClusterLauncher(LocalProcessLauncher):
954 class IPClusterLauncher(LocalProcessLauncher):
955 """Launch the ipcluster program in an external process."""
955 """Launch the ipcluster program in an external process."""
956
956
957 ipcluster_cmd = List(ipclusterz_cmd_argv, config=True)
957 ipcluster_cmd = List(ipclusterz_cmd_argv, config=True)
958 # Command line arguments to pass to ipcluster.
958 # Command line arguments to pass to ipcluster.
959 ipcluster_args = List(
959 ipcluster_args = List(
960 ['--clean-logs', '--log-to-file', '--log-level', str(logging.INFO)], config=True)
960 ['--clean-logs', '--log-to-file', '--log-level', str(logging.INFO)], config=True)
961 ipcluster_subcommand = Str('start')
961 ipcluster_subcommand = Str('start')
962 ipcluster_n = Int(2)
962 ipcluster_n = Int(2)
963
963
964 def find_args(self):
964 def find_args(self):
965 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
965 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
966 ['-n', repr(self.ipcluster_n)] + self.ipcluster_args
966 ['-n', repr(self.ipcluster_n)] + self.ipcluster_args
967
967
968 def start(self):
968 def start(self):
969 self.log.info("Starting ipcluster: %r" % self.args)
969 self.log.info("Starting ipcluster: %r" % self.args)
970 return super(IPClusterLauncher, self).start()
970 return super(IPClusterLauncher, self).start()
971
971
1 NO CONTENT: file renamed from IPython/zmq/parallel/logwatcher.py to IPython/parallel/logwatcher.py
NO CONTENT: file renamed from IPython/zmq/parallel/logwatcher.py to IPython/parallel/logwatcher.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/map.py to IPython/parallel/map.py
NO CONTENT: file renamed from IPython/zmq/parallel/map.py to IPython/parallel/map.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/mongodb.py to IPython/parallel/mongodb.py
NO CONTENT: file renamed from IPython/zmq/parallel/mongodb.py to IPython/parallel/mongodb.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/remotefunction.py to IPython/parallel/remotefunction.py
NO CONTENT: file renamed from IPython/zmq/parallel/remotefunction.py to IPython/parallel/remotefunction.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/remotenamespace.py to IPython/parallel/remotenamespace.py
NO CONTENT: file renamed from IPython/zmq/parallel/remotenamespace.py to IPython/parallel/remotenamespace.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/scheduler.py to IPython/parallel/scheduler.py
NO CONTENT: file renamed from IPython/zmq/parallel/scheduler.py to IPython/parallel/scheduler.py
1 NO CONTENT: file renamed from IPython/zmq/parallel/scripts/__init__.py to IPython/parallel/scripts/__init__.py
NO CONTENT: file renamed from IPython/zmq/parallel/scripts/__init__.py to IPython/parallel/scripts/__init__.py
@@ -1,18 +1,18 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2009 The IPython Development Team
5 # Copyright (C) 2008-2009 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15
15
16 from IPython.zmq.parallel.ipclusterapp import launch_new_instance
16 from IPython.parallel.ipclusterapp import launch_new_instance
17
17
18 launch_new_instance()
18 launch_new_instance()
@@ -1,18 +1,18 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2009 The IPython Development Team
5 # Copyright (C) 2008-2009 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15
15
16 from IPython.zmq.parallel.ipcontrollerapp import launch_new_instance
16 from IPython.parallel.ipcontrollerapp import launch_new_instance
17
17
18 launch_new_instance()
18 launch_new_instance()
@@ -1,20 +1,20 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2009 The IPython Development Team
5 # Copyright (C) 2008-2009 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15
15
16 from IPython.zmq.parallel.ipengineapp import launch_new_instance
16 from IPython.parallel.ipengineapp import launch_new_instance
17
17
18 launch_new_instance()
18 launch_new_instance()
19
19
20
20
@@ -1,20 +1,20 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2009 The IPython Development Team
5 # Copyright (C) 2008-2009 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15
15
16 from IPython.zmq.parallel.iploggerapp import launch_new_instance
16 from IPython.parallel.iploggerapp import launch_new_instance
17
17
18 launch_new_instance()
18 launch_new_instance()
19
19
20
20
@@ -1,272 +1,273 b''
1 """A TaskRecord backend using sqlite3"""
1 """A TaskRecord backend using sqlite3"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2011 The IPython Development Team
3 # Copyright (C) 2011 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 import json
9 import json
10 import os
10 import os
11 import cPickle as pickle
11 import cPickle as pickle
12 from datetime import datetime
12 from datetime import datetime
13
13
14 import sqlite3
14 import sqlite3
15
15
16 from IPython.utils.traitlets import CUnicode, CStr, Instance, List
16 from IPython.utils.traitlets import CUnicode, CStr, Instance, List
17 from .dictdb import BaseDB
17 from .dictdb import BaseDB
18 from .util import ISO8601
18 from .util import ISO8601
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # SQLite operators, adapters, and converters
21 # SQLite operators, adapters, and converters
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 operators = {
24 operators = {
25 '$lt' : lambda a,b: "%s < ?",
25 '$lt' : lambda a,b: "%s < ?",
26 '$gt' : ">",
26 '$gt' : ">",
27 # null is handled weird with ==,!=
27 # null is handled weird with ==,!=
28 '$eq' : "IS",
28 '$eq' : "IS",
29 '$ne' : "IS NOT",
29 '$ne' : "IS NOT",
30 '$lte': "<=",
30 '$lte': "<=",
31 '$gte': ">=",
31 '$gte': ">=",
32 '$in' : ('IS', ' OR '),
32 '$in' : ('IS', ' OR '),
33 '$nin': ('IS NOT', ' AND '),
33 '$nin': ('IS NOT', ' AND '),
34 # '$all': None,
34 # '$all': None,
35 # '$mod': None,
35 # '$mod': None,
36 # '$exists' : None
36 # '$exists' : None
37 }
37 }
38
38
39 def _adapt_datetime(dt):
39 def _adapt_datetime(dt):
40 return dt.strftime(ISO8601)
40 return dt.strftime(ISO8601)
41
41
42 def _convert_datetime(ds):
42 def _convert_datetime(ds):
43 if ds is None:
43 if ds is None:
44 return ds
44 return ds
45 else:
45 else:
46 return datetime.strptime(ds, ISO8601)
46 return datetime.strptime(ds, ISO8601)
47
47
48 def _adapt_dict(d):
48 def _adapt_dict(d):
49 return json.dumps(d)
49 return json.dumps(d)
50
50
51 def _convert_dict(ds):
51 def _convert_dict(ds):
52 if ds is None:
52 if ds is None:
53 return ds
53 return ds
54 else:
54 else:
55 return json.loads(ds)
55 return json.loads(ds)
56
56
57 def _adapt_bufs(bufs):
57 def _adapt_bufs(bufs):
58 # this is *horrible*
58 # this is *horrible*
59 # copy buffers into single list and pickle it:
59 # copy buffers into single list and pickle it:
60 if bufs and isinstance(bufs[0], (bytes, buffer)):
60 if bufs and isinstance(bufs[0], (bytes, buffer)):
61 return sqlite3.Binary(pickle.dumps(map(bytes, bufs),-1))
61 return sqlite3.Binary(pickle.dumps(map(bytes, bufs),-1))
62 elif bufs:
62 elif bufs:
63 return bufs
63 return bufs
64 else:
64 else:
65 return None
65 return None
66
66
67 def _convert_bufs(bs):
67 def _convert_bufs(bs):
68 if bs is None:
68 if bs is None:
69 return []
69 return []
70 else:
70 else:
71 return pickle.loads(bytes(bs))
71 return pickle.loads(bytes(bs))
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # SQLiteDB class
74 # SQLiteDB class
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 class SQLiteDB(BaseDB):
77 class SQLiteDB(BaseDB):
78 """SQLite3 TaskRecord backend."""
78 """SQLite3 TaskRecord backend."""
79
79
80 filename = CUnicode('tasks.db', config=True)
80 filename = CUnicode('tasks.db', config=True)
81 location = CUnicode('', config=True)
81 location = CUnicode('', config=True)
82 table = CUnicode("", config=True)
82 table = CUnicode("", config=True)
83
83
84 _db = Instance('sqlite3.Connection')
84 _db = Instance('sqlite3.Connection')
85 _keys = List(['msg_id' ,
85 _keys = List(['msg_id' ,
86 'header' ,
86 'header' ,
87 'content',
87 'content',
88 'buffers',
88 'buffers',
89 'submitted',
89 'submitted',
90 'client_uuid' ,
90 'client_uuid' ,
91 'engine_uuid' ,
91 'engine_uuid' ,
92 'started',
92 'started',
93 'completed',
93 'completed',
94 'resubmitted',
94 'resubmitted',
95 'result_header' ,
95 'result_header' ,
96 'result_content' ,
96 'result_content' ,
97 'result_buffers' ,
97 'result_buffers' ,
98 'queue' ,
98 'queue' ,
99 'pyin' ,
99 'pyin' ,
100 'pyout',
100 'pyout',
101 'pyerr',
101 'pyerr',
102 'stdout',
102 'stdout',
103 'stderr',
103 'stderr',
104 ])
104 ])
105
105
106 def __init__(self, **kwargs):
106 def __init__(self, **kwargs):
107 super(SQLiteDB, self).__init__(**kwargs)
107 super(SQLiteDB, self).__init__(**kwargs)
108 if not self.table:
108 if not self.table:
109 # use session, and prefix _, since starting with # is illegal
109 # use session, and prefix _, since starting with # is illegal
110 self.table = '_'+self.session.replace('-','_')
110 self.table = '_'+self.session.replace('-','_')
111 if not self.location:
111 if not self.location:
112 if hasattr(self.config.Global, 'cluster_dir'):
112 if hasattr(self.config.Global, 'cluster_dir'):
113 self.location = self.config.Global.cluster_dir
113 self.location = self.config.Global.cluster_dir
114 else:
114 else:
115 self.location = '.'
115 self.location = '.'
116 self._init_db()
116 self._init_db()
117
117
118 def _defaults(self):
118 def _defaults(self):
119 """create an empty record"""
119 """create an empty record"""
120 d = {}
120 d = {}
121 for key in self._keys:
121 for key in self._keys:
122 d[key] = None
122 d[key] = None
123 return d
123 return d
124
124
125 def _init_db(self):
125 def _init_db(self):
126 """Connect to the database and get new session number."""
126 """Connect to the database and get new session number."""
127 # register adapters
127 # register adapters
128 sqlite3.register_adapter(datetime, _adapt_datetime)
128 sqlite3.register_adapter(datetime, _adapt_datetime)
129 sqlite3.register_converter('datetime', _convert_datetime)
129 sqlite3.register_converter('datetime', _convert_datetime)
130 sqlite3.register_adapter(dict, _adapt_dict)
130 sqlite3.register_adapter(dict, _adapt_dict)
131 sqlite3.register_converter('dict', _convert_dict)
131 sqlite3.register_converter('dict', _convert_dict)
132 sqlite3.register_adapter(list, _adapt_bufs)
132 sqlite3.register_adapter(list, _adapt_bufs)
133 sqlite3.register_converter('bufs', _convert_bufs)
133 sqlite3.register_converter('bufs', _convert_bufs)
134 # connect to the db
134 # connect to the db
135 dbfile = os.path.join(self.location, self.filename)
135 dbfile = os.path.join(self.location, self.filename)
136 self._db = sqlite3.connect(dbfile, detect_types=sqlite3.PARSE_DECLTYPES)
136 self._db = sqlite3.connect(dbfile, detect_types=sqlite3.PARSE_DECLTYPES, cached_statements=16)
137 # print dir(self._db)
137
138
138 self._db.execute("""CREATE TABLE IF NOT EXISTS %s
139 self._db.execute("""CREATE TABLE IF NOT EXISTS %s
139 (msg_id text PRIMARY KEY,
140 (msg_id text PRIMARY KEY,
140 header dict text,
141 header dict text,
141 content dict text,
142 content dict text,
142 buffers bufs blob,
143 buffers bufs blob,
143 submitted datetime text,
144 submitted datetime text,
144 client_uuid text,
145 client_uuid text,
145 engine_uuid text,
146 engine_uuid text,
146 started datetime text,
147 started datetime text,
147 completed datetime text,
148 completed datetime text,
148 resubmitted datetime text,
149 resubmitted datetime text,
149 result_header dict text,
150 result_header dict text,
150 result_content dict text,
151 result_content dict text,
151 result_buffers bufs blob,
152 result_buffers bufs blob,
152 queue text,
153 queue text,
153 pyin text,
154 pyin text,
154 pyout text,
155 pyout text,
155 pyerr text,
156 pyerr text,
156 stdout text,
157 stdout text,
157 stderr text)
158 stderr text)
158 """%self.table)
159 """%self.table)
159 # self._db.execute("""CREATE TABLE IF NOT EXISTS %s_buffers
160 # self._db.execute("""CREATE TABLE IF NOT EXISTS %s_buffers
160 # (msg_id text, result integer, buffer blob)
161 # (msg_id text, result integer, buffer blob)
161 # """%self.table)
162 # """%self.table)
162 self._db.commit()
163 self._db.commit()
163
164
164 def _dict_to_list(self, d):
165 def _dict_to_list(self, d):
165 """turn a mongodb-style record dict into a list."""
166 """turn a mongodb-style record dict into a list."""
166
167
167 return [ d[key] for key in self._keys ]
168 return [ d[key] for key in self._keys ]
168
169
169 def _list_to_dict(self, line):
170 def _list_to_dict(self, line):
170 """Inverse of dict_to_list"""
171 """Inverse of dict_to_list"""
171 d = self._defaults()
172 d = self._defaults()
172 for key,value in zip(self._keys, line):
173 for key,value in zip(self._keys, line):
173 d[key] = value
174 d[key] = value
174
175
175 return d
176 return d
176
177
177 def _render_expression(self, check):
178 def _render_expression(self, check):
178 """Turn a mongodb-style search dict into an SQL query."""
179 """Turn a mongodb-style search dict into an SQL query."""
179 expressions = []
180 expressions = []
180 args = []
181 args = []
181
182
182 skeys = set(check.keys())
183 skeys = set(check.keys())
183 skeys.difference_update(set(self._keys))
184 skeys.difference_update(set(self._keys))
184 skeys.difference_update(set(['buffers', 'result_buffers']))
185 skeys.difference_update(set(['buffers', 'result_buffers']))
185 if skeys:
186 if skeys:
186 raise KeyError("Illegal testing key(s): %s"%skeys)
187 raise KeyError("Illegal testing key(s): %s"%skeys)
187
188
188 for name,sub_check in check.iteritems():
189 for name,sub_check in check.iteritems():
189 if isinstance(sub_check, dict):
190 if isinstance(sub_check, dict):
190 for test,value in sub_check.iteritems():
191 for test,value in sub_check.iteritems():
191 try:
192 try:
192 op = operators[test]
193 op = operators[test]
193 except KeyError:
194 except KeyError:
194 raise KeyError("Unsupported operator: %r"%test)
195 raise KeyError("Unsupported operator: %r"%test)
195 if isinstance(op, tuple):
196 if isinstance(op, tuple):
196 op, join = op
197 op, join = op
197 expr = "%s %s ?"%(name, op)
198 expr = "%s %s ?"%(name, op)
198 if isinstance(value, (tuple,list)):
199 if isinstance(value, (tuple,list)):
199 expr = '( %s )'%( join.join([expr]*len(value)) )
200 expr = '( %s )'%( join.join([expr]*len(value)) )
200 args.extend(value)
201 args.extend(value)
201 else:
202 else:
202 args.append(value)
203 args.append(value)
203 expressions.append(expr)
204 expressions.append(expr)
204 else:
205 else:
205 # it's an equality check
206 # it's an equality check
206 expressions.append("%s IS ?"%name)
207 expressions.append("%s IS ?"%name)
207 args.append(sub_check)
208 args.append(sub_check)
208
209
209 expr = " AND ".join(expressions)
210 expr = " AND ".join(expressions)
210 return expr, args
211 return expr, args
211
212
212 def add_record(self, msg_id, rec):
213 def add_record(self, msg_id, rec):
213 """Add a new Task Record, by msg_id."""
214 """Add a new Task Record, by msg_id."""
214 d = self._defaults()
215 d = self._defaults()
215 d.update(rec)
216 d.update(rec)
216 d['msg_id'] = msg_id
217 d['msg_id'] = msg_id
217 line = self._dict_to_list(d)
218 line = self._dict_to_list(d)
218 tups = '(%s)'%(','.join(['?']*len(line)))
219 tups = '(%s)'%(','.join(['?']*len(line)))
219 self._db.execute("INSERT INTO %s VALUES %s"%(self.table, tups), line)
220 self._db.execute("INSERT INTO %s VALUES %s"%(self.table, tups), line)
220 self._db.commit()
221 self._db.commit()
221
222
222 def get_record(self, msg_id):
223 def get_record(self, msg_id):
223 """Get a specific Task Record, by msg_id."""
224 """Get a specific Task Record, by msg_id."""
224 cursor = self._db.execute("""SELECT * FROM %s WHERE msg_id==?"""%self.table, (msg_id,))
225 cursor = self._db.execute("""SELECT * FROM %s WHERE msg_id==?"""%self.table, (msg_id,))
225 line = cursor.fetchone()
226 line = cursor.fetchone()
226 if line is None:
227 if line is None:
227 raise KeyError("No such msg: %r"%msg_id)
228 raise KeyError("No such msg: %r"%msg_id)
228 return self._list_to_dict(line)
229 return self._list_to_dict(line)
229
230
230 def update_record(self, msg_id, rec):
231 def update_record(self, msg_id, rec):
231 """Update the data in an existing record."""
232 """Update the data in an existing record."""
232 query = "UPDATE %s SET "%self.table
233 query = "UPDATE %s SET "%self.table
233 sets = []
234 sets = []
234 keys = sorted(rec.keys())
235 keys = sorted(rec.keys())
235 values = []
236 values = []
236 for key in keys:
237 for key in keys:
237 sets.append('%s = ?'%key)
238 sets.append('%s = ?'%key)
238 values.append(rec[key])
239 values.append(rec[key])
239 query += ', '.join(sets)
240 query += ', '.join(sets)
240 query += ' WHERE msg_id == %r'%msg_id
241 query += ' WHERE msg_id == %r'%msg_id
241 self._db.execute(query, values)
242 self._db.execute(query, values)
242 self._db.commit()
243 self._db.commit()
243
244
244 def drop_record(self, msg_id):
245 def drop_record(self, msg_id):
245 """Remove a record from the DB."""
246 """Remove a record from the DB."""
246 self._db.execute("""DELETE FROM %s WHERE mgs_id==?"""%self.table, (msg_id,))
247 self._db.execute("""DELETE FROM %s WHERE mgs_id==?"""%self.table, (msg_id,))
247 self._db.commit()
248 self._db.commit()
248
249
249 def drop_matching_records(self, check):
250 def drop_matching_records(self, check):
250 """Remove a record from the DB."""
251 """Remove a record from the DB."""
251 expr,args = self._render_expression(check)
252 expr,args = self._render_expression(check)
252 query = "DELETE FROM %s WHERE %s"%(self.table, expr)
253 query = "DELETE FROM %s WHERE %s"%(self.table, expr)
253 self._db.execute(query,args)
254 self._db.execute(query,args)
254 self._db.commit()
255 self._db.commit()
255
256
256 def find_records(self, check, id_only=False):
257 def find_records(self, check, id_only=False):
257 """Find records matching a query dict."""
258 """Find records matching a query dict."""
258 req = 'msg_id' if id_only else '*'
259 req = 'msg_id' if id_only else '*'
259 expr,args = self._render_expression(check)
260 expr,args = self._render_expression(check)
260 query = """SELECT %s FROM %s WHERE %s"""%(req, self.table, expr)
261 query = """SELECT %s FROM %s WHERE %s"""%(req, self.table, expr)
261 cursor = self._db.execute(query, args)
262 cursor = self._db.execute(query, args)
262 matches = cursor.fetchall()
263 matches = cursor.fetchall()
263 if id_only:
264 if id_only:
264 return [ m[0] for m in matches ]
265 return [ m[0] for m in matches ]
265 else:
266 else:
266 records = {}
267 records = {}
267 for line in matches:
268 for line in matches:
268 rec = self._list_to_dict(line)
269 rec = self._list_to_dict(line)
269 records[rec['msg_id']] = rec
270 records[rec['msg_id']] = rec
270 return records
271 return records
271
272
272 __all__ = ['SQLiteDB'] No newline at end of file
273 __all__ = ['SQLiteDB']
@@ -1,489 +1,489 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 Kernel adapted from kernel.py to use ZMQ Streams
3 Kernel adapted from kernel.py to use ZMQ Streams
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2011 The IPython Development Team
6 # Copyright (C) 2010-2011 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 # Standard library imports.
16 # Standard library imports.
17 from __future__ import print_function
17 from __future__ import print_function
18
18
19 import sys
19 import sys
20 import time
20 import time
21
21
22 from code import CommandCompiler
22 from code import CommandCompiler
23 from datetime import datetime
23 from datetime import datetime
24 from pprint import pprint
24 from pprint import pprint
25 from signal import SIGTERM, SIGKILL
25 from signal import SIGTERM, SIGKILL
26
26
27 # System library imports.
27 # System library imports.
28 import zmq
28 import zmq
29 from zmq.eventloop import ioloop, zmqstream
29 from zmq.eventloop import ioloop, zmqstream
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.core import ultratb
32 from IPython.core import ultratb
33 from IPython.utils.traitlets import Instance, List, Int, Dict, Set, Str
33 from IPython.utils.traitlets import Instance, List, Int, Dict, Set, Str
34 from IPython.zmq.completer import KernelCompleter
34 from IPython.zmq.completer import KernelCompleter
35 from IPython.zmq.iostream import OutStream
35 from IPython.zmq.iostream import OutStream
36 from IPython.zmq.displayhook import DisplayHook
36 from IPython.zmq.displayhook import DisplayHook
37
37
38 from . import heartmonitor
38 from . import heartmonitor
39 from .client import Client
39 from .client import Client
40 from .error import wrap_exception
40 from .error import wrap_exception
41 from .factory import SessionFactory
41 from .factory import SessionFactory
42 from .streamsession import StreamSession
42 from .streamsession import StreamSession
43 from .util import serialize_object, unpack_apply_message, ISO8601, Namespace
43 from .util import serialize_object, unpack_apply_message, ISO8601, Namespace
44
44
45 def printer(*args):
45 def printer(*args):
46 pprint(args, stream=sys.__stdout__)
46 pprint(args, stream=sys.__stdout__)
47
47
48
48
49 class _Passer:
49 class _Passer:
50 """Empty class that implements `send()` that does nothing."""
50 """Empty class that implements `send()` that does nothing."""
51 def send(self, *args, **kwargs):
51 def send(self, *args, **kwargs):
52 pass
52 pass
53 send_multipart = send
53 send_multipart = send
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Main kernel class
57 # Main kernel class
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class Kernel(SessionFactory):
60 class Kernel(SessionFactory):
61
61
62 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
63 # Kernel interface
63 # Kernel interface
64 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
65
65
66 # kwargs:
66 # kwargs:
67 int_id = Int(-1, config=True)
67 int_id = Int(-1, config=True)
68 user_ns = Dict(config=True)
68 user_ns = Dict(config=True)
69 exec_lines = List(config=True)
69 exec_lines = List(config=True)
70
70
71 control_stream = Instance(zmqstream.ZMQStream)
71 control_stream = Instance(zmqstream.ZMQStream)
72 task_stream = Instance(zmqstream.ZMQStream)
72 task_stream = Instance(zmqstream.ZMQStream)
73 iopub_stream = Instance(zmqstream.ZMQStream)
73 iopub_stream = Instance(zmqstream.ZMQStream)
74 client = Instance('IPython.zmq.parallel.client.Client')
74 client = Instance('IPython.parallel.client.Client')
75
75
76 # internals
76 # internals
77 shell_streams = List()
77 shell_streams = List()
78 compiler = Instance(CommandCompiler, (), {})
78 compiler = Instance(CommandCompiler, (), {})
79 completer = Instance(KernelCompleter)
79 completer = Instance(KernelCompleter)
80
80
81 aborted = Set()
81 aborted = Set()
82 shell_handlers = Dict()
82 shell_handlers = Dict()
83 control_handlers = Dict()
83 control_handlers = Dict()
84
84
85 def _set_prefix(self):
85 def _set_prefix(self):
86 self.prefix = "engine.%s"%self.int_id
86 self.prefix = "engine.%s"%self.int_id
87
87
88 def _connect_completer(self):
88 def _connect_completer(self):
89 self.completer = KernelCompleter(self.user_ns)
89 self.completer = KernelCompleter(self.user_ns)
90
90
91 def __init__(self, **kwargs):
91 def __init__(self, **kwargs):
92 super(Kernel, self).__init__(**kwargs)
92 super(Kernel, self).__init__(**kwargs)
93 self._set_prefix()
93 self._set_prefix()
94 self._connect_completer()
94 self._connect_completer()
95
95
96 self.on_trait_change(self._set_prefix, 'id')
96 self.on_trait_change(self._set_prefix, 'id')
97 self.on_trait_change(self._connect_completer, 'user_ns')
97 self.on_trait_change(self._connect_completer, 'user_ns')
98
98
99 # Build dict of handlers for message types
99 # Build dict of handlers for message types
100 for msg_type in ['execute_request', 'complete_request', 'apply_request',
100 for msg_type in ['execute_request', 'complete_request', 'apply_request',
101 'clear_request']:
101 'clear_request']:
102 self.shell_handlers[msg_type] = getattr(self, msg_type)
102 self.shell_handlers[msg_type] = getattr(self, msg_type)
103
103
104 for msg_type in ['shutdown_request', 'abort_request']+self.shell_handlers.keys():
104 for msg_type in ['shutdown_request', 'abort_request']+self.shell_handlers.keys():
105 self.control_handlers[msg_type] = getattr(self, msg_type)
105 self.control_handlers[msg_type] = getattr(self, msg_type)
106
106
107 self._initial_exec_lines()
107 self._initial_exec_lines()
108
108
109 def _wrap_exception(self, method=None):
109 def _wrap_exception(self, method=None):
110 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
110 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
111 content=wrap_exception(e_info)
111 content=wrap_exception(e_info)
112 return content
112 return content
113
113
114 def _initial_exec_lines(self):
114 def _initial_exec_lines(self):
115 s = _Passer()
115 s = _Passer()
116 content = dict(silent=True, user_variable=[],user_expressions=[])
116 content = dict(silent=True, user_variable=[],user_expressions=[])
117 for line in self.exec_lines:
117 for line in self.exec_lines:
118 self.log.debug("executing initialization: %s"%line)
118 self.log.debug("executing initialization: %s"%line)
119 content.update({'code':line})
119 content.update({'code':line})
120 msg = self.session.msg('execute_request', content)
120 msg = self.session.msg('execute_request', content)
121 self.execute_request(s, [], msg)
121 self.execute_request(s, [], msg)
122
122
123
123
124 #-------------------- control handlers -----------------------------
124 #-------------------- control handlers -----------------------------
125 def abort_queues(self):
125 def abort_queues(self):
126 for stream in self.shell_streams:
126 for stream in self.shell_streams:
127 if stream:
127 if stream:
128 self.abort_queue(stream)
128 self.abort_queue(stream)
129
129
130 def abort_queue(self, stream):
130 def abort_queue(self, stream):
131 while True:
131 while True:
132 try:
132 try:
133 msg = self.session.recv(stream, zmq.NOBLOCK,content=True)
133 msg = self.session.recv(stream, zmq.NOBLOCK,content=True)
134 except zmq.ZMQError as e:
134 except zmq.ZMQError as e:
135 if e.errno == zmq.EAGAIN:
135 if e.errno == zmq.EAGAIN:
136 break
136 break
137 else:
137 else:
138 return
138 return
139 else:
139 else:
140 if msg is None:
140 if msg is None:
141 return
141 return
142 else:
142 else:
143 idents,msg = msg
143 idents,msg = msg
144
144
145 # assert self.reply_socketly_socket.rcvmore(), "Unexpected missing message part."
145 # assert self.reply_socketly_socket.rcvmore(), "Unexpected missing message part."
146 # msg = self.reply_socket.recv_json()
146 # msg = self.reply_socket.recv_json()
147 self.log.info("Aborting:")
147 self.log.info("Aborting:")
148 self.log.info(str(msg))
148 self.log.info(str(msg))
149 msg_type = msg['msg_type']
149 msg_type = msg['msg_type']
150 reply_type = msg_type.split('_')[0] + '_reply'
150 reply_type = msg_type.split('_')[0] + '_reply'
151 # reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
151 # reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
152 # self.reply_socket.send(ident,zmq.SNDMORE)
152 # self.reply_socket.send(ident,zmq.SNDMORE)
153 # self.reply_socket.send_json(reply_msg)
153 # self.reply_socket.send_json(reply_msg)
154 reply_msg = self.session.send(stream, reply_type,
154 reply_msg = self.session.send(stream, reply_type,
155 content={'status' : 'aborted'}, parent=msg, ident=idents)[0]
155 content={'status' : 'aborted'}, parent=msg, ident=idents)[0]
156 self.log.debug(str(reply_msg))
156 self.log.debug(str(reply_msg))
157 # We need to wait a bit for requests to come in. This can probably
157 # We need to wait a bit for requests to come in. This can probably
158 # be set shorter for true asynchronous clients.
158 # be set shorter for true asynchronous clients.
159 time.sleep(0.05)
159 time.sleep(0.05)
160
160
161 def abort_request(self, stream, ident, parent):
161 def abort_request(self, stream, ident, parent):
162 """abort a specifig msg by id"""
162 """abort a specifig msg by id"""
163 msg_ids = parent['content'].get('msg_ids', None)
163 msg_ids = parent['content'].get('msg_ids', None)
164 if isinstance(msg_ids, basestring):
164 if isinstance(msg_ids, basestring):
165 msg_ids = [msg_ids]
165 msg_ids = [msg_ids]
166 if not msg_ids:
166 if not msg_ids:
167 self.abort_queues()
167 self.abort_queues()
168 for mid in msg_ids:
168 for mid in msg_ids:
169 self.aborted.add(str(mid))
169 self.aborted.add(str(mid))
170
170
171 content = dict(status='ok')
171 content = dict(status='ok')
172 reply_msg = self.session.send(stream, 'abort_reply', content=content,
172 reply_msg = self.session.send(stream, 'abort_reply', content=content,
173 parent=parent, ident=ident)
173 parent=parent, ident=ident)
174 self.log.debug(str(reply_msg))
174 self.log.debug(str(reply_msg))
175
175
176 def shutdown_request(self, stream, ident, parent):
176 def shutdown_request(self, stream, ident, parent):
177 """kill ourself. This should really be handled in an external process"""
177 """kill ourself. This should really be handled in an external process"""
178 try:
178 try:
179 self.abort_queues()
179 self.abort_queues()
180 except:
180 except:
181 content = self._wrap_exception('shutdown')
181 content = self._wrap_exception('shutdown')
182 else:
182 else:
183 content = dict(parent['content'])
183 content = dict(parent['content'])
184 content['status'] = 'ok'
184 content['status'] = 'ok'
185 msg = self.session.send(stream, 'shutdown_reply',
185 msg = self.session.send(stream, 'shutdown_reply',
186 content=content, parent=parent, ident=ident)
186 content=content, parent=parent, ident=ident)
187 self.log.debug(str(msg))
187 self.log.debug(str(msg))
188 dc = ioloop.DelayedCallback(lambda : sys.exit(0), 1000, self.loop)
188 dc = ioloop.DelayedCallback(lambda : sys.exit(0), 1000, self.loop)
189 dc.start()
189 dc.start()
190
190
191 def dispatch_control(self, msg):
191 def dispatch_control(self, msg):
192 idents,msg = self.session.feed_identities(msg, copy=False)
192 idents,msg = self.session.feed_identities(msg, copy=False)
193 try:
193 try:
194 msg = self.session.unpack_message(msg, content=True, copy=False)
194 msg = self.session.unpack_message(msg, content=True, copy=False)
195 except:
195 except:
196 self.log.error("Invalid Message", exc_info=True)
196 self.log.error("Invalid Message", exc_info=True)
197 return
197 return
198
198
199 header = msg['header']
199 header = msg['header']
200 msg_id = header['msg_id']
200 msg_id = header['msg_id']
201
201
202 handler = self.control_handlers.get(msg['msg_type'], None)
202 handler = self.control_handlers.get(msg['msg_type'], None)
203 if handler is None:
203 if handler is None:
204 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r"%msg['msg_type'])
204 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r"%msg['msg_type'])
205 else:
205 else:
206 handler(self.control_stream, idents, msg)
206 handler(self.control_stream, idents, msg)
207
207
208
208
209 #-------------------- queue helpers ------------------------------
209 #-------------------- queue helpers ------------------------------
210
210
211 def check_dependencies(self, dependencies):
211 def check_dependencies(self, dependencies):
212 if not dependencies:
212 if not dependencies:
213 return True
213 return True
214 if len(dependencies) == 2 and dependencies[0] in 'any all'.split():
214 if len(dependencies) == 2 and dependencies[0] in 'any all'.split():
215 anyorall = dependencies[0]
215 anyorall = dependencies[0]
216 dependencies = dependencies[1]
216 dependencies = dependencies[1]
217 else:
217 else:
218 anyorall = 'all'
218 anyorall = 'all'
219 results = self.client.get_results(dependencies,status_only=True)
219 results = self.client.get_results(dependencies,status_only=True)
220 if results['status'] != 'ok':
220 if results['status'] != 'ok':
221 return False
221 return False
222
222
223 if anyorall == 'any':
223 if anyorall == 'any':
224 if not results['completed']:
224 if not results['completed']:
225 return False
225 return False
226 else:
226 else:
227 if results['pending']:
227 if results['pending']:
228 return False
228 return False
229
229
230 return True
230 return True
231
231
232 def check_aborted(self, msg_id):
232 def check_aborted(self, msg_id):
233 return msg_id in self.aborted
233 return msg_id in self.aborted
234
234
235 #-------------------- queue handlers -----------------------------
235 #-------------------- queue handlers -----------------------------
236
236
237 def clear_request(self, stream, idents, parent):
237 def clear_request(self, stream, idents, parent):
238 """Clear our namespace."""
238 """Clear our namespace."""
239 self.user_ns = {}
239 self.user_ns = {}
240 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
240 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
241 content = dict(status='ok'))
241 content = dict(status='ok'))
242 self._initial_exec_lines()
242 self._initial_exec_lines()
243
243
244 def execute_request(self, stream, ident, parent):
244 def execute_request(self, stream, ident, parent):
245 self.log.debug('execute request %s'%parent)
245 self.log.debug('execute request %s'%parent)
246 try:
246 try:
247 code = parent[u'content'][u'code']
247 code = parent[u'content'][u'code']
248 except:
248 except:
249 self.log.error("Got bad msg: %s"%parent, exc_info=True)
249 self.log.error("Got bad msg: %s"%parent, exc_info=True)
250 return
250 return
251 self.session.send(self.iopub_stream, u'pyin', {u'code':code},parent=parent,
251 self.session.send(self.iopub_stream, u'pyin', {u'code':code},parent=parent,
252 ident='%s.pyin'%self.prefix)
252 ident='%s.pyin'%self.prefix)
253 started = datetime.now().strftime(ISO8601)
253 started = datetime.now().strftime(ISO8601)
254 try:
254 try:
255 comp_code = self.compiler(code, '<zmq-kernel>')
255 comp_code = self.compiler(code, '<zmq-kernel>')
256 # allow for not overriding displayhook
256 # allow for not overriding displayhook
257 if hasattr(sys.displayhook, 'set_parent'):
257 if hasattr(sys.displayhook, 'set_parent'):
258 sys.displayhook.set_parent(parent)
258 sys.displayhook.set_parent(parent)
259 sys.stdout.set_parent(parent)
259 sys.stdout.set_parent(parent)
260 sys.stderr.set_parent(parent)
260 sys.stderr.set_parent(parent)
261 exec comp_code in self.user_ns, self.user_ns
261 exec comp_code in self.user_ns, self.user_ns
262 except:
262 except:
263 exc_content = self._wrap_exception('execute')
263 exc_content = self._wrap_exception('execute')
264 # exc_msg = self.session.msg(u'pyerr', exc_content, parent)
264 # exc_msg = self.session.msg(u'pyerr', exc_content, parent)
265 self.session.send(self.iopub_stream, u'pyerr', exc_content, parent=parent,
265 self.session.send(self.iopub_stream, u'pyerr', exc_content, parent=parent,
266 ident='%s.pyerr'%self.prefix)
266 ident='%s.pyerr'%self.prefix)
267 reply_content = exc_content
267 reply_content = exc_content
268 else:
268 else:
269 reply_content = {'status' : 'ok'}
269 reply_content = {'status' : 'ok'}
270
270
271 reply_msg = self.session.send(stream, u'execute_reply', reply_content, parent=parent,
271 reply_msg = self.session.send(stream, u'execute_reply', reply_content, parent=parent,
272 ident=ident, subheader = dict(started=started))
272 ident=ident, subheader = dict(started=started))
273 self.log.debug(str(reply_msg))
273 self.log.debug(str(reply_msg))
274 if reply_msg['content']['status'] == u'error':
274 if reply_msg['content']['status'] == u'error':
275 self.abort_queues()
275 self.abort_queues()
276
276
277 def complete_request(self, stream, ident, parent):
277 def complete_request(self, stream, ident, parent):
278 matches = {'matches' : self.complete(parent),
278 matches = {'matches' : self.complete(parent),
279 'status' : 'ok'}
279 'status' : 'ok'}
280 completion_msg = self.session.send(stream, 'complete_reply',
280 completion_msg = self.session.send(stream, 'complete_reply',
281 matches, parent, ident)
281 matches, parent, ident)
282 # print >> sys.__stdout__, completion_msg
282 # print >> sys.__stdout__, completion_msg
283
283
284 def complete(self, msg):
284 def complete(self, msg):
285 return self.completer.complete(msg.content.line, msg.content.text)
285 return self.completer.complete(msg.content.line, msg.content.text)
286
286
287 def apply_request(self, stream, ident, parent):
287 def apply_request(self, stream, ident, parent):
288 # flush previous reply, so this request won't block it
288 # flush previous reply, so this request won't block it
289 stream.flush(zmq.POLLOUT)
289 stream.flush(zmq.POLLOUT)
290
290
291 try:
291 try:
292 content = parent[u'content']
292 content = parent[u'content']
293 bufs = parent[u'buffers']
293 bufs = parent[u'buffers']
294 msg_id = parent['header']['msg_id']
294 msg_id = parent['header']['msg_id']
295 # bound = parent['header'].get('bound', False)
295 # bound = parent['header'].get('bound', False)
296 except:
296 except:
297 self.log.error("Got bad msg: %s"%parent, exc_info=True)
297 self.log.error("Got bad msg: %s"%parent, exc_info=True)
298 return
298 return
299 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
299 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
300 # self.iopub_stream.send(pyin_msg)
300 # self.iopub_stream.send(pyin_msg)
301 # self.session.send(self.iopub_stream, u'pyin', {u'code':code},parent=parent)
301 # self.session.send(self.iopub_stream, u'pyin', {u'code':code},parent=parent)
302 sub = {'dependencies_met' : True, 'engine' : self.ident,
302 sub = {'dependencies_met' : True, 'engine' : self.ident,
303 'started': datetime.now().strftime(ISO8601)}
303 'started': datetime.now().strftime(ISO8601)}
304 try:
304 try:
305 # allow for not overriding displayhook
305 # allow for not overriding displayhook
306 if hasattr(sys.displayhook, 'set_parent'):
306 if hasattr(sys.displayhook, 'set_parent'):
307 sys.displayhook.set_parent(parent)
307 sys.displayhook.set_parent(parent)
308 sys.stdout.set_parent(parent)
308 sys.stdout.set_parent(parent)
309 sys.stderr.set_parent(parent)
309 sys.stderr.set_parent(parent)
310 # exec "f(*args,**kwargs)" in self.user_ns, self.user_ns
310 # exec "f(*args,**kwargs)" in self.user_ns, self.user_ns
311 working = self.user_ns
311 working = self.user_ns
312 # suffix =
312 # suffix =
313 prefix = "_"+str(msg_id).replace("-","")+"_"
313 prefix = "_"+str(msg_id).replace("-","")+"_"
314
314
315 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
315 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
316 # if bound:
316 # if bound:
317 # bound_ns = Namespace(working)
317 # bound_ns = Namespace(working)
318 # args = [bound_ns]+list(args)
318 # args = [bound_ns]+list(args)
319
319
320 fname = getattr(f, '__name__', 'f')
320 fname = getattr(f, '__name__', 'f')
321
321
322 fname = prefix+"f"
322 fname = prefix+"f"
323 argname = prefix+"args"
323 argname = prefix+"args"
324 kwargname = prefix+"kwargs"
324 kwargname = prefix+"kwargs"
325 resultname = prefix+"result"
325 resultname = prefix+"result"
326
326
327 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
327 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
328 # print ns
328 # print ns
329 working.update(ns)
329 working.update(ns)
330 code = "%s=%s(*%s,**%s)"%(resultname, fname, argname, kwargname)
330 code = "%s=%s(*%s,**%s)"%(resultname, fname, argname, kwargname)
331 try:
331 try:
332 exec code in working,working
332 exec code in working,working
333 result = working.get(resultname)
333 result = working.get(resultname)
334 finally:
334 finally:
335 for key in ns.iterkeys():
335 for key in ns.iterkeys():
336 working.pop(key)
336 working.pop(key)
337 # if bound:
337 # if bound:
338 # working.update(bound_ns)
338 # working.update(bound_ns)
339
339
340 packed_result,buf = serialize_object(result)
340 packed_result,buf = serialize_object(result)
341 result_buf = [packed_result]+buf
341 result_buf = [packed_result]+buf
342 except:
342 except:
343 exc_content = self._wrap_exception('apply')
343 exc_content = self._wrap_exception('apply')
344 # exc_msg = self.session.msg(u'pyerr', exc_content, parent)
344 # exc_msg = self.session.msg(u'pyerr', exc_content, parent)
345 self.session.send(self.iopub_stream, u'pyerr', exc_content, parent=parent,
345 self.session.send(self.iopub_stream, u'pyerr', exc_content, parent=parent,
346 ident='%s.pyerr'%self.prefix)
346 ident='%s.pyerr'%self.prefix)
347 reply_content = exc_content
347 reply_content = exc_content
348 result_buf = []
348 result_buf = []
349
349
350 if exc_content['ename'] == 'UnmetDependency':
350 if exc_content['ename'] == 'UnmetDependency':
351 sub['dependencies_met'] = False
351 sub['dependencies_met'] = False
352 else:
352 else:
353 reply_content = {'status' : 'ok'}
353 reply_content = {'status' : 'ok'}
354
354
355 # put 'ok'/'error' status in header, for scheduler introspection:
355 # put 'ok'/'error' status in header, for scheduler introspection:
356 sub['status'] = reply_content['status']
356 sub['status'] = reply_content['status']
357
357
358 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
358 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
359 parent=parent, ident=ident,buffers=result_buf, subheader=sub)
359 parent=parent, ident=ident,buffers=result_buf, subheader=sub)
360
360
361 # flush i/o
361 # flush i/o
362 # should this be before reply_msg is sent, like in the single-kernel code,
362 # should this be before reply_msg is sent, like in the single-kernel code,
363 # or should nothing get in the way of real results?
363 # or should nothing get in the way of real results?
364 sys.stdout.flush()
364 sys.stdout.flush()
365 sys.stderr.flush()
365 sys.stderr.flush()
366
366
367 def dispatch_queue(self, stream, msg):
367 def dispatch_queue(self, stream, msg):
368 self.control_stream.flush()
368 self.control_stream.flush()
369 idents,msg = self.session.feed_identities(msg, copy=False)
369 idents,msg = self.session.feed_identities(msg, copy=False)
370 try:
370 try:
371 msg = self.session.unpack_message(msg, content=True, copy=False)
371 msg = self.session.unpack_message(msg, content=True, copy=False)
372 except:
372 except:
373 self.log.error("Invalid Message", exc_info=True)
373 self.log.error("Invalid Message", exc_info=True)
374 return
374 return
375
375
376
376
377 header = msg['header']
377 header = msg['header']
378 msg_id = header['msg_id']
378 msg_id = header['msg_id']
379 if self.check_aborted(msg_id):
379 if self.check_aborted(msg_id):
380 self.aborted.remove(msg_id)
380 self.aborted.remove(msg_id)
381 # is it safe to assume a msg_id will not be resubmitted?
381 # is it safe to assume a msg_id will not be resubmitted?
382 reply_type = msg['msg_type'].split('_')[0] + '_reply'
382 reply_type = msg['msg_type'].split('_')[0] + '_reply'
383 reply_msg = self.session.send(stream, reply_type,
383 reply_msg = self.session.send(stream, reply_type,
384 content={'status' : 'aborted'}, parent=msg, ident=idents)
384 content={'status' : 'aborted'}, parent=msg, ident=idents)
385 return
385 return
386 handler = self.shell_handlers.get(msg['msg_type'], None)
386 handler = self.shell_handlers.get(msg['msg_type'], None)
387 if handler is None:
387 if handler is None:
388 self.log.error("UNKNOWN MESSAGE TYPE: %r"%msg['msg_type'])
388 self.log.error("UNKNOWN MESSAGE TYPE: %r"%msg['msg_type'])
389 else:
389 else:
390 handler(stream, idents, msg)
390 handler(stream, idents, msg)
391
391
392 def start(self):
392 def start(self):
393 #### stream mode:
393 #### stream mode:
394 if self.control_stream:
394 if self.control_stream:
395 self.control_stream.on_recv(self.dispatch_control, copy=False)
395 self.control_stream.on_recv(self.dispatch_control, copy=False)
396 self.control_stream.on_err(printer)
396 self.control_stream.on_err(printer)
397
397
398 def make_dispatcher(stream):
398 def make_dispatcher(stream):
399 def dispatcher(msg):
399 def dispatcher(msg):
400 return self.dispatch_queue(stream, msg)
400 return self.dispatch_queue(stream, msg)
401 return dispatcher
401 return dispatcher
402
402
403 for s in self.shell_streams:
403 for s in self.shell_streams:
404 s.on_recv(make_dispatcher(s), copy=False)
404 s.on_recv(make_dispatcher(s), copy=False)
405 s.on_err(printer)
405 s.on_err(printer)
406
406
407 if self.iopub_stream:
407 if self.iopub_stream:
408 self.iopub_stream.on_err(printer)
408 self.iopub_stream.on_err(printer)
409
409
410 #### while True mode:
410 #### while True mode:
411 # while True:
411 # while True:
412 # idle = True
412 # idle = True
413 # try:
413 # try:
414 # msg = self.shell_stream.socket.recv_multipart(
414 # msg = self.shell_stream.socket.recv_multipart(
415 # zmq.NOBLOCK, copy=False)
415 # zmq.NOBLOCK, copy=False)
416 # except zmq.ZMQError, e:
416 # except zmq.ZMQError, e:
417 # if e.errno != zmq.EAGAIN:
417 # if e.errno != zmq.EAGAIN:
418 # raise e
418 # raise e
419 # else:
419 # else:
420 # idle=False
420 # idle=False
421 # self.dispatch_queue(self.shell_stream, msg)
421 # self.dispatch_queue(self.shell_stream, msg)
422 #
422 #
423 # if not self.task_stream.empty():
423 # if not self.task_stream.empty():
424 # idle=False
424 # idle=False
425 # msg = self.task_stream.recv_multipart()
425 # msg = self.task_stream.recv_multipart()
426 # self.dispatch_queue(self.task_stream, msg)
426 # self.dispatch_queue(self.task_stream, msg)
427 # if idle:
427 # if idle:
428 # # don't busywait
428 # # don't busywait
429 # time.sleep(1e-3)
429 # time.sleep(1e-3)
430
430
431 def make_kernel(int_id, identity, control_addr, shell_addrs, iopub_addr, hb_addrs,
431 def make_kernel(int_id, identity, control_addr, shell_addrs, iopub_addr, hb_addrs,
432 client_addr=None, loop=None, context=None, key=None,
432 client_addr=None, loop=None, context=None, key=None,
433 out_stream_factory=OutStream, display_hook_factory=DisplayHook):
433 out_stream_factory=OutStream, display_hook_factory=DisplayHook):
434 """NO LONGER IN USE"""
434 """NO LONGER IN USE"""
435 # create loop, context, and session:
435 # create loop, context, and session:
436 if loop is None:
436 if loop is None:
437 loop = ioloop.IOLoop.instance()
437 loop = ioloop.IOLoop.instance()
438 if context is None:
438 if context is None:
439 context = zmq.Context()
439 context = zmq.Context()
440 c = context
440 c = context
441 session = StreamSession(key=key)
441 session = StreamSession(key=key)
442 # print (session.key)
442 # print (session.key)
443 # print (control_addr, shell_addrs, iopub_addr, hb_addrs)
443 # print (control_addr, shell_addrs, iopub_addr, hb_addrs)
444
444
445 # create Control Stream
445 # create Control Stream
446 control_stream = zmqstream.ZMQStream(c.socket(zmq.PAIR), loop)
446 control_stream = zmqstream.ZMQStream(c.socket(zmq.PAIR), loop)
447 control_stream.setsockopt(zmq.IDENTITY, identity)
447 control_stream.setsockopt(zmq.IDENTITY, identity)
448 control_stream.connect(control_addr)
448 control_stream.connect(control_addr)
449
449
450 # create Shell Streams (MUX, Task, etc.):
450 # create Shell Streams (MUX, Task, etc.):
451 shell_streams = []
451 shell_streams = []
452 for addr in shell_addrs:
452 for addr in shell_addrs:
453 stream = zmqstream.ZMQStream(c.socket(zmq.PAIR), loop)
453 stream = zmqstream.ZMQStream(c.socket(zmq.PAIR), loop)
454 stream.setsockopt(zmq.IDENTITY, identity)
454 stream.setsockopt(zmq.IDENTITY, identity)
455 stream.connect(addr)
455 stream.connect(addr)
456 shell_streams.append(stream)
456 shell_streams.append(stream)
457
457
458 # create iopub stream:
458 # create iopub stream:
459 iopub_stream = zmqstream.ZMQStream(c.socket(zmq.PUB), loop)
459 iopub_stream = zmqstream.ZMQStream(c.socket(zmq.PUB), loop)
460 iopub_stream.setsockopt(zmq.IDENTITY, identity)
460 iopub_stream.setsockopt(zmq.IDENTITY, identity)
461 iopub_stream.connect(iopub_addr)
461 iopub_stream.connect(iopub_addr)
462
462
463 # Redirect input streams and set a display hook.
463 # Redirect input streams and set a display hook.
464 if out_stream_factory:
464 if out_stream_factory:
465 sys.stdout = out_stream_factory(session, iopub_stream, u'stdout')
465 sys.stdout = out_stream_factory(session, iopub_stream, u'stdout')
466 sys.stdout.topic = 'engine.%i.stdout'%int_id
466 sys.stdout.topic = 'engine.%i.stdout'%int_id
467 sys.stderr = out_stream_factory(session, iopub_stream, u'stderr')
467 sys.stderr = out_stream_factory(session, iopub_stream, u'stderr')
468 sys.stderr.topic = 'engine.%i.stderr'%int_id
468 sys.stderr.topic = 'engine.%i.stderr'%int_id
469 if display_hook_factory:
469 if display_hook_factory:
470 sys.displayhook = display_hook_factory(session, iopub_stream)
470 sys.displayhook = display_hook_factory(session, iopub_stream)
471 sys.displayhook.topic = 'engine.%i.pyout'%int_id
471 sys.displayhook.topic = 'engine.%i.pyout'%int_id
472
472
473
473
474 # launch heartbeat
474 # launch heartbeat
475 heart = heartmonitor.Heart(*map(str, hb_addrs), heart_id=identity)
475 heart = heartmonitor.Heart(*map(str, hb_addrs), heart_id=identity)
476 heart.start()
476 heart.start()
477
477
478 # create (optional) Client
478 # create (optional) Client
479 if client_addr:
479 if client_addr:
480 client = Client(client_addr, username=identity)
480 client = Client(client_addr, username=identity)
481 else:
481 else:
482 client = None
482 client = None
483
483
484 kernel = Kernel(id=int_id, session=session, control_stream=control_stream,
484 kernel = Kernel(id=int_id, session=session, control_stream=control_stream,
485 shell_streams=shell_streams, iopub_stream=iopub_stream,
485 shell_streams=shell_streams, iopub_stream=iopub_stream,
486 client=client, loop=loop)
486 client=client, loop=loop)
487 kernel.start()
487 kernel.start()
488 return loop, c, kernel
488 return loop, c, kernel
489
489
1 NO CONTENT: file renamed from IPython/zmq/parallel/streamsession.py to IPython/parallel/streamsession.py
NO CONTENT: file renamed from IPython/zmq/parallel/streamsession.py to IPython/parallel/streamsession.py
@@ -1,106 +1,106 b''
1 """Thread for popping Tasks from zmq to Python Queue"""
1 """Thread for popping Tasks from zmq to Python Queue"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team
3 # Copyright (C) 2010-2011 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9
9
10 import time
10 import time
11 from threading import Thread
11 from threading import Thread
12
12
13 try:
13 try:
14 from queue import Queue
14 from queue import Queue
15 except:
15 except:
16 from Queue import Queue
16 from Queue import Queue
17
17
18 import zmq
18 import zmq
19 from zmq.core.poll import _poll as poll
19 from zmq.core.poll import _poll as poll
20 from zmq.devices import ThreadDevice
20 from zmq.devices import ThreadDevice
21 from IPython.zmq.parallel import streamsession as ss
21 from IPython.parallel import streamsession as ss
22
22
23
23
24 class QueueStream(object):
24 class QueueStream(object):
25 def __init__(self, in_queue, out_queue):
25 def __init__(self, in_queue, out_queue):
26 self.in_queue = in_queue
26 self.in_queue = in_queue
27 self.out_queue = out_queue
27 self.out_queue = out_queue
28
28
29 def send_multipart(self, *args, **kwargs):
29 def send_multipart(self, *args, **kwargs):
30 while self.out_queue.full():
30 while self.out_queue.full():
31 time.sleep(1e-3)
31 time.sleep(1e-3)
32 self.out_queue.put(('send_multipart', args, kwargs))
32 self.out_queue.put(('send_multipart', args, kwargs))
33
33
34 def send(self, *args, **kwargs):
34 def send(self, *args, **kwargs):
35 while self.out_queue.full():
35 while self.out_queue.full():
36 time.sleep(1e-3)
36 time.sleep(1e-3)
37 self.out_queue.put(('send', args, kwargs))
37 self.out_queue.put(('send', args, kwargs))
38
38
39 def recv_multipart(self):
39 def recv_multipart(self):
40 return self.in_queue.get()
40 return self.in_queue.get()
41
41
42 def empty(self):
42 def empty(self):
43 return self.in_queue.empty()
43 return self.in_queue.empty()
44
44
45 class TaskThread(ThreadDevice):
45 class TaskThread(ThreadDevice):
46 """Class for popping Tasks from C-ZMQ->Python Queue"""
46 """Class for popping Tasks from C-ZMQ->Python Queue"""
47 max_qsize = 100
47 max_qsize = 100
48 in_socket = None
48 in_socket = None
49 out_socket = None
49 out_socket = None
50 # queue = None
50 # queue = None
51
51
52 def __init__(self, queue_type, mon_type, engine_id, max_qsize=100):
52 def __init__(self, queue_type, mon_type, engine_id, max_qsize=100):
53 ThreadDevice.__init__(self, 0, queue_type, mon_type)
53 ThreadDevice.__init__(self, 0, queue_type, mon_type)
54 self.session = ss.StreamSession(username='TaskNotifier[%s]'%engine_id)
54 self.session = ss.StreamSession(username='TaskNotifier[%s]'%engine_id)
55 self.engine_id = engine_id
55 self.engine_id = engine_id
56 self.in_queue = Queue(max_qsize)
56 self.in_queue = Queue(max_qsize)
57 self.out_queue = Queue(max_qsize)
57 self.out_queue = Queue(max_qsize)
58 self.max_qsize = max_qsize
58 self.max_qsize = max_qsize
59
59
60 @property
60 @property
61 def queues(self):
61 def queues(self):
62 return self.in_queue, self.out_queue
62 return self.in_queue, self.out_queue
63
63
64 @property
64 @property
65 def can_recv(self):
65 def can_recv(self):
66 # print self.in_queue.full(), poll((self.queue_socket, zmq.POLLIN),1e-3)
66 # print self.in_queue.full(), poll((self.queue_socket, zmq.POLLIN),1e-3)
67 return (not self.in_queue.full()) and poll([(self.queue_socket, zmq.POLLIN)], 1e-3 )
67 return (not self.in_queue.full()) and poll([(self.queue_socket, zmq.POLLIN)], 1e-3 )
68
68
69 @property
69 @property
70 def can_send(self):
70 def can_send(self):
71 return not self.out_queue.empty()
71 return not self.out_queue.empty()
72
72
73 def run(self):
73 def run(self):
74 print 'running'
74 print 'running'
75 self.queue_socket,self.mon_socket = self._setup_sockets()
75 self.queue_socket,self.mon_socket = self._setup_sockets()
76 print 'setup'
76 print 'setup'
77
77
78 while True:
78 while True:
79 while not self.can_send and not self.can_recv:
79 while not self.can_send and not self.can_recv:
80 # print 'idle'
80 # print 'idle'
81 # nothing to do, wait
81 # nothing to do, wait
82 time.sleep(1e-3)
82 time.sleep(1e-3)
83 while self.can_send:
83 while self.can_send:
84 # flush out queue
84 # flush out queue
85 print 'flushing...'
85 print 'flushing...'
86 meth, args, kwargs = self.out_queue.get()
86 meth, args, kwargs = self.out_queue.get()
87 getattr(self.queue_socket, meth)(*args, **kwargs)
87 getattr(self.queue_socket, meth)(*args, **kwargs)
88 print 'flushed'
88 print 'flushed'
89
89
90 if self.can_recv:
90 if self.can_recv:
91 print 'recving'
91 print 'recving'
92 # get another job from zmq
92 # get another job from zmq
93 msg = self.queue_socket.recv_multipart(0, copy=False)
93 msg = self.queue_socket.recv_multipart(0, copy=False)
94 # put it in the Queue
94 # put it in the Queue
95 self.in_queue.put(msg)
95 self.in_queue.put(msg)
96 idents,msg = self.session.feed_identities(msg, copy=False)
96 idents,msg = self.session.feed_identities(msg, copy=False)
97 msg = self.session.unpack_message(msg, content=False, copy=False)
97 msg = self.session.unpack_message(msg, content=False, copy=False)
98 # notify the Controller that we got it
98 # notify the Controller that we got it
99 self.mon_socket.send('tracktask', zmq.SNDMORE)
99 self.mon_socket.send('tracktask', zmq.SNDMORE)
100 header = msg['header']
100 header = msg['header']
101 msg_id = header['msg_id']
101 msg_id = header['msg_id']
102 content = dict(engine_id=self.engine_id, msg_id = msg_id)
102 content = dict(engine_id=self.engine_id, msg_id = msg_id)
103 self.session.send(self.mon_socket, 'task_receipt', content=content)
103 self.session.send(self.mon_socket, 'task_receipt', content=content)
104 print 'recvd'
104 print 'recvd'
105
105
106 No newline at end of file
106
@@ -1,69 +1,69 b''
1 """toplevel setup/teardown for parallel tests."""
1 """toplevel setup/teardown for parallel tests."""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import tempfile
14 import tempfile
15 import time
15 import time
16 from subprocess import Popen, PIPE, STDOUT
16 from subprocess import Popen, PIPE, STDOUT
17
17
18 from IPython.zmq.parallel import client
18 from IPython.parallel import client
19
19
20 processes = []
20 processes = []
21 blackhole = tempfile.TemporaryFile()
21 blackhole = tempfile.TemporaryFile()
22
22
23 # nose setup/teardown
23 # nose setup/teardown
24
24
25 def setup():
25 def setup():
26 cp = Popen('ipcontrollerz --profile iptest -r --log-level 10 --log-to-file'.split(), stdout=blackhole, stderr=STDOUT)
26 cp = Popen('ipcontrollerz --profile iptest -r --log-level 10 --log-to-file'.split(), stdout=blackhole, stderr=STDOUT)
27 processes.append(cp)
27 processes.append(cp)
28 time.sleep(.5)
28 time.sleep(.5)
29 add_engines(1)
29 add_engines(1)
30 c = client.Client(profile='iptest')
30 c = client.Client(profile='iptest')
31 while not c.ids:
31 while not c.ids:
32 time.sleep(.1)
32 time.sleep(.1)
33 c.spin()
33 c.spin()
34 c.close()
34 c.close()
35
35
36 def add_engines(n=1, profile='iptest'):
36 def add_engines(n=1, profile='iptest'):
37 rc = client.Client(profile=profile)
37 rc = client.Client(profile=profile)
38 base = len(rc)
38 base = len(rc)
39 eps = []
39 eps = []
40 for i in range(n):
40 for i in range(n):
41 ep = Popen(['ipenginez']+ ['--profile', profile, '--log-level', '10', '--log-to-file'], stdout=blackhole, stderr=STDOUT)
41 ep = Popen(['ipenginez']+ ['--profile', profile, '--log-level', '10', '--log-to-file'], stdout=blackhole, stderr=STDOUT)
42 # ep.start()
42 # ep.start()
43 processes.append(ep)
43 processes.append(ep)
44 eps.append(ep)
44 eps.append(ep)
45 while len(rc) < base+n:
45 while len(rc) < base+n:
46 time.sleep(.1)
46 time.sleep(.1)
47 rc.spin()
47 rc.spin()
48 rc.close()
48 rc.close()
49 return eps
49 return eps
50
50
51 def teardown():
51 def teardown():
52 time.sleep(1)
52 time.sleep(1)
53 while processes:
53 while processes:
54 p = processes.pop()
54 p = processes.pop()
55 if p.poll() is None:
55 if p.poll() is None:
56 try:
56 try:
57 p.terminate()
57 p.terminate()
58 except Exception, e:
58 except Exception, e:
59 print e
59 print e
60 pass
60 pass
61 if p.poll() is None:
61 if p.poll() is None:
62 time.sleep(.25)
62 time.sleep(.25)
63 if p.poll() is None:
63 if p.poll() is None:
64 try:
64 try:
65 print 'killing'
65 print 'killing'
66 p.kill()
66 p.kill()
67 except:
67 except:
68 print "couldn't shutdown process: ", p
68 print "couldn't shutdown process: ", p
69
69
@@ -1,119 +1,119 b''
1 """base class for parallel client tests"""
1 """base class for parallel client tests"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 import sys
10 import sys
11 import tempfile
11 import tempfile
12 import time
12 import time
13 from signal import SIGINT
13 from signal import SIGINT
14 from multiprocessing import Process
14 from multiprocessing import Process
15
15
16 from nose import SkipTest
16 from nose import SkipTest
17
17
18 import zmq
18 import zmq
19 from zmq.tests import BaseZMQTestCase
19 from zmq.tests import BaseZMQTestCase
20
20
21 from IPython.external.decorator import decorator
21 from IPython.external.decorator import decorator
22
22
23 from IPython.zmq.parallel import error
23 from IPython.parallel import error
24 from IPython.zmq.parallel.client import Client
24 from IPython.parallel.client import Client
25 from IPython.zmq.parallel.ipcluster import launch_process
25 from IPython.parallel.ipcluster import launch_process
26 from IPython.zmq.parallel.entry_point import select_random_ports
26 from IPython.parallel.entry_point import select_random_ports
27 from IPython.zmq.parallel.tests import processes,add_engines
27 from IPython.parallel.tests import processes,add_engines
28
28
29 # simple tasks for use in apply tests
29 # simple tasks for use in apply tests
30
30
31 def segfault():
31 def segfault():
32 """this will segfault"""
32 """this will segfault"""
33 import ctypes
33 import ctypes
34 ctypes.memset(-1,0,1)
34 ctypes.memset(-1,0,1)
35
35
36 def wait(n):
36 def wait(n):
37 """sleep for a time"""
37 """sleep for a time"""
38 import time
38 import time
39 time.sleep(n)
39 time.sleep(n)
40 return n
40 return n
41
41
42 def raiser(eclass):
42 def raiser(eclass):
43 """raise an exception"""
43 """raise an exception"""
44 raise eclass()
44 raise eclass()
45
45
46 # test decorator for skipping tests when libraries are unavailable
46 # test decorator for skipping tests when libraries are unavailable
47 def skip_without(*names):
47 def skip_without(*names):
48 """skip a test if some names are not importable"""
48 """skip a test if some names are not importable"""
49 @decorator
49 @decorator
50 def skip_without_names(f, *args, **kwargs):
50 def skip_without_names(f, *args, **kwargs):
51 """decorator to skip tests in the absence of numpy."""
51 """decorator to skip tests in the absence of numpy."""
52 for name in names:
52 for name in names:
53 try:
53 try:
54 __import__(name)
54 __import__(name)
55 except ImportError:
55 except ImportError:
56 raise SkipTest
56 raise SkipTest
57 return f(*args, **kwargs)
57 return f(*args, **kwargs)
58 return skip_without_names
58 return skip_without_names
59
59
60 class ClusterTestCase(BaseZMQTestCase):
60 class ClusterTestCase(BaseZMQTestCase):
61
61
62 def add_engines(self, n=1, block=True):
62 def add_engines(self, n=1, block=True):
63 """add multiple engines to our cluster"""
63 """add multiple engines to our cluster"""
64 self.engines.extend(add_engines(n))
64 self.engines.extend(add_engines(n))
65 if block:
65 if block:
66 self.wait_on_engines()
66 self.wait_on_engines()
67
67
68 def wait_on_engines(self, timeout=5):
68 def wait_on_engines(self, timeout=5):
69 """wait for our engines to connect."""
69 """wait for our engines to connect."""
70 n = len(self.engines)+self.base_engine_count
70 n = len(self.engines)+self.base_engine_count
71 tic = time.time()
71 tic = time.time()
72 while time.time()-tic < timeout and len(self.client.ids) < n:
72 while time.time()-tic < timeout and len(self.client.ids) < n:
73 time.sleep(0.1)
73 time.sleep(0.1)
74
74
75 assert not len(self.client.ids) < n, "waiting for engines timed out"
75 assert not len(self.client.ids) < n, "waiting for engines timed out"
76
76
77 def connect_client(self):
77 def connect_client(self):
78 """connect a client with my Context, and track its sockets for cleanup"""
78 """connect a client with my Context, and track its sockets for cleanup"""
79 c = Client(profile='iptest', context=self.context)
79 c = Client(profile='iptest', context=self.context)
80 for name in filter(lambda n:n.endswith('socket'), dir(c)):
80 for name in filter(lambda n:n.endswith('socket'), dir(c)):
81 s = getattr(c, name)
81 s = getattr(c, name)
82 s.setsockopt(zmq.LINGER, 0)
82 s.setsockopt(zmq.LINGER, 0)
83 self.sockets.append(s)
83 self.sockets.append(s)
84 return c
84 return c
85
85
86 def assertRaisesRemote(self, etype, f, *args, **kwargs):
86 def assertRaisesRemote(self, etype, f, *args, **kwargs):
87 try:
87 try:
88 try:
88 try:
89 f(*args, **kwargs)
89 f(*args, **kwargs)
90 except error.CompositeError as e:
90 except error.CompositeError as e:
91 e.raise_exception()
91 e.raise_exception()
92 except error.RemoteError as e:
92 except error.RemoteError as e:
93 self.assertEquals(etype.__name__, e.ename, "Should have raised %r, but raised %r"%(e.ename, etype.__name__))
93 self.assertEquals(etype.__name__, e.ename, "Should have raised %r, but raised %r"%(e.ename, etype.__name__))
94 else:
94 else:
95 self.fail("should have raised a RemoteError")
95 self.fail("should have raised a RemoteError")
96
96
97 def setUp(self):
97 def setUp(self):
98 BaseZMQTestCase.setUp(self)
98 BaseZMQTestCase.setUp(self)
99 self.client = self.connect_client()
99 self.client = self.connect_client()
100 self.base_engine_count=len(self.client.ids)
100 self.base_engine_count=len(self.client.ids)
101 self.engines=[]
101 self.engines=[]
102
102
103 def tearDown(self):
103 def tearDown(self):
104 # self.client.clear(block=True)
104 # self.client.clear(block=True)
105 # close fds:
105 # close fds:
106 for e in filter(lambda e: e.poll() is not None, processes):
106 for e in filter(lambda e: e.poll() is not None, processes):
107 processes.remove(e)
107 processes.remove(e)
108
108
109 # allow flushing of incoming messages to prevent crash on socket close
109 # allow flushing of incoming messages to prevent crash on socket close
110 self.client.wait(timeout=2)
110 self.client.wait(timeout=2)
111 # time.sleep(2)
111 # time.sleep(2)
112 self.client.spin()
112 self.client.spin()
113 self.client.close()
113 self.client.close()
114 BaseZMQTestCase.tearDown(self)
114 BaseZMQTestCase.tearDown(self)
115 # this will be redundant when pyzmq merges PR #88
115 # this will be redundant when pyzmq merges PR #88
116 # self.context.term()
116 # self.context.term()
117 # print tempfile.TemporaryFile().fileno(),
117 # print tempfile.TemporaryFile().fileno(),
118 # sys.stdout.flush()
118 # sys.stdout.flush()
119 No newline at end of file
119
@@ -1,69 +1,69 b''
1 """Tests for asyncresult.py"""
1 """Tests for asyncresult.py"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14
14
15 from IPython.zmq.parallel.error import TimeoutError
15 from IPython.parallel.error import TimeoutError
16
16
17 from IPython.zmq.parallel.tests import add_engines
17 from IPython.parallel.tests import add_engines
18 from .clienttest import ClusterTestCase
18 from .clienttest import ClusterTestCase
19
19
20 def setup():
20 def setup():
21 add_engines(2)
21 add_engines(2)
22
22
23 def wait(n):
23 def wait(n):
24 import time
24 import time
25 time.sleep(n)
25 time.sleep(n)
26 return n
26 return n
27
27
28 class AsyncResultTest(ClusterTestCase):
28 class AsyncResultTest(ClusterTestCase):
29
29
30 def test_single_result(self):
30 def test_single_result(self):
31 eid = self.client.ids[-1]
31 eid = self.client.ids[-1]
32 ar = self.client[eid].apply_async(lambda : 42)
32 ar = self.client[eid].apply_async(lambda : 42)
33 self.assertEquals(ar.get(), 42)
33 self.assertEquals(ar.get(), 42)
34 ar = self.client[[eid]].apply_async(lambda : 42)
34 ar = self.client[[eid]].apply_async(lambda : 42)
35 self.assertEquals(ar.get(), [42])
35 self.assertEquals(ar.get(), [42])
36 ar = self.client[-1:].apply_async(lambda : 42)
36 ar = self.client[-1:].apply_async(lambda : 42)
37 self.assertEquals(ar.get(), [42])
37 self.assertEquals(ar.get(), [42])
38
38
39 def test_get_after_done(self):
39 def test_get_after_done(self):
40 ar = self.client[-1].apply_async(lambda : 42)
40 ar = self.client[-1].apply_async(lambda : 42)
41 self.assertFalse(ar.ready())
41 self.assertFalse(ar.ready())
42 ar.wait()
42 ar.wait()
43 self.assertTrue(ar.ready())
43 self.assertTrue(ar.ready())
44 self.assertEquals(ar.get(), 42)
44 self.assertEquals(ar.get(), 42)
45 self.assertEquals(ar.get(), 42)
45 self.assertEquals(ar.get(), 42)
46
46
47 def test_get_before_done(self):
47 def test_get_before_done(self):
48 ar = self.client[-1].apply_async(wait, 0.1)
48 ar = self.client[-1].apply_async(wait, 0.1)
49 self.assertRaises(TimeoutError, ar.get, 0)
49 self.assertRaises(TimeoutError, ar.get, 0)
50 ar.wait(0)
50 ar.wait(0)
51 self.assertFalse(ar.ready())
51 self.assertFalse(ar.ready())
52 self.assertEquals(ar.get(), 0.1)
52 self.assertEquals(ar.get(), 0.1)
53
53
54 def test_get_after_error(self):
54 def test_get_after_error(self):
55 ar = self.client[-1].apply_async(lambda : 1/0)
55 ar = self.client[-1].apply_async(lambda : 1/0)
56 ar.wait()
56 ar.wait()
57 self.assertRaisesRemote(ZeroDivisionError, ar.get)
57 self.assertRaisesRemote(ZeroDivisionError, ar.get)
58 self.assertRaisesRemote(ZeroDivisionError, ar.get)
58 self.assertRaisesRemote(ZeroDivisionError, ar.get)
59 self.assertRaisesRemote(ZeroDivisionError, ar.get_dict)
59 self.assertRaisesRemote(ZeroDivisionError, ar.get_dict)
60
60
61 def test_get_dict(self):
61 def test_get_dict(self):
62 n = len(self.client)
62 n = len(self.client)
63 ar = self.client[:].apply_async(lambda : 5)
63 ar = self.client[:].apply_async(lambda : 5)
64 self.assertEquals(ar.get(), [5]*n)
64 self.assertEquals(ar.get(), [5]*n)
65 d = ar.get_dict()
65 d = ar.get_dict()
66 self.assertEquals(sorted(d.keys()), sorted(self.client.ids))
66 self.assertEquals(sorted(d.keys()), sorted(self.client.ids))
67 for eid,r in d.iteritems():
67 for eid,r in d.iteritems():
68 self.assertEquals(r, 5)
68 self.assertEquals(r, 5)
69
69
@@ -1,138 +1,147 b''
1 """Tests for parallel client.py"""
1 """Tests for parallel client.py"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import time
14 import time
15 from tempfile import mktemp
15 from tempfile import mktemp
16
16
17 import zmq
17 import zmq
18
18
19 from IPython.zmq.parallel import client as clientmod
19 from IPython.parallel import client as clientmod
20 from IPython.zmq.parallel import error
20 from IPython.parallel import error
21 from IPython.zmq.parallel.asyncresult import AsyncResult, AsyncHubResult
21 from IPython.parallel.asyncresult import AsyncResult, AsyncHubResult
22 from IPython.zmq.parallel.view import LoadBalancedView, DirectView
22 from IPython.parallel.view import LoadBalancedView, DirectView
23
23
24 from clienttest import ClusterTestCase, segfault, wait, add_engines
24 from clienttest import ClusterTestCase, segfault, wait, add_engines
25
25
26 def setup():
26 def setup():
27 add_engines(4)
27 add_engines(4)
28
28
29 class TestClient(ClusterTestCase):
29 class TestClient(ClusterTestCase):
30
30
31 def test_ids(self):
31 def test_ids(self):
32 n = len(self.client.ids)
32 n = len(self.client.ids)
33 self.add_engines(3)
33 self.add_engines(3)
34 self.assertEquals(len(self.client.ids), n+3)
34 self.assertEquals(len(self.client.ids), n+3)
35
35
36 def test_view_indexing(self):
36 def test_view_indexing(self):
37 """test index access for views"""
37 """test index access for views"""
38 self.add_engines(2)
38 self.add_engines(2)
39 targets = self.client._build_targets('all')[-1]
39 targets = self.client._build_targets('all')[-1]
40 v = self.client[:]
40 v = self.client[:]
41 self.assertEquals(v.targets, targets)
41 self.assertEquals(v.targets, targets)
42 t = self.client.ids[2]
42 t = self.client.ids[2]
43 v = self.client[t]
43 v = self.client[t]
44 self.assert_(isinstance(v, DirectView))
44 self.assert_(isinstance(v, DirectView))
45 self.assertEquals(v.targets, t)
45 self.assertEquals(v.targets, t)
46 t = self.client.ids[2:4]
46 t = self.client.ids[2:4]
47 v = self.client[t]
47 v = self.client[t]
48 self.assert_(isinstance(v, DirectView))
48 self.assert_(isinstance(v, DirectView))
49 self.assertEquals(v.targets, t)
49 self.assertEquals(v.targets, t)
50 v = self.client[::2]
50 v = self.client[::2]
51 self.assert_(isinstance(v, DirectView))
51 self.assert_(isinstance(v, DirectView))
52 self.assertEquals(v.targets, targets[::2])
52 self.assertEquals(v.targets, targets[::2])
53 v = self.client[1::3]
53 v = self.client[1::3]
54 self.assert_(isinstance(v, DirectView))
54 self.assert_(isinstance(v, DirectView))
55 self.assertEquals(v.targets, targets[1::3])
55 self.assertEquals(v.targets, targets[1::3])
56 v = self.client[:-3]
56 v = self.client[:-3]
57 self.assert_(isinstance(v, DirectView))
57 self.assert_(isinstance(v, DirectView))
58 self.assertEquals(v.targets, targets[:-3])
58 self.assertEquals(v.targets, targets[:-3])
59 v = self.client[-1]
59 v = self.client[-1]
60 self.assert_(isinstance(v, DirectView))
60 self.assert_(isinstance(v, DirectView))
61 self.assertEquals(v.targets, targets[-1])
61 self.assertEquals(v.targets, targets[-1])
62 self.assertRaises(TypeError, lambda : self.client[None])
62 self.assertRaises(TypeError, lambda : self.client[None])
63
63
64 def test_lbview_targets(self):
65 """test load_balanced_view targets"""
66 v = self.client.load_balanced_view()
67 self.assertEquals(v.targets, None)
68 v = self.client.load_balanced_view(-1)
69 self.assertEquals(v.targets, [self.client.ids[-1]])
70 v = self.client.load_balanced_view('all')
71 self.assertEquals(v.targets, self.client.ids)
72
64 def test_targets(self):
73 def test_targets(self):
65 """test various valid targets arguments"""
74 """test various valid targets arguments"""
66 build = self.client._build_targets
75 build = self.client._build_targets
67 ids = self.client.ids
76 ids = self.client.ids
68 idents,targets = build(None)
77 idents,targets = build(None)
69 self.assertEquals(ids, targets)
78 self.assertEquals(ids, targets)
70
79
71 def test_clear(self):
80 def test_clear(self):
72 """test clear behavior"""
81 """test clear behavior"""
73 # self.add_engines(2)
82 # self.add_engines(2)
74 v = self.client[:]
83 v = self.client[:]
75 v.block=True
84 v.block=True
76 v.push(dict(a=5))
85 v.push(dict(a=5))
77 v.pull('a')
86 v.pull('a')
78 id0 = self.client.ids[-1]
87 id0 = self.client.ids[-1]
79 self.client.clear(targets=id0)
88 self.client.clear(targets=id0)
80 self.client[:-1].pull('a')
89 self.client[:-1].pull('a')
81 self.assertRaisesRemote(NameError, self.client[id0].get, 'a')
90 self.assertRaisesRemote(NameError, self.client[id0].get, 'a')
82 self.client.clear(block=True)
91 self.client.clear(block=True)
83 for i in self.client.ids:
92 for i in self.client.ids:
84 # print i
93 # print i
85 self.assertRaisesRemote(NameError, self.client[i].get, 'a')
94 self.assertRaisesRemote(NameError, self.client[i].get, 'a')
86
95
87 def test_get_result(self):
96 def test_get_result(self):
88 """test getting results from the Hub."""
97 """test getting results from the Hub."""
89 c = clientmod.Client(profile='iptest')
98 c = clientmod.Client(profile='iptest')
90 # self.add_engines(1)
99 # self.add_engines(1)
91 t = c.ids[-1]
100 t = c.ids[-1]
92 ar = c[t].apply_async(wait, 1)
101 ar = c[t].apply_async(wait, 1)
93 # give the monitor time to notice the message
102 # give the monitor time to notice the message
94 time.sleep(.25)
103 time.sleep(.25)
95 ahr = self.client.get_result(ar.msg_ids)
104 ahr = self.client.get_result(ar.msg_ids)
96 self.assertTrue(isinstance(ahr, AsyncHubResult))
105 self.assertTrue(isinstance(ahr, AsyncHubResult))
97 self.assertEquals(ahr.get(), ar.get())
106 self.assertEquals(ahr.get(), ar.get())
98 ar2 = self.client.get_result(ar.msg_ids)
107 ar2 = self.client.get_result(ar.msg_ids)
99 self.assertFalse(isinstance(ar2, AsyncHubResult))
108 self.assertFalse(isinstance(ar2, AsyncHubResult))
100 c.close()
109 c.close()
101
110
102 def test_ids_list(self):
111 def test_ids_list(self):
103 """test client.ids"""
112 """test client.ids"""
104 # self.add_engines(2)
113 # self.add_engines(2)
105 ids = self.client.ids
114 ids = self.client.ids
106 self.assertEquals(ids, self.client._ids)
115 self.assertEquals(ids, self.client._ids)
107 self.assertFalse(ids is self.client._ids)
116 self.assertFalse(ids is self.client._ids)
108 ids.remove(ids[-1])
117 ids.remove(ids[-1])
109 self.assertNotEquals(ids, self.client._ids)
118 self.assertNotEquals(ids, self.client._ids)
110
119
111 def test_queue_status(self):
120 def test_queue_status(self):
112 # self.addEngine(4)
121 # self.addEngine(4)
113 ids = self.client.ids
122 ids = self.client.ids
114 id0 = ids[0]
123 id0 = ids[0]
115 qs = self.client.queue_status(targets=id0)
124 qs = self.client.queue_status(targets=id0)
116 self.assertTrue(isinstance(qs, dict))
125 self.assertTrue(isinstance(qs, dict))
117 self.assertEquals(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
126 self.assertEquals(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
118 allqs = self.client.queue_status()
127 allqs = self.client.queue_status()
119 self.assertTrue(isinstance(allqs, dict))
128 self.assertTrue(isinstance(allqs, dict))
120 self.assertEquals(sorted(allqs.keys()), self.client.ids)
129 self.assertEquals(sorted(allqs.keys()), self.client.ids)
121 for eid,qs in allqs.items():
130 for eid,qs in allqs.items():
122 self.assertTrue(isinstance(qs, dict))
131 self.assertTrue(isinstance(qs, dict))
123 self.assertEquals(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
132 self.assertEquals(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
124
133
125 def test_shutdown(self):
134 def test_shutdown(self):
126 # self.addEngine(4)
135 # self.addEngine(4)
127 ids = self.client.ids
136 ids = self.client.ids
128 id0 = ids[0]
137 id0 = ids[0]
129 self.client.shutdown(id0, block=True)
138 self.client.shutdown(id0, block=True)
130 while id0 in self.client.ids:
139 while id0 in self.client.ids:
131 time.sleep(0.1)
140 time.sleep(0.1)
132 self.client.spin()
141 self.client.spin()
133
142
134 self.assertRaises(IndexError, lambda : self.client[id0])
143 self.assertRaises(IndexError, lambda : self.client[id0])
135
144
136 def test_result_status(self):
145 def test_result_status(self):
137 pass
146 pass
138 # to be written
147 # to be written
@@ -1,101 +1,101 b''
1 """Tests for dependency.py"""
1 """Tests for dependency.py"""
2
2
3 __docformat__ = "restructuredtext en"
3 __docformat__ = "restructuredtext en"
4
4
5 #-------------------------------------------------------------------------------
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2011 The IPython Development Team
6 # Copyright (C) 2011 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15
15
16 # import
16 # import
17 import os
17 import os
18
18
19 from IPython.utils.pickleutil import can, uncan
19 from IPython.utils.pickleutil import can, uncan
20
20
21 from IPython.zmq.parallel import dependency as dmod
21 from IPython.parallel import dependency as dmod
22 from IPython.zmq.parallel.util import interactive
22 from IPython.parallel.util import interactive
23
23
24 from IPython.zmq.parallel.tests import add_engines
24 from IPython.parallel.tests import add_engines
25 from .clienttest import ClusterTestCase
25 from .clienttest import ClusterTestCase
26
26
27 def setup():
27 def setup():
28 add_engines(1)
28 add_engines(1)
29
29
30 @dmod.require('time')
30 @dmod.require('time')
31 def wait(n):
31 def wait(n):
32 time.sleep(n)
32 time.sleep(n)
33 return n
33 return n
34
34
35 mixed = map(str, range(10))
35 mixed = map(str, range(10))
36 completed = map(str, range(0,10,2))
36 completed = map(str, range(0,10,2))
37 failed = map(str, range(1,10,2))
37 failed = map(str, range(1,10,2))
38
38
39 class DependencyTest(ClusterTestCase):
39 class DependencyTest(ClusterTestCase):
40
40
41 def setUp(self):
41 def setUp(self):
42 ClusterTestCase.setUp(self)
42 ClusterTestCase.setUp(self)
43 self.user_ns = {'__builtins__' : __builtins__}
43 self.user_ns = {'__builtins__' : __builtins__}
44 self.view = self.client.load_balanced_view()
44 self.view = self.client.load_balanced_view()
45 self.dview = self.client[-1]
45 self.dview = self.client[-1]
46 self.succeeded = set(map(str, range(0,25,2)))
46 self.succeeded = set(map(str, range(0,25,2)))
47 self.failed = set(map(str, range(1,25,2)))
47 self.failed = set(map(str, range(1,25,2)))
48
48
49 def assertMet(self, dep):
49 def assertMet(self, dep):
50 self.assertTrue(dep.check(self.succeeded, self.failed), "Dependency should be met")
50 self.assertTrue(dep.check(self.succeeded, self.failed), "Dependency should be met")
51
51
52 def assertUnmet(self, dep):
52 def assertUnmet(self, dep):
53 self.assertFalse(dep.check(self.succeeded, self.failed), "Dependency should not be met")
53 self.assertFalse(dep.check(self.succeeded, self.failed), "Dependency should not be met")
54
54
55 def assertUnreachable(self, dep):
55 def assertUnreachable(self, dep):
56 self.assertTrue(dep.unreachable(self.succeeded, self.failed), "Dependency should be unreachable")
56 self.assertTrue(dep.unreachable(self.succeeded, self.failed), "Dependency should be unreachable")
57
57
58 def assertReachable(self, dep):
58 def assertReachable(self, dep):
59 self.assertFalse(dep.unreachable(self.succeeded, self.failed), "Dependency should be reachable")
59 self.assertFalse(dep.unreachable(self.succeeded, self.failed), "Dependency should be reachable")
60
60
61 def cancan(self, f):
61 def cancan(self, f):
62 """decorator to pass through canning into self.user_ns"""
62 """decorator to pass through canning into self.user_ns"""
63 return uncan(can(f), self.user_ns)
63 return uncan(can(f), self.user_ns)
64
64
65 def test_require_imports(self):
65 def test_require_imports(self):
66 """test that @require imports names"""
66 """test that @require imports names"""
67 @self.cancan
67 @self.cancan
68 @dmod.require('urllib')
68 @dmod.require('urllib')
69 @interactive
69 @interactive
70 def encode(dikt):
70 def encode(dikt):
71 return urllib.urlencode(dikt)
71 return urllib.urlencode(dikt)
72 # must pass through canning to properly connect namespaces
72 # must pass through canning to properly connect namespaces
73 self.assertEquals(encode(dict(a=5)), 'a=5')
73 self.assertEquals(encode(dict(a=5)), 'a=5')
74
74
75 def test_success_only(self):
75 def test_success_only(self):
76 dep = dmod.Dependency(mixed, success=True, failure=False)
76 dep = dmod.Dependency(mixed, success=True, failure=False)
77 self.assertUnmet(dep)
77 self.assertUnmet(dep)
78 self.assertUnreachable(dep)
78 self.assertUnreachable(dep)
79 dep.all=False
79 dep.all=False
80 self.assertMet(dep)
80 self.assertMet(dep)
81 self.assertReachable(dep)
81 self.assertReachable(dep)
82 dep = dmod.Dependency(completed, success=True, failure=False)
82 dep = dmod.Dependency(completed, success=True, failure=False)
83 self.assertMet(dep)
83 self.assertMet(dep)
84 self.assertReachable(dep)
84 self.assertReachable(dep)
85 dep.all=False
85 dep.all=False
86 self.assertMet(dep)
86 self.assertMet(dep)
87 self.assertReachable(dep)
87 self.assertReachable(dep)
88
88
89 def test_failure_only(self):
89 def test_failure_only(self):
90 dep = dmod.Dependency(mixed, success=False, failure=True)
90 dep = dmod.Dependency(mixed, success=False, failure=True)
91 self.assertUnmet(dep)
91 self.assertUnmet(dep)
92 self.assertUnreachable(dep)
92 self.assertUnreachable(dep)
93 dep.all=False
93 dep.all=False
94 self.assertMet(dep)
94 self.assertMet(dep)
95 self.assertReachable(dep)
95 self.assertReachable(dep)
96 dep = dmod.Dependency(completed, success=False, failure=True)
96 dep = dmod.Dependency(completed, success=False, failure=True)
97 self.assertUnmet(dep)
97 self.assertUnmet(dep)
98 self.assertUnreachable(dep)
98 self.assertUnreachable(dep)
99 dep.all=False
99 dep.all=False
100 self.assertUnmet(dep)
100 self.assertUnmet(dep)
101 self.assertUnreachable(dep)
101 self.assertUnreachable(dep)
@@ -1,108 +1,108 b''
1 """test serialization with newserialized"""
1 """test serialization with newserialized"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 from unittest import TestCase
14 from unittest import TestCase
15
15
16 from IPython.testing.parametric import parametric
16 from IPython.testing.parametric import parametric
17 from IPython.utils import newserialized as ns
17 from IPython.utils import newserialized as ns
18 from IPython.utils.pickleutil import can, uncan, CannedObject, CannedFunction
18 from IPython.utils.pickleutil import can, uncan, CannedObject, CannedFunction
19 from IPython.zmq.parallel.tests.clienttest import skip_without
19 from IPython.parallel.tests.clienttest import skip_without
20
20
21
21
22 class CanningTestCase(TestCase):
22 class CanningTestCase(TestCase):
23 def test_canning(self):
23 def test_canning(self):
24 d = dict(a=5,b=6)
24 d = dict(a=5,b=6)
25 cd = can(d)
25 cd = can(d)
26 self.assertTrue(isinstance(cd, dict))
26 self.assertTrue(isinstance(cd, dict))
27
27
28 def test_canned_function(self):
28 def test_canned_function(self):
29 f = lambda : 7
29 f = lambda : 7
30 cf = can(f)
30 cf = can(f)
31 self.assertTrue(isinstance(cf, CannedFunction))
31 self.assertTrue(isinstance(cf, CannedFunction))
32
32
33 @parametric
33 @parametric
34 def test_can_roundtrip(cls):
34 def test_can_roundtrip(cls):
35 objs = [
35 objs = [
36 dict(),
36 dict(),
37 set(),
37 set(),
38 list(),
38 list(),
39 ['a',1,['a',1],u'e'],
39 ['a',1,['a',1],u'e'],
40 ]
40 ]
41 return map(cls.run_roundtrip, objs)
41 return map(cls.run_roundtrip, objs)
42
42
43 @classmethod
43 @classmethod
44 def run_roundtrip(self, obj):
44 def run_roundtrip(self, obj):
45 o = uncan(can(obj))
45 o = uncan(can(obj))
46 assert o == obj, "failed assertion: %r == %r"%(o,obj)
46 assert o == obj, "failed assertion: %r == %r"%(o,obj)
47
47
48 def test_serialized_interfaces(self):
48 def test_serialized_interfaces(self):
49
49
50 us = {'a':10, 'b':range(10)}
50 us = {'a':10, 'b':range(10)}
51 s = ns.serialize(us)
51 s = ns.serialize(us)
52 uus = ns.unserialize(s)
52 uus = ns.unserialize(s)
53 self.assertTrue(isinstance(s, ns.SerializeIt))
53 self.assertTrue(isinstance(s, ns.SerializeIt))
54 self.assertEquals(uus, us)
54 self.assertEquals(uus, us)
55
55
56 def test_pickle_serialized(self):
56 def test_pickle_serialized(self):
57 obj = {'a':1.45345, 'b':'asdfsdf', 'c':10000L}
57 obj = {'a':1.45345, 'b':'asdfsdf', 'c':10000L}
58 original = ns.UnSerialized(obj)
58 original = ns.UnSerialized(obj)
59 originalSer = ns.SerializeIt(original)
59 originalSer = ns.SerializeIt(original)
60 firstData = originalSer.getData()
60 firstData = originalSer.getData()
61 firstTD = originalSer.getTypeDescriptor()
61 firstTD = originalSer.getTypeDescriptor()
62 firstMD = originalSer.getMetadata()
62 firstMD = originalSer.getMetadata()
63 self.assertEquals(firstTD, 'pickle')
63 self.assertEquals(firstTD, 'pickle')
64 self.assertEquals(firstMD, {})
64 self.assertEquals(firstMD, {})
65 unSerialized = ns.UnSerializeIt(originalSer)
65 unSerialized = ns.UnSerializeIt(originalSer)
66 secondObj = unSerialized.getObject()
66 secondObj = unSerialized.getObject()
67 for k, v in secondObj.iteritems():
67 for k, v in secondObj.iteritems():
68 self.assertEquals(obj[k], v)
68 self.assertEquals(obj[k], v)
69 secondSer = ns.SerializeIt(ns.UnSerialized(secondObj))
69 secondSer = ns.SerializeIt(ns.UnSerialized(secondObj))
70 self.assertEquals(firstData, secondSer.getData())
70 self.assertEquals(firstData, secondSer.getData())
71 self.assertEquals(firstTD, secondSer.getTypeDescriptor() )
71 self.assertEquals(firstTD, secondSer.getTypeDescriptor() )
72 self.assertEquals(firstMD, secondSer.getMetadata())
72 self.assertEquals(firstMD, secondSer.getMetadata())
73
73
74 @skip_without('numpy')
74 @skip_without('numpy')
75 def test_ndarray_serialized(self):
75 def test_ndarray_serialized(self):
76 import numpy
76 import numpy
77 a = numpy.linspace(0.0, 1.0, 1000)
77 a = numpy.linspace(0.0, 1.0, 1000)
78 unSer1 = ns.UnSerialized(a)
78 unSer1 = ns.UnSerialized(a)
79 ser1 = ns.SerializeIt(unSer1)
79 ser1 = ns.SerializeIt(unSer1)
80 td = ser1.getTypeDescriptor()
80 td = ser1.getTypeDescriptor()
81 self.assertEquals(td, 'ndarray')
81 self.assertEquals(td, 'ndarray')
82 md = ser1.getMetadata()
82 md = ser1.getMetadata()
83 self.assertEquals(md['shape'], a.shape)
83 self.assertEquals(md['shape'], a.shape)
84 self.assertEquals(md['dtype'], a.dtype.str)
84 self.assertEquals(md['dtype'], a.dtype.str)
85 buff = ser1.getData()
85 buff = ser1.getData()
86 self.assertEquals(buff, numpy.getbuffer(a))
86 self.assertEquals(buff, numpy.getbuffer(a))
87 s = ns.Serialized(buff, td, md)
87 s = ns.Serialized(buff, td, md)
88 final = ns.unserialize(s)
88 final = ns.unserialize(s)
89 self.assertEquals(numpy.getbuffer(a), numpy.getbuffer(final))
89 self.assertEquals(numpy.getbuffer(a), numpy.getbuffer(final))
90 self.assertTrue((a==final).all())
90 self.assertTrue((a==final).all())
91 self.assertEquals(a.dtype.str, final.dtype.str)
91 self.assertEquals(a.dtype.str, final.dtype.str)
92 self.assertEquals(a.shape, final.shape)
92 self.assertEquals(a.shape, final.shape)
93 # test non-copying:
93 # test non-copying:
94 a[2] = 1e9
94 a[2] = 1e9
95 self.assertTrue((a==final).all())
95 self.assertTrue((a==final).all())
96
96
97 def test_uncan_function_globals(self):
97 def test_uncan_function_globals(self):
98 """test that uncanning a module function restores it into its module"""
98 """test that uncanning a module function restores it into its module"""
99 from re import search
99 from re import search
100 cf = can(search)
100 cf = can(search)
101 csearch = uncan(cf)
101 csearch = uncan(cf)
102 self.assertEqual(csearch.__module__, search.__module__)
102 self.assertEqual(csearch.__module__, search.__module__)
103 self.assertNotEqual(csearch('asd', 'asdf'), None)
103 self.assertNotEqual(csearch('asd', 'asdf'), None)
104 csearch = uncan(cf, dict(a=5))
104 csearch = uncan(cf, dict(a=5))
105 self.assertEqual(csearch.__module__, search.__module__)
105 self.assertEqual(csearch.__module__, search.__module__)
106 self.assertNotEqual(csearch('asd', 'asdf'), None)
106 self.assertNotEqual(csearch('asd', 'asdf'), None)
107
107
108 No newline at end of file
108
@@ -1,111 +1,111 b''
1 """test building messages with streamsession"""
1 """test building messages with streamsession"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import uuid
15 import uuid
16 import zmq
16 import zmq
17
17
18 from zmq.tests import BaseZMQTestCase
18 from zmq.tests import BaseZMQTestCase
19 from zmq.eventloop.zmqstream import ZMQStream
19 from zmq.eventloop.zmqstream import ZMQStream
20 # from IPython.zmq.tests import SessionTestCase
20 # from IPython.zmq.tests import SessionTestCase
21 from IPython.zmq.parallel import streamsession as ss
21 from IPython.parallel import streamsession as ss
22
22
23 class SessionTestCase(BaseZMQTestCase):
23 class SessionTestCase(BaseZMQTestCase):
24
24
25 def setUp(self):
25 def setUp(self):
26 BaseZMQTestCase.setUp(self)
26 BaseZMQTestCase.setUp(self)
27 self.session = ss.StreamSession()
27 self.session = ss.StreamSession()
28
28
29 class TestSession(SessionTestCase):
29 class TestSession(SessionTestCase):
30
30
31 def test_msg(self):
31 def test_msg(self):
32 """message format"""
32 """message format"""
33 msg = self.session.msg('execute')
33 msg = self.session.msg('execute')
34 thekeys = set('header msg_id parent_header msg_type content'.split())
34 thekeys = set('header msg_id parent_header msg_type content'.split())
35 s = set(msg.keys())
35 s = set(msg.keys())
36 self.assertEquals(s, thekeys)
36 self.assertEquals(s, thekeys)
37 self.assertTrue(isinstance(msg['content'],dict))
37 self.assertTrue(isinstance(msg['content'],dict))
38 self.assertTrue(isinstance(msg['header'],dict))
38 self.assertTrue(isinstance(msg['header'],dict))
39 self.assertTrue(isinstance(msg['parent_header'],dict))
39 self.assertTrue(isinstance(msg['parent_header'],dict))
40 self.assertEquals(msg['msg_type'], 'execute')
40 self.assertEquals(msg['msg_type'], 'execute')
41
41
42
42
43
43
44 def test_args(self):
44 def test_args(self):
45 """initialization arguments for StreamSession"""
45 """initialization arguments for StreamSession"""
46 s = self.session
46 s = self.session
47 self.assertTrue(s.pack is ss.default_packer)
47 self.assertTrue(s.pack is ss.default_packer)
48 self.assertTrue(s.unpack is ss.default_unpacker)
48 self.assertTrue(s.unpack is ss.default_unpacker)
49 self.assertEquals(s.username, os.environ.get('USER', 'username'))
49 self.assertEquals(s.username, os.environ.get('USER', 'username'))
50
50
51 s = ss.StreamSession(username=None)
51 s = ss.StreamSession(username=None)
52 self.assertEquals(s.username, os.environ.get('USER', 'username'))
52 self.assertEquals(s.username, os.environ.get('USER', 'username'))
53
53
54 self.assertRaises(TypeError, ss.StreamSession, packer='hi')
54 self.assertRaises(TypeError, ss.StreamSession, packer='hi')
55 self.assertRaises(TypeError, ss.StreamSession, unpacker='hi')
55 self.assertRaises(TypeError, ss.StreamSession, unpacker='hi')
56 u = str(uuid.uuid4())
56 u = str(uuid.uuid4())
57 s = ss.StreamSession(username='carrot', session=u)
57 s = ss.StreamSession(username='carrot', session=u)
58 self.assertEquals(s.session, u)
58 self.assertEquals(s.session, u)
59 self.assertEquals(s.username, 'carrot')
59 self.assertEquals(s.username, 'carrot')
60
60
61 def test_tracking(self):
61 def test_tracking(self):
62 """test tracking messages"""
62 """test tracking messages"""
63 a,b = self.create_bound_pair(zmq.PAIR, zmq.PAIR)
63 a,b = self.create_bound_pair(zmq.PAIR, zmq.PAIR)
64 s = self.session
64 s = self.session
65 stream = ZMQStream(a)
65 stream = ZMQStream(a)
66 msg = s.send(a, 'hello', track=False)
66 msg = s.send(a, 'hello', track=False)
67 self.assertTrue(msg['tracker'] is None)
67 self.assertTrue(msg['tracker'] is None)
68 msg = s.send(a, 'hello', track=True)
68 msg = s.send(a, 'hello', track=True)
69 self.assertTrue(isinstance(msg['tracker'], zmq.MessageTracker))
69 self.assertTrue(isinstance(msg['tracker'], zmq.MessageTracker))
70 M = zmq.Message(b'hi there', track=True)
70 M = zmq.Message(b'hi there', track=True)
71 msg = s.send(a, 'hello', buffers=[M], track=True)
71 msg = s.send(a, 'hello', buffers=[M], track=True)
72 t = msg['tracker']
72 t = msg['tracker']
73 self.assertTrue(isinstance(t, zmq.MessageTracker))
73 self.assertTrue(isinstance(t, zmq.MessageTracker))
74 self.assertRaises(zmq.NotDone, t.wait, .1)
74 self.assertRaises(zmq.NotDone, t.wait, .1)
75 del M
75 del M
76 t.wait(1) # this will raise
76 t.wait(1) # this will raise
77
77
78
78
79 # def test_rekey(self):
79 # def test_rekey(self):
80 # """rekeying dict around json str keys"""
80 # """rekeying dict around json str keys"""
81 # d = {'0': uuid.uuid4(), 0:uuid.uuid4()}
81 # d = {'0': uuid.uuid4(), 0:uuid.uuid4()}
82 # self.assertRaises(KeyError, ss.rekey, d)
82 # self.assertRaises(KeyError, ss.rekey, d)
83 #
83 #
84 # d = {'0': uuid.uuid4(), 1:uuid.uuid4(), 'asdf':uuid.uuid4()}
84 # d = {'0': uuid.uuid4(), 1:uuid.uuid4(), 'asdf':uuid.uuid4()}
85 # d2 = {0:d['0'],1:d[1],'asdf':d['asdf']}
85 # d2 = {0:d['0'],1:d[1],'asdf':d['asdf']}
86 # rd = ss.rekey(d)
86 # rd = ss.rekey(d)
87 # self.assertEquals(d2,rd)
87 # self.assertEquals(d2,rd)
88 #
88 #
89 # d = {'1.5':uuid.uuid4(),'1':uuid.uuid4()}
89 # d = {'1.5':uuid.uuid4(),'1':uuid.uuid4()}
90 # d2 = {1.5:d['1.5'],1:d['1']}
90 # d2 = {1.5:d['1.5'],1:d['1']}
91 # rd = ss.rekey(d)
91 # rd = ss.rekey(d)
92 # self.assertEquals(d2,rd)
92 # self.assertEquals(d2,rd)
93 #
93 #
94 # d = {'1.0':uuid.uuid4(),'1':uuid.uuid4()}
94 # d = {'1.0':uuid.uuid4(),'1':uuid.uuid4()}
95 # self.assertRaises(KeyError, ss.rekey, d)
95 # self.assertRaises(KeyError, ss.rekey, d)
96 #
96 #
97 def test_unique_msg_ids(self):
97 def test_unique_msg_ids(self):
98 """test that messages receive unique ids"""
98 """test that messages receive unique ids"""
99 ids = set()
99 ids = set()
100 for i in range(2**12):
100 for i in range(2**12):
101 h = self.session.msg_header('test')
101 h = self.session.msg_header('test')
102 msg_id = h['msg_id']
102 msg_id = h['msg_id']
103 self.assertTrue(msg_id not in ids)
103 self.assertTrue(msg_id not in ids)
104 ids.add(msg_id)
104 ids.add(msg_id)
105
105
106 def test_feed_identities(self):
106 def test_feed_identities(self):
107 """scrub the front for zmq IDENTITIES"""
107 """scrub the front for zmq IDENTITIES"""
108 theids = "engine client other".split()
108 theids = "engine client other".split()
109 content = dict(code='whoda',stuff=object())
109 content = dict(code='whoda',stuff=object())
110 themsg = self.session.msg('execute',content=content)
110 themsg = self.session.msg('execute',content=content)
111 pmsg = theids
111 pmsg = theids
@@ -1,301 +1,301 b''
1 """test View objects"""
1 """test View objects"""
2 #-------------------------------------------------------------------------------
2 #-------------------------------------------------------------------------------
3 # Copyright (C) 2011 The IPython Development Team
3 # Copyright (C) 2011 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-------------------------------------------------------------------------------
7 #-------------------------------------------------------------------------------
8
8
9 #-------------------------------------------------------------------------------
9 #-------------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12
12
13 import time
13 import time
14 from tempfile import mktemp
14 from tempfile import mktemp
15
15
16 import zmq
16 import zmq
17
17
18 from IPython.zmq.parallel import client as clientmod
18 from IPython import parallel as pmod
19 from IPython.zmq.parallel import error
19 from IPython.parallel import error
20 from IPython.zmq.parallel.asyncresult import AsyncResult, AsyncHubResult, AsyncMapResult
20 from IPython.parallel.asyncresult import AsyncResult, AsyncHubResult, AsyncMapResult
21 from IPython.zmq.parallel.view import LoadBalancedView, DirectView
21 from IPython.parallel.view import LoadBalancedView, DirectView
22 from IPython.zmq.parallel.util import interactive
22 from IPython.parallel.util import interactive
23
23
24 from IPython.zmq.parallel.tests import add_engines
24 from IPython.parallel.tests import add_engines
25
25
26 from .clienttest import ClusterTestCase, segfault, wait, skip_without
26 from .clienttest import ClusterTestCase, segfault, wait, skip_without
27
27
28 def setup():
28 def setup():
29 add_engines(3)
29 add_engines(3)
30
30
31 class TestView(ClusterTestCase):
31 class TestView(ClusterTestCase):
32
32
33 def test_segfault_task(self):
33 def test_segfault_task(self):
34 """test graceful handling of engine death (balanced)"""
34 """test graceful handling of engine death (balanced)"""
35 # self.add_engines(1)
35 # self.add_engines(1)
36 ar = self.client[-1].apply_async(segfault)
36 ar = self.client[-1].apply_async(segfault)
37 self.assertRaisesRemote(error.EngineError, ar.get)
37 self.assertRaisesRemote(error.EngineError, ar.get)
38 eid = ar.engine_id
38 eid = ar.engine_id
39 while eid in self.client.ids:
39 while eid in self.client.ids:
40 time.sleep(.01)
40 time.sleep(.01)
41 self.client.spin()
41 self.client.spin()
42
42
43 def test_segfault_mux(self):
43 def test_segfault_mux(self):
44 """test graceful handling of engine death (direct)"""
44 """test graceful handling of engine death (direct)"""
45 # self.add_engines(1)
45 # self.add_engines(1)
46 eid = self.client.ids[-1]
46 eid = self.client.ids[-1]
47 ar = self.client[eid].apply_async(segfault)
47 ar = self.client[eid].apply_async(segfault)
48 self.assertRaisesRemote(error.EngineError, ar.get)
48 self.assertRaisesRemote(error.EngineError, ar.get)
49 eid = ar.engine_id
49 eid = ar.engine_id
50 while eid in self.client.ids:
50 while eid in self.client.ids:
51 time.sleep(.01)
51 time.sleep(.01)
52 self.client.spin()
52 self.client.spin()
53
53
54 def test_push_pull(self):
54 def test_push_pull(self):
55 """test pushing and pulling"""
55 """test pushing and pulling"""
56 data = dict(a=10, b=1.05, c=range(10), d={'e':(1,2),'f':'hi'})
56 data = dict(a=10, b=1.05, c=range(10), d={'e':(1,2),'f':'hi'})
57 t = self.client.ids[-1]
57 t = self.client.ids[-1]
58 v = self.client[t]
58 v = self.client[t]
59 push = v.push
59 push = v.push
60 pull = v.pull
60 pull = v.pull
61 v.block=True
61 v.block=True
62 nengines = len(self.client)
62 nengines = len(self.client)
63 push({'data':data})
63 push({'data':data})
64 d = pull('data')
64 d = pull('data')
65 self.assertEquals(d, data)
65 self.assertEquals(d, data)
66 self.client[:].push({'data':data})
66 self.client[:].push({'data':data})
67 d = self.client[:].pull('data', block=True)
67 d = self.client[:].pull('data', block=True)
68 self.assertEquals(d, nengines*[data])
68 self.assertEquals(d, nengines*[data])
69 ar = push({'data':data}, block=False)
69 ar = push({'data':data}, block=False)
70 self.assertTrue(isinstance(ar, AsyncResult))
70 self.assertTrue(isinstance(ar, AsyncResult))
71 r = ar.get()
71 r = ar.get()
72 ar = self.client[:].pull('data', block=False)
72 ar = self.client[:].pull('data', block=False)
73 self.assertTrue(isinstance(ar, AsyncResult))
73 self.assertTrue(isinstance(ar, AsyncResult))
74 r = ar.get()
74 r = ar.get()
75 self.assertEquals(r, nengines*[data])
75 self.assertEquals(r, nengines*[data])
76 self.client[:].push(dict(a=10,b=20))
76 self.client[:].push(dict(a=10,b=20))
77 r = self.client[:].pull(('a','b'))
77 r = self.client[:].pull(('a','b'))
78 self.assertEquals(r, nengines*[[10,20]])
78 self.assertEquals(r, nengines*[[10,20]])
79
79
80 def test_push_pull_function(self):
80 def test_push_pull_function(self):
81 "test pushing and pulling functions"
81 "test pushing and pulling functions"
82 def testf(x):
82 def testf(x):
83 return 2.0*x
83 return 2.0*x
84
84
85 t = self.client.ids[-1]
85 t = self.client.ids[-1]
86 self.client[t].block=True
86 self.client[t].block=True
87 push = self.client[t].push
87 push = self.client[t].push
88 pull = self.client[t].pull
88 pull = self.client[t].pull
89 execute = self.client[t].execute
89 execute = self.client[t].execute
90 push({'testf':testf})
90 push({'testf':testf})
91 r = pull('testf')
91 r = pull('testf')
92 self.assertEqual(r(1.0), testf(1.0))
92 self.assertEqual(r(1.0), testf(1.0))
93 execute('r = testf(10)')
93 execute('r = testf(10)')
94 r = pull('r')
94 r = pull('r')
95 self.assertEquals(r, testf(10))
95 self.assertEquals(r, testf(10))
96 ar = self.client[:].push({'testf':testf}, block=False)
96 ar = self.client[:].push({'testf':testf}, block=False)
97 ar.get()
97 ar.get()
98 ar = self.client[:].pull('testf', block=False)
98 ar = self.client[:].pull('testf', block=False)
99 rlist = ar.get()
99 rlist = ar.get()
100 for r in rlist:
100 for r in rlist:
101 self.assertEqual(r(1.0), testf(1.0))
101 self.assertEqual(r(1.0), testf(1.0))
102 execute("def g(x): return x*x")
102 execute("def g(x): return x*x")
103 r = pull(('testf','g'))
103 r = pull(('testf','g'))
104 self.assertEquals((r[0](10),r[1](10)), (testf(10), 100))
104 self.assertEquals((r[0](10),r[1](10)), (testf(10), 100))
105
105
106 def test_push_function_globals(self):
106 def test_push_function_globals(self):
107 """test that pushed functions have access to globals"""
107 """test that pushed functions have access to globals"""
108 @interactive
108 @interactive
109 def geta():
109 def geta():
110 return a
110 return a
111 # self.add_engines(1)
111 # self.add_engines(1)
112 v = self.client[-1]
112 v = self.client[-1]
113 v.block=True
113 v.block=True
114 v['f'] = geta
114 v['f'] = geta
115 self.assertRaisesRemote(NameError, v.execute, 'b=f()')
115 self.assertRaisesRemote(NameError, v.execute, 'b=f()')
116 v.execute('a=5')
116 v.execute('a=5')
117 v.execute('b=f()')
117 v.execute('b=f()')
118 self.assertEquals(v['b'], 5)
118 self.assertEquals(v['b'], 5)
119
119
120 def test_push_function_defaults(self):
120 def test_push_function_defaults(self):
121 """test that pushed functions preserve default args"""
121 """test that pushed functions preserve default args"""
122 def echo(a=10):
122 def echo(a=10):
123 return a
123 return a
124 v = self.client[-1]
124 v = self.client[-1]
125 v.block=True
125 v.block=True
126 v['f'] = echo
126 v['f'] = echo
127 v.execute('b=f()')
127 v.execute('b=f()')
128 self.assertEquals(v['b'], 10)
128 self.assertEquals(v['b'], 10)
129
129
130 def test_get_result(self):
130 def test_get_result(self):
131 """test getting results from the Hub."""
131 """test getting results from the Hub."""
132 c = clientmod.Client(profile='iptest')
132 c = pmod.Client(profile='iptest')
133 # self.add_engines(1)
133 # self.add_engines(1)
134 t = c.ids[-1]
134 t = c.ids[-1]
135 v = c[t]
135 v = c[t]
136 v2 = self.client[t]
136 v2 = self.client[t]
137 ar = v.apply_async(wait, 1)
137 ar = v.apply_async(wait, 1)
138 # give the monitor time to notice the message
138 # give the monitor time to notice the message
139 time.sleep(.25)
139 time.sleep(.25)
140 ahr = v2.get_result(ar.msg_ids)
140 ahr = v2.get_result(ar.msg_ids)
141 self.assertTrue(isinstance(ahr, AsyncHubResult))
141 self.assertTrue(isinstance(ahr, AsyncHubResult))
142 self.assertEquals(ahr.get(), ar.get())
142 self.assertEquals(ahr.get(), ar.get())
143 ar2 = v2.get_result(ar.msg_ids)
143 ar2 = v2.get_result(ar.msg_ids)
144 self.assertFalse(isinstance(ar2, AsyncHubResult))
144 self.assertFalse(isinstance(ar2, AsyncHubResult))
145 c.spin()
145 c.spin()
146 c.close()
146 c.close()
147
147
148 def test_run_newline(self):
148 def test_run_newline(self):
149 """test that run appends newline to files"""
149 """test that run appends newline to files"""
150 tmpfile = mktemp()
150 tmpfile = mktemp()
151 with open(tmpfile, 'w') as f:
151 with open(tmpfile, 'w') as f:
152 f.write("""def g():
152 f.write("""def g():
153 return 5
153 return 5
154 """)
154 """)
155 v = self.client[-1]
155 v = self.client[-1]
156 v.run(tmpfile, block=True)
156 v.run(tmpfile, block=True)
157 self.assertEquals(v.apply_sync(lambda f: f(), clientmod.Reference('g')), 5)
157 self.assertEquals(v.apply_sync(lambda f: f(), pmod.Reference('g')), 5)
158
158
159 def test_apply_tracked(self):
159 def test_apply_tracked(self):
160 """test tracking for apply"""
160 """test tracking for apply"""
161 # self.add_engines(1)
161 # self.add_engines(1)
162 t = self.client.ids[-1]
162 t = self.client.ids[-1]
163 v = self.client[t]
163 v = self.client[t]
164 v.block=False
164 v.block=False
165 def echo(n=1024*1024, **kwargs):
165 def echo(n=1024*1024, **kwargs):
166 with v.temp_flags(**kwargs):
166 with v.temp_flags(**kwargs):
167 return v.apply(lambda x: x, 'x'*n)
167 return v.apply(lambda x: x, 'x'*n)
168 ar = echo(1, track=False)
168 ar = echo(1, track=False)
169 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
169 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
170 self.assertTrue(ar.sent)
170 self.assertTrue(ar.sent)
171 ar = echo(track=True)
171 ar = echo(track=True)
172 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
172 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
173 self.assertEquals(ar.sent, ar._tracker.done)
173 self.assertEquals(ar.sent, ar._tracker.done)
174 ar._tracker.wait()
174 ar._tracker.wait()
175 self.assertTrue(ar.sent)
175 self.assertTrue(ar.sent)
176
176
177 def test_push_tracked(self):
177 def test_push_tracked(self):
178 t = self.client.ids[-1]
178 t = self.client.ids[-1]
179 ns = dict(x='x'*1024*1024)
179 ns = dict(x='x'*1024*1024)
180 v = self.client[t]
180 v = self.client[t]
181 ar = v.push(ns, block=False, track=False)
181 ar = v.push(ns, block=False, track=False)
182 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
182 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
183 self.assertTrue(ar.sent)
183 self.assertTrue(ar.sent)
184
184
185 ar = v.push(ns, block=False, track=True)
185 ar = v.push(ns, block=False, track=True)
186 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
186 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
187 self.assertEquals(ar.sent, ar._tracker.done)
187 self.assertEquals(ar.sent, ar._tracker.done)
188 ar._tracker.wait()
188 ar._tracker.wait()
189 self.assertTrue(ar.sent)
189 self.assertTrue(ar.sent)
190 ar.get()
190 ar.get()
191
191
192 def test_scatter_tracked(self):
192 def test_scatter_tracked(self):
193 t = self.client.ids
193 t = self.client.ids
194 x='x'*1024*1024
194 x='x'*1024*1024
195 ar = self.client[t].scatter('x', x, block=False, track=False)
195 ar = self.client[t].scatter('x', x, block=False, track=False)
196 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
196 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
197 self.assertTrue(ar.sent)
197 self.assertTrue(ar.sent)
198
198
199 ar = self.client[t].scatter('x', x, block=False, track=True)
199 ar = self.client[t].scatter('x', x, block=False, track=True)
200 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
200 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
201 self.assertEquals(ar.sent, ar._tracker.done)
201 self.assertEquals(ar.sent, ar._tracker.done)
202 ar._tracker.wait()
202 ar._tracker.wait()
203 self.assertTrue(ar.sent)
203 self.assertTrue(ar.sent)
204 ar.get()
204 ar.get()
205
205
206 def test_remote_reference(self):
206 def test_remote_reference(self):
207 v = self.client[-1]
207 v = self.client[-1]
208 v['a'] = 123
208 v['a'] = 123
209 ra = clientmod.Reference('a')
209 ra = pmod.Reference('a')
210 b = v.apply_sync(lambda x: x, ra)
210 b = v.apply_sync(lambda x: x, ra)
211 self.assertEquals(b, 123)
211 self.assertEquals(b, 123)
212
212
213
213
214 def test_scatter_gather(self):
214 def test_scatter_gather(self):
215 view = self.client[:]
215 view = self.client[:]
216 seq1 = range(16)
216 seq1 = range(16)
217 view.scatter('a', seq1)
217 view.scatter('a', seq1)
218 seq2 = view.gather('a', block=True)
218 seq2 = view.gather('a', block=True)
219 self.assertEquals(seq2, seq1)
219 self.assertEquals(seq2, seq1)
220 self.assertRaisesRemote(NameError, view.gather, 'asdf', block=True)
220 self.assertRaisesRemote(NameError, view.gather, 'asdf', block=True)
221
221
222 @skip_without('numpy')
222 @skip_without('numpy')
223 def test_scatter_gather_numpy(self):
223 def test_scatter_gather_numpy(self):
224 import numpy
224 import numpy
225 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
225 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
226 view = self.client[:]
226 view = self.client[:]
227 a = numpy.arange(64)
227 a = numpy.arange(64)
228 view.scatter('a', a)
228 view.scatter('a', a)
229 b = view.gather('a', block=True)
229 b = view.gather('a', block=True)
230 assert_array_equal(b, a)
230 assert_array_equal(b, a)
231
231
232 def test_map(self):
232 def test_map(self):
233 view = self.client[:]
233 view = self.client[:]
234 def f(x):
234 def f(x):
235 return x**2
235 return x**2
236 data = range(16)
236 data = range(16)
237 r = view.map_sync(f, data)
237 r = view.map_sync(f, data)
238 self.assertEquals(r, map(f, data))
238 self.assertEquals(r, map(f, data))
239
239
240 def test_scatterGatherNonblocking(self):
240 def test_scatterGatherNonblocking(self):
241 data = range(16)
241 data = range(16)
242 view = self.client[:]
242 view = self.client[:]
243 view.scatter('a', data, block=False)
243 view.scatter('a', data, block=False)
244 ar = view.gather('a', block=False)
244 ar = view.gather('a', block=False)
245 self.assertEquals(ar.get(), data)
245 self.assertEquals(ar.get(), data)
246
246
247 @skip_without('numpy')
247 @skip_without('numpy')
248 def test_scatter_gather_numpy_nonblocking(self):
248 def test_scatter_gather_numpy_nonblocking(self):
249 import numpy
249 import numpy
250 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
250 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
251 a = numpy.arange(64)
251 a = numpy.arange(64)
252 view = self.client[:]
252 view = self.client[:]
253 ar = view.scatter('a', a, block=False)
253 ar = view.scatter('a', a, block=False)
254 self.assertTrue(isinstance(ar, AsyncResult))
254 self.assertTrue(isinstance(ar, AsyncResult))
255 amr = view.gather('a', block=False)
255 amr = view.gather('a', block=False)
256 self.assertTrue(isinstance(amr, AsyncMapResult))
256 self.assertTrue(isinstance(amr, AsyncMapResult))
257 assert_array_equal(amr.get(), a)
257 assert_array_equal(amr.get(), a)
258
258
259 def test_execute(self):
259 def test_execute(self):
260 view = self.client[:]
260 view = self.client[:]
261 # self.client.debug=True
261 # self.client.debug=True
262 execute = view.execute
262 execute = view.execute
263 ar = execute('c=30', block=False)
263 ar = execute('c=30', block=False)
264 self.assertTrue(isinstance(ar, AsyncResult))
264 self.assertTrue(isinstance(ar, AsyncResult))
265 ar = execute('d=[0,1,2]', block=False)
265 ar = execute('d=[0,1,2]', block=False)
266 self.client.wait(ar, 1)
266 self.client.wait(ar, 1)
267 self.assertEquals(len(ar.get()), len(self.client))
267 self.assertEquals(len(ar.get()), len(self.client))
268 for c in view['c']:
268 for c in view['c']:
269 self.assertEquals(c, 30)
269 self.assertEquals(c, 30)
270
270
271 def test_abort(self):
271 def test_abort(self):
272 view = self.client[-1]
272 view = self.client[-1]
273 ar = view.execute('import time; time.sleep(0.25)', block=False)
273 ar = view.execute('import time; time.sleep(0.25)', block=False)
274 ar2 = view.apply_async(lambda : 2)
274 ar2 = view.apply_async(lambda : 2)
275 ar3 = view.apply_async(lambda : 3)
275 ar3 = view.apply_async(lambda : 3)
276 view.abort(ar2)
276 view.abort(ar2)
277 view.abort(ar3.msg_ids)
277 view.abort(ar3.msg_ids)
278 self.assertRaises(error.TaskAborted, ar2.get)
278 self.assertRaises(error.TaskAborted, ar2.get)
279 self.assertRaises(error.TaskAborted, ar3.get)
279 self.assertRaises(error.TaskAborted, ar3.get)
280
280
281 def test_temp_flags(self):
281 def test_temp_flags(self):
282 view = self.client[-1]
282 view = self.client[-1]
283 view.block=True
283 view.block=True
284 with view.temp_flags(block=False):
284 with view.temp_flags(block=False):
285 self.assertFalse(view.block)
285 self.assertFalse(view.block)
286 self.assertTrue(view.block)
286 self.assertTrue(view.block)
287
287
288 def test_importer(self):
288 def test_importer(self):
289 view = self.client[-1]
289 view = self.client[-1]
290 view.clear(block=True)
290 view.clear(block=True)
291 with view.importer:
291 with view.importer:
292 import re
292 import re
293
293
294 @interactive
294 @interactive
295 def findall(pat, s):
295 def findall(pat, s):
296 # this globals() step isn't necessary in real code
296 # this globals() step isn't necessary in real code
297 # only to prevent a closure in the test
297 # only to prevent a closure in the test
298 return globals()['re'].findall(pat, s)
298 return globals()['re'].findall(pat, s)
299
299
300 self.assertEquals(view.apply_sync(findall, '\w+', 'hello world'), 'hello world'.split())
300 self.assertEquals(view.apply_sync(findall, '\w+', 'hello world'), 'hello world'.split())
301
301
1 NO CONTENT: file renamed from IPython/zmq/parallel/util.py to IPython/parallel/util.py
NO CONTENT: file renamed from IPython/zmq/parallel/util.py to IPython/parallel/util.py
@@ -1,1028 +1,1028 b''
1 """Views of remote engines."""
1 """Views of remote engines."""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team
3 # Copyright (C) 2010 The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 import imp
13 import imp
14 import sys
14 import sys
15 import warnings
15 import warnings
16 from contextlib import contextmanager
16 from contextlib import contextmanager
17 from types import ModuleType
17 from types import ModuleType
18
18
19 import zmq
19 import zmq
20
20
21 from IPython.testing import decorators as testdec
21 from IPython.testing import decorators as testdec
22 from IPython.utils.traitlets import HasTraits, Any, Bool, List, Dict, Set, Int, Instance, CFloat
22 from IPython.utils.traitlets import HasTraits, Any, Bool, List, Dict, Set, Int, Instance, CFloat
23
23
24 from IPython.external.decorator import decorator
24 from IPython.external.decorator import decorator
25
25
26 from . import map as Map
26 from . import map as Map
27 from . import util
27 from . import util
28 from .asyncresult import AsyncResult, AsyncMapResult
28 from .asyncresult import AsyncResult, AsyncMapResult
29 from .dependency import Dependency, dependent
29 from .dependency import Dependency, dependent
30 from .remotefunction import ParallelFunction, parallel, remote
30 from .remotefunction import ParallelFunction, parallel, remote
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Decorators
33 # Decorators
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 @decorator
36 @decorator
37 def save_ids(f, self, *args, **kwargs):
37 def save_ids(f, self, *args, **kwargs):
38 """Keep our history and outstanding attributes up to date after a method call."""
38 """Keep our history and outstanding attributes up to date after a method call."""
39 n_previous = len(self.client.history)
39 n_previous = len(self.client.history)
40 try:
40 try:
41 ret = f(self, *args, **kwargs)
41 ret = f(self, *args, **kwargs)
42 finally:
42 finally:
43 nmsgs = len(self.client.history) - n_previous
43 nmsgs = len(self.client.history) - n_previous
44 msg_ids = self.client.history[-nmsgs:]
44 msg_ids = self.client.history[-nmsgs:]
45 self.history.extend(msg_ids)
45 self.history.extend(msg_ids)
46 map(self.outstanding.add, msg_ids)
46 map(self.outstanding.add, msg_ids)
47 return ret
47 return ret
48
48
49 @decorator
49 @decorator
50 def sync_results(f, self, *args, **kwargs):
50 def sync_results(f, self, *args, **kwargs):
51 """sync relevant results from self.client to our results attribute."""
51 """sync relevant results from self.client to our results attribute."""
52 ret = f(self, *args, **kwargs)
52 ret = f(self, *args, **kwargs)
53 delta = self.outstanding.difference(self.client.outstanding)
53 delta = self.outstanding.difference(self.client.outstanding)
54 completed = self.outstanding.intersection(delta)
54 completed = self.outstanding.intersection(delta)
55 self.outstanding = self.outstanding.difference(completed)
55 self.outstanding = self.outstanding.difference(completed)
56 for msg_id in completed:
56 for msg_id in completed:
57 self.results[msg_id] = self.client.results[msg_id]
57 self.results[msg_id] = self.client.results[msg_id]
58 return ret
58 return ret
59
59
60 @decorator
60 @decorator
61 def spin_after(f, self, *args, **kwargs):
61 def spin_after(f, self, *args, **kwargs):
62 """call spin after the method."""
62 """call spin after the method."""
63 ret = f(self, *args, **kwargs)
63 ret = f(self, *args, **kwargs)
64 self.spin()
64 self.spin()
65 return ret
65 return ret
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Classes
68 # Classes
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 class View(HasTraits):
71 class View(HasTraits):
72 """Base View class for more convenint apply(f,*args,**kwargs) syntax via attributes.
72 """Base View class for more convenint apply(f,*args,**kwargs) syntax via attributes.
73
73
74 Don't use this class, use subclasses.
74 Don't use this class, use subclasses.
75
75
76 Methods
76 Methods
77 -------
77 -------
78
78
79 spin
79 spin
80 flushes incoming results and registration state changes
80 flushes incoming results and registration state changes
81 control methods spin, and requesting `ids` also ensures up to date
81 control methods spin, and requesting `ids` also ensures up to date
82
82
83 wait
83 wait
84 wait on one or more msg_ids
84 wait on one or more msg_ids
85
85
86 execution methods
86 execution methods
87 apply
87 apply
88 legacy: execute, run
88 legacy: execute, run
89
89
90 data movement
90 data movement
91 push, pull, scatter, gather
91 push, pull, scatter, gather
92
92
93 query methods
93 query methods
94 get_result, queue_status, purge_results, result_status
94 get_result, queue_status, purge_results, result_status
95
95
96 control methods
96 control methods
97 abort, shutdown
97 abort, shutdown
98
98
99 """
99 """
100 # flags
100 # flags
101 block=Bool(False)
101 block=Bool(False)
102 track=Bool(True)
102 track=Bool(True)
103 targets = Any()
103 targets = Any()
104
104
105 history=List()
105 history=List()
106 outstanding = Set()
106 outstanding = Set()
107 results = Dict()
107 results = Dict()
108 client = Instance('IPython.zmq.parallel.client.Client')
108 client = Instance('IPython.parallel.client.Client')
109
109
110 _socket = Instance('zmq.Socket')
110 _socket = Instance('zmq.Socket')
111 _flag_names = List(['targets', 'block', 'track'])
111 _flag_names = List(['targets', 'block', 'track'])
112 _targets = Any()
112 _targets = Any()
113 _idents = Any()
113 _idents = Any()
114
114
115 def __init__(self, client=None, socket=None, **flags):
115 def __init__(self, client=None, socket=None, **flags):
116 super(View, self).__init__(client=client, _socket=socket)
116 super(View, self).__init__(client=client, _socket=socket)
117 self.block = client.block
117 self.block = client.block
118
118
119 self.set_flags(**flags)
119 self.set_flags(**flags)
120
120
121 assert not self.__class__ is View, "Don't use base View objects, use subclasses"
121 assert not self.__class__ is View, "Don't use base View objects, use subclasses"
122
122
123
123
124 def __repr__(self):
124 def __repr__(self):
125 strtargets = str(self.targets)
125 strtargets = str(self.targets)
126 if len(strtargets) > 16:
126 if len(strtargets) > 16:
127 strtargets = strtargets[:12]+'...]'
127 strtargets = strtargets[:12]+'...]'
128 return "<%s %s>"%(self.__class__.__name__, strtargets)
128 return "<%s %s>"%(self.__class__.__name__, strtargets)
129
129
130 def set_flags(self, **kwargs):
130 def set_flags(self, **kwargs):
131 """set my attribute flags by keyword.
131 """set my attribute flags by keyword.
132
132
133 Views determine behavior with a few attributes (`block`, `track`, etc.).
133 Views determine behavior with a few attributes (`block`, `track`, etc.).
134 These attributes can be set all at once by name with this method.
134 These attributes can be set all at once by name with this method.
135
135
136 Parameters
136 Parameters
137 ----------
137 ----------
138
138
139 block : bool
139 block : bool
140 whether to wait for results
140 whether to wait for results
141 track : bool
141 track : bool
142 whether to create a MessageTracker to allow the user to
142 whether to create a MessageTracker to allow the user to
143 safely edit after arrays and buffers during non-copying
143 safely edit after arrays and buffers during non-copying
144 sends.
144 sends.
145 """
145 """
146 for name, value in kwargs.iteritems():
146 for name, value in kwargs.iteritems():
147 if name not in self._flag_names:
147 if name not in self._flag_names:
148 raise KeyError("Invalid name: %r"%name)
148 raise KeyError("Invalid name: %r"%name)
149 else:
149 else:
150 setattr(self, name, value)
150 setattr(self, name, value)
151
151
152 @contextmanager
152 @contextmanager
153 def temp_flags(self, **kwargs):
153 def temp_flags(self, **kwargs):
154 """temporarily set flags, for use in `with` statements.
154 """temporarily set flags, for use in `with` statements.
155
155
156 See set_flags for permanent setting of flags
156 See set_flags for permanent setting of flags
157
157
158 Examples
158 Examples
159 --------
159 --------
160
160
161 >>> view.track=False
161 >>> view.track=False
162 ...
162 ...
163 >>> with view.temp_flags(track=True):
163 >>> with view.temp_flags(track=True):
164 ... ar = view.apply(dostuff, my_big_array)
164 ... ar = view.apply(dostuff, my_big_array)
165 ... ar.tracker.wait() # wait for send to finish
165 ... ar.tracker.wait() # wait for send to finish
166 >>> view.track
166 >>> view.track
167 False
167 False
168
168
169 """
169 """
170 # preflight: save flags, and set temporaries
170 # preflight: save flags, and set temporaries
171 saved_flags = {}
171 saved_flags = {}
172 for f in self._flag_names:
172 for f in self._flag_names:
173 saved_flags[f] = getattr(self, f)
173 saved_flags[f] = getattr(self, f)
174 self.set_flags(**kwargs)
174 self.set_flags(**kwargs)
175 # yield to the with-statement block
175 # yield to the with-statement block
176 try:
176 try:
177 yield
177 yield
178 finally:
178 finally:
179 # postflight: restore saved flags
179 # postflight: restore saved flags
180 self.set_flags(**saved_flags)
180 self.set_flags(**saved_flags)
181
181
182
182
183 #----------------------------------------------------------------
183 #----------------------------------------------------------------
184 # apply
184 # apply
185 #----------------------------------------------------------------
185 #----------------------------------------------------------------
186
186
187 @sync_results
187 @sync_results
188 @save_ids
188 @save_ids
189 def _really_apply(self, f, args, kwargs, block=None, **options):
189 def _really_apply(self, f, args, kwargs, block=None, **options):
190 """wrapper for client.send_apply_message"""
190 """wrapper for client.send_apply_message"""
191 raise NotImplementedError("Implement in subclasses")
191 raise NotImplementedError("Implement in subclasses")
192
192
193 def apply(self, f, *args, **kwargs):
193 def apply(self, f, *args, **kwargs):
194 """calls f(*args, **kwargs) on remote engines, returning the result.
194 """calls f(*args, **kwargs) on remote engines, returning the result.
195
195
196 This method sets all apply flags via this View's attributes.
196 This method sets all apply flags via this View's attributes.
197
197
198 if self.block is False:
198 if self.block is False:
199 returns AsyncResult
199 returns AsyncResult
200 else:
200 else:
201 returns actual result of f(*args, **kwargs)
201 returns actual result of f(*args, **kwargs)
202 """
202 """
203 return self._really_apply(f, args, kwargs)
203 return self._really_apply(f, args, kwargs)
204
204
205 def apply_async(self, f, *args, **kwargs):
205 def apply_async(self, f, *args, **kwargs):
206 """calls f(*args, **kwargs) on remote engines in a nonblocking manner.
206 """calls f(*args, **kwargs) on remote engines in a nonblocking manner.
207
207
208 returns AsyncResult
208 returns AsyncResult
209 """
209 """
210 return self._really_apply(f, args, kwargs, block=False)
210 return self._really_apply(f, args, kwargs, block=False)
211
211
212 @spin_after
212 @spin_after
213 def apply_sync(self, f, *args, **kwargs):
213 def apply_sync(self, f, *args, **kwargs):
214 """calls f(*args, **kwargs) on remote engines in a blocking manner,
214 """calls f(*args, **kwargs) on remote engines in a blocking manner,
215 returning the result.
215 returning the result.
216
216
217 returns: actual result of f(*args, **kwargs)
217 returns: actual result of f(*args, **kwargs)
218 """
218 """
219 return self._really_apply(f, args, kwargs, block=True)
219 return self._really_apply(f, args, kwargs, block=True)
220
220
221 #----------------------------------------------------------------
221 #----------------------------------------------------------------
222 # wrappers for client and control methods
222 # wrappers for client and control methods
223 #----------------------------------------------------------------
223 #----------------------------------------------------------------
224 @sync_results
224 @sync_results
225 def spin(self):
225 def spin(self):
226 """spin the client, and sync"""
226 """spin the client, and sync"""
227 self.client.spin()
227 self.client.spin()
228
228
229 @sync_results
229 @sync_results
230 def wait(self, jobs=None, timeout=-1):
230 def wait(self, jobs=None, timeout=-1):
231 """waits on one or more `jobs`, for up to `timeout` seconds.
231 """waits on one or more `jobs`, for up to `timeout` seconds.
232
232
233 Parameters
233 Parameters
234 ----------
234 ----------
235
235
236 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
236 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
237 ints are indices to self.history
237 ints are indices to self.history
238 strs are msg_ids
238 strs are msg_ids
239 default: wait on all outstanding messages
239 default: wait on all outstanding messages
240 timeout : float
240 timeout : float
241 a time in seconds, after which to give up.
241 a time in seconds, after which to give up.
242 default is -1, which means no timeout
242 default is -1, which means no timeout
243
243
244 Returns
244 Returns
245 -------
245 -------
246
246
247 True : when all msg_ids are done
247 True : when all msg_ids are done
248 False : timeout reached, some msg_ids still outstanding
248 False : timeout reached, some msg_ids still outstanding
249 """
249 """
250 if jobs is None:
250 if jobs is None:
251 jobs = self.history
251 jobs = self.history
252 return self.client.wait(jobs, timeout)
252 return self.client.wait(jobs, timeout)
253
253
254 def abort(self, jobs=None, targets=None, block=None):
254 def abort(self, jobs=None, targets=None, block=None):
255 """Abort jobs on my engines.
255 """Abort jobs on my engines.
256
256
257 Parameters
257 Parameters
258 ----------
258 ----------
259
259
260 jobs : None, str, list of strs, optional
260 jobs : None, str, list of strs, optional
261 if None: abort all jobs.
261 if None: abort all jobs.
262 else: abort specific msg_id(s).
262 else: abort specific msg_id(s).
263 """
263 """
264 block = block if block is not None else self.block
264 block = block if block is not None else self.block
265 targets = targets if targets is not None else self.targets
265 targets = targets if targets is not None else self.targets
266 return self.client.abort(jobs=jobs, targets=targets, block=block)
266 return self.client.abort(jobs=jobs, targets=targets, block=block)
267
267
268 def queue_status(self, targets=None, verbose=False):
268 def queue_status(self, targets=None, verbose=False):
269 """Fetch the Queue status of my engines"""
269 """Fetch the Queue status of my engines"""
270 targets = targets if targets is not None else self.targets
270 targets = targets if targets is not None else self.targets
271 return self.client.queue_status(targets=targets, verbose=verbose)
271 return self.client.queue_status(targets=targets, verbose=verbose)
272
272
273 def purge_results(self, jobs=[], targets=[]):
273 def purge_results(self, jobs=[], targets=[]):
274 """Instruct the controller to forget specific results."""
274 """Instruct the controller to forget specific results."""
275 if targets is None or targets == 'all':
275 if targets is None or targets == 'all':
276 targets = self.targets
276 targets = self.targets
277 return self.client.purge_results(jobs=jobs, targets=targets)
277 return self.client.purge_results(jobs=jobs, targets=targets)
278
278
279 @spin_after
279 @spin_after
280 def get_result(self, indices_or_msg_ids=None):
280 def get_result(self, indices_or_msg_ids=None):
281 """return one or more results, specified by history index or msg_id.
281 """return one or more results, specified by history index or msg_id.
282
282
283 See client.get_result for details.
283 See client.get_result for details.
284
284
285 """
285 """
286
286
287 if indices_or_msg_ids is None:
287 if indices_or_msg_ids is None:
288 indices_or_msg_ids = -1
288 indices_or_msg_ids = -1
289 if isinstance(indices_or_msg_ids, int):
289 if isinstance(indices_or_msg_ids, int):
290 indices_or_msg_ids = self.history[indices_or_msg_ids]
290 indices_or_msg_ids = self.history[indices_or_msg_ids]
291 elif isinstance(indices_or_msg_ids, (list,tuple,set)):
291 elif isinstance(indices_or_msg_ids, (list,tuple,set)):
292 indices_or_msg_ids = list(indices_or_msg_ids)
292 indices_or_msg_ids = list(indices_or_msg_ids)
293 for i,index in enumerate(indices_or_msg_ids):
293 for i,index in enumerate(indices_or_msg_ids):
294 if isinstance(index, int):
294 if isinstance(index, int):
295 indices_or_msg_ids[i] = self.history[index]
295 indices_or_msg_ids[i] = self.history[index]
296 return self.client.get_result(indices_or_msg_ids)
296 return self.client.get_result(indices_or_msg_ids)
297
297
298 #-------------------------------------------------------------------
298 #-------------------------------------------------------------------
299 # Map
299 # Map
300 #-------------------------------------------------------------------
300 #-------------------------------------------------------------------
301
301
302 def map(self, f, *sequences, **kwargs):
302 def map(self, f, *sequences, **kwargs):
303 """override in subclasses"""
303 """override in subclasses"""
304 raise NotImplementedError
304 raise NotImplementedError
305
305
306 def map_async(self, f, *sequences, **kwargs):
306 def map_async(self, f, *sequences, **kwargs):
307 """Parallel version of builtin `map`, using this view's engines.
307 """Parallel version of builtin `map`, using this view's engines.
308
308
309 This is equivalent to map(...block=False)
309 This is equivalent to map(...block=False)
310
310
311 See `self.map` for details.
311 See `self.map` for details.
312 """
312 """
313 if 'block' in kwargs:
313 if 'block' in kwargs:
314 raise TypeError("map_async doesn't take a `block` keyword argument.")
314 raise TypeError("map_async doesn't take a `block` keyword argument.")
315 kwargs['block'] = False
315 kwargs['block'] = False
316 return self.map(f,*sequences,**kwargs)
316 return self.map(f,*sequences,**kwargs)
317
317
318 def map_sync(self, f, *sequences, **kwargs):
318 def map_sync(self, f, *sequences, **kwargs):
319 """Parallel version of builtin `map`, using this view's engines.
319 """Parallel version of builtin `map`, using this view's engines.
320
320
321 This is equivalent to map(...block=True)
321 This is equivalent to map(...block=True)
322
322
323 See `self.map` for details.
323 See `self.map` for details.
324 """
324 """
325 if 'block' in kwargs:
325 if 'block' in kwargs:
326 raise TypeError("map_sync doesn't take a `block` keyword argument.")
326 raise TypeError("map_sync doesn't take a `block` keyword argument.")
327 kwargs['block'] = True
327 kwargs['block'] = True
328 return self.map(f,*sequences,**kwargs)
328 return self.map(f,*sequences,**kwargs)
329
329
330 def imap(self, f, *sequences, **kwargs):
330 def imap(self, f, *sequences, **kwargs):
331 """Parallel version of `itertools.imap`.
331 """Parallel version of `itertools.imap`.
332
332
333 See `self.map` for details.
333 See `self.map` for details.
334
334
335 """
335 """
336
336
337 return iter(self.map_async(f,*sequences, **kwargs))
337 return iter(self.map_async(f,*sequences, **kwargs))
338
338
339 #-------------------------------------------------------------------
339 #-------------------------------------------------------------------
340 # Decorators
340 # Decorators
341 #-------------------------------------------------------------------
341 #-------------------------------------------------------------------
342
342
343 def remote(self, block=True, **flags):
343 def remote(self, block=True, **flags):
344 """Decorator for making a RemoteFunction"""
344 """Decorator for making a RemoteFunction"""
345 block = self.block if block is None else block
345 block = self.block if block is None else block
346 return remote(self, block=block, **flags)
346 return remote(self, block=block, **flags)
347
347
348 def parallel(self, dist='b', block=None, **flags):
348 def parallel(self, dist='b', block=None, **flags):
349 """Decorator for making a ParallelFunction"""
349 """Decorator for making a ParallelFunction"""
350 block = self.block if block is None else block
350 block = self.block if block is None else block
351 return parallel(self, dist=dist, block=block, **flags)
351 return parallel(self, dist=dist, block=block, **flags)
352
352
353 @testdec.skip_doctest
353 @testdec.skip_doctest
354 class DirectView(View):
354 class DirectView(View):
355 """Direct Multiplexer View of one or more engines.
355 """Direct Multiplexer View of one or more engines.
356
356
357 These are created via indexed access to a client:
357 These are created via indexed access to a client:
358
358
359 >>> dv_1 = client[1]
359 >>> dv_1 = client[1]
360 >>> dv_all = client[:]
360 >>> dv_all = client[:]
361 >>> dv_even = client[::2]
361 >>> dv_even = client[::2]
362 >>> dv_some = client[1:3]
362 >>> dv_some = client[1:3]
363
363
364 This object provides dictionary access to engine namespaces:
364 This object provides dictionary access to engine namespaces:
365
365
366 # push a=5:
366 # push a=5:
367 >>> dv['a'] = 5
367 >>> dv['a'] = 5
368 # pull 'foo':
368 # pull 'foo':
369 >>> db['foo']
369 >>> db['foo']
370
370
371 """
371 """
372
372
373 def __init__(self, client=None, socket=None, targets=None):
373 def __init__(self, client=None, socket=None, targets=None):
374 super(DirectView, self).__init__(client=client, socket=socket, targets=targets)
374 super(DirectView, self).__init__(client=client, socket=socket, targets=targets)
375
375
376 @property
376 @property
377 def importer(self):
377 def importer(self):
378 """sync_imports(local=True) as a property.
378 """sync_imports(local=True) as a property.
379
379
380 See sync_imports for details.
380 See sync_imports for details.
381
381
382 In [10]: with v.importer:
382 In [10]: with v.importer:
383 ....: import numpy
383 ....: import numpy
384 ....:
384 ....:
385 importing numpy on engine(s)
385 importing numpy on engine(s)
386
386
387 """
387 """
388 return self.sync_imports(True)
388 return self.sync_imports(True)
389
389
390 @contextmanager
390 @contextmanager
391 def sync_imports(self, local=True):
391 def sync_imports(self, local=True):
392 """Context Manager for performing simultaneous local and remote imports.
392 """Context Manager for performing simultaneous local and remote imports.
393
393
394 'import x as y' will *not* work. The 'as y' part will simply be ignored.
394 'import x as y' will *not* work. The 'as y' part will simply be ignored.
395
395
396 >>> with view.sync_imports():
396 >>> with view.sync_imports():
397 ... from numpy import recarray
397 ... from numpy import recarray
398 importing recarray from numpy on engine(s)
398 importing recarray from numpy on engine(s)
399
399
400 """
400 """
401 import __builtin__
401 import __builtin__
402 local_import = __builtin__.__import__
402 local_import = __builtin__.__import__
403 modules = set()
403 modules = set()
404 results = []
404 results = []
405 @util.interactive
405 @util.interactive
406 def remote_import(name, fromlist, level):
406 def remote_import(name, fromlist, level):
407 """the function to be passed to apply, that actually performs the import
407 """the function to be passed to apply, that actually performs the import
408 on the engine, and loads up the user namespace.
408 on the engine, and loads up the user namespace.
409 """
409 """
410 import sys
410 import sys
411 user_ns = globals()
411 user_ns = globals()
412 mod = __import__(name, fromlist=fromlist, level=level)
412 mod = __import__(name, fromlist=fromlist, level=level)
413 if fromlist:
413 if fromlist:
414 for key in fromlist:
414 for key in fromlist:
415 user_ns[key] = getattr(mod, key)
415 user_ns[key] = getattr(mod, key)
416 else:
416 else:
417 user_ns[name] = sys.modules[name]
417 user_ns[name] = sys.modules[name]
418
418
419 def view_import(name, globals={}, locals={}, fromlist=[], level=-1):
419 def view_import(name, globals={}, locals={}, fromlist=[], level=-1):
420 """the drop-in replacement for __import__, that optionally imports
420 """the drop-in replacement for __import__, that optionally imports
421 locally as well.
421 locally as well.
422 """
422 """
423 # don't override nested imports
423 # don't override nested imports
424 save_import = __builtin__.__import__
424 save_import = __builtin__.__import__
425 __builtin__.__import__ = local_import
425 __builtin__.__import__ = local_import
426
426
427 if imp.lock_held():
427 if imp.lock_held():
428 # this is a side-effect import, don't do it remotely, or even
428 # this is a side-effect import, don't do it remotely, or even
429 # ignore the local effects
429 # ignore the local effects
430 return local_import(name, globals, locals, fromlist, level)
430 return local_import(name, globals, locals, fromlist, level)
431
431
432 imp.acquire_lock()
432 imp.acquire_lock()
433 if local:
433 if local:
434 mod = local_import(name, globals, locals, fromlist, level)
434 mod = local_import(name, globals, locals, fromlist, level)
435 else:
435 else:
436 raise NotImplementedError("remote-only imports not yet implemented")
436 raise NotImplementedError("remote-only imports not yet implemented")
437 imp.release_lock()
437 imp.release_lock()
438
438
439 key = name+':'+','.join(fromlist or [])
439 key = name+':'+','.join(fromlist or [])
440 if level == -1 and key not in modules:
440 if level == -1 and key not in modules:
441 modules.add(key)
441 modules.add(key)
442 if fromlist:
442 if fromlist:
443 print "importing %s from %s on engine(s)"%(','.join(fromlist), name)
443 print "importing %s from %s on engine(s)"%(','.join(fromlist), name)
444 else:
444 else:
445 print "importing %s on engine(s)"%name
445 print "importing %s on engine(s)"%name
446 results.append(self.apply_async(remote_import, name, fromlist, level))
446 results.append(self.apply_async(remote_import, name, fromlist, level))
447 # restore override
447 # restore override
448 __builtin__.__import__ = save_import
448 __builtin__.__import__ = save_import
449
449
450 return mod
450 return mod
451
451
452 # override __import__
452 # override __import__
453 __builtin__.__import__ = view_import
453 __builtin__.__import__ = view_import
454 try:
454 try:
455 # enter the block
455 # enter the block
456 yield
456 yield
457 except ImportError:
457 except ImportError:
458 if not local:
458 if not local:
459 # ignore import errors if not doing local imports
459 # ignore import errors if not doing local imports
460 pass
460 pass
461 finally:
461 finally:
462 # always restore __import__
462 # always restore __import__
463 __builtin__.__import__ = local_import
463 __builtin__.__import__ = local_import
464
464
465 for r in results:
465 for r in results:
466 # raise possible remote ImportErrors here
466 # raise possible remote ImportErrors here
467 r.get()
467 r.get()
468
468
469
469
470 @sync_results
470 @sync_results
471 @save_ids
471 @save_ids
472 def _really_apply(self, f, args=None, kwargs=None, targets=None, block=None, track=None):
472 def _really_apply(self, f, args=None, kwargs=None, targets=None, block=None, track=None):
473 """calls f(*args, **kwargs) on remote engines, returning the result.
473 """calls f(*args, **kwargs) on remote engines, returning the result.
474
474
475 This method sets all of `apply`'s flags via this View's attributes.
475 This method sets all of `apply`'s flags via this View's attributes.
476
476
477 Parameters
477 Parameters
478 ----------
478 ----------
479
479
480 f : callable
480 f : callable
481
481
482 args : list [default: empty]
482 args : list [default: empty]
483
483
484 kwargs : dict [default: empty]
484 kwargs : dict [default: empty]
485
485
486 targets : target list [default: self.targets]
486 targets : target list [default: self.targets]
487 where to run
487 where to run
488 block : bool [default: self.block]
488 block : bool [default: self.block]
489 whether to block
489 whether to block
490 track : bool [default: self.track]
490 track : bool [default: self.track]
491 whether to ask zmq to track the message, for safe non-copying sends
491 whether to ask zmq to track the message, for safe non-copying sends
492
492
493 Returns
493 Returns
494 -------
494 -------
495
495
496 if self.block is False:
496 if self.block is False:
497 returns AsyncResult
497 returns AsyncResult
498 else:
498 else:
499 returns actual result of f(*args, **kwargs) on the engine(s)
499 returns actual result of f(*args, **kwargs) on the engine(s)
500 This will be a list of self.targets is also a list (even length 1), or
500 This will be a list of self.targets is also a list (even length 1), or
501 the single result if self.targets is an integer engine id
501 the single result if self.targets is an integer engine id
502 """
502 """
503 args = [] if args is None else args
503 args = [] if args is None else args
504 kwargs = {} if kwargs is None else kwargs
504 kwargs = {} if kwargs is None else kwargs
505 block = self.block if block is None else block
505 block = self.block if block is None else block
506 track = self.track if track is None else track
506 track = self.track if track is None else track
507 targets = self.targets if targets is None else targets
507 targets = self.targets if targets is None else targets
508
508
509 _idents = self.client._build_targets(targets)[0]
509 _idents = self.client._build_targets(targets)[0]
510 msg_ids = []
510 msg_ids = []
511 trackers = []
511 trackers = []
512 for ident in _idents:
512 for ident in _idents:
513 msg = self.client.send_apply_message(self._socket, f, args, kwargs, track=track,
513 msg = self.client.send_apply_message(self._socket, f, args, kwargs, track=track,
514 ident=ident)
514 ident=ident)
515 if track:
515 if track:
516 trackers.append(msg['tracker'])
516 trackers.append(msg['tracker'])
517 msg_ids.append(msg['msg_id'])
517 msg_ids.append(msg['msg_id'])
518 tracker = None if track is False else zmq.MessageTracker(*trackers)
518 tracker = None if track is False else zmq.MessageTracker(*trackers)
519 ar = AsyncResult(self.client, msg_ids, fname=f.__name__, targets=targets, tracker=tracker)
519 ar = AsyncResult(self.client, msg_ids, fname=f.__name__, targets=targets, tracker=tracker)
520 if block:
520 if block:
521 try:
521 try:
522 return ar.get()
522 return ar.get()
523 except KeyboardInterrupt:
523 except KeyboardInterrupt:
524 pass
524 pass
525 return ar
525 return ar
526
526
527 @spin_after
527 @spin_after
528 def map(self, f, *sequences, **kwargs):
528 def map(self, f, *sequences, **kwargs):
529 """view.map(f, *sequences, block=self.block) => list|AsyncMapResult
529 """view.map(f, *sequences, block=self.block) => list|AsyncMapResult
530
530
531 Parallel version of builtin `map`, using this View's `targets`.
531 Parallel version of builtin `map`, using this View's `targets`.
532
532
533 There will be one task per target, so work will be chunked
533 There will be one task per target, so work will be chunked
534 if the sequences are longer than `targets`.
534 if the sequences are longer than `targets`.
535
535
536 Results can be iterated as they are ready, but will become available in chunks.
536 Results can be iterated as they are ready, but will become available in chunks.
537
537
538 Parameters
538 Parameters
539 ----------
539 ----------
540
540
541 f : callable
541 f : callable
542 function to be mapped
542 function to be mapped
543 *sequences: one or more sequences of matching length
543 *sequences: one or more sequences of matching length
544 the sequences to be distributed and passed to `f`
544 the sequences to be distributed and passed to `f`
545 block : bool
545 block : bool
546 whether to wait for the result or not [default self.block]
546 whether to wait for the result or not [default self.block]
547
547
548 Returns
548 Returns
549 -------
549 -------
550
550
551 if block=False:
551 if block=False:
552 AsyncMapResult
552 AsyncMapResult
553 An object like AsyncResult, but which reassembles the sequence of results
553 An object like AsyncResult, but which reassembles the sequence of results
554 into a single list. AsyncMapResults can be iterated through before all
554 into a single list. AsyncMapResults can be iterated through before all
555 results are complete.
555 results are complete.
556 else:
556 else:
557 list
557 list
558 the result of map(f,*sequences)
558 the result of map(f,*sequences)
559 """
559 """
560
560
561 block = kwargs.pop('block', self.block)
561 block = kwargs.pop('block', self.block)
562 for k in kwargs.keys():
562 for k in kwargs.keys():
563 if k not in ['block', 'track']:
563 if k not in ['block', 'track']:
564 raise TypeError("invalid keyword arg, %r"%k)
564 raise TypeError("invalid keyword arg, %r"%k)
565
565
566 assert len(sequences) > 0, "must have some sequences to map onto!"
566 assert len(sequences) > 0, "must have some sequences to map onto!"
567 pf = ParallelFunction(self, f, block=block, **kwargs)
567 pf = ParallelFunction(self, f, block=block, **kwargs)
568 return pf.map(*sequences)
568 return pf.map(*sequences)
569
569
570 def execute(self, code, targets=None, block=None):
570 def execute(self, code, targets=None, block=None):
571 """Executes `code` on `targets` in blocking or nonblocking manner.
571 """Executes `code` on `targets` in blocking or nonblocking manner.
572
572
573 ``execute`` is always `bound` (affects engine namespace)
573 ``execute`` is always `bound` (affects engine namespace)
574
574
575 Parameters
575 Parameters
576 ----------
576 ----------
577
577
578 code : str
578 code : str
579 the code string to be executed
579 the code string to be executed
580 block : bool
580 block : bool
581 whether or not to wait until done to return
581 whether or not to wait until done to return
582 default: self.block
582 default: self.block
583 """
583 """
584 return self._really_apply(util._execute, args=(code,), block=block, targets=targets)
584 return self._really_apply(util._execute, args=(code,), block=block, targets=targets)
585
585
586 def run(self, filename, targets=None, block=None):
586 def run(self, filename, targets=None, block=None):
587 """Execute contents of `filename` on my engine(s).
587 """Execute contents of `filename` on my engine(s).
588
588
589 This simply reads the contents of the file and calls `execute`.
589 This simply reads the contents of the file and calls `execute`.
590
590
591 Parameters
591 Parameters
592 ----------
592 ----------
593
593
594 filename : str
594 filename : str
595 The path to the file
595 The path to the file
596 targets : int/str/list of ints/strs
596 targets : int/str/list of ints/strs
597 the engines on which to execute
597 the engines on which to execute
598 default : all
598 default : all
599 block : bool
599 block : bool
600 whether or not to wait until done
600 whether or not to wait until done
601 default: self.block
601 default: self.block
602
602
603 """
603 """
604 with open(filename, 'r') as f:
604 with open(filename, 'r') as f:
605 # add newline in case of trailing indented whitespace
605 # add newline in case of trailing indented whitespace
606 # which will cause SyntaxError
606 # which will cause SyntaxError
607 code = f.read()+'\n'
607 code = f.read()+'\n'
608 return self.execute(code, block=block, targets=targets)
608 return self.execute(code, block=block, targets=targets)
609
609
610 def update(self, ns):
610 def update(self, ns):
611 """update remote namespace with dict `ns`
611 """update remote namespace with dict `ns`
612
612
613 See `push` for details.
613 See `push` for details.
614 """
614 """
615 return self.push(ns, block=self.block, track=self.track)
615 return self.push(ns, block=self.block, track=self.track)
616
616
617 def push(self, ns, targets=None, block=None, track=None):
617 def push(self, ns, targets=None, block=None, track=None):
618 """update remote namespace with dict `ns`
618 """update remote namespace with dict `ns`
619
619
620 Parameters
620 Parameters
621 ----------
621 ----------
622
622
623 ns : dict
623 ns : dict
624 dict of keys with which to update engine namespace(s)
624 dict of keys with which to update engine namespace(s)
625 block : bool [default : self.block]
625 block : bool [default : self.block]
626 whether to wait to be notified of engine receipt
626 whether to wait to be notified of engine receipt
627
627
628 """
628 """
629
629
630 block = block if block is not None else self.block
630 block = block if block is not None else self.block
631 track = track if track is not None else self.track
631 track = track if track is not None else self.track
632 targets = targets if targets is not None else self.targets
632 targets = targets if targets is not None else self.targets
633 # applier = self.apply_sync if block else self.apply_async
633 # applier = self.apply_sync if block else self.apply_async
634 if not isinstance(ns, dict):
634 if not isinstance(ns, dict):
635 raise TypeError("Must be a dict, not %s"%type(ns))
635 raise TypeError("Must be a dict, not %s"%type(ns))
636 return self._really_apply(util._push, (ns,), block=block, track=track, targets=targets)
636 return self._really_apply(util._push, (ns,), block=block, track=track, targets=targets)
637
637
638 def get(self, key_s):
638 def get(self, key_s):
639 """get object(s) by `key_s` from remote namespace
639 """get object(s) by `key_s` from remote namespace
640
640
641 see `pull` for details.
641 see `pull` for details.
642 """
642 """
643 # block = block if block is not None else self.block
643 # block = block if block is not None else self.block
644 return self.pull(key_s, block=True)
644 return self.pull(key_s, block=True)
645
645
646 def pull(self, names, targets=None, block=True):
646 def pull(self, names, targets=None, block=True):
647 """get object(s) by `name` from remote namespace
647 """get object(s) by `name` from remote namespace
648
648
649 will return one object if it is a key.
649 will return one object if it is a key.
650 can also take a list of keys, in which case it will return a list of objects.
650 can also take a list of keys, in which case it will return a list of objects.
651 """
651 """
652 block = block if block is not None else self.block
652 block = block if block is not None else self.block
653 targets = targets if targets is not None else self.targets
653 targets = targets if targets is not None else self.targets
654 applier = self.apply_sync if block else self.apply_async
654 applier = self.apply_sync if block else self.apply_async
655 if isinstance(names, basestring):
655 if isinstance(names, basestring):
656 pass
656 pass
657 elif isinstance(names, (list,tuple,set)):
657 elif isinstance(names, (list,tuple,set)):
658 for key in names:
658 for key in names:
659 if not isinstance(key, basestring):
659 if not isinstance(key, basestring):
660 raise TypeError("keys must be str, not type %r"%type(key))
660 raise TypeError("keys must be str, not type %r"%type(key))
661 else:
661 else:
662 raise TypeError("names must be strs, not %r"%names)
662 raise TypeError("names must be strs, not %r"%names)
663 return self._really_apply(util._pull, (names,), block=block, targets=targets)
663 return self._really_apply(util._pull, (names,), block=block, targets=targets)
664
664
665 def scatter(self, key, seq, dist='b', flatten=False, targets=None, block=None, track=None):
665 def scatter(self, key, seq, dist='b', flatten=False, targets=None, block=None, track=None):
666 """
666 """
667 Partition a Python sequence and send the partitions to a set of engines.
667 Partition a Python sequence and send the partitions to a set of engines.
668 """
668 """
669 block = block if block is not None else self.block
669 block = block if block is not None else self.block
670 track = track if track is not None else self.track
670 track = track if track is not None else self.track
671 targets = targets if targets is not None else self.targets
671 targets = targets if targets is not None else self.targets
672
672
673 mapObject = Map.dists[dist]()
673 mapObject = Map.dists[dist]()
674 nparts = len(targets)
674 nparts = len(targets)
675 msg_ids = []
675 msg_ids = []
676 trackers = []
676 trackers = []
677 for index, engineid in enumerate(targets):
677 for index, engineid in enumerate(targets):
678 partition = mapObject.getPartition(seq, index, nparts)
678 partition = mapObject.getPartition(seq, index, nparts)
679 if flatten and len(partition) == 1:
679 if flatten and len(partition) == 1:
680 ns = {key: partition[0]}
680 ns = {key: partition[0]}
681 else:
681 else:
682 ns = {key: partition}
682 ns = {key: partition}
683 r = self.push(ns, block=False, track=track, targets=engineid)
683 r = self.push(ns, block=False, track=track, targets=engineid)
684 msg_ids.extend(r.msg_ids)
684 msg_ids.extend(r.msg_ids)
685 if track:
685 if track:
686 trackers.append(r._tracker)
686 trackers.append(r._tracker)
687
687
688 if track:
688 if track:
689 tracker = zmq.MessageTracker(*trackers)
689 tracker = zmq.MessageTracker(*trackers)
690 else:
690 else:
691 tracker = None
691 tracker = None
692
692
693 r = AsyncResult(self.client, msg_ids, fname='scatter', targets=targets, tracker=tracker)
693 r = AsyncResult(self.client, msg_ids, fname='scatter', targets=targets, tracker=tracker)
694 if block:
694 if block:
695 r.wait()
695 r.wait()
696 else:
696 else:
697 return r
697 return r
698
698
699 @sync_results
699 @sync_results
700 @save_ids
700 @save_ids
701 def gather(self, key, dist='b', targets=None, block=None):
701 def gather(self, key, dist='b', targets=None, block=None):
702 """
702 """
703 Gather a partitioned sequence on a set of engines as a single local seq.
703 Gather a partitioned sequence on a set of engines as a single local seq.
704 """
704 """
705 block = block if block is not None else self.block
705 block = block if block is not None else self.block
706 targets = targets if targets is not None else self.targets
706 targets = targets if targets is not None else self.targets
707 mapObject = Map.dists[dist]()
707 mapObject = Map.dists[dist]()
708 msg_ids = []
708 msg_ids = []
709
709
710 for index, engineid in enumerate(targets):
710 for index, engineid in enumerate(targets):
711 msg_ids.extend(self.pull(key, block=False, targets=engineid).msg_ids)
711 msg_ids.extend(self.pull(key, block=False, targets=engineid).msg_ids)
712
712
713 r = AsyncMapResult(self.client, msg_ids, mapObject, fname='gather')
713 r = AsyncMapResult(self.client, msg_ids, mapObject, fname='gather')
714
714
715 if block:
715 if block:
716 try:
716 try:
717 return r.get()
717 return r.get()
718 except KeyboardInterrupt:
718 except KeyboardInterrupt:
719 pass
719 pass
720 return r
720 return r
721
721
722 def __getitem__(self, key):
722 def __getitem__(self, key):
723 return self.get(key)
723 return self.get(key)
724
724
725 def __setitem__(self,key, value):
725 def __setitem__(self,key, value):
726 self.update({key:value})
726 self.update({key:value})
727
727
728 def clear(self, targets=None, block=False):
728 def clear(self, targets=None, block=False):
729 """Clear the remote namespaces on my engines."""
729 """Clear the remote namespaces on my engines."""
730 block = block if block is not None else self.block
730 block = block if block is not None else self.block
731 targets = targets if targets is not None else self.targets
731 targets = targets if targets is not None else self.targets
732 return self.client.clear(targets=targets, block=block)
732 return self.client.clear(targets=targets, block=block)
733
733
734 def kill(self, targets=None, block=True):
734 def kill(self, targets=None, block=True):
735 """Kill my engines."""
735 """Kill my engines."""
736 block = block if block is not None else self.block
736 block = block if block is not None else self.block
737 targets = targets if targets is not None else self.targets
737 targets = targets if targets is not None else self.targets
738 return self.client.kill(targets=targets, block=block)
738 return self.client.kill(targets=targets, block=block)
739
739
740 #----------------------------------------
740 #----------------------------------------
741 # activate for %px,%autopx magics
741 # activate for %px,%autopx magics
742 #----------------------------------------
742 #----------------------------------------
743 def activate(self):
743 def activate(self):
744 """Make this `View` active for parallel magic commands.
744 """Make this `View` active for parallel magic commands.
745
745
746 IPython has a magic command syntax to work with `MultiEngineClient` objects.
746 IPython has a magic command syntax to work with `MultiEngineClient` objects.
747 In a given IPython session there is a single active one. While
747 In a given IPython session there is a single active one. While
748 there can be many `Views` created and used by the user,
748 there can be many `Views` created and used by the user,
749 there is only one active one. The active `View` is used whenever
749 there is only one active one. The active `View` is used whenever
750 the magic commands %px and %autopx are used.
750 the magic commands %px and %autopx are used.
751
751
752 The activate() method is called on a given `View` to make it
752 The activate() method is called on a given `View` to make it
753 active. Once this has been done, the magic commands can be used.
753 active. Once this has been done, the magic commands can be used.
754 """
754 """
755
755
756 try:
756 try:
757 # This is injected into __builtins__.
757 # This is injected into __builtins__.
758 ip = get_ipython()
758 ip = get_ipython()
759 except NameError:
759 except NameError:
760 print "The IPython parallel magics (%result, %px, %autopx) only work within IPython."
760 print "The IPython parallel magics (%result, %px, %autopx) only work within IPython."
761 else:
761 else:
762 pmagic = ip.plugin_manager.get_plugin('parallelmagic')
762 pmagic = ip.plugin_manager.get_plugin('parallelmagic')
763 if pmagic is not None:
763 if pmagic is not None:
764 pmagic.active_multiengine_client = self
764 pmagic.active_multiengine_client = self
765 else:
765 else:
766 print "You must first load the parallelmagic extension " \
766 print "You must first load the parallelmagic extension " \
767 "by doing '%load_ext parallelmagic'"
767 "by doing '%load_ext parallelmagic'"
768
768
769
769
770 @testdec.skip_doctest
770 @testdec.skip_doctest
771 class LoadBalancedView(View):
771 class LoadBalancedView(View):
772 """An load-balancing View that only executes via the Task scheduler.
772 """An load-balancing View that only executes via the Task scheduler.
773
773
774 Load-balanced views can be created with the client's `view` method:
774 Load-balanced views can be created with the client's `view` method:
775
775
776 >>> v = client.load_balanced_view()
776 >>> v = client.load_balanced_view()
777
777
778 or targets can be specified, to restrict the potential destinations:
778 or targets can be specified, to restrict the potential destinations:
779
779
780 >>> v = client.client.load_balanced_view(([1,3])
780 >>> v = client.client.load_balanced_view(([1,3])
781
781
782 which would restrict loadbalancing to between engines 1 and 3.
782 which would restrict loadbalancing to between engines 1 and 3.
783
783
784 """
784 """
785
785
786 follow=Any()
786 follow=Any()
787 after=Any()
787 after=Any()
788 timeout=CFloat()
788 timeout=CFloat()
789
789
790 _task_scheme = Any()
790 _task_scheme = Any()
791 _flag_names = List(['targets', 'block', 'track', 'follow', 'after', 'timeout'])
791 _flag_names = List(['targets', 'block', 'track', 'follow', 'after', 'timeout'])
792
792
793 def __init__(self, client=None, socket=None, **flags):
793 def __init__(self, client=None, socket=None, **flags):
794 super(LoadBalancedView, self).__init__(client=client, socket=socket, **flags)
794 super(LoadBalancedView, self).__init__(client=client, socket=socket, **flags)
795 self._task_scheme=client._task_scheme
795 self._task_scheme=client._task_scheme
796
796
797 def _validate_dependency(self, dep):
797 def _validate_dependency(self, dep):
798 """validate a dependency.
798 """validate a dependency.
799
799
800 For use in `set_flags`.
800 For use in `set_flags`.
801 """
801 """
802 if dep is None or isinstance(dep, (str, AsyncResult, Dependency)):
802 if dep is None or isinstance(dep, (str, AsyncResult, Dependency)):
803 return True
803 return True
804 elif isinstance(dep, (list,set, tuple)):
804 elif isinstance(dep, (list,set, tuple)):
805 for d in dep:
805 for d in dep:
806 if not isinstance(d, (str, AsyncResult)):
806 if not isinstance(d, (str, AsyncResult)):
807 return False
807 return False
808 elif isinstance(dep, dict):
808 elif isinstance(dep, dict):
809 if set(dep.keys()) != set(Dependency().as_dict().keys()):
809 if set(dep.keys()) != set(Dependency().as_dict().keys()):
810 return False
810 return False
811 if not isinstance(dep['msg_ids'], list):
811 if not isinstance(dep['msg_ids'], list):
812 return False
812 return False
813 for d in dep['msg_ids']:
813 for d in dep['msg_ids']:
814 if not isinstance(d, str):
814 if not isinstance(d, str):
815 return False
815 return False
816 else:
816 else:
817 return False
817 return False
818
818
819 return True
819 return True
820
820
821 def _render_dependency(self, dep):
821 def _render_dependency(self, dep):
822 """helper for building jsonable dependencies from various input forms."""
822 """helper for building jsonable dependencies from various input forms."""
823 if isinstance(dep, Dependency):
823 if isinstance(dep, Dependency):
824 return dep.as_dict()
824 return dep.as_dict()
825 elif isinstance(dep, AsyncResult):
825 elif isinstance(dep, AsyncResult):
826 return dep.msg_ids
826 return dep.msg_ids
827 elif dep is None:
827 elif dep is None:
828 return []
828 return []
829 else:
829 else:
830 # pass to Dependency constructor
830 # pass to Dependency constructor
831 return list(Dependency(dep))
831 return list(Dependency(dep))
832
832
833 def set_flags(self, **kwargs):
833 def set_flags(self, **kwargs):
834 """set my attribute flags by keyword.
834 """set my attribute flags by keyword.
835
835
836 A View is a wrapper for the Client's apply method, but with attributes
836 A View is a wrapper for the Client's apply method, but with attributes
837 that specify keyword arguments, those attributes can be set by keyword
837 that specify keyword arguments, those attributes can be set by keyword
838 argument with this method.
838 argument with this method.
839
839
840 Parameters
840 Parameters
841 ----------
841 ----------
842
842
843 block : bool
843 block : bool
844 whether to wait for results
844 whether to wait for results
845 track : bool
845 track : bool
846 whether to create a MessageTracker to allow the user to
846 whether to create a MessageTracker to allow the user to
847 safely edit after arrays and buffers during non-copying
847 safely edit after arrays and buffers during non-copying
848 sends.
848 sends.
849 #
849 #
850 after : Dependency or collection of msg_ids
850 after : Dependency or collection of msg_ids
851 Only for load-balanced execution (targets=None)
851 Only for load-balanced execution (targets=None)
852 Specify a list of msg_ids as a time-based dependency.
852 Specify a list of msg_ids as a time-based dependency.
853 This job will only be run *after* the dependencies
853 This job will only be run *after* the dependencies
854 have been met.
854 have been met.
855
855
856 follow : Dependency or collection of msg_ids
856 follow : Dependency or collection of msg_ids
857 Only for load-balanced execution (targets=None)
857 Only for load-balanced execution (targets=None)
858 Specify a list of msg_ids as a location-based dependency.
858 Specify a list of msg_ids as a location-based dependency.
859 This job will only be run on an engine where this dependency
859 This job will only be run on an engine where this dependency
860 is met.
860 is met.
861
861
862 timeout : float/int or None
862 timeout : float/int or None
863 Only for load-balanced execution (targets=None)
863 Only for load-balanced execution (targets=None)
864 Specify an amount of time (in seconds) for the scheduler to
864 Specify an amount of time (in seconds) for the scheduler to
865 wait for dependencies to be met before failing with a
865 wait for dependencies to be met before failing with a
866 DependencyTimeout.
866 DependencyTimeout.
867 """
867 """
868
868
869 super(LoadBalancedView, self).set_flags(**kwargs)
869 super(LoadBalancedView, self).set_flags(**kwargs)
870 for name in ('follow', 'after'):
870 for name in ('follow', 'after'):
871 if name in kwargs:
871 if name in kwargs:
872 value = kwargs[name]
872 value = kwargs[name]
873 if self._validate_dependency(value):
873 if self._validate_dependency(value):
874 setattr(self, name, value)
874 setattr(self, name, value)
875 else:
875 else:
876 raise ValueError("Invalid dependency: %r"%value)
876 raise ValueError("Invalid dependency: %r"%value)
877 if 'timeout' in kwargs:
877 if 'timeout' in kwargs:
878 t = kwargs['timeout']
878 t = kwargs['timeout']
879 if not isinstance(t, (int, long, float, type(None))):
879 if not isinstance(t, (int, long, float, type(None))):
880 raise TypeError("Invalid type for timeout: %r"%type(t))
880 raise TypeError("Invalid type for timeout: %r"%type(t))
881 if t is not None:
881 if t is not None:
882 if t < 0:
882 if t < 0:
883 raise ValueError("Invalid timeout: %s"%t)
883 raise ValueError("Invalid timeout: %s"%t)
884 self.timeout = t
884 self.timeout = t
885
885
886 @sync_results
886 @sync_results
887 @save_ids
887 @save_ids
888 def _really_apply(self, f, args=None, kwargs=None, block=None, track=None,
888 def _really_apply(self, f, args=None, kwargs=None, block=None, track=None,
889 after=None, follow=None, timeout=None,
889 after=None, follow=None, timeout=None,
890 targets=None):
890 targets=None):
891 """calls f(*args, **kwargs) on a remote engine, returning the result.
891 """calls f(*args, **kwargs) on a remote engine, returning the result.
892
892
893 This method temporarily sets all of `apply`'s flags for a single call.
893 This method temporarily sets all of `apply`'s flags for a single call.
894
894
895 Parameters
895 Parameters
896 ----------
896 ----------
897
897
898 f : callable
898 f : callable
899
899
900 args : list [default: empty]
900 args : list [default: empty]
901
901
902 kwargs : dict [default: empty]
902 kwargs : dict [default: empty]
903
903
904 block : bool [default: self.block]
904 block : bool [default: self.block]
905 whether to block
905 whether to block
906 track : bool [default: self.track]
906 track : bool [default: self.track]
907 whether to ask zmq to track the message, for safe non-copying sends
907 whether to ask zmq to track the message, for safe non-copying sends
908
908
909 !!!!!! TODO: THE REST HERE !!!!
909 !!!!!! TODO: THE REST HERE !!!!
910
910
911 Returns
911 Returns
912 -------
912 -------
913
913
914 if self.block is False:
914 if self.block is False:
915 returns AsyncResult
915 returns AsyncResult
916 else:
916 else:
917 returns actual result of f(*args, **kwargs) on the engine(s)
917 returns actual result of f(*args, **kwargs) on the engine(s)
918 This will be a list of self.targets is also a list (even length 1), or
918 This will be a list of self.targets is also a list (even length 1), or
919 the single result if self.targets is an integer engine id
919 the single result if self.targets is an integer engine id
920 """
920 """
921
921
922 # validate whether we can run
922 # validate whether we can run
923 if self._socket.closed:
923 if self._socket.closed:
924 msg = "Task farming is disabled"
924 msg = "Task farming is disabled"
925 if self._task_scheme == 'pure':
925 if self._task_scheme == 'pure':
926 msg += " because the pure ZMQ scheduler cannot handle"
926 msg += " because the pure ZMQ scheduler cannot handle"
927 msg += " disappearing engines."
927 msg += " disappearing engines."
928 raise RuntimeError(msg)
928 raise RuntimeError(msg)
929
929
930 if self._task_scheme == 'pure':
930 if self._task_scheme == 'pure':
931 # pure zmq scheme doesn't support dependencies
931 # pure zmq scheme doesn't support dependencies
932 msg = "Pure ZMQ scheduler doesn't support dependencies"
932 msg = "Pure ZMQ scheduler doesn't support dependencies"
933 if (follow or after):
933 if (follow or after):
934 # hard fail on DAG dependencies
934 # hard fail on DAG dependencies
935 raise RuntimeError(msg)
935 raise RuntimeError(msg)
936 if isinstance(f, dependent):
936 if isinstance(f, dependent):
937 # soft warn on functional dependencies
937 # soft warn on functional dependencies
938 warnings.warn(msg, RuntimeWarning)
938 warnings.warn(msg, RuntimeWarning)
939
939
940 # build args
940 # build args
941 args = [] if args is None else args
941 args = [] if args is None else args
942 kwargs = {} if kwargs is None else kwargs
942 kwargs = {} if kwargs is None else kwargs
943 block = self.block if block is None else block
943 block = self.block if block is None else block
944 track = self.track if track is None else track
944 track = self.track if track is None else track
945 after = self.after if after is None else after
945 after = self.after if after is None else after
946 follow = self.follow if follow is None else follow
946 follow = self.follow if follow is None else follow
947 timeout = self.timeout if timeout is None else timeout
947 timeout = self.timeout if timeout is None else timeout
948 targets = self.targets if targets is None else targets
948 targets = self.targets if targets is None else targets
949
949
950 if targets is None:
950 if targets is None:
951 idents = []
951 idents = []
952 else:
952 else:
953 idents = self.client._build_targets(targets)[0]
953 idents = self.client._build_targets(targets)[0]
954
954
955 after = self._render_dependency(after)
955 after = self._render_dependency(after)
956 follow = self._render_dependency(follow)
956 follow = self._render_dependency(follow)
957 subheader = dict(after=after, follow=follow, timeout=timeout, targets=idents)
957 subheader = dict(after=after, follow=follow, timeout=timeout, targets=idents)
958
958
959 msg = self.client.send_apply_message(self._socket, f, args, kwargs, track=track,
959 msg = self.client.send_apply_message(self._socket, f, args, kwargs, track=track,
960 subheader=subheader)
960 subheader=subheader)
961 tracker = None if track is False else msg['tracker']
961 tracker = None if track is False else msg['tracker']
962
962
963 ar = AsyncResult(self.client, msg['msg_id'], fname=f.__name__, targets=None, tracker=tracker)
963 ar = AsyncResult(self.client, msg['msg_id'], fname=f.__name__, targets=None, tracker=tracker)
964
964
965 if block:
965 if block:
966 try:
966 try:
967 return ar.get()
967 return ar.get()
968 except KeyboardInterrupt:
968 except KeyboardInterrupt:
969 pass
969 pass
970 return ar
970 return ar
971
971
972 @spin_after
972 @spin_after
973 @save_ids
973 @save_ids
974 def map(self, f, *sequences, **kwargs):
974 def map(self, f, *sequences, **kwargs):
975 """view.map(f, *sequences, block=self.block, chunksize=1) => list|AsyncMapResult
975 """view.map(f, *sequences, block=self.block, chunksize=1) => list|AsyncMapResult
976
976
977 Parallel version of builtin `map`, load-balanced by this View.
977 Parallel version of builtin `map`, load-balanced by this View.
978
978
979 `block`, and `chunksize` can be specified by keyword only.
979 `block`, and `chunksize` can be specified by keyword only.
980
980
981 Each `chunksize` elements will be a separate task, and will be
981 Each `chunksize` elements will be a separate task, and will be
982 load-balanced. This lets individual elements be available for iteration
982 load-balanced. This lets individual elements be available for iteration
983 as soon as they arrive.
983 as soon as they arrive.
984
984
985 Parameters
985 Parameters
986 ----------
986 ----------
987
987
988 f : callable
988 f : callable
989 function to be mapped
989 function to be mapped
990 *sequences: one or more sequences of matching length
990 *sequences: one or more sequences of matching length
991 the sequences to be distributed and passed to `f`
991 the sequences to be distributed and passed to `f`
992 block : bool
992 block : bool
993 whether to wait for the result or not [default self.block]
993 whether to wait for the result or not [default self.block]
994 track : bool
994 track : bool
995 whether to create a MessageTracker to allow the user to
995 whether to create a MessageTracker to allow the user to
996 safely edit after arrays and buffers during non-copying
996 safely edit after arrays and buffers during non-copying
997 sends.
997 sends.
998 chunksize : int
998 chunksize : int
999 how many elements should be in each task [default 1]
999 how many elements should be in each task [default 1]
1000
1000
1001 Returns
1001 Returns
1002 -------
1002 -------
1003
1003
1004 if block=False:
1004 if block=False:
1005 AsyncMapResult
1005 AsyncMapResult
1006 An object like AsyncResult, but which reassembles the sequence of results
1006 An object like AsyncResult, but which reassembles the sequence of results
1007 into a single list. AsyncMapResults can be iterated through before all
1007 into a single list. AsyncMapResults can be iterated through before all
1008 results are complete.
1008 results are complete.
1009 else:
1009 else:
1010 the result of map(f,*sequences)
1010 the result of map(f,*sequences)
1011
1011
1012 """
1012 """
1013
1013
1014 # default
1014 # default
1015 block = kwargs.get('block', self.block)
1015 block = kwargs.get('block', self.block)
1016 chunksize = kwargs.get('chunksize', 1)
1016 chunksize = kwargs.get('chunksize', 1)
1017
1017
1018 keyset = set(kwargs.keys())
1018 keyset = set(kwargs.keys())
1019 extra_keys = keyset.difference_update(set(['block', 'chunksize']))
1019 extra_keys = keyset.difference_update(set(['block', 'chunksize']))
1020 if extra_keys:
1020 if extra_keys:
1021 raise TypeError("Invalid kwargs: %s"%list(extra_keys))
1021 raise TypeError("Invalid kwargs: %s"%list(extra_keys))
1022
1022
1023 assert len(sequences) > 0, "must have some sequences to map onto!"
1023 assert len(sequences) > 0, "must have some sequences to map onto!"
1024
1024
1025 pf = ParallelFunction(self, f, block=block, chunksize=chunksize)
1025 pf = ParallelFunction(self, f, block=block, chunksize=chunksize)
1026 return pf.map(*sequences)
1026 return pf.map(*sequences)
1027
1027
1028 __all__ = ['LoadBalancedView', 'DirectView'] No newline at end of file
1028 __all__ = ['LoadBalancedView', 'DirectView']
@@ -1,153 +1,153 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Pickle related utilities. Perhaps this should be called 'can'."""
3 """Pickle related utilities. Perhaps this should be called 'can'."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 import copy
18 import copy
19 import sys
19 import sys
20 from types import FunctionType
20 from types import FunctionType
21
21
22 import codeutil
22 import codeutil
23
23
24 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-------------------------------------------------------------------------------
26 #-------------------------------------------------------------------------------
27
27
28
28
29 class CannedObject(object):
29 class CannedObject(object):
30 def __init__(self, obj, keys=[]):
30 def __init__(self, obj, keys=[]):
31 self.keys = keys
31 self.keys = keys
32 self.obj = copy.copy(obj)
32 self.obj = copy.copy(obj)
33 for key in keys:
33 for key in keys:
34 setattr(self.obj, key, can(getattr(obj, key)))
34 setattr(self.obj, key, can(getattr(obj, key)))
35
35
36
36
37 def getObject(self, g=None):
37 def getObject(self, g=None):
38 if g is None:
38 if g is None:
39 g = globals()
39 g = globals()
40 for key in self.keys:
40 for key in self.keys:
41 setattr(self.obj, key, uncan(getattr(self.obj, key), g))
41 setattr(self.obj, key, uncan(getattr(self.obj, key), g))
42 return self.obj
42 return self.obj
43
43
44 class Reference(CannedObject):
44 class Reference(CannedObject):
45 """object for wrapping a remote reference by name."""
45 """object for wrapping a remote reference by name."""
46 def __init__(self, name):
46 def __init__(self, name):
47 if not isinstance(name, basestring):
47 if not isinstance(name, basestring):
48 raise TypeError("illegal name: %r"%name)
48 raise TypeError("illegal name: %r"%name)
49 self.name = name
49 self.name = name
50
50
51 def __repr__(self):
51 def __repr__(self):
52 return "<Reference: %r>"%self.name
52 return "<Reference: %r>"%self.name
53
53
54 def getObject(self, g=None):
54 def getObject(self, g=None):
55 if g is None:
55 if g is None:
56 g = globals()
56 g = globals()
57 try:
57 try:
58 return g[self.name]
58 return g[self.name]
59 except KeyError:
59 except KeyError:
60 raise NameError("name %r is not defined"%self.name)
60 raise NameError("name %r is not defined"%self.name)
61
61
62
62
63 class CannedFunction(CannedObject):
63 class CannedFunction(CannedObject):
64
64
65 def __init__(self, f):
65 def __init__(self, f):
66 self._checkType(f)
66 self._checkType(f)
67 self.code = f.func_code
67 self.code = f.func_code
68 self.defaults = f.func_defaults
68 self.defaults = f.func_defaults
69 self.module = f.__module__ or '__main__'
69 self.module = f.__module__ or '__main__'
70 self.__name__ = f.__name__
70 self.__name__ = f.__name__
71
71
72 def _checkType(self, obj):
72 def _checkType(self, obj):
73 assert isinstance(obj, FunctionType), "Not a function type"
73 assert isinstance(obj, FunctionType), "Not a function type"
74
74
75 def getObject(self, g=None):
75 def getObject(self, g=None):
76 # try to load function back into its module:
76 # try to load function back into its module:
77 if not self.module.startswith('__'):
77 if not self.module.startswith('__'):
78 try:
78 try:
79 __import__(self.module)
79 __import__(self.module)
80 except ImportError:
80 except ImportError:
81 pass
81 pass
82 else:
82 else:
83 g = sys.modules[self.module].__dict__
83 g = sys.modules[self.module].__dict__
84
84
85 if g is None:
85 if g is None:
86 g = globals()
86 g = globals()
87 newFunc = FunctionType(self.code, g, self.__name__, self.defaults)
87 newFunc = FunctionType(self.code, g, self.__name__, self.defaults)
88 return newFunc
88 return newFunc
89
89
90 #-------------------------------------------------------------------------------
90 #-------------------------------------------------------------------------------
91 # Functions
91 # Functions
92 #-------------------------------------------------------------------------------
92 #-------------------------------------------------------------------------------
93
93
94 def can(obj):
94 def can(obj):
95 # import here to prevent module-level circular imports
95 # import here to prevent module-level circular imports
96 from IPython.zmq.parallel.dependency import dependent
96 from IPython.parallel.dependency import dependent
97 if isinstance(obj, dependent):
97 if isinstance(obj, dependent):
98 keys = ('f','df')
98 keys = ('f','df')
99 return CannedObject(obj, keys=keys)
99 return CannedObject(obj, keys=keys)
100 elif isinstance(obj, FunctionType):
100 elif isinstance(obj, FunctionType):
101 return CannedFunction(obj)
101 return CannedFunction(obj)
102 elif isinstance(obj,dict):
102 elif isinstance(obj,dict):
103 return canDict(obj)
103 return canDict(obj)
104 elif isinstance(obj, (list,tuple)):
104 elif isinstance(obj, (list,tuple)):
105 return canSequence(obj)
105 return canSequence(obj)
106 else:
106 else:
107 return obj
107 return obj
108
108
109 def canDict(obj):
109 def canDict(obj):
110 if isinstance(obj, dict):
110 if isinstance(obj, dict):
111 newobj = {}
111 newobj = {}
112 for k, v in obj.iteritems():
112 for k, v in obj.iteritems():
113 newobj[k] = can(v)
113 newobj[k] = can(v)
114 return newobj
114 return newobj
115 else:
115 else:
116 return obj
116 return obj
117
117
118 def canSequence(obj):
118 def canSequence(obj):
119 if isinstance(obj, (list, tuple)):
119 if isinstance(obj, (list, tuple)):
120 t = type(obj)
120 t = type(obj)
121 return t([can(i) for i in obj])
121 return t([can(i) for i in obj])
122 else:
122 else:
123 return obj
123 return obj
124
124
125 def uncan(obj, g=None):
125 def uncan(obj, g=None):
126 if isinstance(obj, CannedObject):
126 if isinstance(obj, CannedObject):
127 return obj.getObject(g)
127 return obj.getObject(g)
128 elif isinstance(obj,dict):
128 elif isinstance(obj,dict):
129 return uncanDict(obj, g)
129 return uncanDict(obj, g)
130 elif isinstance(obj, (list,tuple)):
130 elif isinstance(obj, (list,tuple)):
131 return uncanSequence(obj, g)
131 return uncanSequence(obj, g)
132 else:
132 else:
133 return obj
133 return obj
134
134
135 def uncanDict(obj, g=None):
135 def uncanDict(obj, g=None):
136 if isinstance(obj, dict):
136 if isinstance(obj, dict):
137 newobj = {}
137 newobj = {}
138 for k, v in obj.iteritems():
138 for k, v in obj.iteritems():
139 newobj[k] = uncan(v,g)
139 newobj[k] = uncan(v,g)
140 return newobj
140 return newobj
141 else:
141 else:
142 return obj
142 return obj
143
143
144 def uncanSequence(obj, g=None):
144 def uncanSequence(obj, g=None):
145 if isinstance(obj, (list, tuple)):
145 if isinstance(obj, (list, tuple)):
146 t = type(obj)
146 t = type(obj)
147 return t([uncan(i,g) for i in obj])
147 return t([uncan(i,g) for i in obj])
148 else:
148 else:
149 return obj
149 return obj
150
150
151
151
152 def rebindFunctionGlobals(f, glbls):
152 def rebindFunctionGlobals(f, glbls):
153 return FunctionType(f.func_code, glbls)
153 return FunctionType(f.func_code, glbls)
@@ -1,120 +1,120 b''
1 """Example for generating an arbitrary DAG as a dependency map.
1 """Example for generating an arbitrary DAG as a dependency map.
2
2
3 This demo uses networkx to generate the graph.
3 This demo uses networkx to generate the graph.
4
4
5 Authors
5 Authors
6 -------
6 -------
7 * MinRK
7 * MinRK
8 """
8 """
9 import networkx as nx
9 import networkx as nx
10 from random import randint, random
10 from random import randint, random
11 from IPython.zmq.parallel import client as cmod
11 from IPython import parallel
12
12
13 def randomwait():
13 def randomwait():
14 import time
14 import time
15 from random import random
15 from random import random
16 time.sleep(random())
16 time.sleep(random())
17 return time.time()
17 return time.time()
18
18
19
19
20 def random_dag(nodes, edges):
20 def random_dag(nodes, edges):
21 """Generate a random Directed Acyclic Graph (DAG) with a given number of nodes and edges."""
21 """Generate a random Directed Acyclic Graph (DAG) with a given number of nodes and edges."""
22 G = nx.DiGraph()
22 G = nx.DiGraph()
23 for i in range(nodes):
23 for i in range(nodes):
24 G.add_node(i)
24 G.add_node(i)
25 while edges > 0:
25 while edges > 0:
26 a = randint(0,nodes-1)
26 a = randint(0,nodes-1)
27 b=a
27 b=a
28 while b==a:
28 while b==a:
29 b = randint(0,nodes-1)
29 b = randint(0,nodes-1)
30 G.add_edge(a,b)
30 G.add_edge(a,b)
31 if nx.is_directed_acyclic_graph(G):
31 if nx.is_directed_acyclic_graph(G):
32 edges -= 1
32 edges -= 1
33 else:
33 else:
34 # we closed a loop!
34 # we closed a loop!
35 G.remove_edge(a,b)
35 G.remove_edge(a,b)
36 return G
36 return G
37
37
38 def add_children(G, parent, level, n=2):
38 def add_children(G, parent, level, n=2):
39 """Add children recursively to a binary tree."""
39 """Add children recursively to a binary tree."""
40 if level == 0:
40 if level == 0:
41 return
41 return
42 for i in range(n):
42 for i in range(n):
43 child = parent+str(i)
43 child = parent+str(i)
44 G.add_node(child)
44 G.add_node(child)
45 G.add_edge(parent,child)
45 G.add_edge(parent,child)
46 add_children(G, child, level-1, n)
46 add_children(G, child, level-1, n)
47
47
48 def make_bintree(levels):
48 def make_bintree(levels):
49 """Make a symmetrical binary tree with @levels"""
49 """Make a symmetrical binary tree with @levels"""
50 G = nx.DiGraph()
50 G = nx.DiGraph()
51 root = '0'
51 root = '0'
52 G.add_node(root)
52 G.add_node(root)
53 add_children(G, root, levels, 2)
53 add_children(G, root, levels, 2)
54 return G
54 return G
55
55
56 def submit_jobs(view, G, jobs):
56 def submit_jobs(view, G, jobs):
57 """Submit jobs via client where G describes the time dependencies."""
57 """Submit jobs via client where G describes the time dependencies."""
58 results = {}
58 results = {}
59 for node in nx.topological_sort(G):
59 for node in nx.topological_sort(G):
60 with view.temp_flags(after=[ results[n] for n in G.predecessors(node) ]):
60 with view.temp_flags(after=[ results[n] for n in G.predecessors(node) ]):
61 results[node] = view.apply(jobs[node])
61 results[node] = view.apply(jobs[node])
62 return results
62 return results
63
63
64 def validate_tree(G, results):
64 def validate_tree(G, results):
65 """Validate that jobs executed after their dependencies."""
65 """Validate that jobs executed after their dependencies."""
66 for node in G:
66 for node in G:
67 started = results[node].metadata.started
67 started = results[node].metadata.started
68 for parent in G.predecessors(node):
68 for parent in G.predecessors(node):
69 finished = results[parent].metadata.completed
69 finished = results[parent].metadata.completed
70 assert started > finished, "%s should have happened after %s"%(node, parent)
70 assert started > finished, "%s should have happened after %s"%(node, parent)
71
71
72 def main(nodes, edges):
72 def main(nodes, edges):
73 """Generate a random graph, submit jobs, then validate that the
73 """Generate a random graph, submit jobs, then validate that the
74 dependency order was enforced.
74 dependency order was enforced.
75 Finally, plot the graph, with time on the x-axis, and
75 Finally, plot the graph, with time on the x-axis, and
76 in-degree on the y (just for spread). All arrows must
76 in-degree on the y (just for spread). All arrows must
77 point at least slightly to the right if the graph is valid.
77 point at least slightly to the right if the graph is valid.
78 """
78 """
79 from matplotlib import pyplot as plt
79 from matplotlib import pyplot as plt
80 from matplotlib.dates import date2num
80 from matplotlib.dates import date2num
81 from matplotlib.cm import gist_rainbow
81 from matplotlib.cm import gist_rainbow
82 print "building DAG"
82 print "building DAG"
83 G = random_dag(nodes, edges)
83 G = random_dag(nodes, edges)
84 jobs = {}
84 jobs = {}
85 pos = {}
85 pos = {}
86 colors = {}
86 colors = {}
87 for node in G:
87 for node in G:
88 jobs[node] = randomwait
88 jobs[node] = randomwait
89
89
90 client = cmod.Client()
90 client = parallel.Client()
91 view = client.load_balanced_view()
91 view = client.load_balanced_view()
92 print "submitting %i tasks with %i dependencies"%(nodes,edges)
92 print "submitting %i tasks with %i dependencies"%(nodes,edges)
93 results = submit_jobs(view, G, jobs)
93 results = submit_jobs(view, G, jobs)
94 print "waiting for results"
94 print "waiting for results"
95 view.wait()
95 view.wait()
96 print "done"
96 print "done"
97 for node in G:
97 for node in G:
98 md = results[node].metadata
98 md = results[node].metadata
99 start = date2num(md.started)
99 start = date2num(md.started)
100 runtime = date2num(md.completed) - start
100 runtime = date2num(md.completed) - start
101 pos[node] = (start, runtime)
101 pos[node] = (start, runtime)
102 colors[node] = md.engine_id
102 colors[node] = md.engine_id
103 validate_tree(G, results)
103 validate_tree(G, results)
104 nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(), cmap=gist_rainbow,
104 nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(), cmap=gist_rainbow,
105 with_labels=False)
105 with_labels=False)
106 x,y = zip(*pos.values())
106 x,y = zip(*pos.values())
107 xmin,ymin = map(min, (x,y))
107 xmin,ymin = map(min, (x,y))
108 xmax,ymax = map(max, (x,y))
108 xmax,ymax = map(max, (x,y))
109 xscale = xmax-xmin
109 xscale = xmax-xmin
110 yscale = ymax-ymin
110 yscale = ymax-ymin
111 plt.xlim(xmin-xscale*.1,xmax+xscale*.1)
111 plt.xlim(xmin-xscale*.1,xmax+xscale*.1)
112 plt.ylim(ymin-yscale*.1,ymax+yscale*.1)
112 plt.ylim(ymin-yscale*.1,ymax+yscale*.1)
113 return G,results
113 return G,results
114
114
115 if __name__ == '__main__':
115 if __name__ == '__main__':
116 from matplotlib import pyplot as plt
116 from matplotlib import pyplot as plt
117 # main(5,10)
117 # main(5,10)
118 main(32,96)
118 main(32,96)
119 plt.show()
119 plt.show()
120 No newline at end of file
120
@@ -1,130 +1,128 b''
1 from IPython.zmq.parallel import error
1 from IPython.parallel import *
2 from IPython.zmq.parallel.dependency import Dependency
3 from IPython.zmq.parallel.client import *
4
2
5 client = Client()
3 client = Client()
6
4
7 # this will only run on machines that can import numpy:
5 # this will only run on machines that can import numpy:
8 @require('numpy')
6 @require('numpy')
9 def norm(A):
7 def norm(A):
10 from numpy.linalg import norm
8 from numpy.linalg import norm
11 return norm(A,2)
9 return norm(A,2)
12
10
13 def checkpid(pid):
11 def checkpid(pid):
14 """return the pid of the engine"""
12 """return the pid of the engine"""
15 import os
13 import os
16 return os.getpid() == pid
14 return os.getpid() == pid
17
15
18 def checkhostname(host):
16 def checkhostname(host):
19 import socket
17 import socket
20 return socket.gethostname() == host
18 return socket.gethostname() == host
21
19
22 def getpid():
20 def getpid():
23 import os
21 import os
24 return os.getpid()
22 return os.getpid()
25
23
26 pid0 = client[0].apply_sync(getpid)
24 pid0 = client[0].apply_sync(getpid)
27
25
28 # this will depend on the pid being that of target 0:
26 # this will depend on the pid being that of target 0:
29 @depend(checkpid, pid0)
27 @depend(checkpid, pid0)
30 def getpid2():
28 def getpid2():
31 import os
29 import os
32 return os.getpid()
30 return os.getpid()
33
31
34 view = client.load_balanced_view()
32 view = client.load_balanced_view()
35 view.block=True
33 view.block=True
36
34
37 # will run on anything:
35 # will run on anything:
38 pids1 = [ view.apply(getpid) for i in range(len(client.ids)) ]
36 pids1 = [ view.apply(getpid) for i in range(len(client.ids)) ]
39 print pids1
37 print pids1
40 # will only run on e0:
38 # will only run on e0:
41 pids2 = [ view.apply(getpid2) for i in range(len(client.ids)) ]
39 pids2 = [ view.apply(getpid2) for i in range(len(client.ids)) ]
42 print pids2
40 print pids2
43
41
44 print "now test some dependency behaviors"
42 print "now test some dependency behaviors"
45
43
46 def wait(t):
44 def wait(t):
47 import time
45 import time
48 time.sleep(t)
46 time.sleep(t)
49 return t
47 return t
50
48
51 # fail after some time:
49 # fail after some time:
52 def wait_and_fail(t):
50 def wait_and_fail(t):
53 import time
51 import time
54 time.sleep(t)
52 time.sleep(t)
55 return 1/0
53 return 1/0
56
54
57 successes = [ view.apply_async(wait, 1).msg_ids[0] for i in range(len(client.ids)) ]
55 successes = [ view.apply_async(wait, 1).msg_ids[0] for i in range(len(client.ids)) ]
58 failures = [ view.apply_async(wait_and_fail, 1).msg_ids[0] for i in range(len(client.ids)) ]
56 failures = [ view.apply_async(wait_and_fail, 1).msg_ids[0] for i in range(len(client.ids)) ]
59
57
60 mixed = [failures[0],successes[0]]
58 mixed = [failures[0],successes[0]]
61 d1a = Dependency(mixed, all=False, failure=True) # yes
59 d1a = Dependency(mixed, all=False, failure=True) # yes
62 d1b = Dependency(mixed, all=False) # yes
60 d1b = Dependency(mixed, all=False) # yes
63 d2a = Dependency(mixed, all=True, failure=True) # yes after / no follow
61 d2a = Dependency(mixed, all=True, failure=True) # yes after / no follow
64 d2b = Dependency(mixed, all=True) # no
62 d2b = Dependency(mixed, all=True) # no
65 d3 = Dependency(failures, all=False) # no
63 d3 = Dependency(failures, all=False) # no
66 d4 = Dependency(failures, all=False, failure=True) # yes
64 d4 = Dependency(failures, all=False, failure=True) # yes
67 d5 = Dependency(failures, all=True, failure=True) # yes after / no follow
65 d5 = Dependency(failures, all=True, failure=True) # yes after / no follow
68 d6 = Dependency(successes, all=True, failure=True) # yes after / no follow
66 d6 = Dependency(successes, all=True, failure=True) # yes after / no follow
69
67
70 view.block = False
68 view.block = False
71 flags = view.temp_flags
69 flags = view.temp_flags
72 with flags(after=d1a):
70 with flags(after=d1a):
73 r1a = view.apply(getpid)
71 r1a = view.apply(getpid)
74 with flags(follow=d1b):
72 with flags(follow=d1b):
75 r1b = view.apply(getpid)
73 r1b = view.apply(getpid)
76 with flags(after=d2b, follow=d2a):
74 with flags(after=d2b, follow=d2a):
77 r2a = view.apply(getpid)
75 r2a = view.apply(getpid)
78 with flags(after=d2a, follow=d2b):
76 with flags(after=d2a, follow=d2b):
79 r2b = view.apply(getpid)
77 r2b = view.apply(getpid)
80 with flags(after=d3):
78 with flags(after=d3):
81 r3 = view.apply(getpid)
79 r3 = view.apply(getpid)
82 with flags(after=d4):
80 with flags(after=d4):
83 r4a = view.apply(getpid)
81 r4a = view.apply(getpid)
84 with flags(follow=d4):
82 with flags(follow=d4):
85 r4b = view.apply(getpid)
83 r4b = view.apply(getpid)
86 with flags(after=d3, follow=d4):
84 with flags(after=d3, follow=d4):
87 r4c = view.apply(getpid)
85 r4c = view.apply(getpid)
88 with flags(after=d5):
86 with flags(after=d5):
89 r5 = view.apply(getpid)
87 r5 = view.apply(getpid)
90 with flags(follow=d5, after=d3):
88 with flags(follow=d5, after=d3):
91 r5b = view.apply(getpid)
89 r5b = view.apply(getpid)
92 with flags(follow=d6):
90 with flags(follow=d6):
93 r6 = view.apply(getpid)
91 r6 = view.apply(getpid)
94 with flags(after=d6, follow=d2b):
92 with flags(after=d6, follow=d2b):
95 r6b = view.apply(getpid)
93 r6b = view.apply(getpid)
96
94
97 def should_fail(f):
95 def should_fail(f):
98 try:
96 try:
99 f()
97 f()
100 except error.KernelError:
98 except error.KernelError:
101 pass
99 pass
102 else:
100 else:
103 print 'should have raised'
101 print 'should have raised'
104 # raise Exception("should have raised")
102 # raise Exception("should have raised")
105
103
106 # print r1a.msg_ids
104 # print r1a.msg_ids
107 r1a.get()
105 r1a.get()
108 # print r1b.msg_ids
106 # print r1b.msg_ids
109 r1b.get()
107 r1b.get()
110 # print r2a.msg_ids
108 # print r2a.msg_ids
111 should_fail(r2a.get)
109 should_fail(r2a.get)
112 # print r2b.msg_ids
110 # print r2b.msg_ids
113 should_fail(r2b.get)
111 should_fail(r2b.get)
114 # print r3.msg_ids
112 # print r3.msg_ids
115 should_fail(r3.get)
113 should_fail(r3.get)
116 # print r4a.msg_ids
114 # print r4a.msg_ids
117 r4a.get()
115 r4a.get()
118 # print r4b.msg_ids
116 # print r4b.msg_ids
119 r4b.get()
117 r4b.get()
120 # print r4c.msg_ids
118 # print r4c.msg_ids
121 should_fail(r4c.get)
119 should_fail(r4c.get)
122 # print r5.msg_ids
120 # print r5.msg_ids
123 r5.get()
121 r5.get()
124 # print r5b.msg_ids
122 # print r5b.msg_ids
125 should_fail(r5b.get)
123 should_fail(r5b.get)
126 # print r6.msg_ids
124 # print r6.msg_ids
127 should_fail(r6.get) # assuming > 1 engine
125 should_fail(r6.get) # assuming > 1 engine
128 # print r6b.msg_ids
126 # print r6b.msg_ids
129 should_fail(r6b.get)
127 should_fail(r6b.get)
130 print 'done'
128 print 'done'
@@ -1,37 +1,37 b''
1 from IPython.zmq.parallel.client import *
1 from IPython.parallel import *
2
2
3 client = Client()
3 client = Client()
4 view = client[:]
4 view = client[:]
5
5
6 @view.remote(block=True)
6 @view.remote(block=True)
7 def square(a):
7 def square(a):
8 """return square of a number"""
8 """return square of a number"""
9 return a*a
9 return a*a
10
10
11 squares = map(square, range(42))
11 squares = map(square, range(42))
12
12
13 # but that blocked between each result; not exactly useful
13 # but that blocked between each result; not exactly useful
14
14
15 square.block = False
15 square.block = False
16
16
17 arlist = map(square, range(42))
17 arlist = map(square, range(42))
18 # submitted very fast
18 # submitted very fast
19
19
20 # wait for the results:
20 # wait for the results:
21 squares2 = [ r.get() for r in arlist ]
21 squares2 = [ r.get() for r in arlist ]
22
22
23 # now the more convenient @parallel decorator, which has a map method:
23 # now the more convenient @parallel decorator, which has a map method:
24
24
25 @view.parallel(block=False)
25 @view.parallel(block=False)
26 def psquare(a):
26 def psquare(a):
27 """return square of a number"""
27 """return square of a number"""
28 return a*a
28 return a*a
29
29
30 # this chunks the data into n-negines jobs, not 42 jobs:
30 # this chunks the data into n-negines jobs, not 42 jobs:
31 ar = psquare.map(range(42))
31 ar = psquare.map(range(42))
32
32
33 # wait for the results to be done:
33 # wait for the results to be done:
34 squares3 = ar.get()
34 squares3 = ar.get()
35
35
36 print squares == squares2, squares3==squares
36 print squares == squares2, squares3==squares
37 # True No newline at end of file
37 # True
@@ -1,86 +1,86 b''
1 import time
1 import time
2 import numpy as np
2 import numpy as np
3 from IPython.zmq.parallel import client as clientmod
3 from IPython import parallel
4
4
5 nlist = map(int, np.logspace(2,9,16,base=2))
5 nlist = map(int, np.logspace(2,9,16,base=2))
6 nlist2 = map(int, np.logspace(2,8,15,base=2))
6 nlist2 = map(int, np.logspace(2,8,15,base=2))
7 tlist = map(int, np.logspace(7,22,16,base=2))
7 tlist = map(int, np.logspace(7,22,16,base=2))
8 nt = 16
8 nt = 16
9 def wait(t=0):
9 def wait(t=0):
10 import time
10 import time
11 time.sleep(t)
11 time.sleep(t)
12
12
13 def echo(s=''):
13 def echo(s=''):
14 return s
14 return s
15
15
16 def time_throughput(nmessages, t=0, f=wait):
16 def time_throughput(nmessages, t=0, f=wait):
17 client = clientmod.Client()
17 client = parallel.Client()
18 view = client[None]
18 view = client[None]
19 # do one ping before starting timing
19 # do one ping before starting timing
20 if f is echo:
20 if f is echo:
21 t = np.random.random(t/8)
21 t = np.random.random(t/8)
22 view.apply_sync(echo, '')
22 view.apply_sync(echo, '')
23 client.spin()
23 client.spin()
24 tic = time.time()
24 tic = time.time()
25 for i in xrange(nmessages):
25 for i in xrange(nmessages):
26 view.apply(f, t)
26 view.apply(f, t)
27 lap = time.time()
27 lap = time.time()
28 client.barrier()
28 client.barrier()
29 toc = time.time()
29 toc = time.time()
30 return lap-tic, toc-tic
30 return lap-tic, toc-tic
31
31
32 def time_twisted(nmessages, t=0, f=wait):
32 def time_twisted(nmessages, t=0, f=wait):
33 from IPython.kernel import client as kc
33 from IPython.kernel import client as kc
34 client = kc.TaskClient()
34 client = kc.TaskClient()
35 if f is wait:
35 if f is wait:
36 s = "import time; time.sleep(%f)"%t
36 s = "import time; time.sleep(%f)"%t
37 task = kc.StringTask(s)
37 task = kc.StringTask(s)
38 elif f is echo:
38 elif f is echo:
39 t = np.random.random(t/8)
39 t = np.random.random(t/8)
40 s = "s=t"
40 s = "s=t"
41 task = kc.StringTask(s, push=dict(t=t), pull=['s'])
41 task = kc.StringTask(s, push=dict(t=t), pull=['s'])
42 else:
42 else:
43 raise
43 raise
44 # do one ping before starting timing
44 # do one ping before starting timing
45 client.barrier(client.run(task))
45 client.barrier(client.run(task))
46 tic = time.time()
46 tic = time.time()
47 tids = []
47 tids = []
48 for i in xrange(nmessages):
48 for i in xrange(nmessages):
49 tids.append(client.run(task))
49 tids.append(client.run(task))
50 lap = time.time()
50 lap = time.time()
51 client.barrier(tids)
51 client.barrier(tids)
52 toc = time.time()
52 toc = time.time()
53 return lap-tic, toc-tic
53 return lap-tic, toc-tic
54
54
55 def do_runs(nlist,t=0,f=wait, trials=2, runner=time_throughput):
55 def do_runs(nlist,t=0,f=wait, trials=2, runner=time_throughput):
56 A = np.zeros((len(nlist),2))
56 A = np.zeros((len(nlist),2))
57 for i,n in enumerate(nlist):
57 for i,n in enumerate(nlist):
58 t1 = t2 = 0
58 t1 = t2 = 0
59 for _ in range(trials):
59 for _ in range(trials):
60 time.sleep(.25)
60 time.sleep(.25)
61 ts = runner(n,t,f)
61 ts = runner(n,t,f)
62 t1 += ts[0]
62 t1 += ts[0]
63 t2 += ts[1]
63 t2 += ts[1]
64 t1 /= trials
64 t1 /= trials
65 t2 /= trials
65 t2 /= trials
66 A[i] = (t1,t2)
66 A[i] = (t1,t2)
67 A[i] = n/A[i]
67 A[i] = n/A[i]
68 print n,A[i]
68 print n,A[i]
69 return A
69 return A
70
70
71 def do_echo(n,tlist=[0],f=echo, trials=2, runner=time_throughput):
71 def do_echo(n,tlist=[0],f=echo, trials=2, runner=time_throughput):
72 A = np.zeros((len(tlist),2))
72 A = np.zeros((len(tlist),2))
73 for i,t in enumerate(tlist):
73 for i,t in enumerate(tlist):
74 t1 = t2 = 0
74 t1 = t2 = 0
75 for _ in range(trials):
75 for _ in range(trials):
76 time.sleep(.25)
76 time.sleep(.25)
77 ts = runner(n,t,f)
77 ts = runner(n,t,f)
78 t1 += ts[0]
78 t1 += ts[0]
79 t2 += ts[1]
79 t2 += ts[1]
80 t1 /= trials
80 t1 /= trials
81 t2 /= trials
81 t2 /= trials
82 A[i] = (t1,t2)
82 A[i] = (t1,t2)
83 A[i] = n/A[i]
83 A[i] = n/A[i]
84 print t,A[i]
84 print t,A[i]
85 return A
85 return A
86 No newline at end of file
86
@@ -1,15 +1,15 b''
1 from IPython.zmq.parallel.client import *
1 from IPython.parallel import *
2
2
3 client = Client()
3 client = Client()
4
4
5 for id in client.ids:
5 for id in client.ids:
6 client[id].push(dict(ids=id*id))
6 client[id].push(dict(ids=id*id))
7
7
8 v = client[0]
8 v = client[0]
9 v['a'] = 5
9 v['a'] = 5
10
10
11 print v['a']
11 print v['a']
12
12
13 remotes = client[:]
13 remotes = client[:]
14
14
15 print remotes['ids'] No newline at end of file
15 print remotes['ids']
@@ -1,97 +1,97 b''
1 """
1 """
2 An exceptionally lousy site spider
2 An exceptionally lousy site spider
3 Ken Kinder <ken@kenkinder.com>
3 Ken Kinder <ken@kenkinder.com>
4
4
5 Updated for newparallel by Min Ragan-Kelley <benjaminrk@gmail.com>
5 Updated for newparallel by Min Ragan-Kelley <benjaminrk@gmail.com>
6
6
7 This module gives an example of how the task interface to the
7 This module gives an example of how the task interface to the
8 IPython controller works. Before running this script start the IPython controller
8 IPython controller works. Before running this script start the IPython controller
9 and some engines using something like::
9 and some engines using something like::
10
10
11 ipclusterz start -n 4
11 ipclusterz start -n 4
12 """
12 """
13 import sys
13 import sys
14 from IPython.zmq.parallel import client, error
14 from IPython.parallel import Client, error
15 import time
15 import time
16 import BeautifulSoup # this isn't necessary, but it helps throw the dependency error earlier
16 import BeautifulSoup # this isn't necessary, but it helps throw the dependency error earlier
17
17
18 def fetchAndParse(url, data=None):
18 def fetchAndParse(url, data=None):
19 import urllib2
19 import urllib2
20 import urlparse
20 import urlparse
21 import BeautifulSoup
21 import BeautifulSoup
22 links = []
22 links = []
23 try:
23 try:
24 page = urllib2.urlopen(url, data=data)
24 page = urllib2.urlopen(url, data=data)
25 except Exception:
25 except Exception:
26 return links
26 return links
27 else:
27 else:
28 if page.headers.type == 'text/html':
28 if page.headers.type == 'text/html':
29 doc = BeautifulSoup.BeautifulSoup(page.read())
29 doc = BeautifulSoup.BeautifulSoup(page.read())
30 for node in doc.findAll('a'):
30 for node in doc.findAll('a'):
31 href = node.get('href', None)
31 href = node.get('href', None)
32 if href:
32 if href:
33 links.append(urlparse.urljoin(url, href))
33 links.append(urlparse.urljoin(url, href))
34 return links
34 return links
35
35
36 class DistributedSpider(object):
36 class DistributedSpider(object):
37
37
38 # Time to wait between polling for task results.
38 # Time to wait between polling for task results.
39 pollingDelay = 0.5
39 pollingDelay = 0.5
40
40
41 def __init__(self, site):
41 def __init__(self, site):
42 self.client = client.Client()
42 self.client = Client()
43 self.view = self.client.load_balanced_view()
43 self.view = self.client.load_balanced_view()
44 self.mux = self.client[:]
44 self.mux = self.client[:]
45
45
46 self.allLinks = []
46 self.allLinks = []
47 self.linksWorking = {}
47 self.linksWorking = {}
48 self.linksDone = {}
48 self.linksDone = {}
49
49
50 self.site = site
50 self.site = site
51
51
52 def visitLink(self, url):
52 def visitLink(self, url):
53 if url not in self.allLinks:
53 if url not in self.allLinks:
54 self.allLinks.append(url)
54 self.allLinks.append(url)
55 if url.startswith(self.site):
55 if url.startswith(self.site):
56 print ' ', url
56 print ' ', url
57 self.linksWorking[url] = self.view.apply(fetchAndParse, url)
57 self.linksWorking[url] = self.view.apply(fetchAndParse, url)
58
58
59 def onVisitDone(self, links, url):
59 def onVisitDone(self, links, url):
60 print url, ':'
60 print url, ':'
61 self.linksDone[url] = None
61 self.linksDone[url] = None
62 del self.linksWorking[url]
62 del self.linksWorking[url]
63 for link in links:
63 for link in links:
64 self.visitLink(link)
64 self.visitLink(link)
65
65
66 def run(self):
66 def run(self):
67 self.visitLink(self.site)
67 self.visitLink(self.site)
68 while self.linksWorking:
68 while self.linksWorking:
69 print len(self.linksWorking), 'pending...'
69 print len(self.linksWorking), 'pending...'
70 self.synchronize()
70 self.synchronize()
71 time.sleep(self.pollingDelay)
71 time.sleep(self.pollingDelay)
72
72
73 def synchronize(self):
73 def synchronize(self):
74 for url, ar in self.linksWorking.items():
74 for url, ar in self.linksWorking.items():
75 # Calling get_task_result with block=False will return None if the
75 # Calling get_task_result with block=False will return None if the
76 # task is not done yet. This provides a simple way of polling.
76 # task is not done yet. This provides a simple way of polling.
77 try:
77 try:
78 links = ar.get(0)
78 links = ar.get(0)
79 except error.TimeoutError:
79 except error.TimeoutError:
80 continue
80 continue
81 except Exception as e:
81 except Exception as e:
82 self.linksDone[url] = None
82 self.linksDone[url] = None
83 del self.linksWorking[url]
83 del self.linksWorking[url]
84 print url, ':', e.traceback
84 print url, ':', e.traceback
85 else:
85 else:
86 self.onVisitDone(links, url)
86 self.onVisitDone(links, url)
87
87
88 def main():
88 def main():
89 if len(sys.argv) > 1:
89 if len(sys.argv) > 1:
90 site = sys.argv[1]
90 site = sys.argv[1]
91 else:
91 else:
92 site = raw_input('Enter site to crawl: ')
92 site = raw_input('Enter site to crawl: ')
93 distributedSpider = DistributedSpider(site)
93 distributedSpider = DistributedSpider(site)
94 distributedSpider.run()
94 distributedSpider.run()
95
95
96 if __name__ == '__main__':
96 if __name__ == '__main__':
97 main()
97 main()
@@ -1,19 +1,19 b''
1 """
1 """
2 A Distributed Hello world
2 A Distributed Hello world
3 Ken Kinder <ken@kenkinder.com>
3 Ken Kinder <ken@kenkinder.com>
4 """
4 """
5 from IPython.zmq.parallel import client
5 from IPython.parallel import Client
6
6
7 rc = client.Client()
7 rc = Client()
8
8
9 def sleep_and_echo(t, msg):
9 def sleep_and_echo(t, msg):
10 import time
10 import time
11 time.sleep(t)
11 time.sleep(t)
12 return msg
12 return msg
13
13
14 view = rc.load_balanced_view()
14 view = rc.load_balanced_view()
15
15
16 world = view.apply_async(sleep_and_echo, 3, 'World!')
16 world = view.apply_async(sleep_and_echo, 3, 'World!')
17 hello = view.apply_async(sleep_and_echo, 2, 'Hello')
17 hello = view.apply_async(sleep_and_echo, 2, 'Hello')
18 print "Submitted tasks:", hello.msg_ids, world.msg_ids
18 print "Submitted tasks:", hello.msg_ids, world.msg_ids
19 print hello.get(), world.get()
19 print hello.get(), world.get()
@@ -1,77 +1,77 b''
1 import socket
1 import socket
2
2
3 import uuid
3 import uuid
4 import zmq
4 import zmq
5
5
6 from IPython.zmq.parallel.util import disambiguate_url
6 from IPython.parallel.util import disambiguate_url
7
7
8 class EngineCommunicator(object):
8 class EngineCommunicator(object):
9
9
10 def __init__(self, interface='tcp://*', identity=None):
10 def __init__(self, interface='tcp://*', identity=None):
11 self._ctx = zmq.Context()
11 self._ctx = zmq.Context()
12 self.socket = self._ctx.socket(zmq.XREP)
12 self.socket = self._ctx.socket(zmq.XREP)
13 self.pub = self._ctx.socket(zmq.PUB)
13 self.pub = self._ctx.socket(zmq.PUB)
14 self.sub = self._ctx.socket(zmq.SUB)
14 self.sub = self._ctx.socket(zmq.SUB)
15
15
16 # configure sockets
16 # configure sockets
17 self.identity = identity or bytes(uuid.uuid4())
17 self.identity = identity or bytes(uuid.uuid4())
18 print self.identity
18 print self.identity
19 self.socket.setsockopt(zmq.IDENTITY, self.identity)
19 self.socket.setsockopt(zmq.IDENTITY, self.identity)
20 self.sub.setsockopt(zmq.SUBSCRIBE, b'')
20 self.sub.setsockopt(zmq.SUBSCRIBE, b'')
21
21
22 # bind to ports
22 # bind to ports
23 port = self.socket.bind_to_random_port(interface)
23 port = self.socket.bind_to_random_port(interface)
24 pub_port = self.pub.bind_to_random_port(interface)
24 pub_port = self.pub.bind_to_random_port(interface)
25 self.url = interface+":%i"%port
25 self.url = interface+":%i"%port
26 self.pub_url = interface+":%i"%pub_port
26 self.pub_url = interface+":%i"%pub_port
27 # guess first public IP from socket
27 # guess first public IP from socket
28 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
28 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
29 self.peers = {}
29 self.peers = {}
30
30
31 def __del__(self):
31 def __del__(self):
32 self.socket.close()
32 self.socket.close()
33 self.pub.close()
33 self.pub.close()
34 self.sub.close()
34 self.sub.close()
35 self._ctx.term()
35 self._ctx.term()
36
36
37 @property
37 @property
38 def info(self):
38 def info(self):
39 """return the connection info for this object's sockets."""
39 """return the connection info for this object's sockets."""
40 return (self.identity, self.url, self.pub_url, self.location)
40 return (self.identity, self.url, self.pub_url, self.location)
41
41
42 def connect(self, peers):
42 def connect(self, peers):
43 """connect to peers. `peers` will be a dict of 4-tuples, keyed by name.
43 """connect to peers. `peers` will be a dict of 4-tuples, keyed by name.
44 {peer : (ident, addr, pub_addr, location)}
44 {peer : (ident, addr, pub_addr, location)}
45 where peer is the name, ident is the XREP identity, addr,pub_addr are the
45 where peer is the name, ident is the XREP identity, addr,pub_addr are the
46 """
46 """
47 for peer, (ident, url, pub_url, location) in peers.items():
47 for peer, (ident, url, pub_url, location) in peers.items():
48 self.peers[peer] = ident
48 self.peers[peer] = ident
49 if ident != self.identity:
49 if ident != self.identity:
50 self.sub.connect(disambiguate_url(pub_url, location))
50 self.sub.connect(disambiguate_url(pub_url, location))
51 if ident > self.identity:
51 if ident > self.identity:
52 # prevent duplicate xrep, by only connecting
52 # prevent duplicate xrep, by only connecting
53 # engines to engines with higher IDENTITY
53 # engines to engines with higher IDENTITY
54 # a doubly-connected pair will crash
54 # a doubly-connected pair will crash
55 self.socket.connect(disambiguate_url(url, location))
55 self.socket.connect(disambiguate_url(url, location))
56
56
57 def send(self, peers, msg, flags=0, copy=True):
57 def send(self, peers, msg, flags=0, copy=True):
58 if not isinstance(peers, list):
58 if not isinstance(peers, list):
59 peers = [peers]
59 peers = [peers]
60 if not isinstance(msg, list):
60 if not isinstance(msg, list):
61 msg = [msg]
61 msg = [msg]
62 for p in peers:
62 for p in peers:
63 ident = self.peers[p]
63 ident = self.peers[p]
64 self.socket.send_multipart([ident]+msg, flags=flags, copy=copy)
64 self.socket.send_multipart([ident]+msg, flags=flags, copy=copy)
65
65
66 def recv(self, flags=0, copy=True):
66 def recv(self, flags=0, copy=True):
67 return self.socket.recv_multipart(flags=flags, copy=copy)[1:]
67 return self.socket.recv_multipart(flags=flags, copy=copy)[1:]
68
68
69 def publish(self, msg, flags=0, copy=True):
69 def publish(self, msg, flags=0, copy=True):
70 if not isinstance(msg, list):
70 if not isinstance(msg, list):
71 msg = [msg]
71 msg = [msg]
72 self.pub.send_multipart(msg, copy=copy)
72 self.pub.send_multipart(msg, copy=copy)
73
73
74 def consume(self, flags=0, copy=True):
74 def consume(self, flags=0, copy=True):
75 return self.sub.recv_multipart(flags=flags, copy=copy)
75 return self.sub.recv_multipart(flags=flags, copy=copy)
76
76
77
77
@@ -1,43 +1,43 b''
1 import sys
1 import sys
2
2
3 from IPython.zmq.parallel import client
3 from IPython.parallel import Client
4
4
5
5
6 rc = client.Client()
6 rc = Client()
7 rc.block=True
7 rc.block=True
8 view = rc[:]
8 view = rc[:]
9 view.run('communicator.py')
9 view.run('communicator.py')
10 view.execute('com = EngineCommunicator()')
10 view.execute('com = EngineCommunicator()')
11
11
12 # gather the connection information into a dict
12 # gather the connection information into a dict
13 ar = view.apply_async(lambda : com.info)
13 ar = view.apply_async(lambda : com.info)
14 peers = ar.get_dict()
14 peers = ar.get_dict()
15 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
15 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
16
16
17 # connect the engines to each other:
17 # connect the engines to each other:
18 view.apply_sync(lambda pdict: com.connect(pdict), peers)
18 view.apply_sync(lambda pdict: com.connect(pdict), peers)
19
19
20 # now all the engines are connected, and we can communicate between them:
20 # now all the engines are connected, and we can communicate between them:
21
21
22 def broadcast(client, sender, msg_name, dest_name=None, block=None):
22 def broadcast(client, sender, msg_name, dest_name=None, block=None):
23 """broadcast a message from one engine to all others."""
23 """broadcast a message from one engine to all others."""
24 dest_name = msg_name if dest_name is None else dest_name
24 dest_name = msg_name if dest_name is None else dest_name
25 client[sender].execute('com.publish(%s)'%msg_name, block=None)
25 client[sender].execute('com.publish(%s)'%msg_name, block=None)
26 targets = client.ids
26 targets = client.ids
27 targets.remove(sender)
27 targets.remove(sender)
28 return client[targets].execute('%s=com.consume()'%dest_name, block=None)
28 return client[targets].execute('%s=com.consume()'%dest_name, block=None)
29
29
30 def send(client, sender, targets, msg_name, dest_name=None, block=None):
30 def send(client, sender, targets, msg_name, dest_name=None, block=None):
31 """send a message from one to one-or-more engines."""
31 """send a message from one to one-or-more engines."""
32 dest_name = msg_name if dest_name is None else dest_name
32 dest_name = msg_name if dest_name is None else dest_name
33 def _send(targets, m_name):
33 def _send(targets, m_name):
34 msg = globals()[m_name]
34 msg = globals()[m_name]
35 return com.send(targets, msg)
35 return com.send(targets, msg)
36
36
37 client[sender].apply_async(_send, targets, msg_name)
37 client[sender].apply_async(_send, targets, msg_name)
38
38
39 return client[targets].execute('%s=com.recv()'%dest_name, block=None)
39 return client[targets].execute('%s=com.recv()'%dest_name, block=None)
40
40
41
41
42
42
43
43
@@ -1,144 +1,144 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Run a Monte-Carlo options pricer in parallel."""
2 """Run a Monte-Carlo options pricer in parallel."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Imports
5 # Imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 import sys
8 import sys
9 import time
9 import time
10 from IPython.zmq.parallel import client
10 from IPython.parallel import Client
11 import numpy as np
11 import numpy as np
12 from mcpricer import price_options
12 from mcpricer import price_options
13 from matplotlib import pyplot as plt
13 from matplotlib import pyplot as plt
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Setup parameters for the run
16 # Setup parameters for the run
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 def ask_question(text, the_type, default):
19 def ask_question(text, the_type, default):
20 s = '%s [%r]: ' % (text, the_type(default))
20 s = '%s [%r]: ' % (text, the_type(default))
21 result = raw_input(s)
21 result = raw_input(s)
22 if result:
22 if result:
23 return the_type(result)
23 return the_type(result)
24 else:
24 else:
25 return the_type(default)
25 return the_type(default)
26
26
27 cluster_profile = ask_question("Cluster profile", str, "default")
27 cluster_profile = ask_question("Cluster profile", str, "default")
28 price = ask_question("Initial price", float, 100.0)
28 price = ask_question("Initial price", float, 100.0)
29 rate = ask_question("Interest rate", float, 0.05)
29 rate = ask_question("Interest rate", float, 0.05)
30 days = ask_question("Days to expiration", int, 260)
30 days = ask_question("Days to expiration", int, 260)
31 paths = ask_question("Number of MC paths", int, 10000)
31 paths = ask_question("Number of MC paths", int, 10000)
32 n_strikes = ask_question("Number of strike values", int, 5)
32 n_strikes = ask_question("Number of strike values", int, 5)
33 min_strike = ask_question("Min strike price", float, 90.0)
33 min_strike = ask_question("Min strike price", float, 90.0)
34 max_strike = ask_question("Max strike price", float, 110.0)
34 max_strike = ask_question("Max strike price", float, 110.0)
35 n_sigmas = ask_question("Number of volatility values", int, 5)
35 n_sigmas = ask_question("Number of volatility values", int, 5)
36 min_sigma = ask_question("Min volatility", float, 0.1)
36 min_sigma = ask_question("Min volatility", float, 0.1)
37 max_sigma = ask_question("Max volatility", float, 0.4)
37 max_sigma = ask_question("Max volatility", float, 0.4)
38
38
39 strike_vals = np.linspace(min_strike, max_strike, n_strikes)
39 strike_vals = np.linspace(min_strike, max_strike, n_strikes)
40 sigma_vals = np.linspace(min_sigma, max_sigma, n_sigmas)
40 sigma_vals = np.linspace(min_sigma, max_sigma, n_sigmas)
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Setup for parallel calculation
43 # Setup for parallel calculation
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 # The Client is used to setup the calculation and works with all
46 # The Client is used to setup the calculation and works with all
47 # engines.
47 # engines.
48 c = client.Client(profile=cluster_profile)
48 c = Client(profile=cluster_profile)
49
49
50 # A LoadBalancedView is an interface to the engines that provides dynamic load
50 # A LoadBalancedView is an interface to the engines that provides dynamic load
51 # balancing at the expense of not knowing which engine will execute the code.
51 # balancing at the expense of not knowing which engine will execute the code.
52 view = c.load_balanced_view()
52 view = c.load_balanced_view()
53
53
54 # Initialize the common code on the engines. This Python module has the
54 # Initialize the common code on the engines. This Python module has the
55 # price_options function that prices the options.
55 # price_options function that prices the options.
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Perform parallel calculation
58 # Perform parallel calculation
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 print "Running parallel calculation over strike prices and volatilities..."
61 print "Running parallel calculation over strike prices and volatilities..."
62 print "Strike prices: ", strike_vals
62 print "Strike prices: ", strike_vals
63 print "Volatilities: ", sigma_vals
63 print "Volatilities: ", sigma_vals
64 sys.stdout.flush()
64 sys.stdout.flush()
65
65
66 # Submit tasks to the TaskClient for each (strike, sigma) pair as a MapTask.
66 # Submit tasks to the TaskClient for each (strike, sigma) pair as a MapTask.
67 t1 = time.time()
67 t1 = time.time()
68 async_results = []
68 async_results = []
69 for strike in strike_vals:
69 for strike in strike_vals:
70 for sigma in sigma_vals:
70 for sigma in sigma_vals:
71 ar = view.apply_async(price_options, price, strike, sigma, rate, days, paths)
71 ar = view.apply_async(price_options, price, strike, sigma, rate, days, paths)
72 async_results.append(ar)
72 async_results.append(ar)
73
73
74 print "Submitted tasks: ", len(async_results)
74 print "Submitted tasks: ", len(async_results)
75 sys.stdout.flush()
75 sys.stdout.flush()
76
76
77 # Block until all tasks are completed.
77 # Block until all tasks are completed.
78 c.wait(async_results)
78 c.wait(async_results)
79 t2 = time.time()
79 t2 = time.time()
80 t = t2-t1
80 t = t2-t1
81
81
82 print "Parallel calculation completed, time = %s s" % t
82 print "Parallel calculation completed, time = %s s" % t
83 print "Collecting results..."
83 print "Collecting results..."
84
84
85 # Get the results using TaskClient.get_task_result.
85 # Get the results using TaskClient.get_task_result.
86 results = [ar.get() for ar in async_results]
86 results = [ar.get() for ar in async_results]
87
87
88 # Assemble the result into a structured NumPy array.
88 # Assemble the result into a structured NumPy array.
89 prices = np.empty(n_strikes*n_sigmas,
89 prices = np.empty(n_strikes*n_sigmas,
90 dtype=[('ecall',float),('eput',float),('acall',float),('aput',float)]
90 dtype=[('ecall',float),('eput',float),('acall',float),('aput',float)]
91 )
91 )
92
92
93 for i, price in enumerate(results):
93 for i, price in enumerate(results):
94 prices[i] = tuple(price)
94 prices[i] = tuple(price)
95
95
96 prices.shape = (n_strikes, n_sigmas)
96 prices.shape = (n_strikes, n_sigmas)
97 strike_mesh, sigma_mesh = np.meshgrid(strike_vals, sigma_vals)
97 strike_mesh, sigma_mesh = np.meshgrid(strike_vals, sigma_vals)
98
98
99 print "Results are available: strike_mesh, sigma_mesh, prices"
99 print "Results are available: strike_mesh, sigma_mesh, prices"
100 print "To plot results type 'plot_options(sigma_mesh, strike_mesh, prices)'"
100 print "To plot results type 'plot_options(sigma_mesh, strike_mesh, prices)'"
101
101
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 # Utilities
103 # Utilities
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105
105
106 def plot_options(sigma_mesh, strike_mesh, prices):
106 def plot_options(sigma_mesh, strike_mesh, prices):
107 """
107 """
108 Make a contour plot of the option price in (sigma, strike) space.
108 Make a contour plot of the option price in (sigma, strike) space.
109 """
109 """
110 plt.figure(1)
110 plt.figure(1)
111
111
112 plt.subplot(221)
112 plt.subplot(221)
113 plt.contourf(sigma_mesh, strike_mesh, prices['ecall'])
113 plt.contourf(sigma_mesh, strike_mesh, prices['ecall'])
114 plt.axis('tight')
114 plt.axis('tight')
115 plt.colorbar()
115 plt.colorbar()
116 plt.title('European Call')
116 plt.title('European Call')
117 plt.ylabel("Strike Price")
117 plt.ylabel("Strike Price")
118
118
119 plt.subplot(222)
119 plt.subplot(222)
120 plt.contourf(sigma_mesh, strike_mesh, prices['acall'])
120 plt.contourf(sigma_mesh, strike_mesh, prices['acall'])
121 plt.axis('tight')
121 plt.axis('tight')
122 plt.colorbar()
122 plt.colorbar()
123 plt.title("Asian Call")
123 plt.title("Asian Call")
124
124
125 plt.subplot(223)
125 plt.subplot(223)
126 plt.contourf(sigma_mesh, strike_mesh, prices['eput'])
126 plt.contourf(sigma_mesh, strike_mesh, prices['eput'])
127 plt.axis('tight')
127 plt.axis('tight')
128 plt.colorbar()
128 plt.colorbar()
129 plt.title("European Put")
129 plt.title("European Put")
130 plt.xlabel("Volatility")
130 plt.xlabel("Volatility")
131 plt.ylabel("Strike Price")
131 plt.ylabel("Strike Price")
132
132
133 plt.subplot(224)
133 plt.subplot(224)
134 plt.contourf(sigma_mesh, strike_mesh, prices['aput'])
134 plt.contourf(sigma_mesh, strike_mesh, prices['aput'])
135 plt.axis('tight')
135 plt.axis('tight')
136 plt.colorbar()
136 plt.colorbar()
137 plt.title("Asian Put")
137 plt.title("Asian Put")
138 plt.xlabel("Volatility")
138 plt.xlabel("Volatility")
139
139
140
140
141
141
142
142
143
143
144
144
@@ -1,64 +1,64 b''
1 """Calculate statistics on the digits of pi in parallel.
1 """Calculate statistics on the digits of pi in parallel.
2
2
3 This program uses the functions in :file:`pidigits.py` to calculate
3 This program uses the functions in :file:`pidigits.py` to calculate
4 the frequencies of 2 digit sequences in the digits of pi. The
4 the frequencies of 2 digit sequences in the digits of pi. The
5 results are plotted using matplotlib.
5 results are plotted using matplotlib.
6
6
7 To run, text files from http://www.super-computing.org/
7 To run, text files from http://www.super-computing.org/
8 must be installed in the working directory of the IPython engines.
8 must be installed in the working directory of the IPython engines.
9 The actual filenames to be used can be set with the ``filestring``
9 The actual filenames to be used can be set with the ``filestring``
10 variable below.
10 variable below.
11
11
12 The dataset we have been using for this is the 200 million digit one here:
12 The dataset we have been using for this is the 200 million digit one here:
13 ftp://pi.super-computing.org/.2/pi200m/
13 ftp://pi.super-computing.org/.2/pi200m/
14
14
15 and the files used will be downloaded if they are not in the working directory
15 and the files used will be downloaded if they are not in the working directory
16 of the IPython engines.
16 of the IPython engines.
17 """
17 """
18
18
19 from IPython.zmq.parallel import client
19 from IPython.parallel import Client
20 from matplotlib import pyplot as plt
20 from matplotlib import pyplot as plt
21 import numpy as np
21 import numpy as np
22 from pidigits import *
22 from pidigits import *
23 from timeit import default_timer as clock
23 from timeit import default_timer as clock
24
24
25 # Files with digits of pi (10m digits each)
25 # Files with digits of pi (10m digits each)
26 filestring = 'pi200m.ascii.%(i)02dof20'
26 filestring = 'pi200m.ascii.%(i)02dof20'
27 files = [filestring % {'i':i} for i in range(1,16)]
27 files = [filestring % {'i':i} for i in range(1,16)]
28
28
29 # Connect to the IPython cluster
29 # Connect to the IPython cluster
30 c = client.Client()
30 c = Client()
31 c[:].run('pidigits.py')
31 c[:].run('pidigits.py')
32
32
33 # the number of engines
33 # the number of engines
34 n = len(c)
34 n = len(c)
35 id0 = c.ids[0]
35 id0 = c.ids[0]
36 v = c[:]
36 v = c[:]
37 v.block=True
37 v.block=True
38 # fetch the pi-files
38 # fetch the pi-files
39 print "downloading %i files of pi"%n
39 print "downloading %i files of pi"%n
40 v.map(fetch_pi_file, files[:n])
40 v.map(fetch_pi_file, files[:n])
41 print "done"
41 print "done"
42
42
43 # Run 10m digits on 1 engine
43 # Run 10m digits on 1 engine
44 t1 = clock()
44 t1 = clock()
45 freqs10m = c[id0].apply_sync(compute_two_digit_freqs, files[0])
45 freqs10m = c[id0].apply_sync(compute_two_digit_freqs, files[0])
46 t2 = clock()
46 t2 = clock()
47 digits_per_second1 = 10.0e6/(t2-t1)
47 digits_per_second1 = 10.0e6/(t2-t1)
48 print "Digits per second (1 core, 10m digits): ", digits_per_second1
48 print "Digits per second (1 core, 10m digits): ", digits_per_second1
49
49
50
50
51 # Run n*10m digits on all engines
51 # Run n*10m digits on all engines
52 t1 = clock()
52 t1 = clock()
53 freqs_all = v.map(compute_two_digit_freqs, files[:n])
53 freqs_all = v.map(compute_two_digit_freqs, files[:n])
54 freqs150m = reduce_freqs(freqs_all)
54 freqs150m = reduce_freqs(freqs_all)
55 t2 = clock()
55 t2 = clock()
56 digits_per_second8 = n*10.0e6/(t2-t1)
56 digits_per_second8 = n*10.0e6/(t2-t1)
57 print "Digits per second (%i engines, %i0m digits): "%(n,n), digits_per_second8
57 print "Digits per second (%i engines, %i0m digits): "%(n,n), digits_per_second8
58
58
59 print "Speedup: ", digits_per_second8/digits_per_second1
59 print "Speedup: ", digits_per_second8/digits_per_second1
60
60
61 plot_two_digit_freqs(freqs150m)
61 plot_two_digit_freqs(freqs150m)
62 plt.title("2 digit sequences in %i0m digits of pi"%n)
62 plt.title("2 digit sequences in %i0m digits of pi"%n)
63 plt.show()
63 plt.show()
64
64
@@ -1,59 +1,59 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple Communicator class that has N,E,S,W neighbors connected via 0MQ PEER sockets"""
2 """A simple Communicator class that has N,E,S,W neighbors connected via 0MQ PEER sockets"""
3
3
4 import socket
4 import socket
5
5
6 import zmq
6 import zmq
7
7
8 from IPython.zmq.parallel.util import disambiguate_url
8 from IPython.parallel.util import disambiguate_url
9
9
10 class EngineCommunicator(object):
10 class EngineCommunicator(object):
11 """An object that connects Engines to each other.
11 """An object that connects Engines to each other.
12 north and east sockets listen, while south and west sockets connect.
12 north and east sockets listen, while south and west sockets connect.
13
13
14 This class is useful in cases where there is a set of nodes that
14 This class is useful in cases where there is a set of nodes that
15 must communicate only with their nearest neighbors.
15 must communicate only with their nearest neighbors.
16 """
16 """
17
17
18 def __init__(self, interface='tcp://*', identity=None):
18 def __init__(self, interface='tcp://*', identity=None):
19 self._ctx = zmq.Context()
19 self._ctx = zmq.Context()
20 self.north = self._ctx.socket(zmq.PAIR)
20 self.north = self._ctx.socket(zmq.PAIR)
21 self.west = self._ctx.socket(zmq.PAIR)
21 self.west = self._ctx.socket(zmq.PAIR)
22 self.south = self._ctx.socket(zmq.PAIR)
22 self.south = self._ctx.socket(zmq.PAIR)
23 self.east = self._ctx.socket(zmq.PAIR)
23 self.east = self._ctx.socket(zmq.PAIR)
24
24
25 # bind to ports
25 # bind to ports
26 northport = self.north.bind_to_random_port(interface)
26 northport = self.north.bind_to_random_port(interface)
27 eastport = self.east.bind_to_random_port(interface)
27 eastport = self.east.bind_to_random_port(interface)
28
28
29 self.north_url = interface+":%i"%northport
29 self.north_url = interface+":%i"%northport
30 self.east_url = interface+":%i"%eastport
30 self.east_url = interface+":%i"%eastport
31
31
32 # guess first public IP from socket
32 # guess first public IP from socket
33 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
33 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
34
34
35 def __del__(self):
35 def __del__(self):
36 self.north.close()
36 self.north.close()
37 self.south.close()
37 self.south.close()
38 self.east.close()
38 self.east.close()
39 self.west.close()
39 self.west.close()
40 self._ctx.term()
40 self._ctx.term()
41
41
42 @property
42 @property
43 def info(self):
43 def info(self):
44 """return the connection info for this object's sockets."""
44 """return the connection info for this object's sockets."""
45 return (self.location, self.north_url, self.east_url)
45 return (self.location, self.north_url, self.east_url)
46
46
47 def connect(self, south_peer=None, west_peer=None):
47 def connect(self, south_peer=None, west_peer=None):
48 """connect to peers. `peers` will be a 3-tuples, of the form:
48 """connect to peers. `peers` will be a 3-tuples, of the form:
49 (location, north_addr, east_addr)
49 (location, north_addr, east_addr)
50 as produced by
50 as produced by
51 """
51 """
52 if south_peer is not None:
52 if south_peer is not None:
53 location, url, _ = south_peer
53 location, url, _ = south_peer
54 self.south.connect(disambiguate_url(url, location))
54 self.south.connect(disambiguate_url(url, location))
55 if west_peer is not None:
55 if west_peer is not None:
56 location, _, url = west_peer
56 location, _, url = west_peer
57 self.west.connect(disambiguate_url(url, location))
57 self.west.connect(disambiguate_url(url, location))
58
58
59
59
@@ -1,205 +1,205 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 A simple python program of solving a 2D wave equation in parallel.
3 A simple python program of solving a 2D wave equation in parallel.
4 Domain partitioning and inter-processor communication
4 Domain partitioning and inter-processor communication
5 are done by an object of class MPIRectPartitioner2D
5 are done by an object of class MPIRectPartitioner2D
6 (which is a subclass of RectPartitioner2D and uses MPI via mpi4py)
6 (which is a subclass of RectPartitioner2D and uses MPI via mpi4py)
7
7
8 An example of running the program is (8 processors, 4x2 partition,
8 An example of running the program is (8 processors, 4x2 partition,
9 400x100 grid cells)::
9 400x100 grid cells)::
10
10
11 $ ipclusterz start --profile mpi -n 8 # start 8 engines (assuming mpi profile has been configured)
11 $ ipclusterz start --profile mpi -n 8 # start 8 engines (assuming mpi profile has been configured)
12 $ ./parallelwave-mpi.py --grid 400 100 --partition 4 2 --profile mpi
12 $ ./parallelwave-mpi.py --grid 400 100 --partition 4 2 --profile mpi
13
13
14 See also parallelwave-mpi, which runs the same program, but uses MPI
14 See also parallelwave-mpi, which runs the same program, but uses MPI
15 (via mpi4py) for the inter-engine communication.
15 (via mpi4py) for the inter-engine communication.
16
16
17 Authors
17 Authors
18 -------
18 -------
19
19
20 * Xing Cai
20 * Xing Cai
21 * Min Ragan-Kelley
21 * Min Ragan-Kelley
22
22
23 """
23 """
24
24
25 import sys
25 import sys
26 import time
26 import time
27
27
28 from numpy import exp, zeros, newaxis, sqrt
28 from numpy import exp, zeros, newaxis, sqrt
29
29
30 from IPython.external import argparse
30 from IPython.external import argparse
31 from IPython.zmq.parallel.client import Client, Reference
31 from IPython.parallel.client import Client, Reference
32
32
33 def setup_partitioner(index, num_procs, gnum_cells, parts):
33 def setup_partitioner(index, num_procs, gnum_cells, parts):
34 """create a partitioner in the engine namespace"""
34 """create a partitioner in the engine namespace"""
35 global partitioner
35 global partitioner
36 p = MPIRectPartitioner2D(my_id=index, num_procs=num_procs)
36 p = MPIRectPartitioner2D(my_id=index, num_procs=num_procs)
37 p.redim(global_num_cells=gnum_cells, num_parts=parts)
37 p.redim(global_num_cells=gnum_cells, num_parts=parts)
38 p.prepare_communication()
38 p.prepare_communication()
39 # put the partitioner into the global namespace:
39 # put the partitioner into the global namespace:
40 partitioner=p
40 partitioner=p
41
41
42 def setup_solver(*args, **kwargs):
42 def setup_solver(*args, **kwargs):
43 """create a WaveSolver in the engine namespace"""
43 """create a WaveSolver in the engine namespace"""
44 global solver
44 global solver
45 solver = WaveSolver(*args, **kwargs)
45 solver = WaveSolver(*args, **kwargs)
46
46
47 def wave_saver(u, x, y, t):
47 def wave_saver(u, x, y, t):
48 """save the wave log"""
48 """save the wave log"""
49 global u_hist
49 global u_hist
50 global t_hist
50 global t_hist
51 t_hist.append(t)
51 t_hist.append(t)
52 u_hist.append(1.0*u)
52 u_hist.append(1.0*u)
53
53
54
54
55 # main program:
55 # main program:
56 if __name__ == '__main__':
56 if __name__ == '__main__':
57
57
58 parser = argparse.ArgumentParser()
58 parser = argparse.ArgumentParser()
59 paa = parser.add_argument
59 paa = parser.add_argument
60 paa('--grid', '-g',
60 paa('--grid', '-g',
61 type=int, nargs=2, default=[100,100], dest='grid',
61 type=int, nargs=2, default=[100,100], dest='grid',
62 help="Cells in the grid, e.g. --grid 100 200")
62 help="Cells in the grid, e.g. --grid 100 200")
63 paa('--partition', '-p',
63 paa('--partition', '-p',
64 type=int, nargs=2, default=None,
64 type=int, nargs=2, default=None,
65 help="Process partition grid, e.g. --partition 4 2 for 4x2")
65 help="Process partition grid, e.g. --partition 4 2 for 4x2")
66 paa('-c',
66 paa('-c',
67 type=float, default=1.,
67 type=float, default=1.,
68 help="Wave speed (I think)")
68 help="Wave speed (I think)")
69 paa('-Ly',
69 paa('-Ly',
70 type=float, default=1.,
70 type=float, default=1.,
71 help="system size (in y)")
71 help="system size (in y)")
72 paa('-Lx',
72 paa('-Lx',
73 type=float, default=1.,
73 type=float, default=1.,
74 help="system size (in x)")
74 help="system size (in x)")
75 paa('-t', '--tstop',
75 paa('-t', '--tstop',
76 type=float, default=1.,
76 type=float, default=1.,
77 help="Time units to run")
77 help="Time units to run")
78 paa('--profile',
78 paa('--profile',
79 type=unicode, default=u'default',
79 type=unicode, default=u'default',
80 help="Specify the ipcluster profile for the client to connect to.")
80 help="Specify the ipcluster profile for the client to connect to.")
81 paa('--save',
81 paa('--save',
82 action='store_true',
82 action='store_true',
83 help="Add this flag to save the time/wave history during the run.")
83 help="Add this flag to save the time/wave history during the run.")
84 paa('--scalar',
84 paa('--scalar',
85 action='store_true',
85 action='store_true',
86 help="Also run with scalar interior implementation, to see vector speedup.")
86 help="Also run with scalar interior implementation, to see vector speedup.")
87
87
88 ns = parser.parse_args()
88 ns = parser.parse_args()
89 # set up arguments
89 # set up arguments
90 grid = ns.grid
90 grid = ns.grid
91 partition = ns.partition
91 partition = ns.partition
92 Lx = ns.Lx
92 Lx = ns.Lx
93 Ly = ns.Ly
93 Ly = ns.Ly
94 c = ns.c
94 c = ns.c
95 tstop = ns.tstop
95 tstop = ns.tstop
96 if ns.save:
96 if ns.save:
97 user_action = wave_saver
97 user_action = wave_saver
98 else:
98 else:
99 user_action = None
99 user_action = None
100
100
101 num_cells = 1.0*(grid[0]-1)*(grid[1]-1)
101 num_cells = 1.0*(grid[0]-1)*(grid[1]-1)
102 final_test = True
102 final_test = True
103
103
104 # create the Client
104 # create the Client
105 rc = Client(profile=ns.profile)
105 rc = Client(profile=ns.profile)
106 num_procs = len(rc.ids)
106 num_procs = len(rc.ids)
107
107
108 if partition is None:
108 if partition is None:
109 partition = [1,num_procs]
109 partition = [1,num_procs]
110
110
111 assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs)
111 assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs)
112
112
113 view = rc[:]
113 view = rc[:]
114 print "Running %s system on %s processes until %f"%(grid, partition, tstop)
114 print "Running %s system on %s processes until %f"%(grid, partition, tstop)
115
115
116 # functions defining initial/boundary/source conditions
116 # functions defining initial/boundary/source conditions
117 def I(x,y):
117 def I(x,y):
118 from numpy import exp
118 from numpy import exp
119 return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2))
119 return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2))
120 def f(x,y,t):
120 def f(x,y,t):
121 return 0.0
121 return 0.0
122 # from numpy import exp,sin
122 # from numpy import exp,sin
123 # return 10*exp(-(x - sin(100*t))**2)
123 # return 10*exp(-(x - sin(100*t))**2)
124 def bc(x,y,t):
124 def bc(x,y,t):
125 return 0.0
125 return 0.0
126
126
127 # initial imports, setup rank
127 # initial imports, setup rank
128 view.execute('\n'.join([
128 view.execute('\n'.join([
129 "from mpi4py import MPI",
129 "from mpi4py import MPI",
130 "import numpy",
130 "import numpy",
131 "mpi = MPI.COMM_WORLD",
131 "mpi = MPI.COMM_WORLD",
132 "my_id = MPI.COMM_WORLD.Get_rank()"]), block=True)
132 "my_id = MPI.COMM_WORLD.Get_rank()"]), block=True)
133
133
134 # initialize t_hist/u_hist for saving the state at each step (optional)
134 # initialize t_hist/u_hist for saving the state at each step (optional)
135 view['t_hist'] = []
135 view['t_hist'] = []
136 view['u_hist'] = []
136 view['u_hist'] = []
137
137
138 # set vector/scalar implementation details
138 # set vector/scalar implementation details
139 impl = {}
139 impl = {}
140 impl['ic'] = 'vectorized'
140 impl['ic'] = 'vectorized'
141 impl['inner'] = 'scalar'
141 impl['inner'] = 'scalar'
142 impl['bc'] = 'vectorized'
142 impl['bc'] = 'vectorized'
143
143
144 # execute some files so that the classes we need will be defined on the engines:
144 # execute some files so that the classes we need will be defined on the engines:
145 view.run('RectPartitioner.py')
145 view.run('RectPartitioner.py')
146 view.run('wavesolver.py')
146 view.run('wavesolver.py')
147
147
148 # setup remote partitioner
148 # setup remote partitioner
149 # note that Reference means that the argument passed to setup_partitioner will be the
149 # note that Reference means that the argument passed to setup_partitioner will be the
150 # object named 'my_id' in the engine's namespace
150 # object named 'my_id' in the engine's namespace
151 view.apply_sync(setup_partitioner, Reference('my_id'), num_procs, grid, partition)
151 view.apply_sync(setup_partitioner, Reference('my_id'), num_procs, grid, partition)
152 # wait for initial communication to complete
152 # wait for initial communication to complete
153 view.execute('mpi.barrier()')
153 view.execute('mpi.barrier()')
154 # setup remote solvers
154 # setup remote solvers
155 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
155 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
156
156
157 # lambda for calling solver.solve:
157 # lambda for calling solver.solve:
158 _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs)
158 _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs)
159
159
160 if ns.scalar:
160 if ns.scalar:
161 impl['inner'] = 'scalar'
161 impl['inner'] = 'scalar'
162 # run first with element-wise Python operations for each cell
162 # run first with element-wise Python operations for each cell
163 t0 = time.time()
163 t0 = time.time()
164 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
164 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
165 if final_test:
165 if final_test:
166 # this sum is performed element-wise as results finish
166 # this sum is performed element-wise as results finish
167 s = sum(ar)
167 s = sum(ar)
168 # the L2 norm (RMS) of the result:
168 # the L2 norm (RMS) of the result:
169 norm = sqrt(s/num_cells)
169 norm = sqrt(s/num_cells)
170 else:
170 else:
171 norm = -1
171 norm = -1
172 t1 = time.time()
172 t1 = time.time()
173 print 'scalar inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
173 print 'scalar inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
174
174
175 impl['inner'] = 'vectorized'
175 impl['inner'] = 'vectorized'
176 # setup new solvers
176 # setup new solvers
177 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
177 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
178 view.execute('mpi.barrier()')
178 view.execute('mpi.barrier()')
179
179
180 # run again with numpy vectorized inner-implementation
180 # run again with numpy vectorized inner-implementation
181 t0 = time.time()
181 t0 = time.time()
182 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test)#, user_action=wave_saver)
182 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test)#, user_action=wave_saver)
183 if final_test:
183 if final_test:
184 # this sum is performed element-wise as results finish
184 # this sum is performed element-wise as results finish
185 s = sum(ar)
185 s = sum(ar)
186 # the L2 norm (RMS) of the result:
186 # the L2 norm (RMS) of the result:
187 norm = sqrt(s/num_cells)
187 norm = sqrt(s/num_cells)
188 else:
188 else:
189 norm = -1
189 norm = -1
190 t1 = time.time()
190 t1 = time.time()
191 print 'vector inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
191 print 'vector inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
192
192
193 # if ns.save is True, then u_hist stores the history of u as a list
193 # if ns.save is True, then u_hist stores the history of u as a list
194 # If the partion scheme is Nx1, then u can be reconstructed via 'gather':
194 # If the partion scheme is Nx1, then u can be reconstructed via 'gather':
195 if ns.save and partition[-1] == 1:
195 if ns.save and partition[-1] == 1:
196 import pylab
196 import pylab
197 view.execute('u_last=u_hist[-1]')
197 view.execute('u_last=u_hist[-1]')
198 # map mpi IDs to IPython IDs, which may not match
198 # map mpi IDs to IPython IDs, which may not match
199 ranks = view['my_id']
199 ranks = view['my_id']
200 targets = range(len(ranks))
200 targets = range(len(ranks))
201 for idx in range(len(ranks)):
201 for idx in range(len(ranks)):
202 targets[idx] = ranks.index(idx)
202 targets[idx] = ranks.index(idx)
203 u_last = rc[targets].gather('u_last', block=True)
203 u_last = rc[targets].gather('u_last', block=True)
204 pylab.pcolor(u_last)
204 pylab.pcolor(u_last)
205 pylab.show()
205 pylab.show()
@@ -1,209 +1,209 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 A simple python program of solving a 2D wave equation in parallel.
3 A simple python program of solving a 2D wave equation in parallel.
4 Domain partitioning and inter-processor communication
4 Domain partitioning and inter-processor communication
5 are done by an object of class ZMQRectPartitioner2D
5 are done by an object of class ZMQRectPartitioner2D
6 (which is a subclass of RectPartitioner2D and uses 0MQ via pyzmq)
6 (which is a subclass of RectPartitioner2D and uses 0MQ via pyzmq)
7
7
8 An example of running the program is (8 processors, 4x2 partition,
8 An example of running the program is (8 processors, 4x2 partition,
9 200x200 grid cells)::
9 200x200 grid cells)::
10
10
11 $ ipclusterz start -n 8 # start 8 engines
11 $ ipclusterz start -n 8 # start 8 engines
12 $ ./parallelwave.py --grid 200 200 --partition 4 2
12 $ ./parallelwave.py --grid 200 200 --partition 4 2
13
13
14 See also parallelwave-mpi, which runs the same program, but uses MPI
14 See also parallelwave-mpi, which runs the same program, but uses MPI
15 (via mpi4py) for the inter-engine communication.
15 (via mpi4py) for the inter-engine communication.
16
16
17 Authors
17 Authors
18 -------
18 -------
19
19
20 * Xing Cai
20 * Xing Cai
21 * Min Ragan-Kelley
21 * Min Ragan-Kelley
22
22
23 """
23 """
24 #
24 #
25 import sys
25 import sys
26 import time
26 import time
27
27
28 from numpy import exp, zeros, newaxis, sqrt
28 from numpy import exp, zeros, newaxis, sqrt
29
29
30 from IPython.external import argparse
30 from IPython.external import argparse
31 from IPython.zmq.parallel.client import Client, Reference
31 from IPython.parallel.client import Client, Reference
32
32
33 def setup_partitioner(comm, addrs, index, num_procs, gnum_cells, parts):
33 def setup_partitioner(comm, addrs, index, num_procs, gnum_cells, parts):
34 """create a partitioner in the engine namespace"""
34 """create a partitioner in the engine namespace"""
35 global partitioner
35 global partitioner
36 p = ZMQRectPartitioner2D(comm, addrs, my_id=index, num_procs=num_procs)
36 p = ZMQRectPartitioner2D(comm, addrs, my_id=index, num_procs=num_procs)
37 p.redim(global_num_cells=gnum_cells, num_parts=parts)
37 p.redim(global_num_cells=gnum_cells, num_parts=parts)
38 p.prepare_communication()
38 p.prepare_communication()
39 # put the partitioner into the global namespace:
39 # put the partitioner into the global namespace:
40 partitioner=p
40 partitioner=p
41
41
42 def setup_solver(*args, **kwargs):
42 def setup_solver(*args, **kwargs):
43 """create a WaveSolver in the engine namespace."""
43 """create a WaveSolver in the engine namespace."""
44 global solver
44 global solver
45 solver = WaveSolver(*args, **kwargs)
45 solver = WaveSolver(*args, **kwargs)
46
46
47 def wave_saver(u, x, y, t):
47 def wave_saver(u, x, y, t):
48 """save the wave state for each timestep."""
48 """save the wave state for each timestep."""
49 global u_hist
49 global u_hist
50 global t_hist
50 global t_hist
51 t_hist.append(t)
51 t_hist.append(t)
52 u_hist.append(1.0*u)
52 u_hist.append(1.0*u)
53
53
54
54
55 # main program:
55 # main program:
56 if __name__ == '__main__':
56 if __name__ == '__main__':
57
57
58 parser = argparse.ArgumentParser()
58 parser = argparse.ArgumentParser()
59 paa = parser.add_argument
59 paa = parser.add_argument
60 paa('--grid', '-g',
60 paa('--grid', '-g',
61 type=int, nargs=2, default=[100,100], dest='grid',
61 type=int, nargs=2, default=[100,100], dest='grid',
62 help="Cells in the grid, e.g. --grid 100 200")
62 help="Cells in the grid, e.g. --grid 100 200")
63 paa('--partition', '-p',
63 paa('--partition', '-p',
64 type=int, nargs=2, default=None,
64 type=int, nargs=2, default=None,
65 help="Process partition grid, e.g. --partition 4 2 for 4x2")
65 help="Process partition grid, e.g. --partition 4 2 for 4x2")
66 paa('-c',
66 paa('-c',
67 type=float, default=1.,
67 type=float, default=1.,
68 help="Wave speed (I think)")
68 help="Wave speed (I think)")
69 paa('-Ly',
69 paa('-Ly',
70 type=float, default=1.,
70 type=float, default=1.,
71 help="system size (in y)")
71 help="system size (in y)")
72 paa('-Lx',
72 paa('-Lx',
73 type=float, default=1.,
73 type=float, default=1.,
74 help="system size (in x)")
74 help="system size (in x)")
75 paa('-t', '--tstop',
75 paa('-t', '--tstop',
76 type=float, default=1.,
76 type=float, default=1.,
77 help="Time units to run")
77 help="Time units to run")
78 paa('--profile',
78 paa('--profile',
79 type=unicode, default=u'default',
79 type=unicode, default=u'default',
80 help="Specify the ipcluster profile for the client to connect to.")
80 help="Specify the ipcluster profile for the client to connect to.")
81 paa('--save',
81 paa('--save',
82 action='store_true',
82 action='store_true',
83 help="Add this flag to save the time/wave history during the run.")
83 help="Add this flag to save the time/wave history during the run.")
84 paa('--scalar',
84 paa('--scalar',
85 action='store_true',
85 action='store_true',
86 help="Also run with scalar interior implementation, to see vector speedup.")
86 help="Also run with scalar interior implementation, to see vector speedup.")
87
87
88 ns = parser.parse_args()
88 ns = parser.parse_args()
89 # set up arguments
89 # set up arguments
90 grid = ns.grid
90 grid = ns.grid
91 partition = ns.partition
91 partition = ns.partition
92 Lx = ns.Lx
92 Lx = ns.Lx
93 Ly = ns.Ly
93 Ly = ns.Ly
94 c = ns.c
94 c = ns.c
95 tstop = ns.tstop
95 tstop = ns.tstop
96 if ns.save:
96 if ns.save:
97 user_action = wave_saver
97 user_action = wave_saver
98 else:
98 else:
99 user_action = None
99 user_action = None
100
100
101 num_cells = 1.0*(grid[0]-1)*(grid[1]-1)
101 num_cells = 1.0*(grid[0]-1)*(grid[1]-1)
102 final_test = True
102 final_test = True
103
103
104 # create the Client
104 # create the Client
105 rc = Client(profile=ns.profile)
105 rc = Client(profile=ns.profile)
106 num_procs = len(rc.ids)
106 num_procs = len(rc.ids)
107
107
108 if partition is None:
108 if partition is None:
109 partition = [num_procs,1]
109 partition = [num_procs,1]
110 else:
110 else:
111 num_procs = min(num_procs, partition[0]*partition[1])
111 num_procs = min(num_procs, partition[0]*partition[1])
112
112
113 assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs)
113 assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs)
114
114
115 # construct the View:
115 # construct the View:
116 view = rc[:num_procs]
116 view = rc[:num_procs]
117 print "Running %s system on %s processes until %f"%(grid, partition, tstop)
117 print "Running %s system on %s processes until %f"%(grid, partition, tstop)
118
118
119 # functions defining initial/boundary/source conditions
119 # functions defining initial/boundary/source conditions
120 def I(x,y):
120 def I(x,y):
121 from numpy import exp
121 from numpy import exp
122 return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2))
122 return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2))
123 def f(x,y,t):
123 def f(x,y,t):
124 return 0.0
124 return 0.0
125 # from numpy import exp,sin
125 # from numpy import exp,sin
126 # return 10*exp(-(x - sin(100*t))**2)
126 # return 10*exp(-(x - sin(100*t))**2)
127 def bc(x,y,t):
127 def bc(x,y,t):
128 return 0.0
128 return 0.0
129
129
130 # initialize t_hist/u_hist for saving the state at each step (optional)
130 # initialize t_hist/u_hist for saving the state at each step (optional)
131 view['t_hist'] = []
131 view['t_hist'] = []
132 view['u_hist'] = []
132 view['u_hist'] = []
133
133
134 # set vector/scalar implementation details
134 # set vector/scalar implementation details
135 impl = {}
135 impl = {}
136 impl['ic'] = 'vectorized'
136 impl['ic'] = 'vectorized'
137 impl['inner'] = 'scalar'
137 impl['inner'] = 'scalar'
138 impl['bc'] = 'vectorized'
138 impl['bc'] = 'vectorized'
139
139
140 # execute some files so that the classes we need will be defined on the engines:
140 # execute some files so that the classes we need will be defined on the engines:
141 view.execute('import numpy')
141 view.execute('import numpy')
142 view.run('communicator.py')
142 view.run('communicator.py')
143 view.run('RectPartitioner.py')
143 view.run('RectPartitioner.py')
144 view.run('wavesolver.py')
144 view.run('wavesolver.py')
145
145
146 # scatter engine IDs
146 # scatter engine IDs
147 view.scatter('my_id', range(num_procs), flatten=True)
147 view.scatter('my_id', range(num_procs), flatten=True)
148
148
149 # create the engine connectors
149 # create the engine connectors
150 view.execute('com = EngineCommunicator()')
150 view.execute('com = EngineCommunicator()')
151
151
152 # gather the connection information into a single dict
152 # gather the connection information into a single dict
153 ar = view.apply_async(lambda : com.info)
153 ar = view.apply_async(lambda : com.info)
154 peers = ar.get_dict()
154 peers = ar.get_dict()
155 # print peers
155 # print peers
156 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
156 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
157
157
158 # setup remote partitioner
158 # setup remote partitioner
159 # note that Reference means that the argument passed to setup_partitioner will be the
159 # note that Reference means that the argument passed to setup_partitioner will be the
160 # object named 'com' in the engine's namespace
160 # object named 'com' in the engine's namespace
161 view.apply_sync(setup_partitioner, Reference('com'), peers, Reference('my_id'), num_procs, grid, partition)
161 view.apply_sync(setup_partitioner, Reference('com'), peers, Reference('my_id'), num_procs, grid, partition)
162 time.sleep(1)
162 time.sleep(1)
163 # convenience lambda to call solver.solve:
163 # convenience lambda to call solver.solve:
164 _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs)
164 _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs)
165
165
166 if ns.scalar:
166 if ns.scalar:
167 impl['inner'] = 'scalar'
167 impl['inner'] = 'scalar'
168 # setup remote solvers
168 # setup remote solvers
169 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly, partitioner=Reference('partitioner'), dt=0,implementation=impl)
169 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly, partitioner=Reference('partitioner'), dt=0,implementation=impl)
170
170
171 # run first with element-wise Python operations for each cell
171 # run first with element-wise Python operations for each cell
172 t0 = time.time()
172 t0 = time.time()
173 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
173 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
174 if final_test:
174 if final_test:
175 # this sum is performed element-wise as results finish
175 # this sum is performed element-wise as results finish
176 s = sum(ar)
176 s = sum(ar)
177 # the L2 norm (RMS) of the result:
177 # the L2 norm (RMS) of the result:
178 norm = sqrt(s/num_cells)
178 norm = sqrt(s/num_cells)
179 else:
179 else:
180 norm = -1
180 norm = -1
181 t1 = time.time()
181 t1 = time.time()
182 print 'scalar inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
182 print 'scalar inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
183
183
184 # run again with faster numpy-vectorized inner implementation:
184 # run again with faster numpy-vectorized inner implementation:
185 impl['inner'] = 'vectorized'
185 impl['inner'] = 'vectorized'
186 # setup remote solvers
186 # setup remote solvers
187 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
187 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
188
188
189 t0 = time.time()
189 t0 = time.time()
190
190
191 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test)#, user_action=wave_saver)
191 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test)#, user_action=wave_saver)
192 if final_test:
192 if final_test:
193 # this sum is performed element-wise as results finish
193 # this sum is performed element-wise as results finish
194 s = sum(ar)
194 s = sum(ar)
195 # the L2 norm (RMS) of the result:
195 # the L2 norm (RMS) of the result:
196 norm = sqrt(s/num_cells)
196 norm = sqrt(s/num_cells)
197 else:
197 else:
198 norm = -1
198 norm = -1
199 t1 = time.time()
199 t1 = time.time()
200 print 'vector inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
200 print 'vector inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)
201
201
202 # if ns.save is True, then u_hist stores the history of u as a list
202 # if ns.save is True, then u_hist stores the history of u as a list
203 # If the partion scheme is Nx1, then u can be reconstructed via 'gather':
203 # If the partion scheme is Nx1, then u can be reconstructed via 'gather':
204 if ns.save and partition[-1] == 1:
204 if ns.save and partition[-1] == 1:
205 import pylab
205 import pylab
206 view.execute('u_last=u_hist[-1]')
206 view.execute('u_last=u_hist[-1]')
207 u_last = view.gather('u_last', block=True)
207 u_last = view.gather('u_last', block=True)
208 pylab.pcolor(u_last)
208 pylab.pcolor(u_last)
209 pylab.show() No newline at end of file
209 pylab.show()
@@ -1,3 +1,3 b''
1 from IPython.zmq.parallel.client import Client
1 from IPython.parallel.client import Client
2
2
3 client = Client()
3 client = Client()
@@ -1,25 +1,25 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Python wrapper around a submitted workflow job.
2 """Python wrapper around a submitted workflow job.
3
3
4 In reality this would be a more sophisticated script, here we only illustrate
4 In reality this would be a more sophisticated script, here we only illustrate
5 the basic idea by considering that a submitted 'job' is a Python string to be
5 the basic idea by considering that a submitted 'job' is a Python string to be
6 executed.
6 executed.
7 """
7 """
8
8
9 import sys
9 import sys
10
10
11 argv = sys.argv
11 argv = sys.argv
12
12
13 from IPython.zmq.parallel.engine import EngineFactory
13 from IPython.parallel.engine import EngineFactory
14 from IPython.zmq.parallel.ipengineapp import launch_new_instance
14 from IPython.parallel.ipengineapp import launch_new_instance
15
15
16 ns = {}
16 ns = {}
17
17
18 # job
18 # job
19 exec sys.argv[1] in ns
19 exec sys.argv[1] in ns
20
20
21 # this should really use Config:
21 # this should really use Config:
22 EngineFactory.user_ns = ns
22 EngineFactory.user_ns = ns
23
23
24 # start engine with job namespace
24 # start engine with job namespace
25 launch_new_instance()
25 launch_new_instance()
@@ -1,44 +1,44 b''
1 """Mock workflow manager.
1 """Mock workflow manager.
2
2
3 This is a mock work manager whose submitted 'jobs' simply consist of executing
3 This is a mock work manager whose submitted 'jobs' simply consist of executing
4 a python string. What we want is to see the implementation of the ipython
4 a python string. What we want is to see the implementation of the ipython
5 controller part.
5 controller part.
6 """
6 """
7
7
8 from __future__ import print_function
8 from __future__ import print_function
9
9
10 import atexit
10 import atexit
11 import sys
11 import sys
12
12
13 from subprocess import Popen
13 from subprocess import Popen
14
14
15 def cleanup(controller, engines):
15 def cleanup(controller, engines):
16 """Cleanup routine to shut down all subprocesses we opened."""
16 """Cleanup routine to shut down all subprocesses we opened."""
17 import signal, time
17 import signal, time
18
18
19 print('Starting cleanup')
19 print('Starting cleanup')
20 print('Stopping engines...')
20 print('Stopping engines...')
21 for e in engines:
21 for e in engines:
22 e.send_signal(signal.SIGINT)
22 e.send_signal(signal.SIGINT)
23 print('Stopping controller...')
23 print('Stopping controller...')
24 # so it can shut down its queues
24 # so it can shut down its queues
25 controller.send_signal(signal.SIGINT)
25 controller.send_signal(signal.SIGINT)
26 time.sleep(0.1)
26 time.sleep(0.1)
27 print('Killing controller...')
27 print('Killing controller...')
28 controller.kill()
28 controller.kill()
29 print('Cleanup done')
29 print('Cleanup done')
30
30
31
31
32 if __name__ == '__main__':
32 if __name__ == '__main__':
33
33
34 # Start controller in separate process
34 # Start controller in separate process
35 cont = Popen(['python', '-m', 'IPython.zmq.parallel.ipcontrollerapp'])
35 cont = Popen(['python', '-m', 'IPython.parallel.ipcontrollerapp'])
36 print('Started controller')
36 print('Started controller')
37
37
38 # "Submit jobs"
38 # "Submit jobs"
39 eng = []
39 eng = []
40 for i in range(4):
40 for i in range(4):
41 eng.append(Popen(['python', 'job_wrapper.py','x=%s' % i]))
41 eng.append(Popen(['python', 'job_wrapper.py','x=%s' % i]))
42
42
43 # Ensure that all subpro
43 # Ensure that all subpro
44 atexit.register(lambda : cleanup(cont, eng))
44 atexit.register(lambda : cleanup(cont, eng))
@@ -1,173 +1,173 b''
1 .. _dag_dependencies:
1 .. _dag_dependencies:
2
2
3 ================
3 ================
4 DAG Dependencies
4 DAG Dependencies
5 ================
5 ================
6
6
7 Often, parallel workflow is described in terms of a `Directed Acyclic Graph
7 Often, parallel workflow is described in terms of a `Directed Acyclic Graph
8 <http://en.wikipedia.org/wiki/Directed_acyclic_graph>`_ or DAG. A popular library
8 <http://en.wikipedia.org/wiki/Directed_acyclic_graph>`_ or DAG. A popular library
9 for working with Graphs is NetworkX_. Here, we will walk through a demo mapping
9 for working with Graphs is NetworkX_. Here, we will walk through a demo mapping
10 a nx DAG to task dependencies.
10 a nx DAG to task dependencies.
11
11
12 The full script that runs this demo can be found in
12 The full script that runs this demo can be found in
13 :file:`docs/examples/newparallel/dagdeps.py`.
13 :file:`docs/examples/newparallel/dagdeps.py`.
14
14
15 Why are DAGs good for task dependencies?
15 Why are DAGs good for task dependencies?
16 ----------------------------------------
16 ----------------------------------------
17
17
18 The 'G' in DAG is 'Graph'. A Graph is a collection of **nodes** and **edges** that connect
18 The 'G' in DAG is 'Graph'. A Graph is a collection of **nodes** and **edges** that connect
19 the nodes. For our purposes, each node would be a task, and each edge would be a
19 the nodes. For our purposes, each node would be a task, and each edge would be a
20 dependency. The 'D' in DAG stands for 'Directed'. This means that each edge has a
20 dependency. The 'D' in DAG stands for 'Directed'. This means that each edge has a
21 direction associated with it. So we can interpret the edge (a,b) as meaning that b depends
21 direction associated with it. So we can interpret the edge (a,b) as meaning that b depends
22 on a, whereas the edge (b,a) would mean a depends on b. The 'A' is 'Acyclic', meaning that
22 on a, whereas the edge (b,a) would mean a depends on b. The 'A' is 'Acyclic', meaning that
23 there must not be any closed loops in the graph. This is important for dependencies,
23 there must not be any closed loops in the graph. This is important for dependencies,
24 because if a loop were closed, then a task could ultimately depend on itself, and never be
24 because if a loop were closed, then a task could ultimately depend on itself, and never be
25 able to run. If your workflow can be described as a DAG, then it is impossible for your
25 able to run. If your workflow can be described as a DAG, then it is impossible for your
26 dependencies to cause a deadlock.
26 dependencies to cause a deadlock.
27
27
28 A Sample DAG
28 A Sample DAG
29 ------------
29 ------------
30
30
31 Here, we have a very simple 5-node DAG:
31 Here, we have a very simple 5-node DAG:
32
32
33 .. figure:: simpledag.*
33 .. figure:: simpledag.*
34
34
35 With NetworkX, an arrow is just a fattened bit on the edge. Here, we can see that task 0
35 With NetworkX, an arrow is just a fattened bit on the edge. Here, we can see that task 0
36 depends on nothing, and can run immediately. 1 and 2 depend on 0; 3 depends on
36 depends on nothing, and can run immediately. 1 and 2 depend on 0; 3 depends on
37 1 and 2; and 4 depends only on 1.
37 1 and 2; and 4 depends only on 1.
38
38
39 A possible sequence of events for this workflow:
39 A possible sequence of events for this workflow:
40
40
41 0. Task 0 can run right away
41 0. Task 0 can run right away
42 1. 0 finishes, so 1,2 can start
42 1. 0 finishes, so 1,2 can start
43 2. 1 finishes, 3 is still waiting on 2, but 4 can start right away
43 2. 1 finishes, 3 is still waiting on 2, but 4 can start right away
44 3. 2 finishes, and 3 can finally start
44 3. 2 finishes, and 3 can finally start
45
45
46
46
47 Further, taking failures into account, assuming all dependencies are run with the default
47 Further, taking failures into account, assuming all dependencies are run with the default
48 `success=True,failure=False`, the following cases would occur for each node's failure:
48 `success=True,failure=False`, the following cases would occur for each node's failure:
49
49
50 0. fails: all other tasks fail as Impossible
50 0. fails: all other tasks fail as Impossible
51 1. 2 can still succeed, but 3,4 are unreachable
51 1. 2 can still succeed, but 3,4 are unreachable
52 2. 3 becomes unreachable, but 4 is unaffected
52 2. 3 becomes unreachable, but 4 is unaffected
53 3. and 4. are terminal, and can have no effect on other nodes
53 3. and 4. are terminal, and can have no effect on other nodes
54
54
55 The code to generate the simple DAG:
55 The code to generate the simple DAG:
56
56
57 .. sourcecode:: python
57 .. sourcecode:: python
58
58
59 import networkx as nx
59 import networkx as nx
60
60
61 G = nx.DiGraph()
61 G = nx.DiGraph()
62
62
63 # add 5 nodes, labeled 0-4:
63 # add 5 nodes, labeled 0-4:
64 map(G.add_node, range(5))
64 map(G.add_node, range(5))
65 # 1,2 depend on 0:
65 # 1,2 depend on 0:
66 G.add_edge(0,1)
66 G.add_edge(0,1)
67 G.add_edge(0,2)
67 G.add_edge(0,2)
68 # 3 depends on 1,2
68 # 3 depends on 1,2
69 G.add_edge(1,3)
69 G.add_edge(1,3)
70 G.add_edge(2,3)
70 G.add_edge(2,3)
71 # 4 depends on 1
71 # 4 depends on 1
72 G.add_edge(1,4)
72 G.add_edge(1,4)
73
73
74 # now draw the graph:
74 # now draw the graph:
75 pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1),
75 pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1),
76 3 : (0,2), 4 : (2,2)}
76 3 : (0,2), 4 : (2,2)}
77 nx.draw(G, pos, edge_color='r')
77 nx.draw(G, pos, edge_color='r')
78
78
79
79
80 For demonstration purposes, we have a function that generates a random DAG with a given
80 For demonstration purposes, we have a function that generates a random DAG with a given
81 number of nodes and edges.
81 number of nodes and edges.
82
82
83 .. literalinclude:: ../../examples/newparallel/dagdeps.py
83 .. literalinclude:: ../../examples/newparallel/dagdeps.py
84 :language: python
84 :language: python
85 :lines: 20-36
85 :lines: 20-36
86
86
87 So first, we start with a graph of 32 nodes, with 128 edges:
87 So first, we start with a graph of 32 nodes, with 128 edges:
88
88
89 .. sourcecode:: ipython
89 .. sourcecode:: ipython
90
90
91 In [2]: G = random_dag(32,128)
91 In [2]: G = random_dag(32,128)
92
92
93 Now, we need to build our dict of jobs corresponding to the nodes on the graph:
93 Now, we need to build our dict of jobs corresponding to the nodes on the graph:
94
94
95 .. sourcecode:: ipython
95 .. sourcecode:: ipython
96
96
97 In [3]: jobs = {}
97 In [3]: jobs = {}
98
98
99 # in reality, each job would presumably be different
99 # in reality, each job would presumably be different
100 # randomwait is just a function that sleeps for a random interval
100 # randomwait is just a function that sleeps for a random interval
101 In [4]: for node in G:
101 In [4]: for node in G:
102 ...: jobs[node] = randomwait
102 ...: jobs[node] = randomwait
103
103
104 Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs,
104 Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs,
105 and linking up the dependencies. Since we don't know a job's msg_id until it is submitted,
105 and linking up the dependencies. Since we don't know a job's msg_id until it is submitted,
106 which is necessary for building dependencies, it is critical that we don't submit any jobs
106 which is necessary for building dependencies, it is critical that we don't submit any jobs
107 before other jobs it may depend on. Fortunately, NetworkX provides a
107 before other jobs it may depend on. Fortunately, NetworkX provides a
108 :meth:`topological_sort` method which ensures exactly this. It presents an iterable, that
108 :meth:`topological_sort` method which ensures exactly this. It presents an iterable, that
109 guarantees that when you arrive at a node, you have already visited all the nodes it
109 guarantees that when you arrive at a node, you have already visited all the nodes it
110 on which it depends:
110 on which it depends:
111
111
112 .. sourcecode:: ipython
112 .. sourcecode:: ipython
113
113
114 In [5]: rc = client.Client()
114 In [5]: rc = Client()
115 In [5]: view = rc.load_balanced_view()
115 In [5]: view = rc.load_balanced_view()
116
116
117 In [6]: results = {}
117 In [6]: results = {}
118
118
119 In [7]: for node in G.topological_sort():
119 In [7]: for node in G.topological_sort():
120 ...: # get list of AsyncResult objects from nodes
120 ...: # get list of AsyncResult objects from nodes
121 ...: # leading into this one as dependencies
121 ...: # leading into this one as dependencies
122 ...: deps = [ results[n] for n in G.predecessors(node) ]
122 ...: deps = [ results[n] for n in G.predecessors(node) ]
123 ...: # submit and store AsyncResult object
123 ...: # submit and store AsyncResult object
124 ...: results[node] = view.apply_with_flags(jobs[node], after=deps, block=False)
124 ...: results[node] = view.apply_with_flags(jobs[node], after=deps, block=False)
125
125
126 Now that we have submitted all the jobs, we can wait for the results:
126 Now that we have submitted all the jobs, we can wait for the results:
127
127
128 .. sourcecode:: ipython
128 .. sourcecode:: ipython
129
129
130 In [8]: view.wait(results.values())
130 In [8]: view.wait(results.values())
131
131
132 Now, at least we know that all the jobs ran and did not fail (``r.get()`` would have
132 Now, at least we know that all the jobs ran and did not fail (``r.get()`` would have
133 raised an error if a task failed). But we don't know that the ordering was properly
133 raised an error if a task failed). But we don't know that the ordering was properly
134 respected. For this, we can use the :attr:`metadata` attribute of each AsyncResult.
134 respected. For this, we can use the :attr:`metadata` attribute of each AsyncResult.
135
135
136 These objects store a variety of metadata about each task, including various timestamps.
136 These objects store a variety of metadata about each task, including various timestamps.
137 We can validate that the dependencies were respected by checking that each task was
137 We can validate that the dependencies were respected by checking that each task was
138 started after all of its predecessors were completed:
138 started after all of its predecessors were completed:
139
139
140 .. literalinclude:: ../../examples/newparallel/dagdeps.py
140 .. literalinclude:: ../../examples/newparallel/dagdeps.py
141 :language: python
141 :language: python
142 :lines: 64-70
142 :lines: 64-70
143
143
144 We can also validate the graph visually. By drawing the graph with each node's x-position
144 We can also validate the graph visually. By drawing the graph with each node's x-position
145 as its start time, all arrows must be pointing to the right if dependencies were respected.
145 as its start time, all arrows must be pointing to the right if dependencies were respected.
146 For spreading, the y-position will be the runtime of the task, so long tasks
146 For spreading, the y-position will be the runtime of the task, so long tasks
147 will be at the top, and quick, small tasks will be at the bottom.
147 will be at the top, and quick, small tasks will be at the bottom.
148
148
149 .. sourcecode:: ipython
149 .. sourcecode:: ipython
150
150
151 In [10]: from matplotlib.dates import date2num
151 In [10]: from matplotlib.dates import date2num
152
152
153 In [11]: from matplotlib.cm import gist_rainbow
153 In [11]: from matplotlib.cm import gist_rainbow
154
154
155 In [12]: pos = {}; colors = {}
155 In [12]: pos = {}; colors = {}
156
156
157 In [12]: for node in G:
157 In [12]: for node in G:
158 ...: md = results[node].metadata
158 ...: md = results[node].metadata
159 ...: start = date2num(md.started)
159 ...: start = date2num(md.started)
160 ...: runtime = date2num(md.completed) - start
160 ...: runtime = date2num(md.completed) - start
161 ...: pos[node] = (start, runtime)
161 ...: pos[node] = (start, runtime)
162 ...: colors[node] = md.engine_id
162 ...: colors[node] = md.engine_id
163
163
164 In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(),
164 In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(),
165 ...: cmap=gist_rainbow)
165 ...: cmap=gist_rainbow)
166
166
167 .. figure:: dagdeps.*
167 .. figure:: dagdeps.*
168
168
169 Time started on x, runtime on y, and color-coded by engine-id (in this case there
169 Time started on x, runtime on y, and color-coded by engine-id (in this case there
170 were four engines). Edges denote dependencies.
170 were four engines). Edges denote dependencies.
171
171
172
172
173 .. _NetworkX: http://networkx.lanl.gov/
173 .. _NetworkX: http://networkx.lanl.gov/
@@ -1,284 +1,284 b''
1 =================
1 =================
2 Parallel examples
2 Parallel examples
3 =================
3 =================
4
4
5 .. note::
5 .. note::
6
6
7 Performance numbers from ``IPython.kernel``, not newparallel.
7 Performance numbers from ``IPython.kernel``, not newparallel.
8
8
9 In this section we describe two more involved examples of using an IPython
9 In this section we describe two more involved examples of using an IPython
10 cluster to perform a parallel computation. In these examples, we will be using
10 cluster to perform a parallel computation. In these examples, we will be using
11 IPython's "pylab" mode, which enables interactive plotting using the
11 IPython's "pylab" mode, which enables interactive plotting using the
12 Matplotlib package. IPython can be started in this mode by typing::
12 Matplotlib package. IPython can be started in this mode by typing::
13
13
14 ipython --pylab
14 ipython --pylab
15
15
16 at the system command line.
16 at the system command line.
17
17
18 150 million digits of pi
18 150 million digits of pi
19 ========================
19 ========================
20
20
21 In this example we would like to study the distribution of digits in the
21 In this example we would like to study the distribution of digits in the
22 number pi (in base 10). While it is not known if pi is a normal number (a
22 number pi (in base 10). While it is not known if pi is a normal number (a
23 number is normal in base 10 if 0-9 occur with equal likelihood) numerical
23 number is normal in base 10 if 0-9 occur with equal likelihood) numerical
24 investigations suggest that it is. We will begin with a serial calculation on
24 investigations suggest that it is. We will begin with a serial calculation on
25 10,000 digits of pi and then perform a parallel calculation involving 150
25 10,000 digits of pi and then perform a parallel calculation involving 150
26 million digits.
26 million digits.
27
27
28 In both the serial and parallel calculation we will be using functions defined
28 In both the serial and parallel calculation we will be using functions defined
29 in the :file:`pidigits.py` file, which is available in the
29 in the :file:`pidigits.py` file, which is available in the
30 :file:`docs/examples/newparallel` directory of the IPython source distribution.
30 :file:`docs/examples/newparallel` directory of the IPython source distribution.
31 These functions provide basic facilities for working with the digits of pi and
31 These functions provide basic facilities for working with the digits of pi and
32 can be loaded into IPython by putting :file:`pidigits.py` in your current
32 can be loaded into IPython by putting :file:`pidigits.py` in your current
33 working directory and then doing:
33 working directory and then doing:
34
34
35 .. sourcecode:: ipython
35 .. sourcecode:: ipython
36
36
37 In [1]: run pidigits.py
37 In [1]: run pidigits.py
38
38
39 Serial calculation
39 Serial calculation
40 ------------------
40 ------------------
41
41
42 For the serial calculation, we will use `SymPy <http://www.sympy.org>`_ to
42 For the serial calculation, we will use `SymPy <http://www.sympy.org>`_ to
43 calculate 10,000 digits of pi and then look at the frequencies of the digits
43 calculate 10,000 digits of pi and then look at the frequencies of the digits
44 0-9. Out of 10,000 digits, we expect each digit to occur 1,000 times. While
44 0-9. Out of 10,000 digits, we expect each digit to occur 1,000 times. While
45 SymPy is capable of calculating many more digits of pi, our purpose here is to
45 SymPy is capable of calculating many more digits of pi, our purpose here is to
46 set the stage for the much larger parallel calculation.
46 set the stage for the much larger parallel calculation.
47
47
48 In this example, we use two functions from :file:`pidigits.py`:
48 In this example, we use two functions from :file:`pidigits.py`:
49 :func:`one_digit_freqs` (which calculates how many times each digit occurs)
49 :func:`one_digit_freqs` (which calculates how many times each digit occurs)
50 and :func:`plot_one_digit_freqs` (which uses Matplotlib to plot the result).
50 and :func:`plot_one_digit_freqs` (which uses Matplotlib to plot the result).
51 Here is an interactive IPython session that uses these functions with
51 Here is an interactive IPython session that uses these functions with
52 SymPy:
52 SymPy:
53
53
54 .. sourcecode:: ipython
54 .. sourcecode:: ipython
55
55
56 In [7]: import sympy
56 In [7]: import sympy
57
57
58 In [8]: pi = sympy.pi.evalf(40)
58 In [8]: pi = sympy.pi.evalf(40)
59
59
60 In [9]: pi
60 In [9]: pi
61 Out[9]: 3.141592653589793238462643383279502884197
61 Out[9]: 3.141592653589793238462643383279502884197
62
62
63 In [10]: pi = sympy.pi.evalf(10000)
63 In [10]: pi = sympy.pi.evalf(10000)
64
64
65 In [11]: digits = (d for d in str(pi)[2:]) # create a sequence of digits
65 In [11]: digits = (d for d in str(pi)[2:]) # create a sequence of digits
66
66
67 In [12]: run pidigits.py # load one_digit_freqs/plot_one_digit_freqs
67 In [12]: run pidigits.py # load one_digit_freqs/plot_one_digit_freqs
68
68
69 In [13]: freqs = one_digit_freqs(digits)
69 In [13]: freqs = one_digit_freqs(digits)
70
70
71 In [14]: plot_one_digit_freqs(freqs)
71 In [14]: plot_one_digit_freqs(freqs)
72 Out[14]: [<matplotlib.lines.Line2D object at 0x18a55290>]
72 Out[14]: [<matplotlib.lines.Line2D object at 0x18a55290>]
73
73
74 The resulting plot of the single digit counts shows that each digit occurs
74 The resulting plot of the single digit counts shows that each digit occurs
75 approximately 1,000 times, but that with only 10,000 digits the
75 approximately 1,000 times, but that with only 10,000 digits the
76 statistical fluctuations are still rather large:
76 statistical fluctuations are still rather large:
77
77
78 .. image:: ../parallel/single_digits.*
78 .. image:: ../parallel/single_digits.*
79
79
80 It is clear that to reduce the relative fluctuations in the counts, we need
80 It is clear that to reduce the relative fluctuations in the counts, we need
81 to look at many more digits of pi. That brings us to the parallel calculation.
81 to look at many more digits of pi. That brings us to the parallel calculation.
82
82
83 Parallel calculation
83 Parallel calculation
84 --------------------
84 --------------------
85
85
86 Calculating many digits of pi is a challenging computational problem in itself.
86 Calculating many digits of pi is a challenging computational problem in itself.
87 Because we want to focus on the distribution of digits in this example, we
87 Because we want to focus on the distribution of digits in this example, we
88 will use pre-computed digit of pi from the website of Professor Yasumasa
88 will use pre-computed digit of pi from the website of Professor Yasumasa
89 Kanada at the University of Tokyo (http://www.super-computing.org). These
89 Kanada at the University of Tokyo (http://www.super-computing.org). These
90 digits come in a set of text files (ftp://pi.super-computing.org/.2/pi200m/)
90 digits come in a set of text files (ftp://pi.super-computing.org/.2/pi200m/)
91 that each have 10 million digits of pi.
91 that each have 10 million digits of pi.
92
92
93 For the parallel calculation, we have copied these files to the local hard
93 For the parallel calculation, we have copied these files to the local hard
94 drives of the compute nodes. A total of 15 of these files will be used, for a
94 drives of the compute nodes. A total of 15 of these files will be used, for a
95 total of 150 million digits of pi. To make things a little more interesting we
95 total of 150 million digits of pi. To make things a little more interesting we
96 will calculate the frequencies of all 2 digits sequences (00-99) and then plot
96 will calculate the frequencies of all 2 digits sequences (00-99) and then plot
97 the result using a 2D matrix in Matplotlib.
97 the result using a 2D matrix in Matplotlib.
98
98
99 The overall idea of the calculation is simple: each IPython engine will
99 The overall idea of the calculation is simple: each IPython engine will
100 compute the two digit counts for the digits in a single file. Then in a final
100 compute the two digit counts for the digits in a single file. Then in a final
101 step the counts from each engine will be added up. To perform this
101 step the counts from each engine will be added up. To perform this
102 calculation, we will need two top-level functions from :file:`pidigits.py`:
102 calculation, we will need two top-level functions from :file:`pidigits.py`:
103
103
104 .. literalinclude:: ../../examples/newparallel/pidigits.py
104 .. literalinclude:: ../../examples/newparallel/pidigits.py
105 :language: python
105 :language: python
106 :lines: 41-56
106 :lines: 41-56
107
107
108 We will also use the :func:`plot_two_digit_freqs` function to plot the
108 We will also use the :func:`plot_two_digit_freqs` function to plot the
109 results. The code to run this calculation in parallel is contained in
109 results. The code to run this calculation in parallel is contained in
110 :file:`docs/examples/newparallel/parallelpi.py`. This code can be run in parallel
110 :file:`docs/examples/newparallel/parallelpi.py`. This code can be run in parallel
111 using IPython by following these steps:
111 using IPython by following these steps:
112
112
113 1. Use :command:`ipclusterz` to start 15 engines. We used an 8 core (2 quad
113 1. Use :command:`ipclusterz` to start 15 engines. We used an 8 core (2 quad
114 core CPUs) cluster with hyperthreading enabled which makes the 8 cores
114 core CPUs) cluster with hyperthreading enabled which makes the 8 cores
115 looks like 16 (1 controller + 15 engines) in the OS. However, the maximum
115 looks like 16 (1 controller + 15 engines) in the OS. However, the maximum
116 speedup we can observe is still only 8x.
116 speedup we can observe is still only 8x.
117 2. With the file :file:`parallelpi.py` in your current working directory, open
117 2. With the file :file:`parallelpi.py` in your current working directory, open
118 up IPython in pylab mode and type ``run parallelpi.py``. This will download
118 up IPython in pylab mode and type ``run parallelpi.py``. This will download
119 the pi files via ftp the first time you run it, if they are not
119 the pi files via ftp the first time you run it, if they are not
120 present in the Engines' working directory.
120 present in the Engines' working directory.
121
121
122 When run on our 8 core cluster, we observe a speedup of 7.7x. This is slightly
122 When run on our 8 core cluster, we observe a speedup of 7.7x. This is slightly
123 less than linear scaling (8x) because the controller is also running on one of
123 less than linear scaling (8x) because the controller is also running on one of
124 the cores.
124 the cores.
125
125
126 To emphasize the interactive nature of IPython, we now show how the
126 To emphasize the interactive nature of IPython, we now show how the
127 calculation can also be run by simply typing the commands from
127 calculation can also be run by simply typing the commands from
128 :file:`parallelpi.py` interactively into IPython:
128 :file:`parallelpi.py` interactively into IPython:
129
129
130 .. sourcecode:: ipython
130 .. sourcecode:: ipython
131
131
132 In [1]: from IPython.zmq.parallel import client
132 In [1]: from IPython.parallel import Client
133
133
134 # The Client allows us to use the engines interactively.
134 # The Client allows us to use the engines interactively.
135 # We simply pass Client the name of the cluster profile we
135 # We simply pass Client the name of the cluster profile we
136 # are using.
136 # are using.
137 In [2]: c = client.Client(profile='mycluster')
137 In [2]: c = Client(profile='mycluster')
138 In [3]: view = c.load_balanced_view()
138 In [3]: view = c.load_balanced_view()
139
139
140 In [3]: c.ids
140 In [3]: c.ids
141 Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
141 Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
142
142
143 In [4]: run pidigits.py
143 In [4]: run pidigits.py
144
144
145 In [5]: filestring = 'pi200m.ascii.%(i)02dof20'
145 In [5]: filestring = 'pi200m.ascii.%(i)02dof20'
146
146
147 # Create the list of files to process.
147 # Create the list of files to process.
148 In [6]: files = [filestring % {'i':i} for i in range(1,16)]
148 In [6]: files = [filestring % {'i':i} for i in range(1,16)]
149
149
150 In [7]: files
150 In [7]: files
151 Out[7]:
151 Out[7]:
152 ['pi200m.ascii.01of20',
152 ['pi200m.ascii.01of20',
153 'pi200m.ascii.02of20',
153 'pi200m.ascii.02of20',
154 'pi200m.ascii.03of20',
154 'pi200m.ascii.03of20',
155 'pi200m.ascii.04of20',
155 'pi200m.ascii.04of20',
156 'pi200m.ascii.05of20',
156 'pi200m.ascii.05of20',
157 'pi200m.ascii.06of20',
157 'pi200m.ascii.06of20',
158 'pi200m.ascii.07of20',
158 'pi200m.ascii.07of20',
159 'pi200m.ascii.08of20',
159 'pi200m.ascii.08of20',
160 'pi200m.ascii.09of20',
160 'pi200m.ascii.09of20',
161 'pi200m.ascii.10of20',
161 'pi200m.ascii.10of20',
162 'pi200m.ascii.11of20',
162 'pi200m.ascii.11of20',
163 'pi200m.ascii.12of20',
163 'pi200m.ascii.12of20',
164 'pi200m.ascii.13of20',
164 'pi200m.ascii.13of20',
165 'pi200m.ascii.14of20',
165 'pi200m.ascii.14of20',
166 'pi200m.ascii.15of20']
166 'pi200m.ascii.15of20']
167
167
168 # download the data files if they don't already exist:
168 # download the data files if they don't already exist:
169 In [8]: v.map(fetch_pi_file, files)
169 In [8]: v.map(fetch_pi_file, files)
170
170
171 # This is the parallel calculation using the Client.map method
171 # This is the parallel calculation using the Client.map method
172 # which applies compute_two_digit_freqs to each file in files in parallel.
172 # which applies compute_two_digit_freqs to each file in files in parallel.
173 In [9]: freqs_all = v.map(compute_two_digit_freqs, files)
173 In [9]: freqs_all = v.map(compute_two_digit_freqs, files)
174
174
175 # Add up the frequencies from each engine.
175 # Add up the frequencies from each engine.
176 In [10]: freqs = reduce_freqs(freqs_all)
176 In [10]: freqs = reduce_freqs(freqs_all)
177
177
178 In [11]: plot_two_digit_freqs(freqs)
178 In [11]: plot_two_digit_freqs(freqs)
179 Out[11]: <matplotlib.image.AxesImage object at 0x18beb110>
179 Out[11]: <matplotlib.image.AxesImage object at 0x18beb110>
180
180
181 In [12]: plt.title('2 digit counts of 150m digits of pi')
181 In [12]: plt.title('2 digit counts of 150m digits of pi')
182 Out[12]: <matplotlib.text.Text object at 0x18d1f9b0>
182 Out[12]: <matplotlib.text.Text object at 0x18d1f9b0>
183
183
184 The resulting plot generated by Matplotlib is shown below. The colors indicate
184 The resulting plot generated by Matplotlib is shown below. The colors indicate
185 which two digit sequences are more (red) or less (blue) likely to occur in the
185 which two digit sequences are more (red) or less (blue) likely to occur in the
186 first 150 million digits of pi. We clearly see that the sequence "41" is
186 first 150 million digits of pi. We clearly see that the sequence "41" is
187 most likely and that "06" and "07" are least likely. Further analysis would
187 most likely and that "06" and "07" are least likely. Further analysis would
188 show that the relative size of the statistical fluctuations have decreased
188 show that the relative size of the statistical fluctuations have decreased
189 compared to the 10,000 digit calculation.
189 compared to the 10,000 digit calculation.
190
190
191 .. image:: ../parallel/two_digit_counts.*
191 .. image:: ../parallel/two_digit_counts.*
192
192
193
193
194 Parallel options pricing
194 Parallel options pricing
195 ========================
195 ========================
196
196
197 An option is a financial contract that gives the buyer of the contract the
197 An option is a financial contract that gives the buyer of the contract the
198 right to buy (a "call") or sell (a "put") a secondary asset (a stock for
198 right to buy (a "call") or sell (a "put") a secondary asset (a stock for
199 example) at a particular date in the future (the expiration date) for a
199 example) at a particular date in the future (the expiration date) for a
200 pre-agreed upon price (the strike price). For this right, the buyer pays the
200 pre-agreed upon price (the strike price). For this right, the buyer pays the
201 seller a premium (the option price). There are a wide variety of flavors of
201 seller a premium (the option price). There are a wide variety of flavors of
202 options (American, European, Asian, etc.) that are useful for different
202 options (American, European, Asian, etc.) that are useful for different
203 purposes: hedging against risk, speculation, etc.
203 purposes: hedging against risk, speculation, etc.
204
204
205 Much of modern finance is driven by the need to price these contracts
205 Much of modern finance is driven by the need to price these contracts
206 accurately based on what is known about the properties (such as volatility) of
206 accurately based on what is known about the properties (such as volatility) of
207 the underlying asset. One method of pricing options is to use a Monte Carlo
207 the underlying asset. One method of pricing options is to use a Monte Carlo
208 simulation of the underlying asset price. In this example we use this approach
208 simulation of the underlying asset price. In this example we use this approach
209 to price both European and Asian (path dependent) options for various strike
209 to price both European and Asian (path dependent) options for various strike
210 prices and volatilities.
210 prices and volatilities.
211
211
212 The code for this example can be found in the :file:`docs/examples/newparallel`
212 The code for this example can be found in the :file:`docs/examples/newparallel`
213 directory of the IPython source. The function :func:`price_options` in
213 directory of the IPython source. The function :func:`price_options` in
214 :file:`mcpricer.py` implements the basic Monte Carlo pricing algorithm using
214 :file:`mcpricer.py` implements the basic Monte Carlo pricing algorithm using
215 the NumPy package and is shown here:
215 the NumPy package and is shown here:
216
216
217 .. literalinclude:: ../../examples/newparallel/mcpricer.py
217 .. literalinclude:: ../../examples/newparallel/mcpricer.py
218 :language: python
218 :language: python
219
219
220 To run this code in parallel, we will use IPython's :class:`LoadBalancedView` class,
220 To run this code in parallel, we will use IPython's :class:`LoadBalancedView` class,
221 which distributes work to the engines using dynamic load balancing. This
221 which distributes work to the engines using dynamic load balancing. This
222 view is a wrapper of the :class:`Client` class shown in
222 view is a wrapper of the :class:`Client` class shown in
223 the previous example. The parallel calculation using :class:`LoadBalancedView` can
223 the previous example. The parallel calculation using :class:`LoadBalancedView` can
224 be found in the file :file:`mcpricer.py`. The code in this file creates a
224 be found in the file :file:`mcpricer.py`. The code in this file creates a
225 :class:`TaskClient` instance and then submits a set of tasks using
225 :class:`TaskClient` instance and then submits a set of tasks using
226 :meth:`TaskClient.run` that calculate the option prices for different
226 :meth:`TaskClient.run` that calculate the option prices for different
227 volatilities and strike prices. The results are then plotted as a 2D contour
227 volatilities and strike prices. The results are then plotted as a 2D contour
228 plot using Matplotlib.
228 plot using Matplotlib.
229
229
230 .. literalinclude:: ../../examples/newparallel/mcdriver.py
230 .. literalinclude:: ../../examples/newparallel/mcdriver.py
231 :language: python
231 :language: python
232
232
233 To use this code, start an IPython cluster using :command:`ipclusterz`, open
233 To use this code, start an IPython cluster using :command:`ipclusterz`, open
234 IPython in the pylab mode with the file :file:`mcdriver.py` in your current
234 IPython in the pylab mode with the file :file:`mcdriver.py` in your current
235 working directory and then type:
235 working directory and then type:
236
236
237 .. sourcecode:: ipython
237 .. sourcecode:: ipython
238
238
239 In [7]: run mcdriver.py
239 In [7]: run mcdriver.py
240 Submitted tasks: [0, 1, 2, ...]
240 Submitted tasks: [0, 1, 2, ...]
241
241
242 Once all the tasks have finished, the results can be plotted using the
242 Once all the tasks have finished, the results can be plotted using the
243 :func:`plot_options` function. Here we make contour plots of the Asian
243 :func:`plot_options` function. Here we make contour plots of the Asian
244 call and Asian put options as function of the volatility and strike price:
244 call and Asian put options as function of the volatility and strike price:
245
245
246 .. sourcecode:: ipython
246 .. sourcecode:: ipython
247
247
248 In [8]: plot_options(sigma_vals, K_vals, prices['acall'])
248 In [8]: plot_options(sigma_vals, K_vals, prices['acall'])
249
249
250 In [9]: plt.figure()
250 In [9]: plt.figure()
251 Out[9]: <matplotlib.figure.Figure object at 0x18c178d0>
251 Out[9]: <matplotlib.figure.Figure object at 0x18c178d0>
252
252
253 In [10]: plot_options(sigma_vals, K_vals, prices['aput'])
253 In [10]: plot_options(sigma_vals, K_vals, prices['aput'])
254
254
255 These results are shown in the two figures below. On a 8 core cluster the
255 These results are shown in the two figures below. On a 8 core cluster the
256 entire calculation (10 strike prices, 10 volatilities, 100,000 paths for each)
256 entire calculation (10 strike prices, 10 volatilities, 100,000 paths for each)
257 took 30 seconds in parallel, giving a speedup of 7.7x, which is comparable
257 took 30 seconds in parallel, giving a speedup of 7.7x, which is comparable
258 to the speedup observed in our previous example.
258 to the speedup observed in our previous example.
259
259
260 .. image:: ../parallel/asian_call.*
260 .. image:: ../parallel/asian_call.*
261
261
262 .. image:: ../parallel/asian_put.*
262 .. image:: ../parallel/asian_put.*
263
263
264 Conclusion
264 Conclusion
265 ==========
265 ==========
266
266
267 To conclude these examples, we summarize the key features of IPython's
267 To conclude these examples, we summarize the key features of IPython's
268 parallel architecture that have been demonstrated:
268 parallel architecture that have been demonstrated:
269
269
270 * Serial code can be parallelized often with only a few extra lines of code.
270 * Serial code can be parallelized often with only a few extra lines of code.
271 We have used the :class:`DirectView` and :class:`LoadBalancedView` classes
271 We have used the :class:`DirectView` and :class:`LoadBalancedView` classes
272 for this purpose.
272 for this purpose.
273 * The resulting parallel code can be run without ever leaving the IPython's
273 * The resulting parallel code can be run without ever leaving the IPython's
274 interactive shell.
274 interactive shell.
275 * Any data computed in parallel can be explored interactively through
275 * Any data computed in parallel can be explored interactively through
276 visualization or further numerical calculations.
276 visualization or further numerical calculations.
277 * We have run these examples on a cluster running Windows HPC Server 2008.
277 * We have run these examples on a cluster running Windows HPC Server 2008.
278 IPython's built in support for the Windows HPC job scheduler makes it
278 IPython's built in support for the Windows HPC job scheduler makes it
279 easy to get started with IPython's parallel capabilities.
279 easy to get started with IPython's parallel capabilities.
280
280
281 .. note::
281 .. note::
282
282
283 The newparallel code has never been run on Windows HPC Server, so the last
283 The newparallel code has never been run on Windows HPC Server, so the last
284 conclusion is untested.
284 conclusion is untested.
@@ -1,493 +1,477 b''
1 .. _parallel_details:
1 .. _parallel_details:
2
2
3 ==========================================
3 ==========================================
4 Details of Parallel Computing with IPython
4 Details of Parallel Computing with IPython
5 ==========================================
5 ==========================================
6
6
7 .. note::
7 .. note::
8
8
9 There are still many sections to fill out
9 There are still many sections to fill out
10
10
11
11
12 Caveats
12 Caveats
13 =======
13 =======
14
14
15 First, some caveats about the detailed workings of parallel computing with 0MQ and IPython.
15 First, some caveats about the detailed workings of parallel computing with 0MQ and IPython.
16
16
17 Non-copying sends and numpy arrays
17 Non-copying sends and numpy arrays
18 ----------------------------------
18 ----------------------------------
19
19
20 When numpy arrays are passed as arguments to apply or via data-movement methods, they are not
20 When numpy arrays are passed as arguments to apply or via data-movement methods, they are not
21 copied. This means that you must be careful if you are sending an array that you intend to work
21 copied. This means that you must be careful if you are sending an array that you intend to work
22 on. PyZMQ does allow you to track when a message has been sent so you can know when it is safe
22 on. PyZMQ does allow you to track when a message has been sent so you can know when it is safe
23 to edit the buffer, but IPython only allows for this.
23 to edit the buffer, but IPython only allows for this.
24
24
25 It is also important to note that the non-copying receive of a message is *read-only*. That
25 It is also important to note that the non-copying receive of a message is *read-only*. That
26 means that if you intend to work in-place on an array that you have sent or received, you must
26 means that if you intend to work in-place on an array that you have sent or received, you must
27 copy it. This is true for both numpy arrays sent to engines and numpy arrays retrieved as
27 copy it. This is true for both numpy arrays sent to engines and numpy arrays retrieved as
28 results.
28 results.
29
29
30 The following will fail:
30 The following will fail:
31
31
32 .. sourcecode:: ipython
32 .. sourcecode:: ipython
33
33
34 In [3]: A = numpy.zeros(2)
34 In [3]: A = numpy.zeros(2)
35
35
36 In [4]: def setter(a):
36 In [4]: def setter(a):
37 ...: a[0]=1
37 ...: a[0]=1
38 ...: return a
38 ...: return a
39
39
40 In [5]: rc[0].apply_sync(setter, A)
40 In [5]: rc[0].apply_sync(setter, A)
41 ---------------------------------------------------------------------------
41 ---------------------------------------------------------------------------
42 RemoteError Traceback (most recent call last)
42 RemoteError Traceback (most recent call last)
43 ...
43 ...
44 RemoteError: RuntimeError(array is not writeable)
44 RemoteError: RuntimeError(array is not writeable)
45 Traceback (most recent call last):
45 Traceback (most recent call last):
46 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/streamkernel.py", line 329, in apply_request
46 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 329, in apply_request
47 exec code in working, working
47 exec code in working, working
48 File "<string>", line 1, in <module>
48 File "<string>", line 1, in <module>
49 File "<ipython-input-14-736187483856>", line 2, in setter
49 File "<ipython-input-14-736187483856>", line 2, in setter
50 RuntimeError: array is not writeable
50 RuntimeError: array is not writeable
51
51
52 If you do need to edit the array in-place, just remember to copy the array if it's read-only.
52 If you do need to edit the array in-place, just remember to copy the array if it's read-only.
53 The :attr:`ndarray.flags.writeable` flag will tell you if you can write to an array.
53 The :attr:`ndarray.flags.writeable` flag will tell you if you can write to an array.
54
54
55 .. sourcecode:: ipython
55 .. sourcecode:: ipython
56
56
57 In [3]: A = numpy.zeros(2)
57 In [3]: A = numpy.zeros(2)
58
58
59 In [4]: def setter(a):
59 In [4]: def setter(a):
60 ...: """only copy read-only arrays"""
60 ...: """only copy read-only arrays"""
61 ...: if not a.flags.writeable:
61 ...: if not a.flags.writeable:
62 ...: a=a.copy()
62 ...: a=a.copy()
63 ...: a[0]=1
63 ...: a[0]=1
64 ...: return a
64 ...: return a
65
65
66 In [5]: rc[0].apply_sync(setter, A)
66 In [5]: rc[0].apply_sync(setter, A)
67 Out[5]: array([ 1., 0.])
67 Out[5]: array([ 1., 0.])
68
68
69 # note that results will also be read-only:
69 # note that results will also be read-only:
70 In [6]: _.flags.writeable
70 In [6]: _.flags.writeable
71 Out[6]: False
71 Out[6]: False
72
72
73 If you want to safely edit an array in-place after *sending* it, you must use the `track=True` flag. IPython always performs non-copying sends of arrays, which return immediately. You
73 If you want to safely edit an array in-place after *sending* it, you must use the `track=True` flag. IPython always performs non-copying sends of arrays, which return immediately. You
74 must instruct IPython track those messages *at send time* in order to know for sure that the send has completed. AsyncResults have a :attr:`sent` property, and :meth:`wait_on_send` method
74 must instruct IPython track those messages *at send time* in order to know for sure that the send has completed. AsyncResults have a :attr:`sent` property, and :meth:`wait_on_send` method
75 for checking and waiting for 0MQ to finish with a buffer.
75 for checking and waiting for 0MQ to finish with a buffer.
76
76
77 .. sourcecode:: ipython
77 .. sourcecode:: ipython
78
78
79 In [5]: A = numpy.random.random((1024,1024))
79 In [5]: A = numpy.random.random((1024,1024))
80
80
81 In [6]: view.track=True
81 In [6]: view.track=True
82
82
83 In [7]: ar = view.apply_async(lambda x: 2*x, A)
83 In [7]: ar = view.apply_async(lambda x: 2*x, A)
84
84
85 In [8]: ar.sent
85 In [8]: ar.sent
86 Out[8]: False
86 Out[8]: False
87
87
88 In [9]: ar.wait_on_send() # blocks until sent is True
88 In [9]: ar.wait_on_send() # blocks until sent is True
89
89
90
90
91 What is sendable?
91 What is sendable?
92 -----------------
92 -----------------
93
93
94 If IPython doesn't know what to do with an object, it will pickle it. There is a short list of
94 If IPython doesn't know what to do with an object, it will pickle it. There is a short list of
95 objects that are not pickled: ``buffers``, ``str/bytes`` objects, and ``numpy``
95 objects that are not pickled: ``buffers``, ``str/bytes`` objects, and ``numpy``
96 arrays. These are handled specially by IPython in order to prevent the copying of data. Sending
96 arrays. These are handled specially by IPython in order to prevent the copying of data. Sending
97 bytes or numpy arrays will result in exactly zero in-memory copies of your data (unless the data
97 bytes or numpy arrays will result in exactly zero in-memory copies of your data (unless the data
98 is very small).
98 is very small).
99
99
100 If you have an object that provides a Python buffer interface, then you can always send that
100 If you have an object that provides a Python buffer interface, then you can always send that
101 buffer without copying - and reconstruct the object on the other side in your own code. It is
101 buffer without copying - and reconstruct the object on the other side in your own code. It is
102 possible that the object reconstruction will become extensible, so you can add your own
102 possible that the object reconstruction will become extensible, so you can add your own
103 non-copying types, but this does not yet exist.
103 non-copying types, but this does not yet exist.
104
104
105 Closures
105 Closures
106 ********
106 ********
107
107
108 Just about anything in Python is pickleable. The one notable exception is objects (generally
108 Just about anything in Python is pickleable. The one notable exception is objects (generally
109 functions) with *closures*. Closures can be a complicated topic, but the basic principal is that
109 functions) with *closures*. Closures can be a complicated topic, but the basic principal is that
110 functions that refer to variables in their parent scope have closures.
110 functions that refer to variables in their parent scope have closures.
111
111
112 An example of a function that uses a closure:
112 An example of a function that uses a closure:
113
113
114 .. sourcecode:: python
114 .. sourcecode:: python
115
115
116 def f(a):
116 def f(a):
117 def inner():
117 def inner():
118 # inner will have a closure
118 # inner will have a closure
119 return a
119 return a
120 return echo
120 return echo
121
121
122 f1 = f(1)
122 f1 = f(1)
123 f2 = f(2)
123 f2 = f(2)
124 f1() # returns 1
124 f1() # returns 1
125 f2() # returns 2
125 f2() # returns 2
126
126
127 f1 and f2 will have closures referring to the scope in which `inner` was defined, because they
127 f1 and f2 will have closures referring to the scope in which `inner` was defined, because they
128 use the variable 'a'. As a result, you would not be able to send ``f1`` or ``f2`` with IPython.
128 use the variable 'a'. As a result, you would not be able to send ``f1`` or ``f2`` with IPython.
129 Note that you *would* be able to send `f`. This is only true for interactively defined
129 Note that you *would* be able to send `f`. This is only true for interactively defined
130 functions (as are often used in decorators), and only when there are variables used inside the
130 functions (as are often used in decorators), and only when there are variables used inside the
131 inner function, that are defined in the outer function. If the names are *not* in the outer
131 inner function, that are defined in the outer function. If the names are *not* in the outer
132 function, then there will not be a closure, and the generated function will look in
132 function, then there will not be a closure, and the generated function will look in
133 ``globals()`` for the name:
133 ``globals()`` for the name:
134
134
135 .. sourcecode:: python
135 .. sourcecode:: python
136
136
137 def g(b):
137 def g(b):
138 # note that `b` is not referenced in inner's scope
138 # note that `b` is not referenced in inner's scope
139 def inner():
139 def inner():
140 # this inner will *not* have a closure
140 # this inner will *not* have a closure
141 return a
141 return a
142 return echo
142 return echo
143 g1 = g(1)
143 g1 = g(1)
144 g2 = g(2)
144 g2 = g(2)
145 g1() # raises NameError on 'a'
145 g1() # raises NameError on 'a'
146 a=5
146 a=5
147 g2() # returns 5
147 g2() # returns 5
148
148
149 `g1` and `g2` *will* be sendable with IPython, and will treat the engine's namespace as
149 `g1` and `g2` *will* be sendable with IPython, and will treat the engine's namespace as
150 globals(). The :meth:`pull` method is implemented based on this principal. If we did not
150 globals(). The :meth:`pull` method is implemented based on this principal. If we did not
151 provide pull, you could implement it yourself with `apply`, by simply returning objects out
151 provide pull, you could implement it yourself with `apply`, by simply returning objects out
152 of the global namespace:
152 of the global namespace:
153
153
154 .. sourcecode:: ipython
154 .. sourcecode:: ipython
155
155
156 In [10]: view.apply(lambda : a)
156 In [10]: view.apply(lambda : a)
157
157
158 # is equivalent to
158 # is equivalent to
159 In [11]: view.pull('a')
159 In [11]: view.pull('a')
160
160
161 Running Code
161 Running Code
162 ============
162 ============
163
163
164 There are two principal units of execution in Python: strings of Python code (e.g. 'a=5'),
164 There are two principal units of execution in Python: strings of Python code (e.g. 'a=5'),
165 and Python functions. IPython is designed around the use of functions via the core
165 and Python functions. IPython is designed around the use of functions via the core
166 Client method, called `apply`.
166 Client method, called `apply`.
167
167
168 Apply
168 Apply
169 -----
169 -----
170
170
171 The principal method of remote execution is :meth:`apply`, of View objects. The Client provides
171 The principal method of remote execution is :meth:`apply`, of View objects. The Client provides
172 the full execution and communication API for engines via its low-level
172 the full execution and communication API for engines via its low-level
173 :meth:`send_apply_message` method.
173 :meth:`send_apply_message` method.
174
174
175 f : function
175 f : function
176 The fuction to be called remotely
176 The fuction to be called remotely
177 args : tuple/list
177 args : tuple/list
178 The positional arguments passed to `f`
178 The positional arguments passed to `f`
179 kwargs : dict
179 kwargs : dict
180 The keyword arguments passed to `f`
180 The keyword arguments passed to `f`
181 block : bool (default: self.block)
181
182 flags for all views:
183
184 block : bool (default: view.block)
182 Whether to wait for the result, or return immediately.
185 Whether to wait for the result, or return immediately.
183 False:
186 False:
184 returns AsyncResult
187 returns AsyncResult
185 True:
188 True:
186 returns actual result(s) of f(*args, **kwargs)
189 returns actual result(s) of f(*args, **kwargs)
187 if multiple targets:
190 if multiple targets:
188 list of results, matching `targets`
191 list of results, matching `targets`
189 track : bool
192 track : bool [default view.track]
190 whether to track non-copying sends.
193 whether to track non-copying sends.
191 [default False]
192
194
193 targets : int,list of ints, 'all', None
195 targets : int,list of ints, 'all', None [default view.targets]
194 Specify the destination of the job.
196 Specify the destination of the job.
195 if None:
197 if 'all' or None:
196 Submit via Task queue for load-balancing.
197 if 'all':
198 Run on all active engines
198 Run on all active engines
199 if list:
199 if list:
200 Run on each specified engine
200 Run on each specified engine
201 if int:
201 if int:
202 Run on single engine
202 Run on single engine
203 Not eht
204
203
205 balanced : bool, default None
204 Note that LoadBalancedView uses targets to restrict possible destinations. LoadBalanced calls
206 whether to load-balance. This will default to True
205 will always execute in just one location.
207 if targets is unspecified, or False if targets is specified.
206
208
207 flags only in LoadBalancedViews:
209 If `balanced` and `targets` are both specified, the task will
208
210 be assigne to *one* of the targets by the scheduler.
211
212 after : Dependency or collection of msg_ids
209 after : Dependency or collection of msg_ids
213 Only for load-balanced execution (targets=None)
210 Only for load-balanced execution (targets=None)
214 Specify a list of msg_ids as a time-based dependency.
211 Specify a list of msg_ids as a time-based dependency.
215 This job will only be run *after* the dependencies
212 This job will only be run *after* the dependencies
216 have been met.
213 have been met.
217
214
218 follow : Dependency or collection of msg_ids
215 follow : Dependency or collection of msg_ids
219 Only for load-balanced execution (targets=None)
216 Only for load-balanced execution (targets=None)
220 Specify a list of msg_ids as a location-based dependency.
217 Specify a list of msg_ids as a location-based dependency.
221 This job will only be run on an engine where this dependency
218 This job will only be run on an engine where this dependency
222 is met.
219 is met.
223
220
224 timeout : float/int or None
221 timeout : float/int or None
225 Only for load-balanced execution (targets=None)
222 Only for load-balanced execution (targets=None)
226 Specify an amount of time (in seconds) for the scheduler to
223 Specify an amount of time (in seconds) for the scheduler to
227 wait for dependencies to be met before failing with a
224 wait for dependencies to be met before failing with a
228 DependencyTimeout.
225 DependencyTimeout.
229
226
230 execute and run
227 execute and run
231 ---------------
228 ---------------
232
229
233 For executing strings of Python code, :class:`DirectView`s also provide an :meth:`execute` and a
230 For executing strings of Python code, :class:`DirectView`s also provide an :meth:`execute` and a
234 :meth:`run` method, which rather than take functions and arguments, take simple strings.
231 :meth:`run` method, which rather than take functions and arguments, take simple strings.
235 `execute` simply takes a string of Python code to execute, and sends it to the Engine(s). `run`
232 `execute` simply takes a string of Python code to execute, and sends it to the Engine(s). `run`
236 is the same as `execute`, but for a *file*, rather than a string. It is simply a wrapper that
233 is the same as `execute`, but for a *file*, rather than a string. It is simply a wrapper that
237 does something very similar to ``execute(open(f).read())``.
234 does something very similar to ``execute(open(f).read())``.
238
235
239 .. note::
236 .. note::
240
237
241 TODO: Example
238 TODO: Example
242
239
243 Views
240 Views
244 =====
241 =====
245
242
246 The principal extension of the :class:`~parallel.client.Client` is the
243 The principal extension of the :class:`~parallel.Client` is the
247 :class:`~parallel.view.View` class. The client
244 :class:`~parallel.view.View` class. The client
248
245
249 Two of apply's keyword arguments are set at the construction of the View, and are immutable for
250 a given View: `balanced` and `targets`. `balanced` determines whether the View will be a
251 :class:`.LoadBalancedView` or a :class:`.DirectView`, and `targets` will be the View's `targets`
252 attribute. Attempts to change this will raise errors.
253
254 Views are cached by targets/class, so requesting a view multiple times will always return the
255 *same object*, not create a new one:
256
257 .. sourcecode:: ipython
258
259 In [3]: v1 = rc.load_balanced_view([1,2,3])
260 In [4]: v2 = rc.load_balanced_view([1,2,3])
261
262 In [5]: v2 is v1
263 Out[5]: True
264
265
246
266 DirectView
247 DirectView
267 ----------
248 ----------
268
249
269 The :class:`.DirectView` is the class for the IPython :ref:`Multiplexing Interface
250 The :class:`.DirectView` is the class for the IPython :ref:`Multiplexing Interface
270 <parallel_multiengine>`.
251 <parallel_multiengine>`.
271
252
272 Creating a DirectView
253 Creating a DirectView
273 *********************
254 *********************
274
255
275 DirectViews can be created in two ways, by index access to a client, or by a client's
256 DirectViews can be created in two ways, by index access to a client, or by a client's
276 :meth:`view` method. Index access to a Client works in a few ways. First, you can create
257 :meth:`view` method. Index access to a Client works in a few ways. First, you can create
277 DirectViews to single engines simply by accessing the client by engine id:
258 DirectViews to single engines simply by accessing the client by engine id:
278
259
279 .. sourcecode:: ipython
260 .. sourcecode:: ipython
280
261
281 In [2]: rc[0]
262 In [2]: rc[0]
282 Out[2]: <DirectView 0>
263 Out[2]: <DirectView 0>
283
264
284 You can also create a DirectView with a list of engines:
265 You can also create a DirectView with a list of engines:
285
266
286 .. sourcecode:: ipython
267 .. sourcecode:: ipython
287
268
288 In [2]: rc[0,1,2]
269 In [2]: rc[0,1,2]
289 Out[2]: <DirectView [0,1,2]>
270 Out[2]: <DirectView [0,1,2]>
290
271
291 Other methods for accessing elements, such as slicing and negative indexing, work by passing
272 Other methods for accessing elements, such as slicing and negative indexing, work by passing
292 the index directly to the client's :attr:`ids` list, so:
273 the index directly to the client's :attr:`ids` list, so:
293
274
294 .. sourcecode:: ipython
275 .. sourcecode:: ipython
295
276
296 # negative index
277 # negative index
297 In [2]: rc[-1]
278 In [2]: rc[-1]
298 Out[2]: <DirectView 3>
279 Out[2]: <DirectView 3>
299
280
300 # or slicing:
281 # or slicing:
301 In [3]: rc[::2]
282 In [3]: rc[::2]
302 Out[3]: <DirectView [0,2]>
283 Out[3]: <DirectView [0,2]>
303
284
304 are always the same as:
285 are always the same as:
305
286
306 .. sourcecode:: ipython
287 .. sourcecode:: ipython
307
288
308 In [2]: rc[rc.ids[-1]]
289 In [2]: rc[rc.ids[-1]]
309 Out[2]: <DirectView 3>
290 Out[2]: <DirectView 3>
310
291
311 In [3]: rc[rc.ids[::2]]
292 In [3]: rc[rc.ids[::2]]
312 Out[3]: <DirectView [0,2]>
293 Out[3]: <DirectView [0,2]>
313
294
314 Also note that the slice is evaluated at the time of construction of the DirectView, so the
295 Also note that the slice is evaluated at the time of construction of the DirectView, so the
315 targets will not change over time if engines are added/removed from the cluster. Requesting
296 targets will not change over time if engines are added/removed from the cluster.
316 two views with the same slice at different times will *not* necessarily return the same View
317 if the number of engines has changed.
318
297
319 Execution via DirectView
298 Execution via DirectView
320 ************************
299 ************************
321
300
322 The DirectView is the simplest way to work with one or more engines directly (hence the name).
301 The DirectView is the simplest way to work with one or more engines directly (hence the name).
323
302
324
303
325 Data movement via DirectView
304 Data movement via DirectView
326 ****************************
305 ****************************
327
306
328 Since a Python namespace is just a :class:`dict`, :class:`DirectView` objects provide
307 Since a Python namespace is just a :class:`dict`, :class:`DirectView` objects provide
329 dictionary-style access by key and methods such as :meth:`get` and
308 dictionary-style access by key and methods such as :meth:`get` and
330 :meth:`update` for convenience. This make the remote namespaces of the engines
309 :meth:`update` for convenience. This make the remote namespaces of the engines
331 appear as a local dictionary. Underneath, these methods call :meth:`apply`:
310 appear as a local dictionary. Underneath, these methods call :meth:`apply`:
332
311
333 .. sourcecode:: ipython
312 .. sourcecode:: ipython
334
313
335 In [51]: dview['a']=['foo','bar']
314 In [51]: dview['a']=['foo','bar']
336
315
337 In [52]: dview['a']
316 In [52]: dview['a']
338 Out[52]: [ ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'] ]
317 Out[52]: [ ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'] ]
339
318
340 Scatter and gather
319 Scatter and gather
341 ------------------
320 ------------------
342
321
343 Sometimes it is useful to partition a sequence and push the partitions to
322 Sometimes it is useful to partition a sequence and push the partitions to
344 different engines. In MPI language, this is know as scatter/gather and we
323 different engines. In MPI language, this is know as scatter/gather and we
345 follow that terminology. However, it is important to remember that in
324 follow that terminology. However, it is important to remember that in
346 IPython's :class:`Client` class, :meth:`scatter` is from the
325 IPython's :class:`Client` class, :meth:`scatter` is from the
347 interactive IPython session to the engines and :meth:`gather` is from the
326 interactive IPython session to the engines and :meth:`gather` is from the
348 engines back to the interactive IPython session. For scatter/gather operations
327 engines back to the interactive IPython session. For scatter/gather operations
349 between engines, MPI should be used:
328 between engines, MPI should be used:
350
329
351 .. sourcecode:: ipython
330 .. sourcecode:: ipython
352
331
353 In [58]: dview.scatter('a',range(16))
332 In [58]: dview.scatter('a',range(16))
354 Out[58]: [None,None,None,None]
333 Out[58]: [None,None,None,None]
355
334
356 In [59]: dview['a']
335 In [59]: dview['a']
357 Out[59]: [ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15] ]
336 Out[59]: [ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15] ]
358
337
359 In [60]: dview.gather('a')
338 In [60]: dview.gather('a')
360 Out[60]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
339 Out[60]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
361
340
341 Push and pull
342 -------------
343
344 push
345
346 pull
347
348
349
362
350
363
351
364 LoadBalancedView
352 LoadBalancedView
365 ----------------
353 ----------------
366
354
367 The :class:`.LoadBalancedView`
355 The :class:`.LoadBalancedView`
368
356
369
357
370 Data Movement
358 Data Movement
371 =============
359 =============
372
360
373 push
374
375 pull
376
377 Reference
361 Reference
378
362
379 Results
363 Results
380 =======
364 =======
381
365
382 AsyncResults are the primary class
366 AsyncResults are the primary class
383
367
384 get_result
368 get_result
385
369
386 results,metadata
370 results, metadata
387
371
388 Querying the Hub
372 Querying the Hub
389 ================
373 ================
390
374
391 The Hub sees all traffic that may pass through the schedulers between engines and clients.
375 The Hub sees all traffic that may pass through the schedulers between engines and clients.
392 It does this so that it can track state, allowing multiple clients to retrieve results of
376 It does this so that it can track state, allowing multiple clients to retrieve results of
393 computations submitted by their peers, as well as persisting the state to a database.
377 computations submitted by their peers, as well as persisting the state to a database.
394
378
395 queue_status
379 queue_status
396
380
397 You can check the status of the queues of the engines with this command.
381 You can check the status of the queues of the engines with this command.
398
382
399 result_status
383 result_status
400
384
401 purge_results
385 purge_results
402
386
403 Controlling the Engines
387 Controlling the Engines
404 =======================
388 =======================
405
389
406 There are a few actions you can do with Engines that do not involve execution. These
390 There are a few actions you can do with Engines that do not involve execution. These
407 messages are sent via the Control socket, and bypass any long queues of waiting execution
391 messages are sent via the Control socket, and bypass any long queues of waiting execution
408 jobs
392 jobs
409
393
410 abort
394 abort
411
395
412 Sometimes you may want to prevent a job you have submitted from actually running. The method
396 Sometimes you may want to prevent a job you have submitted from actually running. The method
413 for this is :meth:`abort`. It takes a container of msg_ids, and instructs the Engines to not
397 for this is :meth:`abort`. It takes a container of msg_ids, and instructs the Engines to not
414 run the jobs if they arrive. The jobs will then fail with an AbortedTask error.
398 run the jobs if they arrive. The jobs will then fail with an AbortedTask error.
415
399
416 clear
400 clear
417
401
418 You may want to purge the Engine(s) namespace of any data you have left in it. After
402 You may want to purge the Engine(s) namespace of any data you have left in it. After
419 running `clear`, there will be no names in the Engine's namespace
403 running `clear`, there will be no names in the Engine's namespace
420
404
421 shutdown
405 shutdown
422
406
423 You can also instruct engines (and the Controller) to terminate from a Client. This
407 You can also instruct engines (and the Controller) to terminate from a Client. This
424 can be useful when a job is finished, since you can shutdown all the processes with a
408 can be useful when a job is finished, since you can shutdown all the processes with a
425 single command.
409 single command.
426
410
427 Synchronization
411 Synchronization
428 ===============
412 ===============
429
413
430 Since the Client is a synchronous object, events do not automatically trigger in your
414 Since the Client is a synchronous object, events do not automatically trigger in your
431 interactive session - you must poll the 0MQ sockets for incoming messages. Note that
415 interactive session - you must poll the 0MQ sockets for incoming messages. Note that
432 this polling *does not* actually make any network requests. It simply performs a `select`
416 this polling *does not* actually make any network requests. It simply performs a `select`
433 operation, to check if messages are already in local memory, waiting to be handled.
417 operation, to check if messages are already in local memory, waiting to be handled.
434
418
435 The method that handles incoming messages is :meth:`spin`. This method flushes any waiting
419 The method that handles incoming messages is :meth:`spin`. This method flushes any waiting
436 messages on the various incoming sockets, and updates the state of the Client.
420 messages on the various incoming sockets, and updates the state of the Client.
437
421
438 If you need to wait for particular results to finish, you can use the :meth:`wait` method,
422 If you need to wait for particular results to finish, you can use the :meth:`wait` method,
439 which will call :meth:`spin` until the messages are no longer outstanding. Anything that
423 which will call :meth:`spin` until the messages are no longer outstanding. Anything that
440 represents a collection of messages, such as a list of msg_ids or one or more AsyncResult
424 represents a collection of messages, such as a list of msg_ids or one or more AsyncResult
441 objects, can be passed as argument to wait. A timeout can be specified, which will prevent
425 objects, can be passed as argument to wait. A timeout can be specified, which will prevent
442 the call from blocking for more than a specified time, but the default behavior is to wait
426 the call from blocking for more than a specified time, but the default behavior is to wait
443 forever.
427 forever.
444
428
445
429
446
430
447 The client also has an `outstanding` attribute - a ``set`` of msg_ids that are awaiting replies.
431 The client also has an `outstanding` attribute - a ``set`` of msg_ids that are awaiting replies.
448 This is the default if wait is called with no arguments - i.e. wait on *all* outstanding
432 This is the default if wait is called with no arguments - i.e. wait on *all* outstanding
449 messages.
433 messages.
450
434
451
435
452 .. note::
436 .. note::
453
437
454 TODO wait example
438 TODO wait example
455
439
456 Map
440 Map
457 ===
441 ===
458
442
459 Many parallel computing problems can be expressed as a `map`, or running a single program with a
443 Many parallel computing problems can be expressed as a `map`, or running a single program with a
460 variety of different inputs. Python has a built-in :py-func:`map`, which does exactly this, and
444 variety of different inputs. Python has a built-in :py-func:`map`, which does exactly this, and
461 many parallel execution tools in Python, such as the built-in :py-class:`multiprocessing.Pool`
445 many parallel execution tools in Python, such as the built-in :py-class:`multiprocessing.Pool`
462 object provide implementations of `map`. All View objects provide a :meth:`map` method as well,
446 object provide implementations of `map`. All View objects provide a :meth:`map` method as well,
463 but the load-balanced and direct implementations differ.
447 but the load-balanced and direct implementations differ.
464
448
465 Views' map methods can be called on any number of sequences, but they can also take the `block`
449 Views' map methods can be called on any number of sequences, but they can also take the `block`
466 and `bound` keyword arguments, just like :meth:`~client.apply`, but *only as keywords*.
450 and `bound` keyword arguments, just like :meth:`~client.apply`, but *only as keywords*.
467
451
468 .. sourcecode:: python
452 .. sourcecode:: python
469
453
470 dview.map(*sequences, block=None)
454 dview.map(*sequences, block=None)
471
455
472
456
473 * iter, map_async, reduce
457 * iter, map_async, reduce
474
458
475 Decorators and RemoteFunctions
459 Decorators and RemoteFunctions
476 ==============================
460 ==============================
477
461
478 @parallel
462 @parallel
479
463
480 @remote
464 @remote
481
465
482 RemoteFunction
466 RemoteFunction
483
467
484 ParallelFunction
468 ParallelFunction
485
469
486 Dependencies
470 Dependencies
487 ============
471 ============
488
472
489 @depend
473 @depend
490
474
491 @require
475 @require
492
476
493 Dependency
477 Dependency
@@ -1,244 +1,244 b''
1 .. _ip1par:
1 .. _ip1par:
2
2
3 ============================
3 ============================
4 Overview and getting started
4 Overview and getting started
5 ============================
5 ============================
6
6
7 Introduction
7 Introduction
8 ============
8 ============
9
9
10 This section gives an overview of IPython's sophisticated and powerful
10 This section gives an overview of IPython's sophisticated and powerful
11 architecture for parallel and distributed computing. This architecture
11 architecture for parallel and distributed computing. This architecture
12 abstracts out parallelism in a very general way, which enables IPython to
12 abstracts out parallelism in a very general way, which enables IPython to
13 support many different styles of parallelism including:
13 support many different styles of parallelism including:
14
14
15 * Single program, multiple data (SPMD) parallelism.
15 * Single program, multiple data (SPMD) parallelism.
16 * Multiple program, multiple data (MPMD) parallelism.
16 * Multiple program, multiple data (MPMD) parallelism.
17 * Message passing using MPI.
17 * Message passing using MPI.
18 * Task farming.
18 * Task farming.
19 * Data parallel.
19 * Data parallel.
20 * Combinations of these approaches.
20 * Combinations of these approaches.
21 * Custom user defined approaches.
21 * Custom user defined approaches.
22
22
23 Most importantly, IPython enables all types of parallel applications to
23 Most importantly, IPython enables all types of parallel applications to
24 be developed, executed, debugged and monitored *interactively*. Hence,
24 be developed, executed, debugged and monitored *interactively*. Hence,
25 the ``I`` in IPython. The following are some example usage cases for IPython:
25 the ``I`` in IPython. The following are some example usage cases for IPython:
26
26
27 * Quickly parallelize algorithms that are embarrassingly parallel
27 * Quickly parallelize algorithms that are embarrassingly parallel
28 using a number of simple approaches. Many simple things can be
28 using a number of simple approaches. Many simple things can be
29 parallelized interactively in one or two lines of code.
29 parallelized interactively in one or two lines of code.
30
30
31 * Steer traditional MPI applications on a supercomputer from an
31 * Steer traditional MPI applications on a supercomputer from an
32 IPython session on your laptop.
32 IPython session on your laptop.
33
33
34 * Analyze and visualize large datasets (that could be remote and/or
34 * Analyze and visualize large datasets (that could be remote and/or
35 distributed) interactively using IPython and tools like
35 distributed) interactively using IPython and tools like
36 matplotlib/TVTK.
36 matplotlib/TVTK.
37
37
38 * Develop, test and debug new parallel algorithms
38 * Develop, test and debug new parallel algorithms
39 (that may use MPI) interactively.
39 (that may use MPI) interactively.
40
40
41 * Tie together multiple MPI jobs running on different systems into
41 * Tie together multiple MPI jobs running on different systems into
42 one giant distributed and parallel system.
42 one giant distributed and parallel system.
43
43
44 * Start a parallel job on your cluster and then have a remote
44 * Start a parallel job on your cluster and then have a remote
45 collaborator connect to it and pull back data into their
45 collaborator connect to it and pull back data into their
46 local IPython session for plotting and analysis.
46 local IPython session for plotting and analysis.
47
47
48 * Run a set of tasks on a set of CPUs using dynamic load balancing.
48 * Run a set of tasks on a set of CPUs using dynamic load balancing.
49
49
50 Architecture overview
50 Architecture overview
51 =====================
51 =====================
52
52
53 The IPython architecture consists of four components:
53 The IPython architecture consists of four components:
54
54
55 * The IPython engine.
55 * The IPython engine.
56 * The IPython hub.
56 * The IPython hub.
57 * The IPython schedulers.
57 * The IPython schedulers.
58 * The controller client.
58 * The controller client.
59
59
60 These components live in the :mod:`IPython.zmq.parallel` package and are
60 These components live in the :mod:`IPython.parallel` package and are
61 installed with IPython. They do, however, have additional dependencies
61 installed with IPython. They do, however, have additional dependencies
62 that must be installed. For more information, see our
62 that must be installed. For more information, see our
63 :ref:`installation documentation <install_index>`.
63 :ref:`installation documentation <install_index>`.
64
64
65 .. TODO: include zmq in install_index
65 .. TODO: include zmq in install_index
66
66
67 IPython engine
67 IPython engine
68 ---------------
68 ---------------
69
69
70 The IPython engine is a Python instance that takes Python commands over a
70 The IPython engine is a Python instance that takes Python commands over a
71 network connection. Eventually, the IPython engine will be a full IPython
71 network connection. Eventually, the IPython engine will be a full IPython
72 interpreter, but for now, it is a regular Python interpreter. The engine
72 interpreter, but for now, it is a regular Python interpreter. The engine
73 can also handle incoming and outgoing Python objects sent over a network
73 can also handle incoming and outgoing Python objects sent over a network
74 connection. When multiple engines are started, parallel and distributed
74 connection. When multiple engines are started, parallel and distributed
75 computing becomes possible. An important feature of an IPython engine is
75 computing becomes possible. An important feature of an IPython engine is
76 that it blocks while user code is being executed. Read on for how the
76 that it blocks while user code is being executed. Read on for how the
77 IPython controller solves this problem to expose a clean asynchronous API
77 IPython controller solves this problem to expose a clean asynchronous API
78 to the user.
78 to the user.
79
79
80 IPython controller
80 IPython controller
81 ------------------
81 ------------------
82
82
83 The IPython controller processes provide an interface for working with a set of engines.
83 The IPython controller processes provide an interface for working with a set of engines.
84 At a general level, the controller is a collection of processes to which IPython engines
84 At a general level, the controller is a collection of processes to which IPython engines
85 and clients can connect. The controller is composed of a :class:`Hub` and a collection of
85 and clients can connect. The controller is composed of a :class:`Hub` and a collection of
86 :class:`Schedulers`. These Schedulers are typically run in separate processes but on the
86 :class:`Schedulers`. These Schedulers are typically run in separate processes but on the
87 same machine as the Hub, but can be run anywhere from local threads or on remote machines.
87 same machine as the Hub, but can be run anywhere from local threads or on remote machines.
88
88
89 The controller also provides a single point of contact for users who wish to
89 The controller also provides a single point of contact for users who wish to
90 utilize the engines connected to the controller. There are different ways of
90 utilize the engines connected to the controller. There are different ways of
91 working with a controller. In IPython, all of these models are implemented via
91 working with a controller. In IPython, all of these models are implemented via
92 the client's :meth:`.View.apply` method, with various arguments, or
92 the client's :meth:`.View.apply` method, with various arguments, or
93 constructing :class:`.View` objects to represent subsets of engines. The two
93 constructing :class:`.View` objects to represent subsets of engines. The two
94 primary models for interacting with engines are:
94 primary models for interacting with engines are:
95
95
96 * A **Direct** interface, where engines are addressed explicitly.
96 * A **Direct** interface, where engines are addressed explicitly.
97 * A **LoadBalanced** interface, where the Scheduler is trusted with assigning work to
97 * A **LoadBalanced** interface, where the Scheduler is trusted with assigning work to
98 appropriate engines.
98 appropriate engines.
99
99
100 Advanced users can readily extend the View models to enable other
100 Advanced users can readily extend the View models to enable other
101 styles of parallelism.
101 styles of parallelism.
102
102
103 .. note::
103 .. note::
104
104
105 A single controller and set of engines can be used with multiple models
105 A single controller and set of engines can be used with multiple models
106 simultaneously. This opens the door for lots of interesting things.
106 simultaneously. This opens the door for lots of interesting things.
107
107
108
108
109 The Hub
109 The Hub
110 *******
110 *******
111
111
112 The center of an IPython cluster is the Hub. This is the process that keeps
112 The center of an IPython cluster is the Hub. This is the process that keeps
113 track of engine connections, schedulers, clients, as well as all task requests and
113 track of engine connections, schedulers, clients, as well as all task requests and
114 results. The primary role of the Hub is to facilitate queries of the cluster state, and
114 results. The primary role of the Hub is to facilitate queries of the cluster state, and
115 minimize the necessary information required to establish the many connections involved in
115 minimize the necessary information required to establish the many connections involved in
116 connecting new clients and engines.
116 connecting new clients and engines.
117
117
118
118
119 Schedulers
119 Schedulers
120 **********
120 **********
121
121
122 All actions that can be performed on the engine go through a Scheduler. While the engines
122 All actions that can be performed on the engine go through a Scheduler. While the engines
123 themselves block when user code is run, the schedulers hide that from the user to provide
123 themselves block when user code is run, the schedulers hide that from the user to provide
124 a fully asynchronous interface to a set of engines.
124 a fully asynchronous interface to a set of engines.
125
125
126
126
127 IPython client and views
127 IPython client and views
128 ------------------------
128 ------------------------
129
129
130 There is one primary object, the :class:`~.parallel.client.Client`, for connecting to a cluster.
130 There is one primary object, the :class:`~.parallel.Client`, for connecting to a cluster.
131 For each execution model, there is a corresponding :class:`~.parallel.view.View`. These views
131 For each execution model, there is a corresponding :class:`~.parallel.view.View`. These views
132 allow users to interact with a set of engines through the interface. Here are the two default
132 allow users to interact with a set of engines through the interface. Here are the two default
133 views:
133 views:
134
134
135 * The :class:`DirectView` class for explicit addressing.
135 * The :class:`DirectView` class for explicit addressing.
136 * The :class:`LoadBalancedView` class for destination-agnostic scheduling.
136 * The :class:`LoadBalancedView` class for destination-agnostic scheduling.
137
137
138 Security
138 Security
139 --------
139 --------
140
140
141 IPython uses ZeroMQ for networking, which has provided many advantages, but
141 IPython uses ZeroMQ for networking, which has provided many advantages, but
142 one of the setbacks is its utter lack of security [ZeroMQ]_. By default, no IPython
142 one of the setbacks is its utter lack of security [ZeroMQ]_. By default, no IPython
143 connections are encrypted, but open ports only listen on localhost. The only
143 connections are encrypted, but open ports only listen on localhost. The only
144 source of security for IPython is via ssh-tunnel. IPython supports both shell
144 source of security for IPython is via ssh-tunnel. IPython supports both shell
145 (`openssh`) and `paramiko` based tunnels for connections. There is a key necessary
145 (`openssh`) and `paramiko` based tunnels for connections. There is a key necessary
146 to submit requests, but due to the lack of encryption, it does not provide
146 to submit requests, but due to the lack of encryption, it does not provide
147 significant security if loopback traffic is compromised.
147 significant security if loopback traffic is compromised.
148
148
149 In our architecture, the controller is the only process that listens on
149 In our architecture, the controller is the only process that listens on
150 network ports, and is thus the main point of vulnerability. The standard model
150 network ports, and is thus the main point of vulnerability. The standard model
151 for secure connections is to designate that the controller listen on
151 for secure connections is to designate that the controller listen on
152 localhost, and use ssh-tunnels to connect clients and/or
152 localhost, and use ssh-tunnels to connect clients and/or
153 engines.
153 engines.
154
154
155 To connect and authenticate to the controller an engine or client needs
155 To connect and authenticate to the controller an engine or client needs
156 some information that the controller has stored in a JSON file.
156 some information that the controller has stored in a JSON file.
157 Thus, the JSON files need to be copied to a location where
157 Thus, the JSON files need to be copied to a location where
158 the clients and engines can find them. Typically, this is the
158 the clients and engines can find them. Typically, this is the
159 :file:`~/.ipython/clusterz_default/security` directory on the host where the
159 :file:`~/.ipython/clusterz_default/security` directory on the host where the
160 client/engine is running (which could be a different host than the controller).
160 client/engine is running (which could be a different host than the controller).
161 Once the JSON files are copied over, everything should work fine.
161 Once the JSON files are copied over, everything should work fine.
162
162
163 Currently, there are two JSON files that the controller creates:
163 Currently, there are two JSON files that the controller creates:
164
164
165 ipcontroller-engine.json
165 ipcontroller-engine.json
166 This JSON file has the information necessary for an engine to connect
166 This JSON file has the information necessary for an engine to connect
167 to a controller.
167 to a controller.
168
168
169 ipcontroller-client.json
169 ipcontroller-client.json
170 The client's connection information. This may not differ from the engine's,
170 The client's connection information. This may not differ from the engine's,
171 but since the controller may listen on different ports for clients and
171 but since the controller may listen on different ports for clients and
172 engines, it is stored separately.
172 engines, it is stored separately.
173
173
174 More details of how these JSON files are used are given below.
174 More details of how these JSON files are used are given below.
175
175
176 A detailed description of the security model and its implementation in IPython
176 A detailed description of the security model and its implementation in IPython
177 can be found :ref:`here <parallelsecurity>`.
177 can be found :ref:`here <parallelsecurity>`.
178
178
179 .. warning::
179 .. warning::
180
180
181 Even at its most secure, the Controller listens on ports on localhost, and
181 Even at its most secure, the Controller listens on ports on localhost, and
182 every time you make a tunnel, you open a localhost port on the connecting
182 every time you make a tunnel, you open a localhost port on the connecting
183 machine that points to the Controller. If localhost on the Controller's
183 machine that points to the Controller. If localhost on the Controller's
184 machine, or the machine of any client or engine, is untrusted, then your
184 machine, or the machine of any client or engine, is untrusted, then your
185 Controller is insecure. There is no way around this with ZeroMQ.
185 Controller is insecure. There is no way around this with ZeroMQ.
186
186
187
187
188
188
189 Getting Started
189 Getting Started
190 ===============
190 ===============
191
191
192 To use IPython for parallel computing, you need to start one instance of the
192 To use IPython for parallel computing, you need to start one instance of the
193 controller and one or more instances of the engine. Initially, it is best to
193 controller and one or more instances of the engine. Initially, it is best to
194 simply start a controller and engines on a single host using the
194 simply start a controller and engines on a single host using the
195 :command:`ipclusterz` command. To start a controller and 4 engines on your
195 :command:`ipclusterz` command. To start a controller and 4 engines on your
196 localhost, just do::
196 localhost, just do::
197
197
198 $ ipclusterz start -n 4
198 $ ipclusterz start -n 4
199
199
200 More details about starting the IPython controller and engines can be found
200 More details about starting the IPython controller and engines can be found
201 :ref:`here <parallel_process>`
201 :ref:`here <parallel_process>`
202
202
203 Once you have started the IPython controller and one or more engines, you
203 Once you have started the IPython controller and one or more engines, you
204 are ready to use the engines to do something useful. To make sure
204 are ready to use the engines to do something useful. To make sure
205 everything is working correctly, try the following commands:
205 everything is working correctly, try the following commands:
206
206
207 .. sourcecode:: ipython
207 .. sourcecode:: ipython
208
208
209 In [1]: from IPython.zmq.parallel import client
209 In [1]: from IPython.parallel import Client
210
210
211 In [2]: c = client.Client()
211 In [2]: c = Client()
212
212
213 In [4]: c.ids
213 In [4]: c.ids
214 Out[4]: set([0, 1, 2, 3])
214 Out[4]: set([0, 1, 2, 3])
215
215
216 In [5]: c[:].apply_sync(lambda : "Hello, World")
216 In [5]: c[:].apply_sync(lambda : "Hello, World")
217 Out[5]: [ 'Hello, World', 'Hello, World', 'Hello, World', 'Hello, World' ]
217 Out[5]: [ 'Hello, World', 'Hello, World', 'Hello, World', 'Hello, World' ]
218
218
219
219
220 When a client is created with no arguments, the client tries to find the corresponding
220 When a client is created with no arguments, the client tries to find the corresponding
221 JSON file in the local `~/.ipython/clusterz_default/security` directory. If it finds it,
221 JSON file in the local `~/.ipython/clusterz_default/security` directory. If it finds it,
222 you are set. If you have put the JSON file in a different location or it has a different
222 you are set. If you have put the JSON file in a different location or it has a different
223 name, create the client like this:
223 name, create the client like this:
224
224
225 .. sourcecode:: ipython
225 .. sourcecode:: ipython
226
226
227 In [2]: c = client.Client('/path/to/my/ipcontroller-client.json')
227 In [2]: c = Client('/path/to/my/ipcontroller-client.json')
228
228
229 Remember, a client needs to be able to see the Hub's ports to connect. So if they are on a
229 Remember, a client needs to be able to see the Hub's ports to connect. So if they are on a
230 different machine, you may need to use an ssh server to tunnel access to that machine,
230 different machine, you may need to use an ssh server to tunnel access to that machine,
231 then you would connect to it with:
231 then you would connect to it with:
232
232
233 .. sourcecode:: ipython
233 .. sourcecode:: ipython
234
234
235 In [2]: c = client.Client(sshserver='myhub.example.com')
235 In [2]: c = Client(sshserver='myhub.example.com')
236
236
237 Where 'myhub.example.com' is the url or IP address of the machine on
237 Where 'myhub.example.com' is the url or IP address of the machine on
238 which the Hub process is running (or another machine that has direct access to the Hub's ports).
238 which the Hub process is running (or another machine that has direct access to the Hub's ports).
239
239
240 You are now ready to learn more about the :ref:`Direct
240 You are now ready to learn more about the :ref:`Direct
241 <parallel_multiengine>` and :ref:`LoadBalanced <parallel_task>` interfaces to the
241 <parallel_multiengine>` and :ref:`LoadBalanced <parallel_task>` interfaces to the
242 controller.
242 controller.
243
243
244 .. [ZeroMQ] ZeroMQ. http://www.zeromq.org
244 .. [ZeroMQ] ZeroMQ. http://www.zeromq.org
@@ -1,156 +1,156 b''
1 .. _parallelmpi:
1 .. _parallelmpi:
2
2
3 =======================
3 =======================
4 Using MPI with IPython
4 Using MPI with IPython
5 =======================
5 =======================
6
6
7 .. note::
7 .. note::
8
8
9 Not adapted to zmq yet
9 Not adapted to zmq yet
10 This is out of date wrt ipcluster in general as well
10 This is out of date wrt ipcluster in general as well
11
11
12 Often, a parallel algorithm will require moving data between the engines. One
12 Often, a parallel algorithm will require moving data between the engines. One
13 way of accomplishing this is by doing a pull and then a push using the
13 way of accomplishing this is by doing a pull and then a push using the
14 multiengine client. However, this will be slow as all the data has to go
14 multiengine client. However, this will be slow as all the data has to go
15 through the controller to the client and then back through the controller, to
15 through the controller to the client and then back through the controller, to
16 its final destination.
16 its final destination.
17
17
18 A much better way of moving data between engines is to use a message passing
18 A much better way of moving data between engines is to use a message passing
19 library, such as the Message Passing Interface (MPI) [MPI]_. IPython's
19 library, such as the Message Passing Interface (MPI) [MPI]_. IPython's
20 parallel computing architecture has been designed from the ground up to
20 parallel computing architecture has been designed from the ground up to
21 integrate with MPI. This document describes how to use MPI with IPython.
21 integrate with MPI. This document describes how to use MPI with IPython.
22
22
23 Additional installation requirements
23 Additional installation requirements
24 ====================================
24 ====================================
25
25
26 If you want to use MPI with IPython, you will need to install:
26 If you want to use MPI with IPython, you will need to install:
27
27
28 * A standard MPI implementation such as OpenMPI [OpenMPI]_ or MPICH.
28 * A standard MPI implementation such as OpenMPI [OpenMPI]_ or MPICH.
29 * The mpi4py [mpi4py]_ package.
29 * The mpi4py [mpi4py]_ package.
30
30
31 .. note::
31 .. note::
32
32
33 The mpi4py package is not a strict requirement. However, you need to
33 The mpi4py package is not a strict requirement. However, you need to
34 have *some* way of calling MPI from Python. You also need some way of
34 have *some* way of calling MPI from Python. You also need some way of
35 making sure that :func:`MPI_Init` is called when the IPython engines start
35 making sure that :func:`MPI_Init` is called when the IPython engines start
36 up. There are a number of ways of doing this and a good number of
36 up. There are a number of ways of doing this and a good number of
37 associated subtleties. We highly recommend just using mpi4py as it
37 associated subtleties. We highly recommend just using mpi4py as it
38 takes care of most of these problems. If you want to do something
38 takes care of most of these problems. If you want to do something
39 different, let us know and we can help you get started.
39 different, let us know and we can help you get started.
40
40
41 Starting the engines with MPI enabled
41 Starting the engines with MPI enabled
42 =====================================
42 =====================================
43
43
44 To use code that calls MPI, there are typically two things that MPI requires.
44 To use code that calls MPI, there are typically two things that MPI requires.
45
45
46 1. The process that wants to call MPI must be started using
46 1. The process that wants to call MPI must be started using
47 :command:`mpiexec` or a batch system (like PBS) that has MPI support.
47 :command:`mpiexec` or a batch system (like PBS) that has MPI support.
48 2. Once the process starts, it must call :func:`MPI_Init`.
48 2. Once the process starts, it must call :func:`MPI_Init`.
49
49
50 There are a couple of ways that you can start the IPython engines and get
50 There are a couple of ways that you can start the IPython engines and get
51 these things to happen.
51 these things to happen.
52
52
53 Automatic starting using :command:`mpiexec` and :command:`ipclusterz`
53 Automatic starting using :command:`mpiexec` and :command:`ipclusterz`
54 --------------------------------------------------------------------
54 --------------------------------------------------------------------
55
55
56 The easiest approach is to use the `mpiexec` mode of :command:`ipclusterz`,
56 The easiest approach is to use the `mpiexec` mode of :command:`ipclusterz`,
57 which will first start a controller and then a set of engines using
57 which will first start a controller and then a set of engines using
58 :command:`mpiexec`::
58 :command:`mpiexec`::
59
59
60 $ ipclusterz mpiexec -n 4
60 $ ipclusterz mpiexec -n 4
61
61
62 This approach is best as interrupting :command:`ipclusterz` will automatically
62 This approach is best as interrupting :command:`ipclusterz` will automatically
63 stop and clean up the controller and engines.
63 stop and clean up the controller and engines.
64
64
65 Manual starting using :command:`mpiexec`
65 Manual starting using :command:`mpiexec`
66 ----------------------------------------
66 ----------------------------------------
67
67
68 If you want to start the IPython engines using the :command:`mpiexec`, just
68 If you want to start the IPython engines using the :command:`mpiexec`, just
69 do::
69 do::
70
70
71 $ mpiexec -n 4 ipenginez --mpi=mpi4py
71 $ mpiexec -n 4 ipenginez --mpi=mpi4py
72
72
73 This requires that you already have a controller running and that the FURL
73 This requires that you already have a controller running and that the FURL
74 files for the engines are in place. We also have built in support for
74 files for the engines are in place. We also have built in support for
75 PyTrilinos [PyTrilinos]_, which can be used (assuming is installed) by
75 PyTrilinos [PyTrilinos]_, which can be used (assuming is installed) by
76 starting the engines with::
76 starting the engines with::
77
77
78 $ mpiexec -n 4 ipenginez --mpi=pytrilinos
78 $ mpiexec -n 4 ipenginez --mpi=pytrilinos
79
79
80 Automatic starting using PBS and :command:`ipclusterz`
80 Automatic starting using PBS and :command:`ipclusterz`
81 ------------------------------------------------------
81 ------------------------------------------------------
82
82
83 The :command:`ipclusterz` command also has built-in integration with PBS. For
83 The :command:`ipclusterz` command also has built-in integration with PBS. For
84 more information on this approach, see our documentation on :ref:`ipclusterz
84 more information on this approach, see our documentation on :ref:`ipclusterz
85 <parallel_process>`.
85 <parallel_process>`.
86
86
87 Actually using MPI
87 Actually using MPI
88 ==================
88 ==================
89
89
90 Once the engines are running with MPI enabled, you are ready to go. You can
90 Once the engines are running with MPI enabled, you are ready to go. You can
91 now call any code that uses MPI in the IPython engines. And, all of this can
91 now call any code that uses MPI in the IPython engines. And, all of this can
92 be done interactively. Here we show a simple example that uses mpi4py
92 be done interactively. Here we show a simple example that uses mpi4py
93 [mpi4py]_ version 1.1.0 or later.
93 [mpi4py]_ version 1.1.0 or later.
94
94
95 First, lets define a simply function that uses MPI to calculate the sum of a
95 First, lets define a simply function that uses MPI to calculate the sum of a
96 distributed array. Save the following text in a file called :file:`psum.py`:
96 distributed array. Save the following text in a file called :file:`psum.py`:
97
97
98 .. sourcecode:: python
98 .. sourcecode:: python
99
99
100 from mpi4py import MPI
100 from mpi4py import MPI
101 import numpy as np
101 import numpy as np
102
102
103 def psum(a):
103 def psum(a):
104 s = np.sum(a)
104 s = np.sum(a)
105 rcvBuf = np.array(0.0,'d')
105 rcvBuf = np.array(0.0,'d')
106 MPI.COMM_WORLD.Allreduce([s, MPI.DOUBLE],
106 MPI.COMM_WORLD.Allreduce([s, MPI.DOUBLE],
107 [rcvBuf, MPI.DOUBLE],
107 [rcvBuf, MPI.DOUBLE],
108 op=MPI.SUM)
108 op=MPI.SUM)
109 return rcvBuf
109 return rcvBuf
110
110
111 Now, start an IPython cluster::
111 Now, start an IPython cluster::
112
112
113 $ ipclusterz start -p mpi -n 4
113 $ ipclusterz start -p mpi -n 4
114
114
115 .. note::
115 .. note::
116
116
117 It is assumed here that the mpi profile has been set up, as described :ref:`here
117 It is assumed here that the mpi profile has been set up, as described :ref:`here
118 <parallel_process>`.
118 <parallel_process>`.
119
119
120 Finally, connect to the cluster and use this function interactively. In this
120 Finally, connect to the cluster and use this function interactively. In this
121 case, we create a random array on each engine and sum up all the random arrays
121 case, we create a random array on each engine and sum up all the random arrays
122 using our :func:`psum` function:
122 using our :func:`psum` function:
123
123
124 .. sourcecode:: ipython
124 .. sourcecode:: ipython
125
125
126 In [1]: from IPython.zmq.parallel import client
126 In [1]: from IPython.parallel import Client
127
127
128 In [2]: %load_ext parallel_magic
128 In [2]: %load_ext parallel_magic
129
129
130 In [3]: c = client.Client(profile='mpi')
130 In [3]: c = Client(profile='mpi')
131
131
132 In [4]: view = c[:]
132 In [4]: view = c[:]
133
133
134 In [5]: view.activate()
134 In [5]: view.activate()
135
135
136 # run the contents of the file on each engine:
136 # run the contents of the file on each engine:
137 In [6]: view.run('psum.py')
137 In [6]: view.run('psum.py')
138
138
139 In [6]: px a = np.random.rand(100)
139 In [6]: px a = np.random.rand(100)
140 Parallel execution on engines: [0,1,2,3]
140 Parallel execution on engines: [0,1,2,3]
141
141
142 In [8]: px s = psum(a)
142 In [8]: px s = psum(a)
143 Parallel execution on engines: [0,1,2,3]
143 Parallel execution on engines: [0,1,2,3]
144
144
145 In [9]: view['s']
145 In [9]: view['s']
146 Out[9]: [187.451545803,187.451545803,187.451545803,187.451545803]
146 Out[9]: [187.451545803,187.451545803,187.451545803,187.451545803]
147
147
148 Any Python code that makes calls to MPI can be used in this manner, including
148 Any Python code that makes calls to MPI can be used in this manner, including
149 compiled C, C++ and Fortran libraries that have been exposed to Python.
149 compiled C, C++ and Fortran libraries that have been exposed to Python.
150
150
151 .. [MPI] Message Passing Interface. http://www-unix.mcs.anl.gov/mpi/
151 .. [MPI] Message Passing Interface. http://www-unix.mcs.anl.gov/mpi/
152 .. [mpi4py] MPI for Python. mpi4py: http://mpi4py.scipy.org/
152 .. [mpi4py] MPI for Python. mpi4py: http://mpi4py.scipy.org/
153 .. [OpenMPI] Open MPI. http://www.open-mpi.org/
153 .. [OpenMPI] Open MPI. http://www.open-mpi.org/
154 .. [PyTrilinos] PyTrilinos. http://trilinos.sandia.gov/packages/pytrilinos/
154 .. [PyTrilinos] PyTrilinos. http://trilinos.sandia.gov/packages/pytrilinos/
155
155
156
156
@@ -1,799 +1,802 b''
1 .. _parallel_multiengine:
1 .. _parallel_multiengine:
2
2
3 ==========================
3 ==========================
4 IPython's Direct interface
4 IPython's Direct interface
5 ==========================
5 ==========================
6
6
7 The direct, or multiengine, interface represents one possible way of working with a set of
7 The direct, or multiengine, interface represents one possible way of working with a set of
8 IPython engines. The basic idea behind the multiengine interface is that the
8 IPython engines. The basic idea behind the multiengine interface is that the
9 capabilities of each engine are directly and explicitly exposed to the user.
9 capabilities of each engine are directly and explicitly exposed to the user.
10 Thus, in the multiengine interface, each engine is given an id that is used to
10 Thus, in the multiengine interface, each engine is given an id that is used to
11 identify the engine and give it work to do. This interface is very intuitive
11 identify the engine and give it work to do. This interface is very intuitive
12 and is designed with interactive usage in mind, and is the best place for
12 and is designed with interactive usage in mind, and is the best place for
13 new users of IPython to begin.
13 new users of IPython to begin.
14
14
15 Starting the IPython controller and engines
15 Starting the IPython controller and engines
16 ===========================================
16 ===========================================
17
17
18 To follow along with this tutorial, you will need to start the IPython
18 To follow along with this tutorial, you will need to start the IPython
19 controller and four IPython engines. The simplest way of doing this is to use
19 controller and four IPython engines. The simplest way of doing this is to use
20 the :command:`ipclusterz` command::
20 the :command:`ipclusterz` command::
21
21
22 $ ipclusterz start -n 4
22 $ ipclusterz start -n 4
23
23
24 For more detailed information about starting the controller and engines, see
24 For more detailed information about starting the controller and engines, see
25 our :ref:`introduction <ip1par>` to using IPython for parallel computing.
25 our :ref:`introduction <ip1par>` to using IPython for parallel computing.
26
26
27 Creating a ``Client`` instance
27 Creating a ``Client`` instance
28 ==============================
28 ==============================
29
29
30 The first step is to import the IPython :mod:`IPython.zmq.parallel.client`
30 The first step is to import the IPython :mod:`IPython.parallel`
31 module and then create a :class:`.Client` instance:
31 module and then create a :class:`.Client` instance:
32
32
33 .. sourcecode:: ipython
33 .. sourcecode:: ipython
34
34
35 In [1]: from IPython.zmq.parallel import client
35 In [1]: from IPython.parallel import Client
36
36
37 In [2]: rc = client.Client()
37 In [2]: rc = Client()
38
38
39 This form assumes that the default connection information (stored in
39 This form assumes that the default connection information (stored in
40 :file:`ipcontroller-client.json` found in :file:`IPYTHON_DIR/clusterz_default/security`) is
40 :file:`ipcontroller-client.json` found in :file:`IPYTHON_DIR/clusterz_default/security`) is
41 accurate. If the controller was started on a remote machine, you must copy that connection
41 accurate. If the controller was started on a remote machine, you must copy that connection
42 file to the client machine, or enter its contents as arguments to the Client constructor:
42 file to the client machine, or enter its contents as arguments to the Client constructor:
43
43
44 .. sourcecode:: ipython
44 .. sourcecode:: ipython
45
45
46 # If you have copied the json connector file from the controller:
46 # If you have copied the json connector file from the controller:
47 In [2]: rc = client.Client('/path/to/ipcontroller-client.json')
47 In [2]: rc = Client('/path/to/ipcontroller-client.json')
48 # or to connect with a specific profile you have set up:
48 # or to connect with a specific profile you have set up:
49 In [3]: rc = client.Client(profile='mpi')
49 In [3]: rc = Client(profile='mpi')
50
50
51
51
52 To make sure there are engines connected to the controller, users can get a list
52 To make sure there are engines connected to the controller, users can get a list
53 of engine ids:
53 of engine ids:
54
54
55 .. sourcecode:: ipython
55 .. sourcecode:: ipython
56
56
57 In [3]: rc.ids
57 In [3]: rc.ids
58 Out[3]: [0, 1, 2, 3]
58 Out[3]: [0, 1, 2, 3]
59
59
60 Here we see that there are four engines ready to do work for us.
60 Here we see that there are four engines ready to do work for us.
61
61
62 For direct execution, we will make use of a :class:`DirectView` object, which can be
62 For direct execution, we will make use of a :class:`DirectView` object, which can be
63 constructed via list-access to the client:
63 constructed via list-access to the client:
64
64
65 .. sourcecode:: ipython
65 .. sourcecode:: ipython
66
66
67 In [4]: dview = rc[:] # use all engines
67 In [4]: dview = rc[:] # use all engines
68
68
69 .. seealso::
69 .. seealso::
70
70
71 For more information, see the in-depth explanation of :ref:`Views <parallel_details>`.
71 For more information, see the in-depth explanation of :ref:`Views <parallel_details>`.
72
72
73
73
74 Quick and easy parallelism
74 Quick and easy parallelism
75 ==========================
75 ==========================
76
76
77 In many cases, you simply want to apply a Python function to a sequence of
77 In many cases, you simply want to apply a Python function to a sequence of
78 objects, but *in parallel*. The client interface provides a simple way
78 objects, but *in parallel*. The client interface provides a simple way
79 of accomplishing this: using the DirectView's :meth:`~DirectView.map` method.
79 of accomplishing this: using the DirectView's :meth:`~DirectView.map` method.
80
80
81 Parallel map
81 Parallel map
82 ------------
82 ------------
83
83
84 Python's builtin :func:`map` functions allows a function to be applied to a
84 Python's builtin :func:`map` functions allows a function to be applied to a
85 sequence element-by-element. This type of code is typically trivial to
85 sequence element-by-element. This type of code is typically trivial to
86 parallelize. In fact, since IPython's interface is all about functions anyway,
86 parallelize. In fact, since IPython's interface is all about functions anyway,
87 you can just use the builtin :func:`map` with a :class:`RemoteFunction`, or a
87 you can just use the builtin :func:`map` with a :class:`RemoteFunction`, or a
88 DirectView's :meth:`map` method:
88 DirectView's :meth:`map` method:
89
89
90 .. sourcecode:: ipython
90 .. sourcecode:: ipython
91
91
92 In [62]: serial_result = map(lambda x:x**10, range(32))
92 In [62]: serial_result = map(lambda x:x**10, range(32))
93
93
94 In [63]: parallel_result = dview.map_sync(lambda x: x**10, range(32))
94 In [63]: parallel_result = dview.map_sync(lambda x: x**10, range(32))
95
95
96 In [67]: serial_result==parallel_result
96 In [67]: serial_result==parallel_result
97 Out[67]: True
97 Out[67]: True
98
98
99
99
100 .. note::
100 .. note::
101
101
102 The :class:`DirectView`'s version of :meth:`map` does
102 The :class:`DirectView`'s version of :meth:`map` does
103 not do dynamic load balancing. For a load balanced version, use a
103 not do dynamic load balancing. For a load balanced version, use a
104 :class:`LoadBalancedView`.
104 :class:`LoadBalancedView`.
105
105
106 .. seealso::
106 .. seealso::
107
107
108 :meth:`map` is implemented via :class:`ParallelFunction`.
108 :meth:`map` is implemented via :class:`ParallelFunction`.
109
109
110 Remote function decorators
110 Remote function decorators
111 --------------------------
111 --------------------------
112
112
113 Remote functions are just like normal functions, but when they are called,
113 Remote functions are just like normal functions, but when they are called,
114 they execute on one or more engines, rather than locally. IPython provides
114 they execute on one or more engines, rather than locally. IPython provides
115 two decorators:
115 two decorators:
116
116
117 .. sourcecode:: ipython
117 .. sourcecode:: ipython
118
118
119 In [10]: @dview.remote(block=True)
119 In [10]: @dview.remote(block=True)
120 ...: def getpid():
120 ...: def getpid():
121 ...: import os
121 ...: import os
122 ...: return os.getpid()
122 ...: return os.getpid()
123 ...:
123 ...:
124
124
125 In [11]: getpid()
125 In [11]: getpid()
126 Out[11]: [12345, 12346, 12347, 12348]
126 Out[11]: [12345, 12346, 12347, 12348]
127
127
128 The ``@parallel`` decorator creates parallel functions, that break up an element-wise
128 The ``@parallel`` decorator creates parallel functions, that break up an element-wise
129 operations and distribute them, reconstructing the result.
129 operations and distribute them, reconstructing the result.
130
130
131 .. sourcecode:: ipython
131 .. sourcecode:: ipython
132
132
133 In [12]: import numpy as np
133 In [12]: import numpy as np
134
134
135 In [13]: A = np.random.random((64,48))
135 In [13]: A = np.random.random((64,48))
136
136
137 In [14]: @dview.parallel(block=True)
137 In [14]: @dview.parallel(block=True)
138 ...: def pmul(A,B):
138 ...: def pmul(A,B):
139 ...: return A*B
139 ...: return A*B
140
140
141 In [15]: C_local = A*A
141 In [15]: C_local = A*A
142
142
143 In [16]: C_remote = pmul(A,A)
143 In [16]: C_remote = pmul(A,A)
144
144
145 In [17]: (C_local == C_remote).all()
145 In [17]: (C_local == C_remote).all()
146 Out[17]: True
146 Out[17]: True
147
147
148 .. seealso::
148 .. seealso::
149
149
150 See the docstrings for the :func:`parallel` and :func:`remote` decorators for
150 See the docstrings for the :func:`parallel` and :func:`remote` decorators for
151 options.
151 options.
152
152
153 Calling Python functions
153 Calling Python functions
154 ========================
154 ========================
155
155
156 The most basic type of operation that can be performed on the engines is to
156 The most basic type of operation that can be performed on the engines is to
157 execute Python code or call Python functions. Executing Python code can be
157 execute Python code or call Python functions. Executing Python code can be
158 done in blocking or non-blocking mode (non-blocking is default) using the
158 done in blocking or non-blocking mode (non-blocking is default) using the
159 :meth:`.View.execute` method, and calling functions can be done via the
159 :meth:`.View.execute` method, and calling functions can be done via the
160 :meth:`.View.apply` method.
160 :meth:`.View.apply` method.
161
161
162 apply
162 apply
163 -----
163 -----
164
164
165 The main method for doing remote execution (in fact, all methods that
165 The main method for doing remote execution (in fact, all methods that
166 communicate with the engines are built on top of it), is :meth:`View.apply`.
166 communicate with the engines are built on top of it), is :meth:`View.apply`.
167
167
168 We strive to provide the cleanest interface we can, so `apply` has the following
168 We strive to provide the cleanest interface we can, so `apply` has the following
169 signature:
169 signature:
170
170
171 .. sourcecode:: python
171 .. sourcecode:: python
172
172
173 view.apply(f, *args, **kwargs)
173 view.apply(f, *args, **kwargs)
174
174
175 There are various ways to call functions with IPython, and these flags are set as
175 There are various ways to call functions with IPython, and these flags are set as
176 attributes of the View. The ``DirectView`` has just two of these flags:
176 attributes of the View. The ``DirectView`` has just two of these flags:
177
177
178 dv.block : bool
178 dv.block : bool
179 whether to wait for the result, or return an :class:`AsyncResult` object
179 whether to wait for the result, or return an :class:`AsyncResult` object
180 immediately
180 immediately
181 dv.track : bool
181 dv.track : bool
182 whether to instruct pyzmq to track when
182 whether to instruct pyzmq to track when
183 This is primarily useful for non-copying sends of numpy arrays that you plan to
183 This is primarily useful for non-copying sends of numpy arrays that you plan to
184 edit in-place. You need to know when it becomes safe to edit the buffer
184 edit in-place. You need to know when it becomes safe to edit the buffer
185 without corrupting the message.
185 without corrupting the message.
186
186
187
187
188 Creating a view is simple: index-access on a client creates a :class:`.DirectView`.
188 Creating a view is simple: index-access on a client creates a :class:`.DirectView`.
189
189
190 .. sourcecode:: ipython
190 .. sourcecode:: ipython
191
191
192 In [4]: view = rc[1:3]
192 In [4]: view = rc[1:3]
193 Out[4]: <DirectView [1, 2]>
193 Out[4]: <DirectView [1, 2]>
194
194
195 In [5]: view.apply<tab>
195 In [5]: view.apply<tab>
196 view.apply view.apply_async view.apply_sync view.apply_with_flags
196 view.apply view.apply_async view.apply_sync view.apply_with_flags
197
197
198 For convenience, you can set block temporarily for a single call with the extra sync/async methods.
198 For convenience, you can set block temporarily for a single call with the extra sync/async methods.
199
199
200 Blocking execution
200 Blocking execution
201 ------------------
201 ------------------
202
202
203 In blocking mode, the :class:`.DirectView` object (called ``dview`` in
203 In blocking mode, the :class:`.DirectView` object (called ``dview`` in
204 these examples) submits the command to the controller, which places the
204 these examples) submits the command to the controller, which places the
205 command in the engines' queues for execution. The :meth:`apply` call then
205 command in the engines' queues for execution. The :meth:`apply` call then
206 blocks until the engines are done executing the command:
206 blocks until the engines are done executing the command:
207
207
208 .. sourcecode:: ipython
208 .. sourcecode:: ipython
209
209
210 In [2]: dview = rc[:] # A DirectView of all engines
210 In [2]: dview = rc[:] # A DirectView of all engines
211 In [3]: dview.block=True
211 In [3]: dview.block=True
212 In [4]: dview['a'] = 5
212 In [4]: dview['a'] = 5
213
213
214 In [5]: dview['b'] = 10
214 In [5]: dview['b'] = 10
215
215
216 In [6]: dview.apply(lambda x: a+b+x, 27)
216 In [6]: dview.apply(lambda x: a+b+x, 27)
217 Out[6]: [42, 42, 42, 42]
217 Out[6]: [42, 42, 42, 42]
218
218
219 You can also select blocking execution on a call-by-call basis with the :meth:`apply_sync`
219 You can also select blocking execution on a call-by-call basis with the :meth:`apply_sync`
220 method:
220 method:
221
221
222 In [7]: dview.block=False
222 In [7]: dview.block=False
223
223
224 In [8]: dview.apply_sync(lambda x: a+b+x, 27)
224 In [8]: dview.apply_sync(lambda x: a+b+x, 27)
225 Out[8]: [42, 42, 42, 42]
225 Out[8]: [42, 42, 42, 42]
226
226
227 Python commands can be executed as strings on specific engines by using a View's ``execute``
227 Python commands can be executed as strings on specific engines by using a View's ``execute``
228 method:
228 method:
229
229
230 .. sourcecode:: ipython
230 .. sourcecode:: ipython
231
231
232 In [6]: rc[::2].execute('c=a+b')
232 In [6]: rc[::2].execute('c=a+b')
233
233
234 In [7]: rc[1::2].execute('c=a-b')
234 In [7]: rc[1::2].execute('c=a-b')
235
235
236 In [8]: rc[:]['c'] # shorthand for rc[:].pull('c', block=True)
236 In [8]: rc[:]['c'] # shorthand for rc[:].pull('c', block=True)
237 Out[8]: [15, -5, 15, -5]
237 Out[8]: [15, -5, 15, -5]
238
238
239
239
240 Non-blocking execution
240 Non-blocking execution
241 ----------------------
241 ----------------------
242
242
243 In non-blocking mode, :meth:`apply` submits the command to be executed and
243 In non-blocking mode, :meth:`apply` submits the command to be executed and
244 then returns a :class:`AsyncResult` object immediately. The
244 then returns a :class:`AsyncResult` object immediately. The
245 :class:`AsyncResult` object gives you a way of getting a result at a later
245 :class:`AsyncResult` object gives you a way of getting a result at a later
246 time through its :meth:`get` method.
246 time through its :meth:`get` method.
247
247
248 .. Note::
248 .. Note::
249
249
250 The :class:`AsyncResult` object provides a superset of the interface in
250 The :class:`AsyncResult` object provides a superset of the interface in
251 :py:class:`multiprocessing.pool.AsyncResult`. See the
251 :py:class:`multiprocessing.pool.AsyncResult`. See the
252 `official Python documentation <http://docs.python.org/library/multiprocessing#multiprocessing.pool.AsyncResult>`_
252 `official Python documentation <http://docs.python.org/library/multiprocessing#multiprocessing.pool.AsyncResult>`_
253 for more.
253 for more.
254
254
255
255
256 This allows you to quickly submit long running commands without blocking your
256 This allows you to quickly submit long running commands without blocking your
257 local Python/IPython session:
257 local Python/IPython session:
258
258
259 .. sourcecode:: ipython
259 .. sourcecode:: ipython
260
260
261 # define our function
261 # define our function
262 In [6]: def wait(t):
262 In [6]: def wait(t):
263 ...: import time
263 ...: import time
264 ...: tic = time.time()
264 ...: tic = time.time()
265 ...: time.sleep(t)
265 ...: time.sleep(t)
266 ...: return time.time()-tic
266 ...: return time.time()-tic
267
267
268 # In non-blocking mode
268 # In non-blocking mode
269 In [7]: ar = dview.apply_async(wait, 2)
269 In [7]: ar = dview.apply_async(wait, 2)
270
270
271 # Now block for the result
271 # Now block for the result
272 In [8]: ar.get()
272 In [8]: ar.get()
273 Out[8]: [2.0006198883056641, 1.9997570514678955, 1.9996809959411621, 2.0003249645233154]
273 Out[8]: [2.0006198883056641, 1.9997570514678955, 1.9996809959411621, 2.0003249645233154]
274
274
275 # Again in non-blocking mode
275 # Again in non-blocking mode
276 In [9]: ar = dview.apply_async(wait, 10)
276 In [9]: ar = dview.apply_async(wait, 10)
277
277
278 # Poll to see if the result is ready
278 # Poll to see if the result is ready
279 In [10]: ar.ready()
279 In [10]: ar.ready()
280 Out[10]: False
280 Out[10]: False
281
281
282 # ask for the result, but wait a maximum of 1 second:
282 # ask for the result, but wait a maximum of 1 second:
283 In [45]: ar.get(1)
283 In [45]: ar.get(1)
284 ---------------------------------------------------------------------------
284 ---------------------------------------------------------------------------
285 TimeoutError Traceback (most recent call last)
285 TimeoutError Traceback (most recent call last)
286 /home/you/<ipython-input-45-7cd858bbb8e0> in <module>()
286 /home/you/<ipython-input-45-7cd858bbb8e0> in <module>()
287 ----> 1 ar.get(1)
287 ----> 1 ar.get(1)
288
288
289 /path/to/site-packages/IPython/zmq/parallel/asyncresult.pyc in get(self, timeout)
289 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
290 62 raise self._exception
290 62 raise self._exception
291 63 else:
291 63 else:
292 ---> 64 raise error.TimeoutError("Result not ready.")
292 ---> 64 raise error.TimeoutError("Result not ready.")
293 65
293 65
294 66 def ready(self):
294 66 def ready(self):
295
295
296 TimeoutError: Result not ready.
296 TimeoutError: Result not ready.
297
297
298 .. Note::
298 .. Note::
299
299
300 Note the import inside the function. This is a common model, to ensure
300 Note the import inside the function. This is a common model, to ensure
301 that the appropriate modules are imported where the task is run. You can
301 that the appropriate modules are imported where the task is run. You can
302 also manually import modules into the engine(s) namespace(s) via
302 also manually import modules into the engine(s) namespace(s) via
303 :meth:`view.execute('import numpy')`.
303 :meth:`view.execute('import numpy')`.
304
304
305 Often, it is desirable to wait until a set of :class:`AsyncResult` objects
305 Often, it is desirable to wait until a set of :class:`AsyncResult` objects
306 are done. For this, there is a the method :meth:`wait`. This method takes a
306 are done. For this, there is a the method :meth:`wait`. This method takes a
307 tuple of :class:`AsyncResult` objects (or `msg_ids` or indices to the client's History),
307 tuple of :class:`AsyncResult` objects (or `msg_ids` or indices to the client's History),
308 and blocks until all of the associated results are ready:
308 and blocks until all of the associated results are ready:
309
309
310 .. sourcecode:: ipython
310 .. sourcecode:: ipython
311
311
312 In [72]: dview.block=False
312 In [72]: dview.block=False
313
313
314 # A trivial list of AsyncResults objects
314 # A trivial list of AsyncResults objects
315 In [73]: pr_list = [dview.apply_async(wait, 3) for i in range(10)]
315 In [73]: pr_list = [dview.apply_async(wait, 3) for i in range(10)]
316
316
317 # Wait until all of them are done
317 # Wait until all of them are done
318 In [74]: dview.wait(pr_list)
318 In [74]: dview.wait(pr_list)
319
319
320 # Then, their results are ready using get() or the `.r` attribute
320 # Then, their results are ready using get() or the `.r` attribute
321 In [75]: pr_list[0].get()
321 In [75]: pr_list[0].get()
322 Out[75]: [2.9982571601867676, 2.9982588291168213, 2.9987530708312988, 2.9990990161895752]
322 Out[75]: [2.9982571601867676, 2.9982588291168213, 2.9987530708312988, 2.9990990161895752]
323
324
323
325
324
326 The ``block`` attribute
327 -----------------------
328
325
329 Many View methods(excluding :meth:`apply`) accept
326 The ``block`` and ``targets`` keyword arguments and attributes
330 ``block`` as a keyword argument. As we have seen above, these
327 --------------------------------------------------------------
331 keyword arguments control the blocking mode. The :class:`View` class also has
328
332 a :attr:`block` attribute that controls the default behavior when the keyword
329 Most DirectView methods (excluding :meth:`apply` and :meth:`map`) accept ``block`` and
333 argument is not provided. Thus the following logic is used for :attr:`block`:
330 ``targets`` as keyword arguments. As we have seen above, these keyword arguments control the
331 blocking mode and which engines the command is applied to. The :class:`View` class also has
332 :attr:`block` and :attr:`targets` attributes that control the default behavior when the keyword
333 arguments are not provided. Thus the following logic is used for :attr:`block` and :attr:`targets`:
334
334
335 * If no keyword argument is provided, the instance attributes are used.
335 * If no keyword argument is provided, the instance attributes are used.
336 * Keyword argument, if provided override the instance attributes for
336 * Keyword argument, if provided override the instance attributes for
337 the duration of a single call.
337 the duration of a single call.
338
338
339 The following examples demonstrate how to use the instance attributes:
339 The following examples demonstrate how to use the instance attributes:
340
340
341 .. sourcecode:: ipython
341 .. sourcecode:: ipython
342
342
343 In [16]: dview.targets = [0,2]
344
343 In [17]: dview.block = False
345 In [17]: dview.block = False
344
346
345 In [18]: ar = dview.apply(lambda : 10)
347 In [18]: ar = dview.apply(lambda : 10)
346
348
347 In [19]: ar.get()
349 In [19]: ar.get()
348 Out[19]: [10, 10, 10, 10]
350 Out[19]: [10, 10]
349
351
352 In [16]: dview.targets = v.client.ids # all engines (4)
353
350 In [21]: dview.block = True
354 In [21]: dview.block = True
351
355
352 # Note targets='all' means all engines
353 In [22]: dview.apply(lambda : 42)
356 In [22]: dview.apply(lambda : 42)
354 Out[22]: [42, 42, 42, 42]
357 Out[22]: [42, 42, 42, 42]
355
358
356 The :attr:`block` and :attr:`targets` instance attributes of the
359 The :attr:`block` and :attr:`targets` instance attributes of the
357 :class:`.DirectView` also determine the behavior of the parallel magic commands.
360 :class:`.DirectView` also determine the behavior of the parallel magic commands.
358
361
359 Parallel magic commands
362 Parallel magic commands
360 -----------------------
363 -----------------------
361
364
362 .. warning::
365 .. warning::
363
366
364 The magics have not been changed to work with the zeromq system. The
367 The magics have not been changed to work with the zeromq system. The
365 magics do work, but *do not* print stdin/out like they used to in IPython.kernel.
368 magics do work, but *do not* print stdin/out like they used to in IPython.kernel.
366
369
367 We provide a few IPython magic commands (``%px``, ``%autopx`` and ``%result``)
370 We provide a few IPython magic commands (``%px``, ``%autopx`` and ``%result``)
368 that make it more pleasant to execute Python commands on the engines
371 that make it more pleasant to execute Python commands on the engines
369 interactively. These are simply shortcuts to :meth:`execute` and
372 interactively. These are simply shortcuts to :meth:`execute` and
370 :meth:`get_result` of the :class:`DirectView`. The ``%px`` magic executes a single
373 :meth:`get_result` of the :class:`DirectView`. The ``%px`` magic executes a single
371 Python command on the engines specified by the :attr:`targets` attribute of the
374 Python command on the engines specified by the :attr:`targets` attribute of the
372 :class:`DirectView` instance:
375 :class:`DirectView` instance:
373
376
374 .. sourcecode:: ipython
377 .. sourcecode:: ipython
375
378
376 # load the parallel magic extension:
379 # load the parallel magic extension:
377 In [21]: %load_ext parallelmagic
380 In [21]: %load_ext parallelmagic
378
381
379 # Create a DirectView for all targets
382 # Create a DirectView for all targets
380 In [22]: dv = rc[:]
383 In [22]: dv = rc[:]
381
384
382 # Make this DirectView active for parallel magic commands
385 # Make this DirectView active for parallel magic commands
383 In [23]: dv.activate()
386 In [23]: dv.activate()
384
387
385 In [24]: dv.block=True
388 In [24]: dv.block=True
386
389
387 In [25]: import numpy
390 In [25]: import numpy
388
391
389 In [26]: %px import numpy
392 In [26]: %px import numpy
390 Parallel execution on engines: [0, 1, 2, 3]
393 Parallel execution on engines: [0, 1, 2, 3]
391
394
392 In [27]: %px a = numpy.random.rand(2,2)
395 In [27]: %px a = numpy.random.rand(2,2)
393 Parallel execution on engines: [0, 1, 2, 3]
396 Parallel execution on engines: [0, 1, 2, 3]
394
397
395 In [28]: %px ev = numpy.linalg.eigvals(a)
398 In [28]: %px ev = numpy.linalg.eigvals(a)
396 Parallel execution on engines: [0, 1, 2, 3]
399 Parallel execution on engines: [0, 1, 2, 3]
397
400
398 In [28]: dv['ev']
401 In [28]: dv['ev']
399 Out[28]: [ array([ 1.09522024, -0.09645227]),
402 Out[28]: [ array([ 1.09522024, -0.09645227]),
400 array([ 1.21435496, -0.35546712]),
403 array([ 1.21435496, -0.35546712]),
401 array([ 0.72180653, 0.07133042]),
404 array([ 0.72180653, 0.07133042]),
402 array([ 1.46384341e+00, 1.04353244e-04])
405 array([ 1.46384341e+00, 1.04353244e-04])
403 ]
406 ]
404
407
405 The ``%result`` magic gets the most recent result, or takes an argument
408 The ``%result`` magic gets the most recent result, or takes an argument
406 specifying the index of the result to be requested. It is simply a shortcut to the
409 specifying the index of the result to be requested. It is simply a shortcut to the
407 :meth:`get_result` method:
410 :meth:`get_result` method:
408
411
409 .. sourcecode:: ipython
412 .. sourcecode:: ipython
410
413
411 In [29]: dv.apply_async(lambda : ev)
414 In [29]: dv.apply_async(lambda : ev)
412
415
413 In [30]: %result
416 In [30]: %result
414 Out[30]: [ [ 1.28167017 0.14197338],
417 Out[30]: [ [ 1.28167017 0.14197338],
415 [-0.14093616 1.27877273],
418 [-0.14093616 1.27877273],
416 [-0.37023573 1.06779409],
419 [-0.37023573 1.06779409],
417 [ 0.83664764 -0.25602658] ]
420 [ 0.83664764 -0.25602658] ]
418
421
419 The ``%autopx`` magic switches to a mode where everything you type is executed
422 The ``%autopx`` magic switches to a mode where everything you type is executed
420 on the engines given by the :attr:`targets` attribute:
423 on the engines given by the :attr:`targets` attribute:
421
424
422 .. sourcecode:: ipython
425 .. sourcecode:: ipython
423
426
424 In [30]: dv.block=False
427 In [30]: dv.block=False
425
428
426 In [31]: %autopx
429 In [31]: %autopx
427 Auto Parallel Enabled
430 Auto Parallel Enabled
428 Type %autopx to disable
431 Type %autopx to disable
429
432
430 In [32]: max_evals = []
433 In [32]: max_evals = []
431 <IPython.zmq.parallel.asyncresult.AsyncResult object at 0x17b8a70>
434 <IPython.parallel.asyncresult.AsyncResult object at 0x17b8a70>
432
435
433 In [33]: for i in range(100):
436 In [33]: for i in range(100):
434 ....: a = numpy.random.rand(10,10)
437 ....: a = numpy.random.rand(10,10)
435 ....: a = a+a.transpose()
438 ....: a = a+a.transpose()
436 ....: evals = numpy.linalg.eigvals(a)
439 ....: evals = numpy.linalg.eigvals(a)
437 ....: max_evals.append(evals[0].real)
440 ....: max_evals.append(evals[0].real)
438 ....:
441 ....:
439 ....:
442 ....:
440 <IPython.zmq.parallel.asyncresult.AsyncResult object at 0x17af8f0>
443 <IPython.parallel.asyncresult.AsyncResult object at 0x17af8f0>
441
444
442 In [34]: %autopx
445 In [34]: %autopx
443 Auto Parallel Disabled
446 Auto Parallel Disabled
444
447
445 In [35]: dv.block=True
448 In [35]: dv.block=True
446
449
447 In [36]: px ans= "Average max eigenvalue is: %f"%(sum(max_evals)/len(max_evals))
450 In [36]: px ans= "Average max eigenvalue is: %f"%(sum(max_evals)/len(max_evals))
448 Parallel execution on engines: [0, 1, 2, 3]
451 Parallel execution on engines: [0, 1, 2, 3]
449
452
450 In [37]: dv['ans']
453 In [37]: dv['ans']
451 Out[37]: [ 'Average max eigenvalue is: 10.1387247332',
454 Out[37]: [ 'Average max eigenvalue is: 10.1387247332',
452 'Average max eigenvalue is: 10.2076902286',
455 'Average max eigenvalue is: 10.2076902286',
453 'Average max eigenvalue is: 10.1891484655',
456 'Average max eigenvalue is: 10.1891484655',
454 'Average max eigenvalue is: 10.1158837784',]
457 'Average max eigenvalue is: 10.1158837784',]
455
458
456
459
457 Moving Python objects around
460 Moving Python objects around
458 ============================
461 ============================
459
462
460 In addition to calling functions and executing code on engines, you can
463 In addition to calling functions and executing code on engines, you can
461 transfer Python objects to and from your IPython session and the engines. In
464 transfer Python objects to and from your IPython session and the engines. In
462 IPython, these operations are called :meth:`push` (sending an object to the
465 IPython, these operations are called :meth:`push` (sending an object to the
463 engines) and :meth:`pull` (getting an object from the engines).
466 engines) and :meth:`pull` (getting an object from the engines).
464
467
465 Basic push and pull
468 Basic push and pull
466 -------------------
469 -------------------
467
470
468 Here are some examples of how you use :meth:`push` and :meth:`pull`:
471 Here are some examples of how you use :meth:`push` and :meth:`pull`:
469
472
470 .. sourcecode:: ipython
473 .. sourcecode:: ipython
471
474
472 In [38]: dview.push(dict(a=1.03234,b=3453))
475 In [38]: dview.push(dict(a=1.03234,b=3453))
473 Out[38]: [None,None,None,None]
476 Out[38]: [None,None,None,None]
474
477
475 In [39]: dview.pull('a')
478 In [39]: dview.pull('a')
476 Out[39]: [ 1.03234, 1.03234, 1.03234, 1.03234]
479 Out[39]: [ 1.03234, 1.03234, 1.03234, 1.03234]
477
480
478 In [40]: rc[0].pull('b')
481 In [40]: rc[0].pull('b')
479 Out[40]: 3453
482 Out[40]: 3453
480
483
481 In [41]: dview.pull(('a','b'))
484 In [41]: dview.pull(('a','b'))
482 Out[41]: [ [1.03234, 3453], [1.03234, 3453], [1.03234, 3453], [1.03234, 3453] ]
485 Out[41]: [ [1.03234, 3453], [1.03234, 3453], [1.03234, 3453], [1.03234, 3453] ]
483
486
484 In [43]: dview.push(dict(c='speed'))
487 In [43]: dview.push(dict(c='speed'))
485 Out[43]: [None,None,None,None]
488 Out[43]: [None,None,None,None]
486
489
487 In non-blocking mode :meth:`push` and :meth:`pull` also return
490 In non-blocking mode :meth:`push` and :meth:`pull` also return
488 :class:`AsyncResult` objects:
491 :class:`AsyncResult` objects:
489
492
490 .. sourcecode:: ipython
493 .. sourcecode:: ipython
491
494
492 In [48]: ar = dview.pull('a', block=False)
495 In [48]: ar = dview.pull('a', block=False)
493
496
494 In [49]: ar.get()
497 In [49]: ar.get()
495 Out[49]: [1.03234, 1.03234, 1.03234, 1.03234]
498 Out[49]: [1.03234, 1.03234, 1.03234, 1.03234]
496
499
497
500
498 Dictionary interface
501 Dictionary interface
499 --------------------
502 --------------------
500
503
501 Since a Python namespace is just a :class:`dict`, :class:`DirectView` objects provide
504 Since a Python namespace is just a :class:`dict`, :class:`DirectView` objects provide
502 dictionary-style access by key and methods such as :meth:`get` and
505 dictionary-style access by key and methods such as :meth:`get` and
503 :meth:`update` for convenience. This make the remote namespaces of the engines
506 :meth:`update` for convenience. This make the remote namespaces of the engines
504 appear as a local dictionary. Underneath, these methods call :meth:`apply`:
507 appear as a local dictionary. Underneath, these methods call :meth:`apply`:
505
508
506 .. sourcecode:: ipython
509 .. sourcecode:: ipython
507
510
508 In [51]: dview['a']=['foo','bar']
511 In [51]: dview['a']=['foo','bar']
509
512
510 In [52]: dview['a']
513 In [52]: dview['a']
511 Out[52]: [ ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'] ]
514 Out[52]: [ ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'] ]
512
515
513 Scatter and gather
516 Scatter and gather
514 ------------------
517 ------------------
515
518
516 Sometimes it is useful to partition a sequence and push the partitions to
519 Sometimes it is useful to partition a sequence and push the partitions to
517 different engines. In MPI language, this is know as scatter/gather and we
520 different engines. In MPI language, this is know as scatter/gather and we
518 follow that terminology. However, it is important to remember that in
521 follow that terminology. However, it is important to remember that in
519 IPython's :class:`Client` class, :meth:`scatter` is from the
522 IPython's :class:`Client` class, :meth:`scatter` is from the
520 interactive IPython session to the engines and :meth:`gather` is from the
523 interactive IPython session to the engines and :meth:`gather` is from the
521 engines back to the interactive IPython session. For scatter/gather operations
524 engines back to the interactive IPython session. For scatter/gather operations
522 between engines, MPI should be used:
525 between engines, MPI should be used:
523
526
524 .. sourcecode:: ipython
527 .. sourcecode:: ipython
525
528
526 In [58]: dview.scatter('a',range(16))
529 In [58]: dview.scatter('a',range(16))
527 Out[58]: [None,None,None,None]
530 Out[58]: [None,None,None,None]
528
531
529 In [59]: dview['a']
532 In [59]: dview['a']
530 Out[59]: [ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15] ]
533 Out[59]: [ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15] ]
531
534
532 In [60]: dview.gather('a')
535 In [60]: dview.gather('a')
533 Out[60]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
536 Out[60]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
534
537
535 Other things to look at
538 Other things to look at
536 =======================
539 =======================
537
540
538 How to do parallel list comprehensions
541 How to do parallel list comprehensions
539 --------------------------------------
542 --------------------------------------
540
543
541 In many cases list comprehensions are nicer than using the map function. While
544 In many cases list comprehensions are nicer than using the map function. While
542 we don't have fully parallel list comprehensions, it is simple to get the
545 we don't have fully parallel list comprehensions, it is simple to get the
543 basic effect using :meth:`scatter` and :meth:`gather`:
546 basic effect using :meth:`scatter` and :meth:`gather`:
544
547
545 .. sourcecode:: ipython
548 .. sourcecode:: ipython
546
549
547 In [66]: dview.scatter('x',range(64))
550 In [66]: dview.scatter('x',range(64))
548
551
549 In [67]: %px y = [i**10 for i in x]
552 In [67]: %px y = [i**10 for i in x]
550 Parallel execution on engines: [0, 1, 2, 3]
553 Parallel execution on engines: [0, 1, 2, 3]
551 Out[67]:
554 Out[67]:
552
555
553 In [68]: y = dview.gather('y')
556 In [68]: y = dview.gather('y')
554
557
555 In [69]: print y
558 In [69]: print y
556 [0, 1, 1024, 59049, 1048576, 9765625, 60466176, 282475249, 1073741824,...]
559 [0, 1, 1024, 59049, 1048576, 9765625, 60466176, 282475249, 1073741824,...]
557
560
558 Parallel exceptions
561 Parallel exceptions
559 -------------------
562 -------------------
560
563
561 In the multiengine interface, parallel commands can raise Python exceptions,
564 In the multiengine interface, parallel commands can raise Python exceptions,
562 just like serial commands. But, it is a little subtle, because a single
565 just like serial commands. But, it is a little subtle, because a single
563 parallel command can actually raise multiple exceptions (one for each engine
566 parallel command can actually raise multiple exceptions (one for each engine
564 the command was run on). To express this idea, we have a
567 the command was run on). To express this idea, we have a
565 :exc:`CompositeError` exception class that will be raised in most cases. The
568 :exc:`CompositeError` exception class that will be raised in most cases. The
566 :exc:`CompositeError` class is a special type of exception that wraps one or
569 :exc:`CompositeError` class is a special type of exception that wraps one or
567 more other types of exceptions. Here is how it works:
570 more other types of exceptions. Here is how it works:
568
571
569 .. sourcecode:: ipython
572 .. sourcecode:: ipython
570
573
571 In [76]: dview.block=True
574 In [76]: dview.block=True
572
575
573 In [77]: dview.execute('1/0')
576 In [77]: dview.execute('1/0')
574 ---------------------------------------------------------------------------
577 ---------------------------------------------------------------------------
575 CompositeError Traceback (most recent call last)
578 CompositeError Traceback (most recent call last)
576 /home/you/<ipython-input-10-15c2c22dec39> in <module>()
579 /home/you/<ipython-input-10-15c2c22dec39> in <module>()
577 ----> 1 dview.execute('1/0', block=True)
580 ----> 1 dview.execute('1/0', block=True)
578
581
579 /path/to/site-packages/IPython/zmq/parallel/view.py in execute(self, code, block)
582 /path/to/site-packages/IPython/parallel/view.py in execute(self, code, block)
580 460 default: self.block
583 460 default: self.block
581 461 """
584 461 """
582 --> 462 return self.apply_with_flags(util._execute, args=(code,), block=block)
585 --> 462 return self.apply_with_flags(util._execute, args=(code,), block=block)
583 463
586 463
584 464 def run(self, filename, block=None):
587 464 def run(self, filename, block=None):
585
588
586 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
589 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
587
590
588 /path/to/site-packages/IPython/zmq/parallel/view.py in sync_results(f, self, *args, **kwargs)
591 /path/to/site-packages/IPython/parallel/view.py in sync_results(f, self, *args, **kwargs)
589 46 def sync_results(f, self, *args, **kwargs):
592 46 def sync_results(f, self, *args, **kwargs):
590 47 """sync relevant results from self.client to our results attribute."""
593 47 """sync relevant results from self.client to our results attribute."""
591 ---> 48 ret = f(self, *args, **kwargs)
594 ---> 48 ret = f(self, *args, **kwargs)
592 49 delta = self.outstanding.difference(self.client.outstanding)
595 49 delta = self.outstanding.difference(self.client.outstanding)
593 50 completed = self.outstanding.intersection(delta)
596 50 completed = self.outstanding.intersection(delta)
594
597
595 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
598 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
596
599
597 /path/to/site-packages/IPython/zmq/parallel/view.py in save_ids(f, self, *args, **kwargs)
600 /path/to/site-packages/IPython/parallel/view.py in save_ids(f, self, *args, **kwargs)
598 35 n_previous = len(self.client.history)
601 35 n_previous = len(self.client.history)
599 36 try:
602 36 try:
600 ---> 37 ret = f(self, *args, **kwargs)
603 ---> 37 ret = f(self, *args, **kwargs)
601 38 finally:
604 38 finally:
602 39 nmsgs = len(self.client.history) - n_previous
605 39 nmsgs = len(self.client.history) - n_previous
603
606
604 /path/to/site-packages/IPython/zmq/parallel/view.py in apply_with_flags(self, f, args, kwargs, block, track)
607 /path/to/site-packages/IPython/parallel/view.py in apply_with_flags(self, f, args, kwargs, block, track)
605 398 if block:
608 398 if block:
606 399 try:
609 399 try:
607 --> 400 return ar.get()
610 --> 400 return ar.get()
608 401 except KeyboardInterrupt:
611 401 except KeyboardInterrupt:
609 402 pass
612 402 pass
610
613
611 /path/to/site-packages/IPython/zmq/parallel/asyncresult.pyc in get(self, timeout)
614 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
612 87 return self._result
615 87 return self._result
613 88 else:
616 88 else:
614 ---> 89 raise self._exception
617 ---> 89 raise self._exception
615 90 else:
618 90 else:
616 91 raise error.TimeoutError("Result not ready.")
619 91 raise error.TimeoutError("Result not ready.")
617
620
618 CompositeError: one or more exceptions from call to method: _execute
621 CompositeError: one or more exceptions from call to method: _execute
619 [0:apply]: ZeroDivisionError: integer division or modulo by zero
622 [0:apply]: ZeroDivisionError: integer division or modulo by zero
620 [1:apply]: ZeroDivisionError: integer division or modulo by zero
623 [1:apply]: ZeroDivisionError: integer division or modulo by zero
621 [2:apply]: ZeroDivisionError: integer division or modulo by zero
624 [2:apply]: ZeroDivisionError: integer division or modulo by zero
622 [3:apply]: ZeroDivisionError: integer division or modulo by zero
625 [3:apply]: ZeroDivisionError: integer division or modulo by zero
623
626
624
627
625 Notice how the error message printed when :exc:`CompositeError` is raised has
628 Notice how the error message printed when :exc:`CompositeError` is raised has
626 information about the individual exceptions that were raised on each engine.
629 information about the individual exceptions that were raised on each engine.
627 If you want, you can even raise one of these original exceptions:
630 If you want, you can even raise one of these original exceptions:
628
631
629 .. sourcecode:: ipython
632 .. sourcecode:: ipython
630
633
631 In [80]: try:
634 In [80]: try:
632 ....: dview.execute('1/0')
635 ....: dview.execute('1/0')
633 ....: except client.CompositeError, e:
636 ....: except client.CompositeError, e:
634 ....: e.raise_exception()
637 ....: e.raise_exception()
635 ....:
638 ....:
636 ....:
639 ....:
637 ---------------------------------------------------------------------------
640 ---------------------------------------------------------------------------
638 ZeroDivisionError Traceback (most recent call last)
641 ZeroDivisionError Traceback (most recent call last)
639
642
640 /ipython1-client-r3021/docs/examples/<ipython console> in <module>()
643 /ipython1-client-r3021/docs/examples/<ipython console> in <module>()
641
644
642 /ipython1-client-r3021/ipython1/kernel/error.pyc in raise_exception(self, excid)
645 /ipython1-client-r3021/ipython1/kernel/error.pyc in raise_exception(self, excid)
643 156 raise IndexError("an exception with index %i does not exist"%excid)
646 156 raise IndexError("an exception with index %i does not exist"%excid)
644 157 else:
647 157 else:
645 --> 158 raise et, ev, etb
648 --> 158 raise et, ev, etb
646 159
649 159
647 160 def collect_exceptions(rlist, method):
650 160 def collect_exceptions(rlist, method):
648
651
649 ZeroDivisionError: integer division or modulo by zero
652 ZeroDivisionError: integer division or modulo by zero
650
653
651 If you are working in IPython, you can simple type ``%debug`` after one of
654 If you are working in IPython, you can simple type ``%debug`` after one of
652 these :exc:`CompositeError` exceptions is raised, and inspect the exception
655 these :exc:`CompositeError` exceptions is raised, and inspect the exception
653 instance:
656 instance:
654
657
655 .. sourcecode:: ipython
658 .. sourcecode:: ipython
656
659
657 In [81]: dview.execute('1/0')
660 In [81]: dview.execute('1/0')
658 ---------------------------------------------------------------------------
661 ---------------------------------------------------------------------------
659 CompositeError Traceback (most recent call last)
662 CompositeError Traceback (most recent call last)
660 /home/you/<ipython-input-10-15c2c22dec39> in <module>()
663 /home/you/<ipython-input-10-15c2c22dec39> in <module>()
661 ----> 1 dview.execute('1/0', block=True)
664 ----> 1 dview.execute('1/0', block=True)
662
665
663 /path/to/site-packages/IPython/zmq/parallel/view.py in execute(self, code, block)
666 /path/to/site-packages/IPython/parallel/view.py in execute(self, code, block)
664 460 default: self.block
667 460 default: self.block
665 461 """
668 461 """
666 --> 462 return self.apply_with_flags(util._execute, args=(code,), block=block)
669 --> 462 return self.apply_with_flags(util._execute, args=(code,), block=block)
667 463
670 463
668 464 def run(self, filename, block=None):
671 464 def run(self, filename, block=None):
669
672
670 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
673 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
671
674
672 /path/to/site-packages/IPython/zmq/parallel/view.py in sync_results(f, self, *args, **kwargs)
675 /path/to/site-packages/IPython/parallel/view.py in sync_results(f, self, *args, **kwargs)
673 46 def sync_results(f, self, *args, **kwargs):
676 46 def sync_results(f, self, *args, **kwargs):
674 47 """sync relevant results from self.client to our results attribute."""
677 47 """sync relevant results from self.client to our results attribute."""
675 ---> 48 ret = f(self, *args, **kwargs)
678 ---> 48 ret = f(self, *args, **kwargs)
676 49 delta = self.outstanding.difference(self.client.outstanding)
679 49 delta = self.outstanding.difference(self.client.outstanding)
677 50 completed = self.outstanding.intersection(delta)
680 50 completed = self.outstanding.intersection(delta)
678
681
679 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
682 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
680
683
681 /path/to/site-packages/IPython/zmq/parallel/view.py in save_ids(f, self, *args, **kwargs)
684 /path/to/site-packages/IPython/parallel/view.py in save_ids(f, self, *args, **kwargs)
682 35 n_previous = len(self.client.history)
685 35 n_previous = len(self.client.history)
683 36 try:
686 36 try:
684 ---> 37 ret = f(self, *args, **kwargs)
687 ---> 37 ret = f(self, *args, **kwargs)
685 38 finally:
688 38 finally:
686 39 nmsgs = len(self.client.history) - n_previous
689 39 nmsgs = len(self.client.history) - n_previous
687
690
688 /path/to/site-packages/IPython/zmq/parallel/view.py in apply_with_flags(self, f, args, kwargs, block, track)
691 /path/to/site-packages/IPython/parallel/view.py in apply_with_flags(self, f, args, kwargs, block, track)
689 398 if block:
692 398 if block:
690 399 try:
693 399 try:
691 --> 400 return ar.get()
694 --> 400 return ar.get()
692 401 except KeyboardInterrupt:
695 401 except KeyboardInterrupt:
693 402 pass
696 402 pass
694
697
695 /path/to/site-packages/IPython/zmq/parallel/asyncresult.pyc in get(self, timeout)
698 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
696 87 return self._result
699 87 return self._result
697 88 else:
700 88 else:
698 ---> 89 raise self._exception
701 ---> 89 raise self._exception
699 90 else:
702 90 else:
700 91 raise error.TimeoutError("Result not ready.")
703 91 raise error.TimeoutError("Result not ready.")
701
704
702 CompositeError: one or more exceptions from call to method: _execute
705 CompositeError: one or more exceptions from call to method: _execute
703 [0:apply]: ZeroDivisionError: integer division or modulo by zero
706 [0:apply]: ZeroDivisionError: integer division or modulo by zero
704 [1:apply]: ZeroDivisionError: integer division or modulo by zero
707 [1:apply]: ZeroDivisionError: integer division or modulo by zero
705 [2:apply]: ZeroDivisionError: integer division or modulo by zero
708 [2:apply]: ZeroDivisionError: integer division or modulo by zero
706 [3:apply]: ZeroDivisionError: integer division or modulo by zero
709 [3:apply]: ZeroDivisionError: integer division or modulo by zero
707
710
708 In [82]: %debug
711 In [82]: %debug
709 > /Users/minrk/dev/ip/mine/IPython/zmq/parallel/asyncresult.py(80)get()
712 > /path/to/site-packages/IPython/parallel/asyncresult.py(80)get()
710 79 else:
713 79 else:
711 ---> 80 raise self._exception
714 ---> 80 raise self._exception
712 81 else:
715 81 else:
713
716
714
717
715 # With the debugger running, e is the exceptions instance. We can tab complete
718 # With the debugger running, e is the exceptions instance. We can tab complete
716 # on it and see the extra methods that are available.
719 # on it and see the extra methods that are available.
717 ipdb> e.
720 ipdb> e.
718 e.__class__ e.__getitem__ e.__new__ e.__setstate__ e.args
721 e.__class__ e.__getitem__ e.__new__ e.__setstate__ e.args
719 e.__delattr__ e.__getslice__ e.__reduce__ e.__str__ e.elist
722 e.__delattr__ e.__getslice__ e.__reduce__ e.__str__ e.elist
720 e.__dict__ e.__hash__ e.__reduce_ex__ e.__weakref__ e.message
723 e.__dict__ e.__hash__ e.__reduce_ex__ e.__weakref__ e.message
721 e.__doc__ e.__init__ e.__repr__ e._get_engine_str e.print_tracebacks
724 e.__doc__ e.__init__ e.__repr__ e._get_engine_str e.print_tracebacks
722 e.__getattribute__ e.__module__ e.__setattr__ e._get_traceback e.raise_exception
725 e.__getattribute__ e.__module__ e.__setattr__ e._get_traceback e.raise_exception
723 ipdb> e.print_tracebacks()
726 ipdb> e.print_tracebacks()
724 [0:apply]:
727 [0:apply]:
725 Traceback (most recent call last):
728 Traceback (most recent call last):
726 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/streamkernel.py", line 332, in apply_request
729 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
727 exec code in working, working
730 exec code in working, working
728 File "<string>", line 1, in <module>
731 File "<string>", line 1, in <module>
729 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/client.py", line 69, in _execute
732 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
730 exec code in globals()
733 exec code in globals()
731 File "<string>", line 1, in <module>
734 File "<string>", line 1, in <module>
732 ZeroDivisionError: integer division or modulo by zero
735 ZeroDivisionError: integer division or modulo by zero
733
736
734
737
735 [1:apply]:
738 [1:apply]:
736 Traceback (most recent call last):
739 Traceback (most recent call last):
737 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/streamkernel.py", line 332, in apply_request
740 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
738 exec code in working, working
741 exec code in working, working
739 File "<string>", line 1, in <module>
742 File "<string>", line 1, in <module>
740 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/client.py", line 69, in _execute
743 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
741 exec code in globals()
744 exec code in globals()
742 File "<string>", line 1, in <module>
745 File "<string>", line 1, in <module>
743 ZeroDivisionError: integer division or modulo by zero
746 ZeroDivisionError: integer division or modulo by zero
744
747
745
748
746 [2:apply]:
749 [2:apply]:
747 Traceback (most recent call last):
750 Traceback (most recent call last):
748 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/streamkernel.py", line 332, in apply_request
751 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
749 exec code in working, working
752 exec code in working, working
750 File "<string>", line 1, in <module>
753 File "<string>", line 1, in <module>
751 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/client.py", line 69, in _execute
754 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
752 exec code in globals()
755 exec code in globals()
753 File "<string>", line 1, in <module>
756 File "<string>", line 1, in <module>
754 ZeroDivisionError: integer division or modulo by zero
757 ZeroDivisionError: integer division or modulo by zero
755
758
756
759
757 [3:apply]:
760 [3:apply]:
758 Traceback (most recent call last):
761 Traceback (most recent call last):
759 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/streamkernel.py", line 332, in apply_request
762 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
760 exec code in working, working
763 exec code in working, working
761 File "<string>", line 1, in <module>
764 File "<string>", line 1, in <module>
762 File "/Users/minrk/dev/ip/mine/IPython/zmq/parallel/client.py", line 69, in _execute
765 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
763 exec code in globals()
766 exec code in globals()
764 File "<string>", line 1, in <module>
767 File "<string>", line 1, in <module>
765 ZeroDivisionError: integer division or modulo by zero
768 ZeroDivisionError: integer division or modulo by zero
766
769
767
770
768 .. note::
771 .. note::
769
772
770 TODO: The above tracebacks are not up to date
773 TODO: The above tracebacks are not up to date
771
774
772
775
773 All of this same error handling magic even works in non-blocking mode:
776 All of this same error handling magic even works in non-blocking mode:
774
777
775 .. sourcecode:: ipython
778 .. sourcecode:: ipython
776
779
777 In [83]: dview.block=False
780 In [83]: dview.block=False
778
781
779 In [84]: ar = dview.execute('1/0')
782 In [84]: ar = dview.execute('1/0')
780
783
781 In [85]: ar.get()
784 In [85]: ar.get()
782 ---------------------------------------------------------------------------
785 ---------------------------------------------------------------------------
783 CompositeError Traceback (most recent call last)
786 CompositeError Traceback (most recent call last)
784 /Users/minrk/<ipython-input-3-8531eb3d26fb> in <module>()
787 /Users/minrk/<ipython-input-3-8531eb3d26fb> in <module>()
785 ----> 1 ar.get()
788 ----> 1 ar.get()
786
789
787 /Users/minrk/dev/ip/mine/IPython/zmq/parallel/asyncresult.pyc in get(self, timeout)
790 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
788 78 return self._result
791 78 return self._result
789 79 else:
792 79 else:
790 ---> 80 raise self._exception
793 ---> 80 raise self._exception
791 81 else:
794 81 else:
792 82 raise error.TimeoutError("Result not ready.")
795 82 raise error.TimeoutError("Result not ready.")
793
796
794 CompositeError: one or more exceptions from call to method: _execute
797 CompositeError: one or more exceptions from call to method: _execute
795 [0:apply]: ZeroDivisionError: integer division or modulo by zero
798 [0:apply]: ZeroDivisionError: integer division or modulo by zero
796 [1:apply]: ZeroDivisionError: integer division or modulo by zero
799 [1:apply]: ZeroDivisionError: integer division or modulo by zero
797 [2:apply]: ZeroDivisionError: integer division or modulo by zero
800 [2:apply]: ZeroDivisionError: integer division or modulo by zero
798 [3:apply]: ZeroDivisionError: integer division or modulo by zero
801 [3:apply]: ZeroDivisionError: integer division or modulo by zero
799
802
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now