Revision: 1602 http://trac.macosforge.org/projects/calendarserver/changeset/1602 Author: cdaboo@apple.com Date: 2007-06-09 08:35:51 -0700 (Sat, 09 Jun 2007) Log Message: ----------- Add time-of-day breakdown by method. Add HTML output option. Modified Paths: -------------- CalendarServer/trunk/twistedcaldav/admin/formatters.py CalendarServer/trunk/twistedcaldav/admin/logs.py Modified: CalendarServer/trunk/twistedcaldav/admin/formatters.py =================================================================== --- CalendarServer/trunk/twistedcaldav/admin/formatters.py 2007-06-08 18:31:09 UTC (rev 1601) +++ CalendarServer/trunk/twistedcaldav/admin/formatters.py 2007-06-09 15:35:51 UTC (rev 1602) @@ -122,16 +122,17 @@ values = [types[i](value) for i, value in enumerate(values)] self.writeLine(values) - def writeFrequencies(self, frequencies): + def writeFrequencies(self, frequencies, max_count=None): width = len(frequencies) plot = [[" "] * width for ignore in range(20)] plot.append(["|---"] * 24) plot.append(["%02d " % d for d in range(24)]) - max_count = 0 - for freq in frequencies: - max_count = max(freq, max_count) + if max_count is None: + max_count = 0 + for freq in frequencies: + max_count = max(freq, max_count) for column, freq in enumerate(frequencies): if freq == 0: @@ -143,6 +144,8 @@ self.write("\n".join(["".join(p) for p in plot])) self.write("\n") + + return max_count def writeReport(self, report, name, fields, headings): if self.options.has_key('fields'): @@ -249,8 +252,13 @@ self.write('\n') self.write(' # Requests by time of day:\n') - self.writeFrequencies(report['data']['timeOfDayStats']) + max_count = self.writeFrequencies(report['data']['timeOfDayStats']) self.write('\n') + + for request, freqs in report['data']['requestByTimeOfDayStats'].iteritems(): + self.write(' # %s requests by time of day:\n' % (request,)) + self.writeFrequencies(freqs) + self.write('\n') self.write(' User Agents:\n') @@ -328,3 +336,243 @@ report_logs = report_stats registerFormatter(PlistFormatter) + +class HTMLFormatter(BaseFormatter): + name = "html" + + def writeLine(self, fields, spacing=None): + + if not spacing: + spacing = self.options.get('spacing', 16) + + for f in fields: + if isinstance(f, float): + p = ("% " + str(spacing - 1) + ".2f") % (f,) + elif isinstance(f, int) or isinstance(f, long): + p = ("% " + str(spacing - 1) + "d") % (f,) + else: + p = str(f) + self.write(p) + self.write(' '*(int(spacing) - len(p))) + + self.write('\n') + + def writeTable(self, report, fields, headings): + if self.options.has_key('fields'): + fields = self.options.get('fields', '').split(',') + + self.write("<table>\n") + + self.write(" <tr>\n") + for f in fields: + self.write(" <td>%s</td>\n" % headings[f]) + self.write(" <tr>\n") + + for record in report['records']: + self.writeTableRow((record[f] for f in fields)) + + self.write(" </tr>\n") + self.write("</table>\n") + + def writeTableRow(self, fields): + + self.write(" <tr>\n") + + for f in fields: + align = "" + if isinstance(f, float): + p = ("%.2f") % (f,) + align = " align='right'" + elif isinstance(f, int) or isinstance(f, long): + p = ("%d") % (f,) + align = " align='right'" + else: + p = str(f) + self.write(" <td%s>%s</td>\n" % (align, p,)) + + self.write(" </tr>\n") + + def writeMap(self, reportmap, fields, types, headings): + + self.write("<table border='1'>\n") + + self.write(" <tr>\n") + for f in fields: + self.write(" <td>%s</td>\n" % headings[f]) + self.write(" <tr>\n") + + for key, value in reportmap.iteritems(): + values = (key,) + values += tuple(value[f] for f in fields[1:]) + values = [types[i](value) for i, value in enumerate(values)] + self.writeTableRow(values) + + self.write(" </tr>\n") + self.write("</table>\n") + + def writeFrequencies(self, frequencies, max_count = None): + + if max_count is None: + max_count = 0 + for freq in frequencies: + max_count = max(freq, max_count) + + self.write("<table>\n") + + for row in range(20): + self.write(" <tr>\n") + for freq in frequencies: + scaled = (20 * freq) / max_count + if 19 - row <= scaled: + self.write(" <td><font size='1'>*</font></td>\n") + else: + self.write(" <td><font size='1'> </font></td>\n") + self.write(" </tr>\n") + + self.write(" <tr>\n") + for i in range(len(frequencies) / 4): + self.write(" <td align='center'><font size='1'>|</font></td>\n") + self.write(" <td align='center'><font size='1'>-</font></td>\n") + self.write(" <td align='center'><font size='1'>-</font></td>\n") + self.write(" <td align='center'><font size='1'>-</font></td>\n") + self.write(" </tr>\n") + self.write(" <tr>\n") + for i in range(len(frequencies) / 4): + self.write(" <td colspan='4'><font size='1'>%s</font></td>\n" % (i,)) + self.write(" </tr>\n") + self.write("</table>\n") + + return max_count + + def writeReport(self, report, name, fields, headings): + if self.options.has_key('fields'): + fields = self.options.get('fields', '').split(',') + + if name: + self.write('<h3>%s:</h3>\n' % (name,)) + + for f in fields: + self.write(' %s: %s<br>\n' % (headings[f], report['data'][f])) + + def report_principals(self, report): + fields = ('principalName', 'calendarCount', 'eventCount', 'todoCount', + 'quotaRoot', 'quotaUsed', 'quotaAvail') + + headings = { + 'principalName': 'Name', + 'calendarCount': '# Calendars', + 'eventCount': '# Events', + 'todoCount': '# Todos', + 'quotaRoot': 'Quota', + 'quotaUsed': 'Used', + 'quotaAvail': 'Available', + 'disabled': 'Disaabled', + 'quotaFree': 'Free %', + 'calendarHome': 'Home', + } + + self.writeTable(report, fields, headings) + + report_users = report_groups = report_resources = report_locations = report_principals + + def report_stats(self, report): + fields = ('accountCount', 'groupCount', 'resourceCount', 'locationCount', + 'calendarCount', 'eventCount', + 'todoCount', 'diskUsage') + + headings = { + 'accountCount': '# Accounts ', + 'groupCount': '# Groups ', + 'resourceCount': '# Resources', + 'locationCount': '# Locations', + 'calendarCount': '# Calendars', + 'eventCount': '# Events ', + 'todoCount': '# Todos ', + 'diskUsage': 'Disk Usage ', + } + + self.writeReport(report, 'Statistics', fields, headings) + + def report_logs(self, report): + + self.write('<html>\n<body>\n') + + self.write('<h2>Log Statistics:</h2><br>\n<br>\n') + + self.write("<table>\n") + self.write('<tr><td>Start Date:</td><td>%s</td></tr>\n' % report['data']['dateRange'][0]) + self.write('<tr><td>End Date:</td><td>%s</td></tr>\n' % report['data']['dateRange'][1]) + self.write('<tr><td> </td><td> </td></tr>\n') + self.write('<tr><td>Bytes Out:</td><td>%s (%.2f GB)</td></tr>\n' % (report['data']['bytesOut'], report['data']['bytesOut'] / (1024.0 * 1024 * 1024))) + self.write("</table>\n") + + self.write('<h3># Requests:</h3>\n') + + title_spacing = self.options.get('spacing', 16) - 1 + + fields = ( + 'method', + 'num', + 'numOK', + 'numBAD', + 'numISE', + 'numOther', + 'minbytes', + 'avbytes', + 'maxbytes', + 'mintime', + 'avtime', + 'maxtime', + ) + types = ( + str, + long, + long, + long, + long, + long, + long, + long, + long, + float, + float, + float, + ) + headings = { + 'method': 'Method', + 'num': '# Requests'.rjust(title_spacing), + 'numOK': '# OK'.rjust(title_spacing), + 'numBAD': '# BAD'.rjust(title_spacing), + 'numISE': '# Failed'.rjust(title_spacing), + 'numOther': '# Other'.rjust(title_spacing), + 'minbytes': 'Min. bytes'.rjust(title_spacing), + 'avbytes': 'Av. bytes'.rjust(title_spacing), + 'maxbytes': 'Max. bytes'.rjust(title_spacing), + 'mintime': 'Min. time (ms)'.rjust(title_spacing), + 'avtime': 'Av. time (ms)'.rjust(title_spacing), + 'maxtime': 'Max. time (ms)'.rjust(title_spacing), + } + self.writeMap(report['data']['requestStats'], fields, types, headings) + self.write('<br>\n') + + self.write('<h3># Requests by time of day:</h3>\n') + max_count = self.writeFrequencies(report['data']['timeOfDayStats']) + self.write('<br>\n') + + + for request, freqs in report['data']['requestByTimeOfDayStats'].iteritems(): + self.write('<h3># %s requests by time of day:</h3>\n' % (request,)) + self.writeFrequencies(freqs) + self.write('<br>\n') + + self.write('<h3>User Agents:</h3>\n') + + self.write("<table border='1'>") + self.write(' <tr><td>User Agent</td><td># Requests</td></tr>\n') + for ua, count in report['data']['userAgents'].iteritems(): + self.write(' <tr><td>%s:</td><td align=\'right\'>%s</td></tr>\n' % (ua, count)) + self.write('</table>\n') + + self.write('</body>\n</html>\n') + +registerFormatter(HTMLFormatter) Modified: CalendarServer/trunk/twistedcaldav/admin/logs.py =================================================================== --- CalendarServer/trunk/twistedcaldav/admin/logs.py 2007-06-08 18:31:09 UTC (rev 1601) +++ CalendarServer/trunk/twistedcaldav/admin/logs.py 2007-06-09 15:35:51 UTC (rev 1602) @@ -30,16 +30,16 @@ from twistedcaldav.admin import util -PLIST_VERSION = 2 +PLIST_VERSION = 3 statsTemplate = plistlib.Dict( version=PLIST_VERSION, bytesOut="0", startDate="", endDate="", - requestStats=plistlib.Dict( - ), + requestStats=plistlib.Dict(), timeOfDayStats=[0] * 96, + requestByTimeOfDayStats=plistlib.Dict(), invitations=plistlib.Dict( day=0, week=0, @@ -132,10 +132,16 @@ hour, minute = time.split(":") bucket = int(hour) * 4 + divmod(int(minute), 15)[0] self._data.timeOfDayStats[bucket] = self._data.timeOfDayStats[bucket] + 1 + if request not in self._data.requestByTimeOfDayStats: + self._data.requestByTimeOfDayStats[request] = [0] * 96 + self._data.requestByTimeOfDayStats[request][bucket] = self._data.requestByTimeOfDayStats[request][bucket] + 1 def getTimeOfDayStats(self): return self._data.timeOfDayStats + def getRequestByTimeOfDayStats(self): + return self._data.requestByTimeOfDayStats + def addUserAgent(self, useragent): if useragent in self._data.userAgents: self._data.userAgents[useragent] += 1 @@ -245,6 +251,7 @@ self.stats.getBytes()), 'requestStats': self.stats.getRequestStats(), 'timeOfDayStats': self.stats.getTimeOfDayStats(), + 'requestByTimeOfDayStats': self.stats.getRequestByTimeOfDayStats(), 'userAgents': self.stats.getUserAgents(), } }