Browse Source

- An additional command-line option '--profile_options' is accepted
by the supervisord script for developer use. It accepts the same
command line arguments as the 'supervisord' script.

supervisord -n -c sample.conf --profile_options=cumulative,calls

The values are sort_stats options that can be passed to the
standard Python profiler's PStats sort_stats method.

When you exit supervisor, it will print Python profiling output to
stdout.

(replaces profile_supervisord script)

Chris McDonough 17 năm trước cách đây
mục cha
commit
2f9c736026

+ 12 - 9
CHANGES.txt

@@ -84,15 +84,6 @@ Next Release
     for debugging supervisor itself).  See 'Supervisor Log Levels' in
     README.txt for more info.
 
-  - setup.py {install|develop} now installs a supervisor profiling
-    script for developer use.  It accepts the same command line
-    arguments as the 'supervisord' script.
-
-      profile_supervisord -n -c foo.conf
-
-    When you exit supervisor, it will print Python profiling output to
-    stdout.
-
   - When an event is rebuffered (because all listeners are busy or a
     listener rejected the event), the rebuffered event is now inserted
     in the head of the listener event queue.  This doesn't guarantee
@@ -122,6 +113,18 @@ Next Release
     'OK' (can be used for up checks or speed checks via
     plain-old-HTTP).
 
+  - An additional command-line option '--profile_options' is accepted
+    by the supervisord script for developer use.  It accepts the same
+    command line arguments as the 'supervisord' script.
+
+      supervisord -n -c sample.conf --profile_options=cumulative,calls
+
+    The values are sort_stats options that can be passed to the
+    standard Python profiler's PStats sort_stats method.
+
+    When you exit supervisor, it will print Python profiling output to
+    stdout.
+
 3.0a2
 
   - Fixed the README.txt example for defining the supervisor RPC

+ 0 - 1
setup.py

@@ -86,7 +86,6 @@ dist = setup(
      'supervisor_rpc':['main = supervisor.rpcinterface:make_main_rpcinterface'],
      'console_scripts': [
          'supervisord = supervisor.supervisord:main',
-         'profile_supervisord = supervisor.supervisord:profile',
          'supervisorctl = supervisor.supervisorctl:main',
          ],
       },

+ 10 - 0
src/supervisor/datatypes.py

@@ -300,3 +300,13 @@ def auto_restart(value):
         raise ValueError("invalid 'autorestart' value %r" % value)
     return computed_value
 
+def profile_options(value):
+    options = [x.lower() for x in list_of_strings(value) ]
+    sort_options = []
+    callers = False
+    for thing in options:
+        if thing != 'callers':
+            sort_options.append(thing)
+        else:
+            callers = True
+    return sort_options, callers

+ 4 - 0
src/supervisor/options.py

@@ -55,6 +55,7 @@ from supervisor.datatypes import SocketAddress
 from supervisor.datatypes import url
 from supervisor.datatypes import Automatic
 from supervisor.datatypes import auto_restart
+from supervisor.datatypes import profile_options
 
 from supervisor import loggers
 from supervisor import states
@@ -381,6 +382,8 @@ class ServerOptions(Options):
                  "k", "nocleanup", flag=1, default=0)
         self.add("strip_ansi", "supervisord.strip_ansi",
                  "t", "strip_ansi", flag=1, default=0)
