[CalendarServer-changes] [9767] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Sep 4 09:14:09 PDT 2012


Revision: 9767
          http://trac.macosforge.org/projects/calendarserver/changeset/9767
Author:   cdaboo at apple.com
Date:     2012-09-04 09:14:07 -0700 (Tue, 04 Sep 2012)
Log Message:
-----------
Add option to have stats logged via TCP as well as unix socket. Modify the readStats tool to be able to read from multiple
tcp sockets and print a summary of all hosts.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/contrib/tools/readStats.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2012-09-04 15:24:11 UTC (rev 9766)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2012-09-04 16:14:07 UTC (rev 9767)
@@ -1252,12 +1252,20 @@
 
         # Start listening on the stats socket, for administrators to inspect
         # the current stats on the server.
-        stats = CalDAVStatisticsServer(logger)
-        statsService = GroupOwnedUNIXServer(
-            gid, config.GlobalStatsSocket, stats, mode=0660
-        )
-        statsService.setName("stats")
-        statsService.setServiceParent(s)
+        if config.Stats.EnableUnixStatsSocket:
+            stats = CalDAVStatisticsServer(logger)
+            statsService = GroupOwnedUNIXServer(
+                gid, config.Stats.UnixStatsSocket, stats, mode=0660
+            )
+            statsService.setName("unix-stats")
+            statsService.setServiceParent(s)
+        if config.Stats.EnableTCPStatsSocket:
+            stats = CalDAVStatisticsServer(logger)
+            statsService = TCPServer(
+                config.Stats.TCPStatsPort, stats, interface=""
+            )
+            statsService.setName("tcp-stats")
+            statsService.setServiceParent(s)
 
         # Optionally enable Manhole access
         if config.Manhole.Enabled:
@@ -1320,7 +1328,7 @@
     def deleteStaleSocketFiles(self):
 
         # Check all socket files we use.
-        for checkSocket in [config.ControlSocket, config.GlobalStatsSocket] :
+        for checkSocket in [config.ControlSocket, config.Stats.UnixStatsSocket] :
 
             # See if the file exists.
             if (os.path.exists(checkSocket)):

Modified: CalendarServer/trunk/contrib/tools/readStats.py
===================================================================
--- CalendarServer/trunk/contrib/tools/readStats.py	2012-09-04 15:24:11 UTC (rev 9766)
+++ CalendarServer/trunk/contrib/tools/readStats.py	2012-09-04 16:14:07 UTC (rev 9767)
@@ -30,23 +30,42 @@
 def safeDivision(value, total, factor=1):
     return value * factor / total if total else 0
 
-def readSock(sockname):
-    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-    s.connect(sockname)
-    data = ""
-    while True:
-        d = s.recv(1024)
-        if d:
-            data += d
-        else:
-            break
-    s.close()
+def readSock(sockname, useTCP):
+    try:
+        s = socket.socket(socket.AF_INET if useTCP else socket.AF_UNIX, socket.SOCK_STREAM)
+        s.connect(sockname)
+        data = ""
+        while True:
+            d = s.recv(1024)
+            if d:
+                data += d
+            else:
+                break
+        s.close()
+        data = json.loads(data)
+    except socket.error:
+        data = {"Failed": "Unable to read statistics from server: %s" % (sockname,)}
+    data["Server"] = sockname
     return data
 
-def printStats(data):
+def printStats(stats):
+    if len(stats) == 1 and False:
+        if "Failed" in stats[0]:
+            printFailedStats(stats[0]["Failed"]) 
+        else:
+            try:
+                printStat(stats[0])
+            except KeyError, e:
+                printFailedStats("Unable to find key '%s' in statistics from server socket" % (e,))
+                sys.exit(1)
+            
+    else:
+        printMultipleStats(stats)
+        
+def printStat(stats):
     
-    stats = json.loads(data)
     print "- " * 40
+    print "Server: %s" % (stats["Server"],)
     print datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
     print "Service Uptime: %s" % (datetime.timedelta(seconds=(int(time.time() - stats["System"]["start time"]))),)
     if stats["System"]["cpu count"] > 0:
