Sfoglia il codice sorgente

Show description of process current status in web UI.

Chris McDonough 19 anni fa
parent
commit
84c4ecd246

+ 1 - 7
TODO.txt

@@ -1,8 +1,5 @@
 - Web interface:
 
-   - Present status in addition to state (e.g. spawnerr, etc, same as
-     supervisorctl).
-
    - Support/use POST requests.
 
    - Redirect whenever an action causes the URL to be unreloadable.
@@ -11,14 +8,11 @@
 
 - Unit tests for log rotation.
 
-- Better remote explanation when kill_undead needs to happen from
-  supervisorctl (long blockage).
-
 - Command-line arg tests.
 
 - Log logtail requests.
 
-- Stop logging all RPC requests in info mode when we ship
+- Stop logging all RPC requests in info mode when we ship.
 
 - Test on Linux.
 

+ 4 - 37
src/supervisor/supervisorctl.py

@@ -231,41 +231,6 @@ class Controller(cmd.Cmd):
     def help_exit(self):
         self._output("exit\tExit the supervisor shell.")
 
-    def _interpretProcessInfo(self, info):
-        result = {}
-        result['name'] = info['name']
-        pid = info['pid']
-
-        state = info['state']
-
-        if state == ProcessStates.RUNNING:
-            start = info['start']
-            now = info['now']
-            start_dt = datetime.datetime(*time.gmtime(start)[:6])
-            now_dt = datetime.datetime(*time.gmtime(now)[:6])
-            uptime = now_dt - start_dt
-            desc = 'pid %s, uptime %s' % (info['pid'], uptime)
-
-        elif state in (ProcessStates.FATAL, ProcessStates.BACKOFF):
-            desc = info['spawnerr']
-            if not desc:
-                desc = 'unknown error (try "tail %s")' % info['name']
-
-        elif state in (ProcessStates.STOPPED, ProcessStates.EXITED):
-            if info['start']:
-                stop = info['stop']
-                stop_dt = datetime.datetime(*time.localtime(stop)[:7])
-                desc = stop_dt.strftime('%b %d %I:%M %p')
-            else:
-                desc = 'Not started'
-
-        else:
-            desc = ''
-
-        result['desc'] = desc
-        result['state'] = getProcessStateDescription(state)
-        return result
-
     def do_status(self, arg):
         if not self._upcheck():
             return
@@ -285,11 +250,13 @@ class Controller(cmd.Cmd):
                     else:
                         raise
                     continue
-                newinfo = self._interpretProcessInfo(info)
+                newinfo = {'name':info['name'], 'state':info['statename'],
+                           'desc':info['description']}
                 self._output(template % newinfo)
         else:
             for info in supervisor.getAllProcessInfo():
-                newinfo = self._interpretProcessInfo(info)
+                newinfo = {'name':info['name'], 'state':info['statename'],
+                           'desc':info['description']}
                 self._output(template % newinfo)
 
     def help_status(self):

+ 26 - 18
src/supervisor/tests.py

@@ -556,8 +556,10 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self.assertEqual(data['start'], 10)
         self.assertEqual(data['stop'], 11)
         self.assertEqual(data['state'], ProcessStates.RUNNING)
+        self.assertEqual(data['statename'], 'RUNNING')
         self.assertEqual(data['exitstatus'], 0)
         self.assertEqual(data['spawnerr'], '')
+        self.failUnless(data['description'].startswith('pid 111'))
 
     def test_getAllProcessInfo(self):
         from supervisord import ProcessStates
@@ -567,14 +569,12 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
                                 logfile='/tmp/process1.log')
         p2config = DummyPConfig('process2', '/bin/process2', priority=2,
                                 logfile='/tmp/process2.log')
-        process1 = DummyProcess(options, p1config)
-        process1.reportstatusmsg = 'foo'
+        process1 = DummyProcess(options, p1config, ProcessStates.RUNNING)
         process1.pid = 111
         process1.laststart = 10
         process1.laststop = 11
