PageRenderTime 71ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/IPython/parallel/apps/ipengineapp.py

https://github.com/cboos/ipython
Python | 330 lines | 294 code | 12 blank | 24 comment | 2 complexity | e8ef1e5639e8af2f53cec235b5c06c08 MD5 | raw file
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. """
  4. The IPython engine application
  5. Authors:
  6. * Brian Granger
  7. * MinRK
  8. """
  9. #-----------------------------------------------------------------------------
  10. # Copyright (C) 2008-2011 The IPython Development Team
  11. #
  12. # Distributed under the terms of the BSD License. The full license is in
  13. # the file COPYING, distributed as part of this software.
  14. #-----------------------------------------------------------------------------
  15. #-----------------------------------------------------------------------------
  16. # Imports
  17. #-----------------------------------------------------------------------------
  18. import json
  19. import os
  20. import sys
  21. import time
  22. import zmq
  23. from zmq.eventloop import ioloop
  24. from IPython.core.profiledir import ProfileDir
  25. from IPython.parallel.apps.baseapp import (
  26. BaseParallelApplication,
  27. base_aliases,
  28. base_flags,
  29. catch_config_error,
  30. )
  31. from IPython.zmq.log import EnginePUBHandler
  32. from IPython.zmq.session import (
  33. Session, session_aliases, session_flags
  34. )
  35. from IPython.config.configurable import Configurable
  36. from IPython.parallel.engine.engine import EngineFactory
  37. from IPython.parallel.engine.streamkernel import Kernel
  38. from IPython.parallel.util import disambiguate_url, asbytes
  39. from IPython.utils.importstring import import_item
  40. from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float
  41. #-----------------------------------------------------------------------------
  42. # Module level variables
  43. #-----------------------------------------------------------------------------
  44. #: The default config file name for this application
  45. default_config_file_name = u'ipengine_config.py'
  46. _description = """Start an IPython engine for parallel computing.
  47. IPython engines run in parallel and perform computations on behalf of a client
  48. and controller. A controller needs to be started before the engines. The
  49. engine can be configured using command line options or using a cluster
  50. directory. Cluster directories contain config, log and security files and are
  51. usually located in your ipython directory and named as "profile_name".
  52. See the `profile` and `profile-dir` options for details.
  53. """
  54. _examples = """
  55. ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
  56. ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity
  57. """
  58. #-----------------------------------------------------------------------------
  59. # MPI configuration
  60. #-----------------------------------------------------------------------------
  61. mpi4py_init = """from mpi4py import MPI as mpi
  62. mpi.size = mpi.COMM_WORLD.Get_size()
  63. mpi.rank = mpi.COMM_WORLD.Get_rank()
  64. """
  65. pytrilinos_init = """from PyTrilinos import Epetra
  66. class SimpleStruct:
  67. pass
  68. mpi = SimpleStruct()
  69. mpi.rank = 0
  70. mpi.size = 0
  71. """
  72. class MPI(Configurable):
  73. """Configurable for MPI initialization"""
  74. use = Unicode('', config=True,
  75. help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
  76. )
  77. def _use_changed(self, name, old, new):
  78. # load default init script if it's not set
  79. if not self.init_script:
  80. self.init_script = self.default_inits.get(new, '')
  81. init_script = Unicode('', config=True,
  82. help="Initialization code for MPI")
  83. default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
  84. config=True)
  85. #-----------------------------------------------------------------------------
  86. # Main application
  87. #-----------------------------------------------------------------------------
  88. aliases = dict(
  89. file = 'IPEngineApp.url_file',
  90. c = 'IPEngineApp.startup_command',
  91. s = 'IPEngineApp.startup_script',
  92. url = 'EngineFactory.url',
  93. ssh = 'EngineFactory.sshserver',
  94. sshkey = 'EngineFactory.sshkey',
  95. ip = 'EngineFactory.ip',
  96. transport = 'EngineFactory.transport',
  97. port = 'EngineFactory.regport',
  98. location = 'EngineFactory.location',
  99. timeout = 'EngineFactory.timeout',
  100. mpi = 'MPI.use',
  101. )
  102. aliases.update(base_aliases)
  103. aliases.update(session_aliases)
  104. flags = {}
  105. flags.update(base_flags)
  106. flags.update(session_flags)
  107. class IPEngineApp(BaseParallelApplication):
  108. name = 'ipengine'
  109. description = _description
  110. examples = _examples
  111. config_file_name = Unicode(default_config_file_name)
  112. classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
  113. startup_script = Unicode(u'', config=True,
  114. help='specify a script to be run at startup')
  115. startup_command = Unicode('', config=True,
  116. help='specify a command to be run at startup')
  117. url_file = Unicode(u'', config=True,
  118. help="""The full location of the file containing the connection information for
  119. the controller. If this is not given, the file must be in the
  120. security directory of the cluster directory. This location is
  121. resolved using the `profile` or `profile_dir` options.""",
  122. )
  123. wait_for_url_file = Float(5, config=True,
  124. help="""The maximum number of seconds to wait for url_file to exist.
  125. This is useful for batch-systems and shared-filesystems where the
  126. controller and engine are started at the same time and it
  127. may take a moment for the controller to write the connector files.""")
  128. url_file_name = Unicode(u'ipcontroller-engine.json', config=True)
  129. def _cluster_id_changed(self, name, old, new):
  130. if new:
  131. base = 'ipcontroller-%s' % new
  132. else:
  133. base = 'ipcontroller'
  134. self.url_file_name = "%s-engine.json" % base
  135. log_url = Unicode('', config=True,
  136. help="""The URL for the iploggerapp instance, for forwarding
  137. logging to a central location.""")
  138. aliases = Dict(aliases)
  139. flags = Dict(flags)
  140. def find_url_file(self):
  141. """Set the url file.
  142. Here we don't try to actually see if it exists for is valid as that
  143. is hadled by the connection logic.
  144. """
  145. config = self.config
  146. # Find the actual controller key file
  147. if not self.url_file:
  148. self.url_file = os.path.join(
  149. self.profile_dir.security_dir,
  150. self.url_file_name
  151. )
  152. def load_connector_file(self):
  153. """load config from a JSON connector file,
  154. at a *lower* priority than command-line/config files.
  155. """
  156. self.log.info("Loading url_file %r", self.url_file)
  157. config = self.config
  158. with open(self.url_file) as f:
  159. d = json.loads(f.read())
  160. if 'exec_key' in d:
  161. config.Session.key = asbytes(d['exec_key'])
  162. try:
  163. config.EngineFactory.location
  164. except AttributeError:
  165. config.EngineFactory.location = d['location']
  166. d['url'] = disambiguate_url(d['url'], config.EngineFactory.location)
  167. try:
  168. config.EngineFactory.url
  169. except AttributeError:
  170. config.EngineFactory.url = d['url']
  171. try:
  172. config.EngineFactory.sshserver
  173. except AttributeError:
  174. config.EngineFactory.sshserver = d['ssh']
  175. def init_engine(self):
  176. # This is the working dir by now.
  177. sys.path.insert(0, '')
  178. config = self.config
  179. # print config
  180. self.find_url_file()
  181. # was the url manually specified?
  182. keys = set(self.config.EngineFactory.keys())
  183. keys = keys.union(set(self.config.RegistrationFactory.keys()))
  184. if keys.intersection(set(['ip', 'url', 'port'])):
  185. # Connection info was specified, don't wait for the file
  186. url_specified = True
  187. self.wait_for_url_file = 0
  188. else:
  189. url_specified = False
  190. if self.wait_for_url_file and not os.path.exists(self.url_file):
  191. self.log.warn("url_file %r not found", self.url_file)
  192. self.log.warn("Waiting up to %.1f seconds for it to arrive.", self.wait_for_url_file)
  193. tic = time.time()
  194. while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
  195. # wait for url_file to exist, or until time limit
  196. time.sleep(0.1)
  197. if os.path.exists(self.url_file):
  198. self.load_connector_file()
  199. elif not url_specified:
  200. self.log.fatal("Fatal: url file never arrived: %s", self.url_file)
  201. self.exit(1)
  202. try:
  203. exec_lines = config.Kernel.exec_lines
  204. except AttributeError:
  205. config.Kernel.exec_lines = []
  206. exec_lines = config.Kernel.exec_lines
  207. if self.startup_script:
  208. enc = sys.getfilesystemencoding() or 'utf8'
  209. cmd="execfile(%r)" % self.startup_script.encode(enc)
  210. exec_lines.append(cmd)
  211. if self.startup_command:
  212. exec_lines.append(self.startup_command)
  213. # Create the underlying shell class and Engine
  214. # shell_class = import_item(self.master_config.Global.shell_class)
  215. # print self.config
  216. try:
  217. self.engine = EngineFactory(config=config, log=self.log)
  218. except:
  219. self.log.error("Couldn't start the Engine", exc_info=True)
  220. self.exit(1)
  221. def forward_logging(self):
  222. if self.log_url:
  223. self.log.info("Forwarding logging to %s", self.log_url)
  224. context = self.engine.context
  225. lsock = context.socket(zmq.PUB)
  226. lsock.connect(self.log_url)
  227. self.log.removeHandler(self._log_handler)
  228. handler = EnginePUBHandler(self.engine, lsock)
  229. handler.setLevel(self.log_level)
  230. self.log.addHandler(handler)
  231. self._log_handler = handler
  232. def init_mpi(self):
  233. global mpi
  234. self.mpi = MPI(config=self.config)
  235. mpi_import_statement = self.mpi.init_script
  236. if mpi_import_statement:
  237. try:
  238. self.log.info("Initializing MPI:")
  239. self.log.info(mpi_import_statement)
  240. exec mpi_import_statement in globals()
  241. except:
  242. mpi = None
  243. else:
  244. mpi = None
  245. @catch_config_error
  246. def initialize(self, argv=None):
  247. super(IPEngineApp, self).initialize(argv)
  248. self.init_mpi()
  249. self.init_engine()
  250. self.forward_logging()
  251. def start(self):
  252. self.engine.start()
  253. try:
  254. self.engine.loop.start()
  255. except KeyboardInterrupt:
  256. self.log.critical("Engine Interrupted, shutting down...\n")
  257. def launch_new_instance():
  258. """Create and run the IPython engine"""
  259. app = IPEngineApp.instance()
  260. app.initialize()
  261. app.start()
  262. if __name__ == '__main__':
  263. launch_new_instance()