@@ -66,6 +85,42 @@
     printRequestSummary(stats)
     printHistogramSummary(stats["5 Minutes"])
 
+def printMultipleStats(stats):
+
+    labels = serverLabels(stats)
+ 
+    print "- " * 40
+    print "Servers: %s" % (", ".join(labels),)
+
+    print datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
+
+    times = []
+    for stat in stats:
+        try:
+            t = str(datetime.timedelta(seconds=int(time.time() - stat["System"]["start time"])))
+        except KeyError:
+            t = "-"
+        times.append(t)
+    print "Service Uptime: %s" % (", ".join(times),)
+
+    cpus = []
+    memories = []
+    for stat in stats:
+        if stat["System"]["cpu count"] > 0:
+            cpus.append("%.1f%%" % (stat["System"]["cpu use"],))
+            memories.append("%.1f%%" % (stat["System"]["memory percent"],))
+        else:
+            cpus.append("-")
+            memories("-")
+    print "Current CPU: %s" % (", ".join(cpus),)
+    print "Current Memory Used: %s" % (", ".join(memories),)
+    print
+    printMultiRequestSummary(stats, labels, ("5 Minutes", 5*60,))
+    printMultiHistogramSummary(stats, "5 Minutes")
+
+def serverLabels(stats):
+    return [str(stat["Server"]) for stat in stats]
+
 def printFailedStats(message):
     
     print "- " * 40
@@ -114,6 +169,57 @@
     table.printTable(os=os)
     print os.getvalue()
 
+def printMultiRequestSummary(stats, labels, index):
+    table = tables.Table()
+    table.addHeader(
+        ("Server", "Requests", "Av. Requests", "Av. Response", "Av. Response", "Max. Response",    "Slot",     "CPU", "500's"),
+    )
+    table.addHeader(
+        (      "",         "",   "per second",         "(ms)", "no write(ms)",          "(ms)", "Average", "Average",      ""),
+    )
+    table.setDefaultColumnFormats(
+       (
+            tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY), 
+            tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.2f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f%%", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+        )
+    )
+    
+    key, seconds = index
+    totals = ["Overall:", 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0]
+    for ctr, stat in enumerate(stats):
+
+        stat = stat[key]
+        
+        col = []
+        col.append(labels[ctr])
+        col.append(stat["requests"])
+        col.append(safeDivision(float(stat["requests"]), seconds))
+        col.append(safeDivision(stat["t"], stat["requests"]))
+        col.append(safeDivision(stat["t"] - stat["t-resp-wr"], stat["requests"]))
+        col.append(stat["T-MAX"])
+        col.append(safeDivision(float(stat["slots"]), stat["requests"]))
+        col.append(safeDivision(stat["cpu"], stat["requests"]))
+        col.append(stat["500"])
+        table.addRow(col)
+        for item in xrange(1, len(col)):
+            totals[item] += col[item]
+    
+    for item in (2, 3, 4, 6, 7):
+        totals[item] /= len(stats)
+    
+    table.addFooter(totals)
+
+    os = StringIO()
+    table.printTable(os=os)
+    print os.getvalue()
+
 def printHistogramSummary(stat):
     
     print "5 minute average response histogram"
@@ -152,7 +258,58 @@
     table.printTable(os=os)
     print os.getvalue()
     
+def printMultiHistogramSummary(stats, index):
     
