[CalendarServer-changes] [13484] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu May 15 18:54:55 PDT 2014


Revision: 13484
          http://trac.calendarserver.org//changeset/13484
Author:   cdaboo at apple.com
Date:     2014-05-15 18:54:55 -0700 (Thu, 15 May 2014)
Log Message:
-----------
Fix base class ordering to ensure schedule work group locking occurs. Fix dashboard so it can run even
when the server is down or being started/stopped.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/dashboard_service.py
    CalendarServer/trunk/calendarserver/tools/dashboard.py
    CalendarServer/trunk/requirements-stable.txt
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py

Modified: CalendarServer/trunk/calendarserver/dashboard_service.py
===================================================================
--- CalendarServer/trunk/calendarserver/dashboard_service.py	2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/calendarserver/dashboard_service.py	2014-05-16 01:54:55 UTC (rev 13484)
@@ -124,9 +124,12 @@
         @rtype: L{str}
         """
 
-        txn = self.factory.store.newTransaction()
-        records = (yield JobItem.histogram(txn))
-        yield txn.commit()
+        if self.factory.store:
+            txn = self.factory.store.newTransaction()
+            records = (yield JobItem.histogram(txn))
+            yield txn.commit()
+        else:
+            records = {}
 
         returnValue(records)
 
@@ -139,9 +142,13 @@
         @rtype: L{str}
         """
 
-        queuer = self.factory.store.queuer
-        loads = queuer.workerPool.eachWorkerLoad()
-        level = queuer.workerPool.loadLevel()
+        if self.factory.store:
+            queuer = self.factory.store.queuer
+            loads = queuer.workerPool.eachWorkerLoad()
+            level = queuer.workerPool.loadLevel()
+        else:
+            loads = []
+            level = 0
 
         return succeed({"workers": loads, "level": level})
 

Modified: CalendarServer/trunk/calendarserver/tools/dashboard.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/dashboard.py	2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/calendarserver/tools/dashboard.py	2014-05-16 01:54:55 UTC (rev 13484)
@@ -96,6 +96,11 @@
 
 
 
+def defaultIfNone(x, default):
+    return x if x is not None else default
+
+
+
 class Dashboard(object):
     """
     Main dashboard controller. Use Python's L{sched} feature to schedule
@@ -113,6 +118,7 @@
         self.seconds = 0.1 if usesCurses else 1.0
         self.sched = sched.scheduler(time.time, time.sleep)
         self.client = DashboardClient(("localhost", 8100), True)
+        self.client_error = False
 
 
     @classmethod
@@ -160,11 +166,34 @@
         self.updateDisplay(True)
 
 
+    def resetWindows(self):
+        """
+        Reset the current set of windows.
+        """
+        if self.windows:
+            for window in self.windows:
+                window.deactivate()
+            old_windows = self.windows
+            self.windows = []
+            top = 0
+            for old in old_windows:
+                self.windows.append(old.__class__(self.usesCurses, self.client).makeWindow(top=top))
+                self.windows[-1].activate()
+                top += self.windows[-1].nlines + 1
+
+
     def updateDisplay(self, initialUpdate=False):
         """
         Periodic update of the current window and check for a key press.
         """
         self.client.update()
+        client_error = len(self.client.currentData) == 0
+        if client_error ^ self.client_error:
+            self.client_error = client_error
+            self.resetWindows()
+        elif filter(lambda x: x.requiresReset(), self.windows):
+            self.resetWindows()
+
         try:
             if not self.paused or initialUpdate:
                 for window in filter(
@@ -172,8 +201,9 @@
                     self.windows
                 ):
                     window.update()
-        except Exception as e:
-            print(str(e))
+        except Exception as e: #@UnusedVariable
+            #print(str(e))
+            pass
         if not self.usesCurses:
             print("-------------")
 
@@ -208,7 +238,7 @@
         self.socket = None
         self.sockname = sockname
         self.useTCP = useTCP
-        self.currentData = None
+        self.currentData = {}
         self.items = []
 
 
@@ -223,21 +253,26 @@
                 self.socket.setblocking(0)
             self.socket.sendall(json.dumps(items) + "\r\n")
             data = ""
+            t = time.time()
             while not data.endswith("\n"):
                 try:
                     d = self.socket.recv(1024)
                 except socket.error as se:
                     if se.args[0] != errno.EWOULDBLOCK:
                         raise
+                    if time.time() - t > 5:
+                        raise socket.error
                     continue
                 if d:
                     data += d
                 else:
                     break
             data = json.loads(data)
-        except socket.error as e:
-            data = {"Failed": "Unable to read statistics from server: %s %s" % (self.sockname, e)}
+        except socket.error:
+            data = {}
             self.socket = None
+        except ValueError:
+            data = {}
         return data
 
 
@@ -245,6 +280,7 @@
         """
         Update the current data from the server.
         """
+
         self.currentData = self.readSock(self.items)
 
 
@@ -252,7 +288,8 @@
         """
         Update the current data from the server.
         """
-        return self.readSock([item])[item]
+        data = self.readSock([item])
+        return data[item] if data else None
 
 
     def addItem(self, item):
@@ -282,6 +319,8 @@
     def __init__(self, usesCurses, client):
         self.usesCurses = usesCurses
         self.client = client
+        self.rowCount = 0
+        self.needsReset = False
 
 
     def makeWindow(self, top=0, left=0):
@@ -315,6 +354,14 @@
         return True
 
 
+    def requiresReset(self):
+        """
+        Indicates that the window needs a full reset, because e.g., the
+        number of items it didplays has changed.
+        """
+        return self.needsReset
+
+
     def activate(self):
         """
         About to start displaying.
