Author: chrisz
Date: Mon Sep 19 13:44:31 2005
New Revision: 3184
Modified:
Webware/trunk/WebKit/Launch.py
Webware/trunk/WebKit/Profiler.py
Log:
Improved the Launch.py start script considerably, implementing ideas by me and Ian.
Modified: Webware/trunk/WebKit/Launch.py
==============================================================================
--- Webware/trunk/WebKit/Launch.py (original)
+++ Webware/trunk/WebKit/Launch.py Mon Sep 19 13:44:31 2005
@@ -1,91 +1,398 @@
#!/usr/bin/env python
-import sys
-if sys.version[0] == '1':
- print """Webware requires Python version 2.0+. Webware is currently being
-run with the Python version:
- %s
-This Python interpreter is located at:
- %s
-You may need to change the AppServer script, giving the full path of
-the appropriate Python interpreter.""" % (sys.version, sys.executable)
- sys.exit(1)
+"""Launch.py
-import time; startTime = time.time()
-runProfile = 0 # as in Python's profile module. See doc string of Profiler.py
+DESCRIPTION
-import os, sys, string
+Python launch script for the WebKit application server.
-profiler = None # Forget this and read the doc string of Profiler.py
+This launch script will run in its standard location in the Webware/WebKit
+directory as well as in a WebKit work directory outside of the Webware tree.
-def usage():
- print 'error: Launch.py (of WebKit)'
- print 'usage: Launch.py SERVER ARGS'
- sys.exit(1)
+USAGE
+Launch.py [StartOptions] [AppServer [AppServerOptions]]
-def launchWebKit(server, appWorkPath, args):
- """
- Import and launch the specified WebKit server.
- """
- # allow for a .py on the server name
- if server[-3:]=='.py':
- server = server[:-3]
-
- # clean up sys.path
- def ok(directory):
- return directory not in ['', '.'] and string.lower(directory[-6:])!='webkit'
- sys.path = filter(ok, sys.path)
- sys.path.insert(0, '')
-
- # Import the server's main()
- import WebKit
- code = 'from WebKit.%s import main' % server
- dict = {}
- exec code in dict
- main = dict['main']
-
- # Set up a reference to our profiler so applications can import and use it
- from WebKit import Profiler
- Profiler.startTime = startTime
- if runProfile:
- Profiler.profiler = profiler
-
- # Run!
- args = args + ['workdir=' + appWorkPath]
- main(args)
+StartOptions:
+ --work-dir=... Set the path to the app server working directory.
+ By default this is the directory containing Lauch.py.
+ --webware-dir=... Set the path to the Webware root directory.
+ By default this is the parent directory.
+ --library=... Other directories to be included in the search path.
+ --run-profile Set this to get profiling going (see Profiler.py).
+ --log-file=... Redirect standard output and error to this file.
+ --pid-file=... Set the file path to hold the app server process id.
+ This option is fully supported under Unix only.
+ --user=... The name or uid of the user to run the app server.
+ This option is supported under Unix only.
+ --group=... The name or gid of the group to run the app server.
+ This option is supported under Unix only.
+
+AppServer:
+ The name of the application server module.
+ By default, the ThreadedAppServer will be used.
+
+AppServerOptions:
+ Options that shall be passed to the application server.
+ For instance, the ThreadedAppServer accepts: start, stop, daemon
+
+Please note that the default values for the StartOptions and the AppServer
+can be easily changed at the top of the Launch.py script.
+
+
+CREDITS
+
+* Contributed to Webware for Python by Chuck Esterbrook
+* Improved by Ian Bicking
+* Improved by Christoph Zwerschke
+
+"""
+
+
+# You can change the following default values:
+
+# The path to the app server working directory, if you do not
+# want to use the directory containing this script:
+workDir = None
+
+# The path to the Webware root directory; change this if you
+# you want to start this script from outside that directory:
+webwareDir = '..'
+
+# A list of additional directories (usually some libraries)
+# that you want to include into the search path for modules:
+libraryDirs = []
+
+# To get profiling going, set runProfile = 1 (see also
+# the description in the docstring of Profiler.py):
+runProfile = 0
+
+# The path to the log file, if you want to redirect the
+# standard output and error to a log file:
+logFile = None
+# The pass to the pid file, if you want to check and terminate
+# a running server by storing its server process id:
+pidFile = None
+
+# The name or uid of the server process user, if you want
+# to run the server under a different user:
+user = None
+
+# The name or uid of the server process group, if you want
+# to run the server under a different group:
+group = None
+
+# The default app server to be used:
+appServer = 'ThreadedAppServer'
+
+
+import os, sys
+
+def usage():
+ """Print the docstring and exit with error."""
+ sys.stdout.write(__doc__)
+ sys.exit(2)
+
+def launchWebKit(appServer=appServer, workDir=None, args=None):
+ """Import and launch the specified WebKit app server.
+
+ appServer -- the name of the WebKit app server module
+ workDir -- the server-side work directory of the app server
+ args -- other options that will be given to the app server
+
+ """
+ # Set up the arguments for the app server:
+ if args is None:
+ args = []
+ if workDir:
+ args.append('workdir=' + workDir)
+ # Allow for a .py on the server name:
+ if appServer[-3:]=='.py':
+ appServer = appServer[:-3]
+ # Import the app server's main() function:
+ try:
+ appServerMain = __import__('WebKit.' + appServer, None, None, 'main').main
+ except ImportError:
+ print 'Error: Cannot import the app server module.'
+ raise
+ sys.exit(1)
+ # Run the app server:
+ appServerMain(args) # go!
def main(args=None):
+ """Evaluate the command line arguments and run launchWebKit."""
+ global workDir, webwareDir, libraryDirs, \
+ runProfile, logFile, pidFile, user, group, appServer
if args is None:
args = sys.argv
- if len(args)<2:
+ # Get the name of this script:
+ try:
+ scriptName = args.pop(0)
+ except IndexError:
+ scriptName = None
+ # Get the name of the app server even if placed in front
+ if args and not args[0].startswith('-'):
+ appServer = args.pop(0)
+ # Get all options:
+ from getopt import getopt, GetoptError
+ try:
+ opts, args = getopt(args, '', [
+ 'webware-dir=', 'work-dir=', 'library=', 'run-profile',
+ 'log-file=', 'pid-file=', 'user=', 'group='])
+ except GetoptError, error:
+ print str(error)
usage()
-
- server = args[1] # the server
-
- # figure out directory locations
- webKitPath = os.path.dirname(os.path.join(os.getcwd(), sys.argv[0]))
- webwarePath = os.path.dirname(webKitPath)
-
- # go to Webware dir so that:
- # - importing packages like 'WebUtils' hits this Webware
- # - we cannot import WebKit modules without qualifying them
- os.chdir(webwarePath)
-
- # Go!
- launchWebKit(server, webKitPath, args[2:])
-
-
-if __name__=='__main__':
- if runProfile:
- print 'Profiling is on. See doc string in Profiler.py for more info.'
- from profile import Profile
- profiler = Profile()
- profiler.runcall(main)
- from WebKit import Profiler
- Profiler.dumpStats()
+ for opt, arg in opts:
+ if opt == '--webware-dir':
+ webwareDir = arg
+ elif opt == '--work-dir':
+ workDir = arg
+ elif opt == '--library':
+ libraryDirs.append(arg)
+ elif opt == '--run-profile':
+ runProfile = 1
+ elif opt == '--log-file':
+ logFile = arg
+ elif opt == '--pid-file':
+ pidFile = arg
+ elif opt == '--user':
+ user = arg
+ elif opt == '--group':
+ group = arg
+ # Figure out the group id:
+ gid = group
+ if gid:
+ try:
+ gid = int(gid)
+ except ValueError:
+ try:
+ import grp
+ entry = grp.getgrnam(gid)
+ except KeyError:
+ print "Error: Group %r does not exist" % gid
+ sys.exit(2)
+ except ImportError:
+ print "Error: Group names are not supported."
+ sys.exit(2)
+ gid = entry[2]
+ # Figure out the user id:
+ uid = user
+ if uid:
+ try:
+ uid = int(uid)
+ except ValueError:
+ try:
+ import pwd
+ entry = pwd.getpwnam(uid)
+ except KeyError:
+ print "Error: User %r does not exist" % uid
+ sys.exit(2)
+ except ImportError:
+ "Error: User names are not supported."
+ sys.exit(2)
+ if not gid:
+ gid = entry[3]
+ uid = entry[2]
+ # Figure out the work directory and make it the current directory:
+ if workDir:
+ workDir = os.path.expanduser(workDir)
+ else:
+ if not scriptName or scriptName == '-c':
+ scriptName = 'Launch.py'
+ workDir = os.path.dirname(os.path.abspath(scriptName))
+ try:
+ os.chdir(workDir)
+ except OSError, error:
+ print 'Error: Could not set working directory.'
+ print 'The path %r cannot be used.' % workDir
+ print error.strerror
+ print 'Check the --work-dir option.'
+ sys.exit(1)
+ workDir = os.path.curdir
+ # Expand user components in directories:
+ if webwareDir:
+ webwareDir = os.path.expanduser(webwareDir)
else:
- main()
+ webwareDir = workDir
+ if libraryDirs:
+ libraryDirs = map(os.path.expanduser, libraryDirs)
+ # Get the absolute location of the Webware/WebKit directory:
+ webKitDir = os.path.abspath(os.path.join(webwareDir, 'WebKit'))
+ sysPath = sys.path # memorize the standard Python search path
+ sys.path = [webKitDir] # now include only the Webware/WebKit directory
+ try: # check whether WebKit is really located here
+ from Properties import name
+ except ImportError:
+ name = None
+ if name != 'WebKit':
+ print 'Error: Cannot find the Webware/WebKit directory.'
+ print 'The path %r seems to be wrong.' % webKitDir
+ print 'Check the --webware-dir option.'
+ sys.exit(1)
+ # Now assemble a new clean Python search path:
+ path = [] # the new search path will be collected here
+ absPath = [] # the absolute pathes
+ for p in ['', webwareDir] + libraryDirs + sysPath:
+ ap = os.path.abspath(p)
+ if ap == webKitDir: # do not include the webKitDir
+ continue
+ if ap not in absPath: # include every path only once
+ path.append(p)
+ absPath.append(ap)
+ sys.path = path # set the new search path
+ # Get the name of the app server module:
+ try:
+ appServer = args.pop(0)
+ except IndexError:
+ pass # use the default value
+ # Prepare the arguments for launchWebKit:
+ args = (appServer, workDir, args)
+ # Handle the pid file:
+ if pidFile:
+ pidFile = os.path.expanduser(pidFile)
+ # Read the old pid file:
+ try:
+ pid = int(open(pidFile).read())
+ except:
+ pid = None
+ if pid:
+ print 'According to the pid file, the server is still running.'
+ # Try to kill an already running server:
+ killed = 0
+ try:
+ from signal import SIGTERM, SIGKILL
+ print 'Trying to terminate the server with pid %d...' % pid
+ os.kill(pid, SIGTERM)
+ except OSError, error:
+ from errno import ESRCH
+ if error.errno == ESRCH: # no such process
+ print 'The pid file was stale, continuing with startup...'
+ killed = 1
+ else:
+ print 'Cannot terminate server with pid %d.' % pid
+ print error.strerror
+ sys.exit(1)
+ except (ImportError, AttributeError):
+ print 'Cannot check or terminate server with pid %d.' % pid
+ sys.exit(1)
+ if not killed:
+ from time import sleep
+ try:
+ for i in range(100):
+ sleep(0.1)
+ os.kill(pid, SIGTERM)
+ except OSError, error:
+ from errno import ESRCH
+ if error.errno == ESRCH:
+ print 'Server with pid %d has been terminated.' % pid
+ killed = 1
+ if not killed:
+ try:
+ for i in range(100):
+ sleep(0.1)
+ os.kill(pid, SIGKILL)
+ except OSError, error:
+ from errno import ESRCH
+ if error.errno == ESRCH:
+ print 'Server with pid %d has been killed by force.' % pid
+ killed = 1
+ if not killed:
+ print 'Server with pid %d cannot be terminated.' % pid
+ sys.exit(1)
+ # Write a new pid file:
+ try:
+ open(pidFile, 'w').write(str(os.getpid()))
+ except:
+ print 'The pid file %r could not be written.' % pidFile
+ sys.exit(1)
+ olduid = oldgid = stdout = stderr = log = None
+ try:
+ # Change server process group:
+ if gid:
+ try:
+ oldgid = os.getgid()
+ if gid != oldgid:
+ os.setgid(gid)
+ if group:
+ print 'Changed server process group to %r.' % group
+ else:
+ oldgid = None
+ except:
+ if group:
+ print 'Could not set server process group to %r.' % group
+ raise
+ oldgid = None
+ sys.exit(1)
+ # Change server process user:
+ if uid:
+ try:
+ olduid = os.getuid()
+ if uid != olduid:
+ os.setuid(uid)
+ print 'Changed server process user to %r.' % user
+ else:
+ olduid = None
+ except:
+ print 'Could not change server process user to %r.' % user
+ olduid = None
+ sys.exit(1)
+ print 'Starting WebKit.%s...' % appServer
+ # Handle the log file:
+ if logFile:
+ logFile = os.path.expanduser(logFile)
+ try:
+ log = open(logFile, 'a', 1) # append, line buffered mode
+ print 'Output has been redirected to %r...' % logFile
+ stdout, stderr = sys.stdout, sys.stderr
+ sys.stdout = sys.stderr = log
+ except IOError, error:
+ print 'Cannot redirect output to %r.' % logFile
+ print error.strerror
+ log = None
+ sys.exit(1)
+ else:
+ print
+ # Set up a reference to our profiler so apps can import and use it:
+ from WebKit import Profiler
+ from time import time
+ Profiler.startTime = time()
+ # Now start the app server:
+ if runProfile:
+ print 'Profiling is on. See docstring in Profiler.py for more info.'
+ print
+ from profile import Profile
+ profiler = Profile()
+ Profiler.profiler = profiler
+ Profiler.runCall(launchWebKit, *args)
+ Profiler.dumpStats()
+ else:
+ launchWebKit(*args)
+ finally:
+ # Close the log file properly:
+ if log:
+ sys.stdout, sys.stderr = stdout, stderr
+ log.close()
+ # Restore server process group:
+ if oldgid:
+ try:
+ os.setgid(oldgid)
+ except:
+ pass
+ # Restore server process user:
+ if olduid:
+ try:
+ os.setuid(olduid)
+ except:
+ pass
+ # Remove the pid file again:
+ if pidFile:
+ try:
+ os.remove(pidFile)
+ except:
+ print 'The pid file could not be removed.'
+
+if __name__ == '__main__':
+ main()
Modified: Webware/trunk/WebKit/Profiler.py
==============================================================================
--- Webware/trunk/WebKit/Profiler.py (original)
+++ Webware/trunk/WebKit/Profiler.py Mon Sep 19 13:44:31 2005
@@ -1,31 +1,27 @@
-"""
-Stores some values related to performance. These are usually set by
-Launch.py or AppServer.py.
+"""Profiler.py
+
+Stores some values related to performance.
+These are usually set by Launch.py or AppServer.py.
-To get profiling going, locate the "runProfile = 0" line towards the
-top of WebKit/Launch.py and change the "0" to a "1". When the app
-server shuts down it will write Webware/profile.pstats which can be
-quickly examined from the command line:
+To get profiling going, locate the "runProfile = 0" line towards the top
+of WebKit/Launch.py and change the "0" to a "1", or start the Launch.py
+script with the --run-profile option. When the app server shuts down it
+will write a profiling report "profile.pstats" in the directory containing
+the Launch.py script which can be quickly examined from the command line:
$ cd Webware
- $ bin/printprof.py profile.pstats
+ $ bin/printprof.py WebKit/profile.pstats
You might also wish to dump the profiling stats on demand (as in, by
-clicking or reloading a URL for that purpose). Read further for
-details.
-
+clicking or reloading a URL for that purpose). Read further for details.
The variables in this module are:
profiler
An instance of Python's profile.Profiler, but only if Launch.py is
- modified to say "runProfile = 1". Otherwise, this is None. You
- could access this from a servlet in order to dump stats:
+ started with profiling enabled. Otherwise, this is None.
+ You could access this from a servlet in order to dump stats:
- from WebKit.Profiler import profiler
- profiler.dump_stats('profile.pstats')
-
- Or more conveniently:
from WebKit.Profiler import dumpStats
dumpStats()
@@ -44,18 +40,17 @@
developing with AutoReload on.
"""
-profiler = None
-startTime = None
-readyTime = None
-readyDuration = None
-
+profiler = startTime = readyTime = readyDuration = None
# Convenience
statsFilename = 'profile.pstats'
-def dumpStats():
- profiler.dump_stats(statsFilename)
+def runCall(func, *args, **kwargs):
+ profiler.runcall(func, *args, **kwargs)
+
+def dumpStats(file=statsFilename):
+ profiler.dump_stats(file)
def reset():
"""
|