+    # Totals first
+    keys = ("requests", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s", "Over 1s", "Over 10s",)
+    totals = {
+        "T"        : dict([(k, 0) for k in keys]),
+        "T-RESP-WR": dict([(k, 0) for k in keys]),
+    }
+    
+    for stat in stats:
+        for i in ("T", "T-RESP-WR",):
+            totals[i][keys[0]] += stat[index][keys[0]]
+            for k in keys[1:]:
+                totals[i][k] += stat[index][i][k]
+
+    print "5 minute average response histogram"
+    table = tables.Table()
+    table.addHeader(
+        ("", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s",  "Over 1s", "Over 10s"),
+    )
+    table.setDefaultColumnFormats(
+       (
+            tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.CENTER_JUSTIFY), 
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f%%", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            tables.Table.ColumnFormat("%.1f%%", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+        )
+    )
+    for i in ("T", "T-RESP-WR",):
+        table.addRow((
+            "Overall Response" if i == "T" else "Response without Write",
+            (totals[i]["<10ms"], safeDivision(totals[i]["<10ms"], totals[i]["requests"], 100.0)),
+            (totals[i]["10ms<->100ms"], safeDivision(totals[i]["10ms<->100ms"], totals[i]["requests"], 100.0)),
+            (totals[i]["100ms<->1s"], safeDivision(totals[i]["100ms<->1s"], totals[i]["requests"], 100.0)),
+            (totals[i]["1s<->10s"], safeDivision(totals[i]["1s<->10s"], totals[i]["requests"], 100.0)),
+            (totals[i]["10s<->30s"], safeDivision(totals[i]["10s<->30s"], totals[i]["requests"], 100.0)),
+            (totals[i]["30s<->60s"], safeDivision(totals[i]["30s<->60s"], totals[i]["requests"], 100.0)),
+            (totals[i][">60s"], safeDivision(totals[i][">60s"], totals[i]["requests"], 100.0)),
+            safeDivision(totals[i]["Over 1s"], totals[i]["requests"], 100.0),
+            safeDivision(totals[i]["Over 10s"], totals[i]["requests"], 100.0),
+        ))
+    os = StringIO()
+    table.printTable(os=os)
+    print os.getvalue()
+    
+    
 def usage(error_msg=None):
     if error_msg:
         print error_msg
@@ -162,6 +319,7 @@
     -h            Print this help and exit
     -s            Name of local socket to read from
     -t            Delay in seconds between each sample [10 seconds]
+    --tcp host:port Use TCP connection with host:port
 
 Description:
     This utility will print a summary of statistics read from a
@@ -177,25 +335,22 @@
 if __name__ == '__main__':
     
     delay = 10
-    sockname = "data/Logs/state/caldavd-stats.sock"
+    servers = ("data/Logs/state/caldavd-stats.sock",)
+    useTCP = False
 
-    options, args = getopt.getopt(sys.argv[1:], "hs:t:", [])
+    options, args = getopt.getopt(sys.argv[1:], "hs:t:", ["tcp=",])
 
     for option, value in options:
         if option == "-h":
             usage()
         elif option == "-s":
-            sockname = value
+            servers = value.split(",")
         elif option == "-t":
             delay = int(value)
+        elif option == "--tcp":
+            servers = [(host, int(port),) for host, port in [server.split(":") for server in value.split(",")]]
+            useTCP = True
 
     while True:
-        try:
-            printStats(readSock(sockname))
-        except socket.error:
-            printFailedStats("Unable to read statistics from server socket: %s" % (sockname,))
-        except KeyError, e:
-            printFailedStats("Unable to find key '%s' in statistics from server socket" % (e,))
-            sys.exit(1)
-
+        printStats([readSock(server, useTCP) for server in servers])
         time.sleep(delay)

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2012-09-04 15:24:11 UTC (rev 9766)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2012-09-04 16:14:07 UTC (rev 9767)
@@ -446,7 +446,12 @@
     "AccountingPrincipals": [],
     "AccountingLogRoot"   : "accounting",
 
-    "GlobalStatsSocket"           : "caldavd-stats.sock", 
+    "Stats" : {
+        "EnableUnixStatsSocket"  : False,
+        "UnixStatsSocket"        : "caldavd-stats.sock",
+        "EnableTCPStatsSocket"   : False,
+        "TCPStatsPort"           : 8100,
+    },
     
     "LogDatabase" : {
         "LabelsInSQL"            : False,
@@ -1027,7 +1032,7 @@
     ("LogRoot", ("LogDatabase", "StatisticsLogFile",)),
     ("LogRoot", "AccountingLogRoot"),
     ("RunRoot", "PIDFile"),
-    ("RunRoot", "GlobalStatsSocket"),
+    ("RunRoot", ("Stats", "UnixStatsSocket",)),
     ("RunRoot", "ControlSocket"),
 ]
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120904/b3216554/attachment-0001.html>


More information about the calendarserver-changes mailing list