@@ -342,7 +389,7 @@
 
 
     def clientData(self):
-        return self.client.currentData[self.clientItem]
+        return self.client.currentData.get(self.clientItem)
 
 
     def readItem(self, item):
@@ -417,13 +464,17 @@
     FORMAT_WIDTH = 78
 
     def makeWindow(self, top=0, left=0):
-        nlines = self.readItem("jobcount")
-        self._createWindow("Jobs", nlines + 5, ncols=self.FORMAT_WIDTH, begin_y=top, begin_x=left)
+        nlines = defaultIfNone(self.readItem("jobcount"), 0)
+        self.rowCount = nlines
+        self._createWindow("Jobs", self.rowCount + 5, ncols=self.FORMAT_WIDTH, begin_y=top, begin_x=left)
         return self
 
 
     def update(self):
-        records = self.clientData()
+        records = defaultIfNone(self.clientData(), {})
+        if len(records) != self.rowCount:
+            self.needsReset = True
+            return
         self.iter += 1
 
         if self.usesCurses:
@@ -496,20 +547,24 @@
 
     help = "display server child job assignments"
     clientItem = "job_assignments"
-    FORMAT_WIDTH = 32
+    FORMAT_WIDTH = 40
 
     def makeWindow(self, top=0, left=0):
-        slots = self.readItem(self.clientItem)["workers"]
+        slots = defaultIfNone(self.readItem(self.clientItem), {"workers": ()})["workers"]
+        self.rowCount = len(slots)
         self._createWindow(
-            "Job Assignments", len(slots) + 5, self.FORMAT_WIDTH,
+            "Job Assignments", self.rowCount + 5, self.FORMAT_WIDTH,
             begin_y=top, begin_x=left
         )
         return self
 
 
     def update(self):
-        data = self.clientData()
+        data = defaultIfNone(self.clientData(), {"workers": {}, "level": 0})
         records = data["workers"]
+        if len(records) != self.rowCount:
+            self.needsReset = True
+            return
         self.iter += 1
 
         if self.usesCurses:
@@ -522,25 +577,28 @@
 
         x = 1
         y = 1
