PageRenderTime 30ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/IPython/parallel/apps/baseapp.py

https://github.com/cboos/ipython
Python | 263 lines | 201 code | 23 blank | 39 comment | 1 complexity | 87fd63b99ef230d64357ba1453a9364c MD5 | raw file
  1. # encoding: utf-8
  2. """
  3. The Base Application class for IPython.parallel apps
  4. Authors:
  5. * Brian Granger
  6. * Min RK
  7. """
  8. #-----------------------------------------------------------------------------
  9. # Copyright (C) 2008-2011 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. # Imports
  16. #-----------------------------------------------------------------------------
  17. from __future__ import with_statement
  18. import os
  19. import logging
  20. import re
  21. import sys
  22. from subprocess import Popen, PIPE
  23. from IPython.config.application import catch_config_error
  24. from IPython.core import release
  25. from IPython.core.crashhandler import CrashHandler
  26. from IPython.core.application import (
  27. BaseIPythonApplication,
  28. base_aliases as base_ip_aliases,
  29. base_flags as base_ip_flags
  30. )
  31. from IPython.utils.path import expand_path
  32. from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
  33. #-----------------------------------------------------------------------------
  34. # Module errors
  35. #-----------------------------------------------------------------------------
  36. class PIDFileError(Exception):
  37. pass
  38. #-----------------------------------------------------------------------------
  39. # Crash handler for this application
  40. #-----------------------------------------------------------------------------
  41. class ParallelCrashHandler(CrashHandler):
  42. """sys.excepthook for IPython itself, leaves a detailed report on disk."""
  43. def __init__(self, app):
  44. contact_name = release.authors['Min'][0]
  45. contact_email = release.author_email
  46. bug_tracker = 'https://github.com/ipython/ipython/issues'
  47. super(ParallelCrashHandler,self).__init__(
  48. app, contact_name, contact_email, bug_tracker
  49. )
  50. #-----------------------------------------------------------------------------
  51. # Main application
  52. #-----------------------------------------------------------------------------
  53. base_aliases = {}
  54. base_aliases.update(base_ip_aliases)
  55. base_aliases.update({
  56. 'profile-dir' : 'ProfileDir.location',
  57. 'work-dir' : 'BaseParallelApplication.work_dir',
  58. 'log-to-file' : 'BaseParallelApplication.log_to_file',
  59. 'clean-logs' : 'BaseParallelApplication.clean_logs',
  60. 'log-url' : 'BaseParallelApplication.log_url',
  61. 'cluster-id' : 'BaseParallelApplication.cluster_id',
  62. })
  63. base_flags = {
  64. 'log-to-file' : (
  65. {'BaseParallelApplication' : {'log_to_file' : True}},
  66. "send log output to a file"
  67. )
  68. }
  69. base_flags.update(base_ip_flags)
  70. class BaseParallelApplication(BaseIPythonApplication):
  71. """The base Application for IPython.parallel apps
  72. Principle extensions to BaseIPyythonApplication:
  73. * work_dir
  74. * remote logging via pyzmq
  75. * IOLoop instance
  76. """
  77. crash_handler_class = ParallelCrashHandler
  78. def _log_level_default(self):
  79. # temporarily override default_log_level to INFO
  80. return logging.INFO
  81. work_dir = Unicode(os.getcwdu(), config=True,
  82. help='Set the working dir for the process.'
  83. )
  84. def _work_dir_changed(self, name, old, new):
  85. self.work_dir = unicode(expand_path(new))
  86. log_to_file = Bool(config=True,
  87. help="whether to log to a file")
  88. clean_logs = Bool(False, config=True,
  89. help="whether to cleanup old logfiles before starting")
  90. log_url = Unicode('', config=True,
  91. help="The ZMQ URL of the iplogger to aggregate logging.")
  92. cluster_id = Unicode('', config=True,
  93. help="""String id to add to runtime files, to prevent name collisions when
  94. using multiple clusters with a single profile simultaneously.
  95. When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
  96. Since this is text inserted into filenames, typical recommendations apply:
  97. Simple character strings are ideal, and spaces are not recommended (but should
  98. generally work).
  99. """
  100. )
  101. def _cluster_id_changed(self, name, old, new):
  102. self.name = self.__class__.name
  103. if new:
  104. self.name += '-%s'%new
  105. def _config_files_default(self):
  106. return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
  107. loop = Instance('zmq.eventloop.ioloop.IOLoop')
  108. def _loop_default(self):
  109. from zmq.eventloop.ioloop import IOLoop
  110. return IOLoop.instance()
  111. aliases = Dict(base_aliases)
  112. flags = Dict(base_flags)
  113. @catch_config_error
  114. def initialize(self, argv=None):
  115. """initialize the app"""
  116. super(BaseParallelApplication, self).initialize(argv)
  117. self.to_work_dir()
  118. self.reinit_logging()
  119. def to_work_dir(self):
  120. wd = self.work_dir
  121. if unicode(wd) != os.getcwdu():
  122. os.chdir(wd)
  123. self.log.info("Changing to working dir: %s" % wd)
  124. # This is the working dir by now.
  125. sys.path.insert(0, '')
  126. def reinit_logging(self):
  127. # Remove old log files
  128. log_dir = self.profile_dir.log_dir
  129. if self.clean_logs:
  130. for f in os.listdir(log_dir):
  131. if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
  132. os.remove(os.path.join(log_dir, f))
  133. if self.log_to_file:
  134. # Start logging to the new log file
  135. log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
  136. logfile = os.path.join(log_dir, log_filename)
  137. open_log_file = open(logfile, 'w')
  138. else:
  139. open_log_file = None
  140. if open_log_file is not None:
  141. self.log.removeHandler(self._log_handler)
  142. self._log_handler = logging.StreamHandler(open_log_file)
  143. self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
  144. self._log_handler.setFormatter(self._log_formatter)
  145. self.log.addHandler(self._log_handler)
  146. # do not propagate log messages to root logger
  147. # ipcluster app will sometimes print duplicate messages during shutdown
  148. # if this is 1 (default):
  149. self.log.propagate = False
  150. def write_pid_file(self, overwrite=False):
  151. """Create a .pid file in the pid_dir with my pid.
  152. This must be called after pre_construct, which sets `self.pid_dir`.
  153. This raises :exc:`PIDFileError` if the pid file exists already.
  154. """
  155. pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
  156. if os.path.isfile(pid_file):
  157. pid = self.get_pid_from_file()
  158. if not overwrite:
  159. raise PIDFileError(
  160. 'The pid file [%s] already exists. \nThis could mean that this '
  161. 'server is already running with [pid=%s].' % (pid_file, pid)
  162. )
  163. with open(pid_file, 'w') as f:
  164. self.log.info("Creating pid file: %s" % pid_file)
  165. f.write(repr(os.getpid())+'\n')
  166. def remove_pid_file(self):
  167. """Remove the pid file.
  168. This should be called at shutdown by registering a callback with
  169. :func:`reactor.addSystemEventTrigger`. This needs to return
  170. ``None``.
  171. """
  172. pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
  173. if os.path.isfile(pid_file):
  174. try:
  175. self.log.info("Removing pid file: %s" % pid_file)
  176. os.remove(pid_file)
  177. except:
  178. self.log.warn("Error removing the pid file: %s" % pid_file)
  179. def get_pid_from_file(self):
  180. """Get the pid from the pid file.
  181. If the pid file doesn't exist a :exc:`PIDFileError` is raised.
  182. """
  183. pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
  184. if os.path.isfile(pid_file):
  185. with open(pid_file, 'r') as f:
  186. s = f.read().strip()
  187. try:
  188. pid = int(s)
  189. except:
  190. raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
  191. return pid
  192. else:
  193. raise PIDFileError('pid file not found: %s' % pid_file)
  194. def check_pid(self, pid):
  195. if os.name == 'nt':
  196. try:
  197. import ctypes
  198. # returns 0 if no such process (of ours) exists
  199. # positive int otherwise
  200. p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
  201. except Exception:
  202. self.log.warn(
  203. "Could not determine whether pid %i is running via `OpenProcess`. "
  204. " Making the likely assumption that it is."%pid
  205. )
  206. return True
  207. return bool(p)
  208. else:
  209. try:
  210. p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
  211. output,_ = p.communicate()
  212. except OSError:
  213. self.log.warn(
  214. "Could not determine whether pid %i is running via `ps x`. "
  215. " Making the likely assumption that it is."%pid
  216. )
  217. return True
  218. pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
  219. return pid in pids