-        process2 = DummyProcess(options, p2config)
-        process2.reportstatusmsg = 'bar'
-        process2.pid = 222
+        process2 = DummyProcess(options, p2config, ProcessStates.STOPPED)
+        process2.pid = 0
         process2.laststart = 20
         process2.laststop = 11
         supervisord = DummySupervisor({'process1':process1,
@@ -593,18 +593,22 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self.assertEqual(p1info['start'], 10)
         self.assertEqual(p1info['stop'], 11)
         self.assertEqual(p1info['state'], ProcessStates.RUNNING)
+        self.assertEqual(p1info['statename'], 'RUNNING')
         self.assertEqual(p1info['exitstatus'], 0)
         self.assertEqual(p1info['spawnerr'], '')
+        self.failUnless(p1info['description'].startswith('pid 111'))
 
         p2info = info[1]
         self.assertEqual(p2info['logfile'], '/tmp/process2.log')
         self.assertEqual(p2info['name'], 'process2')
-        self.assertEqual(p2info['pid'], 222)
+        self.assertEqual(p2info['pid'], 0)
         self.assertEqual(p2info['start'], 20)
         self.assertEqual(p2info['stop'], 11)
-        self.assertEqual(p2info['state'], ProcessStates.RUNNING)
+        self.assertEqual(p2info['state'], ProcessStates.STOPPED)
+        self.assertEqual(p2info['statename'], 'STOPPED')
         self.assertEqual(p2info['exitstatus'], 0)
         self.assertEqual(p2info['spawnerr'], '')
+        self.assertEqual(p2info['description'], 'Dec 31 07:00 PM')
 
     def test_readProcessLog_unreadable(self):
         options = DummyOptions()
@@ -1751,7 +1755,7 @@ class ControllerTests(unittest.TestCase):
         controller.stdout = StringIO()
         result = controller.do_status('foo')
         self.assertEqual(result, None)
-        expected = "foo            RUNNING    pid 11, uptime 0:01:40\n"
+        expected = "foo            RUNNING    foo description\n"
         self.assertEqual(controller.stdout.getvalue(), expected)
 
     def test_status_allprocesses(self):
@@ -1761,9 +1765,9 @@ class ControllerTests(unittest.TestCase):
         result = controller.do_status('')
         self.assertEqual(result, None)
         expected = """\
-foo            RUNNING    pid 11, uptime 0:01:40
-bar            FATAL      screwed
-baz            STOPPED    Jun 26 07:42 PM
+foo            RUNNING    foo description
+bar            FATAL      bar description
+baz            STOPPED    baz description
 """
         self.assertEqual(controller.stdout.getvalue(), expected)
 
@@ -2021,9 +2025,9 @@ baz            STOPPED    Jun 26 07:42 PM
         result = controller.do_open('http://localhost:9002')
         self.assertEqual(result, None)
         self.assertEqual(controller.stdout.getvalue(), """\
-foo            RUNNING    pid 11, uptime 0:01:40
-bar            FATAL      screwed
-baz            STOPPED    Jun 26 07:42 PM
+foo            RUNNING    foo description
+bar            FATAL      bar description
+baz            STOPPED    baz description
 """)
         
 class TailFProducerTests(unittest.TestCase):
@@ -2378,31 +2382,34 @@ class DummySupervisorRPCNamespace:
             'name':'foo',
             'pid':11,
             'state':ProcessStates.RUNNING,
+            'statename':'RUNNING',
             'start':_NOW - 100,
             'stop':0,
             'spawnerr':'',
-            'reportstatusmsg':'',
             'now':_NOW,
+            'description':'foo description',
              },
             {
             'name':'bar',
             'pid':12,
             'state':ProcessStates.FATAL,
+            'statename':'FATAL',
             'start':_NOW - 100,
             'stop':_NOW - 50,
             'spawnerr':'screwed',
-            'reportstatusmsg':'statusmsg',
             'now':_NOW,
+            'description':'bar description',
              },
             {
             'name':'baz',
             'pid':12,
             'state':ProcessStates.STOPPED,
+            'statename':'STOPPED',
             'start':_NOW - 100,
             'stop':_NOW - 25,
             'spawnerr':'',
-            'reportstatusmsg':'OK',
             'now':_NOW,
+            'description':'baz description',
              },
             ]
                 
@@ -2412,11 +2419,12 @@ class DummySupervisorRPCNamespace:
             'name':'foo',
             'pid':11,
             'state':ProcessStates.RUNNING,
+            'statename':'RUNNING',
             'start':_NOW - 100,
             'stop':0,
             'spawnerr':'',
-            'reportstatusmsg':'',
             'now':_NOW,
+            'description':'foo description',
              }
 
     def startProcess(self, name):

+ 6 - 2
src/supervisor/ui/status.html

@@ -13,15 +13,16 @@
   <div style="margin-bottom: 5px;">
     <small>(c) 2006 Chris McDonough</small>
   </div>
-  <div meld:id="statusmessage" style="width: 597px"></div>
+  <div meld:id="statusmessage" style="width: 797px"></div>
 </div>
 
 <div meld:id="content">
 
 <form meld:id="form" action="index.html" method="POST">
 
-<table width="600" border="0" meld:id="statustable">
+<table width="800" border="0" meld:id="statustable">
   <th width="130" class="tableheader">State</th>
+  <th width="200" class="tableheader">Description</th>
   <th width="159" class="tableheader">Name</th>
   <th width="297" class="tableheader">Action</th>
   <tbody meld:id="tbody">