+        self.add("profile_options", "supervisord.profile_options",
+                 "", "profile_options=", profile_options, default=None)
         self.add("sockchmod", "supervisord.sockchmod", "p:", "socket-mode=",
                  octal_type, default=0700)
         self.add("sockchown", "supervisord.sockchown", "o:", "socket-owner=",
@@ -549,6 +552,7 @@ class ServerOptions(Options):
         section.environment = dict_of_key_value_pairs(get('environment', ''))
         section.process_group_configs = self.process_groups_from_parser(parser)
         section.rpcinterface_factories = self.rpcinterfaces_from_parser(parser)
+        section.profile_options = None
         return section
 
     def process_groups_from_parser(self, parser):

+ 49 - 35
src/supervisor/supervisord.py

@@ -38,6 +38,10 @@ Options:
 -a/--minfds NUM -- the minimum number of file descriptors for start success
 -t/--strip_ansi -- strip ansi escape codes from process output
 --minprocs NUM  -- the minimum number of processes available for start success
+--profile_options OPTIONS -- run supervisord under profiler and output
+                             results based on OPTIONS, which  is a comma-sep'd
+                             list of 'cumulative', 'calls', and/or 'callers',
+                             e.g. 'cumulative,callers')
 """
 
 import os
@@ -64,8 +68,7 @@ class Supervisor:
         self.options = options
         self.process_groups = {}
 
-    def main(self, args=None, test=False, first=False):
-        self.options.realize(args, doc=__doc__)
+    def main(self):
         self.options.cleanup_fds()
         info_messages = []
         critical_messages = []
@@ -73,7 +76,7 @@ class Supervisor:
         setuid_msg = self.options.set_uid()
         if setuid_msg:
             critical_messages.append(setuid_msg)
-        if first:
+        if self.options.first:
             rlimit_messages = self.options.set_rlimits()
             info_messages.extend(rlimit_messages)
         warn_messages.extend(self.options.parse_warnings)
@@ -90,9 +93,9 @@ class Supervisor:
         for config in self.options.process_group_configs:
             config.after_setuid()
 
-        self.run(test)
+        self.run()
 
-    def run(self, test=False):
+    def run(self):
         self.process_groups = {} # clear
         self.stop_groups = None # clear
         events.clear()
@@ -108,7 +111,7 @@ class Supervisor:
             # writing pid file needs to come *after* daemonizing or pid
             # will be wrong
             self.options.write_pidfile()
-            self.runforever(test)
+            self.runforever()
         finally:
             self.options.cleanup()
 
@@ -158,7 +161,7 @@ class Supervisor:
                 # stop group queue
                 self.stop_groups.append(group)
 
-    def runforever(self, test=False):
+    def runforever(self):
         events.notify(events.SupervisorRunningEvent())
         timeout = 1
 
@@ -238,7 +241,7 @@ class Supervisor:
             if self.options.mood < SupervisorStates.RUNNING:
                 self.ordered_stop_groups_phase_2()
 
-            if test:
+            if self.options.test:
                 break
 
     def reap(self, once=False):
@@ -280,42 +283,53 @@ class Supervisor:
     def get_state(self):
         return self.options.mood
 
-# Main program
-def main(test=False):
-    assert os.name == "posix", "This code makes Unix-specific assumptions"
-    first = True
-    while 1:
-        # if we hup, restart by making a new Supervisor()
-        # the test argument just makes it possible to unit test this code
-        options = ServerOptions()
-        d = Supervisor(options)
-        try:
-            d.main(None, test, first)
-        except asyncore.ExitNow:
-            pass
-        first = False
-        if test:
-            return d
-        if options.mood < SupervisorStates.RUNNING:
-            break
-        if d.options.httpserver:
-            d.options.httpserver.close()
-
-def profile(test=False):
+# profile entry point
+def profile(cmd, globals, locals, sort_order, callers):
     import profile
     import pstats
     import tempfile
     fd, fn = tempfile.mkstemp()
     try:
-        profile.runctx('main(test)', globals(), locals(), fn)
+        profile.runctx(cmd, globals, locals, fn)
         stats = pstats.Stats(fn)
         stats.strip_dirs()
-        #stats.sort_stats('calls', 'time', 'cumulative')
-        stats.sort_stats('cumulative', 'calls', 'time')
-        #stats.print_callers(.3)
-        stats.print_stats(.3)
+        # calls,time,cumulative and cumulative,calls,time are useful
+        stats.sort_stats(*sort_order or ('cumulative', 'calls', 'time'))
+        if callers:
+            stats.print_callers(.3)
+        else:
+            stats.print_stats(.3)
     finally:
         os.remove(fn)
 
+
+# Main program
+def main(args=None, test=False):
+    assert os.name == "posix", "This code makes Unix-specific assumptions"
+    # if we hup, restart by making a new Supervisor()
+    first = True
+    while 1:
+        options = ServerOptions()
+        options.realize(args, doc=__doc__)
+        options.first = first
+        options.test = test
+        if options.profile_options:
+            sort_order, callers = options.profile_options
+            profile('go(options)', globals(), locals(), sort_order, callers)
+        else:
+            go(options)
+        if test or (options.mood < SupervisorStates.RESTARTING):
+            break
+        if options.httpserver:
+            options.httpserver.close()
+        first = False
+
+def go(options):
+    d = Supervisor(options)
+    try:
+        d.main()
+    except asyncore.ExitNow:
+        pass
+
 if __name__ == "__main__":
     main()

+ 70 - 11
src/supervisor/tests/test_supervisord.py

@@ -2,6 +2,9 @@ import unittest
 import time
 import signal
 import sys
+import os
+import tempfile
+import shutil
 
 from supervisor.tests.base import DummyOptions
 from supervisor.tests.base import DummyPConfig
@@ -10,6 +13,52 @@ from supervisor.tests.base import DummyProcess
 from supervisor.tests.base import DummyProcessGroup
 from supervisor.tests.base import DummyDispatcher
 
+class EntryPointTests(unittest.TestCase):
+    def test_main_noprofile(self):
+        from supervisor.supervisord import main
+        conf = os.path.join(
+            os.path.abspath(os.path.dirname(__file__)), 'fixtures',
+            'donothing.conf')
+        import StringIO
+        new_stdout = StringIO.StringIO()
+        old_stdout = sys.stdout
+        try:
+            tempdir = tempfile.mkdtemp()
+            sock = os.path.join(tempdir, 'sock')
+            log = os.path.join(tempdir, 'log')
+            pid = os.path.join(tempdir, 'pid')
+            sys.stdout = new_stdout
+            main(args=['-c', conf, '-w', sock, '-l', log, '-j', pid, '-n'],
+                 test=True)
+        finally:
+            sys.stdout = old_stdout
+            shutil.rmtree(tempdir)
+        output = new_stdout.getvalue()
+        self.failUnless(output.find('supervisord started') != 1, output)
+
+    def test_main_profile(self):
+        from supervisor.supervisord import main
+        conf = os.path.join(
+            os.path.abspath(os.path.dirname(__file__)), 'fixtures',
+            'donothing.conf')
+        import StringIO
+        new_stdout = StringIO.StringIO()
+        old_stdout = sys.stdout
+        try:
+            tempdir = tempfile.mkdtemp()
+            sock = os.path.join(tempdir, 'sock')
+            log = os.path.join(tempdir, 'log')
+            pid = os.path.join(tempdir, 'pid')
+            sys.stdout = new_stdout
+            main(args=['-c', conf, '-w', sock, '-l', log, '-j', pid, '-n',
+                       '--profile_options=cumulative,calls'], test=True)
+        finally:
+            sys.stdout = old_stdout
+            shutil.rmtree(tempdir)
+        output = new_stdout.getvalue()
+        self.failUnless(output.find('cumulative time, call count') != -1,
+                        output)
+
 class SupervisordTests(unittest.TestCase):
     def tearDown(self):
         from supervisor.events import clear
@@ -27,9 +76,10 @@ class SupervisordTests(unittest.TestCase):
         pconfig = DummyPConfig(options, 'foo', 'foo', '/bin/foo')
         gconfigs = [DummyPGroupConfig(options,'foo', pconfigs=[pconfig])]
         options.process_group_configs = gconfigs
+        options.test = True
+        options.first = True
         supervisord = self._makeOne(options)
-        supervisord.main(args='abc', test=True, first=True)
-        self.assertEqual(options.realizeargs, 'abc')
+        supervisord.main()
         self.assertEqual(options.environment_processed, True)
         self.assertEqual(options.fds_cleaned_up, True)
         self.assertEqual(options.rlimits_set, True)
@@ -132,7 +182,8 @@ class SupervisordTests(unittest.TestCase):
         events.subscribe(events.SupervisorStateChangeEvent, callback)
         options = DummyOptions()
         supervisord = self._makeOne(options)
-        supervisord.runforever(test=True)
+        options.test = True
+        supervisord.runforever()
         self.assertEqual(L, [1])
 
     def test_runforever_emits_generic_specific_event(self):
@@ -142,8 +193,9 @@ class SupervisordTests(unittest.TestCase):
             L.append(2)
         events.subscribe(events.SupervisorRunningEvent, callback)
         options = DummyOptions()
+        options.test = True
         supervisord = self._makeOne(options)
-        supervisord.runforever(test=True)
+        supervisord.runforever()
         self.assertEqual(L, [2])
 
     def test_runforever_select_eintr(self):
@@ -151,7 +203,8 @@ class SupervisordTests(unittest.TestCase):
         import errno
         options.select_error = errno.EINTR
         supervisord = self._makeOne(options)
-        supervisord.runforever(test=True)
+        options.test = True
+        supervisord.runforever()
         self.assertEqual(options.logger.data[0], 'EINTR encountered in select')
 
     def test_runforever_select_uncaught_exception(self):
@@ -160,7 +213,8 @@ class SupervisordTests(unittest.TestCase):
         options.select_error = errno.EBADF
         supervisord = self._makeOne(options)
         import select
-        self.assertRaises(select.error, supervisord.runforever, test=True)
+        options.test = True
+        self.assertRaises(select.error, supervisord.runforever)
 
     def test_runforever_select_dispatchers(self):
         options = DummyOptions()
@@ -175,7 +229,8 @@ class SupervisordTests(unittest.TestCase):
         pgroup.dispatchers = {6:readable, 7:writable, 8:error}
         supervisord.process_groups = {'foo': pgroup}
         options.select_result = [6], [7, 8], []
-        supervisord.runforever(test=True)
+        options.test = True
+        supervisord.runforever()
         self.assertEqual(pgroup.transitioned, True)
         self.assertEqual(readable.read_event_handled, True)
         self.assertEqual(writable.write_event_handled, True)
@@ -193,7 +248,8 @@ class SupervisordTests(unittest.TestCase):
         pgroup.dispatchers = {6:exitnow}
         supervisord.process_groups = {'foo': pgroup}
         options.select_result = [6], [], []
-        self.assertRaises(asyncore.ExitNow, supervisord.runforever, test=True)
+        options.test = True
+        self.assertRaises(asyncore.ExitNow, supervisord.runforever)
 
     def test_runforever_stopping_emits_events(self):
         options = DummyOptions()
@@ -208,7 +264,8 @@ class SupervisordTests(unittest.TestCase):
         from supervisor import events
         events.subscribe(events.SupervisorStateChangeEvent, callback)
         import asyncore
-        self.assertRaises(asyncore.ExitNow, supervisord.runforever, test=True)
+        options.test = True
+        self.assertRaises(asyncore.ExitNow, supervisord.runforever)
         self.assertTrue(pgroup.all_stopped)
         self.assertTrue(isinstance(L[0], events.SupervisorRunningEvent))
         self.assertTrue(isinstance(L[0], events.SupervisorStateChangeEvent))
@@ -227,8 +284,9 @@ class SupervisordTests(unittest.TestCase):
             L.append(1)
         supervisord.process_groups = {'foo': pgroup}
         supervisord.options.mood = 0
+        supervisord.options.test = True
         import asyncore
-        self.assertRaises(asyncore.ExitNow, supervisord.runforever, test=True)
+        self.assertRaises(asyncore.ExitNow, supervisord.runforever)
         self.assertEqual(pgroup.all_stopped, True)
 
     def test_exit_delayed(self):
@@ -245,7 +303,8 @@ class SupervisordTests(unittest.TestCase):
         supervisord.process_groups = {'foo': pgroup}
         supervisord.options.mood = 0
         import asyncore
-        supervisord.runforever(test=True)
+        supervisord.options.test = True
+        supervisord.runforever()
         self.assertNotEqual(supervisord.lastdelayreport, 0)
 
     def test_getSupervisorStateDescription(self):