-        s = " {:>4}{:>12}{:>12} ".format(
-            "Slot", "assigned", "completed"
+        s = " {:>4}{:>12}{:>8}{:>12} ".format(
+            "Slot", "assigned", "load", "completed"
         )
         if self.usesCurses:
             self.window.addstr(y, x, s, curses.A_REVERSE)
         else:
             print(s)
         y += 1
+        total_assigned = 0
         total_completed = 0
         for ctr, details in enumerate(records):
-            assigned, completed = details
+            assigned, load, completed = details
+            total_assigned += assigned
             total_completed += completed
             changed = (
                 ctr in self.lastResult and
                 self.lastResult[ctr] != assigned
             )
-            s = " {:>4}{:>12}{:>12} ".format(
+            s = " {:>4}{:>12}{:>8}{:>12} ".format(
                 ctr,
                 assigned,
+                load,
                 completed,
             )
             try:
@@ -555,8 +613,9 @@
                 pass
             y += 1
 
-        s = " {:<6}{:>10}{:>12}".format(
+        s = " {:<6}{:>10}{:>8}{:>12}".format(
             "Total:",
+            total_assigned,
             "{}%".format(data["level"]),
             total_completed,
         )
@@ -585,17 +644,21 @@
     FORMAT_WIDTH = 72
 
     def makeWindow(self, top=0, left=0):
-        slots = self.readItem(self.clientItem)["slots"]
+        slots = defaultIfNone(self.readItem(self.clientItem), {"slots": ()})["slots"]
+        self.rowCount = len(slots)
         self._createWindow(
-            "HTTP Slots", len(slots) + 5, self.FORMAT_WIDTH,
+            "HTTP Slots", self.rowCount + 5, self.FORMAT_WIDTH,
             begin_y=top, begin_x=left
         )
         return self
 
 
     def update(self):
-        data = self.clientData()
+        data = defaultIfNone(self.clientData(), {"slots": {}, "overloaded": False})
         records = data["slots"]
+        if len(records) != self.rowCount:
+            self.needsReset = True
+            return
         self.iter += 1
 
         if self.usesCurses:
@@ -688,13 +751,22 @@
     clientItem = "stats_system"
 
     def makeWindow(self, top=0, left=0):
-        slots = self.readItem(self.clientItem)
-        self._createWindow("System", len(slots) + 3, begin_y=top, begin_x=left)
+        slots = defaultIfNone(self.readItem(self.clientItem), (1, 2, 3, 4,))
+        self.rowCount = len(slots)
+        self._createWindow("System", self.rowCount + 3, begin_y=top, begin_x=left)
         return self
 
 
     def update(self):
-        records = self.clientData()
+        records = defaultIfNone(self.clientData(), {
+            "cpu use": 0.0,
+            "memory percent": 0.0,
+            "memory used": 0,
+            "start time": time.time(),
+        })
+        if len(records) != self.rowCount:
+            self.needsReset = True
+            return
         self.iter += 1
 
         if self.usesCurses:
@@ -763,7 +835,7 @@
 
 
     def update(self):
-        records = self.clientData()
+        records = defaultIfNone(self.clientData(), {})
         self.iter += 1
 
         if self.usesCurses:
@@ -787,7 +859,15 @@
             print(s2)
         y += 2
         for key, seconds in (("current", 60,), ("1m", 60,), ("5m", 5 * 60,), ("1h", 60 * 60,),):
-            stat = records[key]
+            stat = records.get(key, {
+                "requests": 0,
+                "t": 0.0,
+                "t-resp-wr": 0.0,
+                "T-MAX": 0.0,
+                "slots": 0,
+                "cpu": 0.0,
+                "500": 0,
+            })
             s = " {:<8}{:>8}{:>10.1f}{:>10.1f}{:>10.1f}{:>10.1f}{:>8.2f}{:>7.1f}%{:>8} ".format(
                 key,
                 stat["requests"],

Modified: CalendarServer/trunk/requirements-stable.txt
===================================================================
--- CalendarServer/trunk/requirements-stable.txt	2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/requirements-stable.txt	2014-05-16 01:54:55 UTC (rev 13484)
@@ -1,7 +1,7 @@
 # For CalendarServer development, don't try to get these projects from PyPI; use svn.
 
 -e .
--e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@13475#egg=twextpy
+-e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@13483#egg=twextpy
 -e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
 -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@13420#egg=pycalendar
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py	2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py	2014-05-16 01:54:55 UTC (rev 13484)
@@ -49,7 +49,7 @@
 
 
 
-class ScheduleWorkMixin(object):
+class ScheduleWorkMixin(WorkItem):
     """
     Base class for common schedule work item behavior.
     """
@@ -131,7 +131,7 @@
 
 
 
-class ScheduleOrganizerWork(WorkItem, fromTable(schema.SCHEDULE_ORGANIZER_WORK), ScheduleWorkMixin):
+class ScheduleOrganizerWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_ORGANIZER_WORK)):
     """
     The associated work item table is SCHEDULE_ORGANIZER_WORK.
 
@@ -272,7 +272,7 @@
 
 
 
-class ScheduleReplyWork(WorkItem, fromTable(schema.SCHEDULE_REPLY_WORK), ScheduleReplyWorkMixin):
+class ScheduleReplyWork(ScheduleReplyWorkMixin, fromTable(schema.SCHEDULE_REPLY_WORK)):
     """
     The associated work item table is SCHEDULE_REPLY_WORK.
 
@@ -353,7 +353,7 @@
 
 
 
-class ScheduleReplyCancelWork(WorkItem, fromTable(schema.SCHEDULE_REPLY_CANCEL_WORK), ScheduleReplyWorkMixin):
+class ScheduleReplyCancelWork(ScheduleReplyWorkMixin, fromTable(schema.SCHEDULE_REPLY_CANCEL_WORK)):
     """
     The associated work item table is SCHEDULE_REPLY_CANCEL_WORK.
 
@@ -416,7 +416,7 @@
 
 
 
-class ScheduleRefreshWork(WorkItem, fromTable(schema.SCHEDULE_REFRESH_WORK), ScheduleWorkMixin):
+class ScheduleRefreshWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_REFRESH_WORK)):
     """
     The associated work item table is SCHEDULE_REFRESH_WORK.
 
@@ -604,7 +604,7 @@
 
 
 
-class ScheduleAutoReplyWork(WorkItem, fromTable(schema.SCHEDULE_AUTO_REPLY_WORK), ScheduleWorkMixin):
+class ScheduleAutoReplyWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_AUTO_REPLY_WORK)):
     """
     The associated work item table is SCHEDULE_AUTO_REPLY_WORK.
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140515/ba70323f/attachment-0001.html>


More information about the calendarserver-changes mailing list