@@ -29,6 +30,9 @@
       <td meld:id="status_td">
            <span meld:id="status_text" class="statusnominal">Status</span>
       </td>
+      <td meld:id="info_td">
+           <span meld:id="info_text">Info</span>
+      </td>
       <td meld:id="name_td"><a href="#" meld:id="name_anchor"
                                target="_blank">Name</a></td>
       <td meld:id="action_td">

+ 13 - 8
src/supervisor/web.py

@@ -368,19 +368,22 @@ class StatusView(MeldView):
                     return NOT_DONE_YET
 
         supervisord = self.context.supervisord
+        rpcinterface = xmlrpc.RPCInterface(supervisord)
 
         processnames = supervisord.processes.keys()
         processnames.sort()
         data = []
         for processname in processnames:
-            process = supervisord.processes[processname]
-            state = process.get_state()
-            from supervisord import getProcessStateDescription
-            statedesc = getProcessStateDescription(state)
-            actions = self.actions_for_process(process)
-            data.append({'status':statedesc, 'name':processname,
-                         'actions':actions,
-                         'state':state})
+            actions = self.actions_for_process(
+                supervisord.processes[processname])
+            info = rpcinterface.supervisor.getProcessInfo(processname)
+            data.append({
+                'status':info['statename'],
+                'name':processname,
+                'actions':actions,
+                'state':info['state'],
+                'description':info['description'],
+                })
         
         root = self.clone()
 
@@ -395,6 +398,8 @@ class StatusView(MeldView):
             status_text.content(item['status'])
             status_text.attrib['class'] = self.css_class_for_state(
                 item['state'])
+            info_text = element.findmeld('info_text')
+            info_text.content(item['description'])
             anchor = element.findmeld('name_anchor')
             processname = item['name']
             anchor.attributes(href='tail.html?processname=%s' % processname)

+ 50 - 9
src/supervisor/xmlrpc.py

@@ -1,13 +1,6 @@
 import xmlrpclib
 import os
-from supervisord import ProcessStates
-from supervisord import SupervisorStates
-from supervisord import getSupervisorStateDescription
-import signal
-import time
-from medusa.xmlrpc_handler import xmlrpc_handler
-from medusa.http_server import get_header
-from medusa import producers
+import datetime
 import sys
 import types
 import re
@@ -15,6 +8,16 @@ import traceback
 import StringIO
 import tempfile
 import errno
+import signal
+import time
+
+from supervisord import ProcessStates
+from supervisord import SupervisorStates
+from supervisord import getSupervisorStateDescription
+from supervisord import getProcessStateDescription
+from medusa.xmlrpc_handler import xmlrpc_handler
+from medusa.http_server import get_header
+from medusa import producers
 
 from http import NOT_DONE_YET
 from options import readFile
@@ -497,6 +500,39 @@ class SupervisorNamespaceRPCInterface:
         killall.rpcinterface = self
         return killall # deferred
 
+    def _interpretProcessInfo(self, info):
+        result = {}
+        result['name'] = info['name']
+        pid = info['pid']
+
+        state = info['state']
+
+        if state == ProcessStates.RUNNING:
+            start = info['start']
+            now = info['now']
+            start_dt = datetime.datetime(*time.gmtime(start)[:6])
+            now_dt = datetime.datetime(*time.gmtime(now)[:6])
+            uptime = now_dt - start_dt
+            desc = 'pid %s, uptime %s' % (info['pid'], uptime)
+
+        elif state in (ProcessStates.FATAL, ProcessStates.BACKOFF):
+            desc = info['spawnerr']
+            if not desc:
+                desc = 'unknown error (try "tail %s")' % info['name']
+
+        elif state in (ProcessStates.STOPPED, ProcessStates.EXITED):
+            if info['start']:
+                stop = info['stop']
+                stop_dt = datetime.datetime(*time.localtime(stop)[:7])
+                desc = stop_dt.strftime('%b %d %I:%M %p')
+            else:
+                desc = 'Not started'
+
+        else:
+            desc = ''
+
+        return desc
+
     def getProcessInfo(self, name):
         """ Get info about a process named name
 
@@ -517,17 +553,22 @@ class SupervisorNamespaceRPCInterface:
         spawnerr = process.spawnerr or ''
         exitstatus = process.exitstatus or 0
 
-        return {
+        info = {
             'name':name,
             'start':start,
             'stop':stop,
             'now':now,
             'state':state,
+            'statename':getProcessStateDescription(state),
             'spawnerr':spawnerr,
             'exitstatus':exitstatus,
             'logfile':process.config.logfile,
             'pid':process.pid
             }
+        
+        description = self._interpretProcessInfo(info)
+        info['description'] = description
+        return info
 
     def getAllProcessInfo(self):
         """ Get info about all processes