diff --git a/setup.py b/setup.py index 3f702bd..f344ea4 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,6 @@ keywords = "django", description = "Performance logging middlware and analysis tools for Django", install_requires=[ - 'texttable', 'progressbar', ], classifiers = [ diff --git a/src/timelog/lib.py b/src/timelog/lib.py index 31bd554..ca031f2 100644 --- a/src/timelog/lib.py +++ b/src/timelog/lib.py @@ -2,18 +2,44 @@ from re import compile from django.conf import settings -from texttable import Texttable from progressbar import ProgressBar, Percentage, Bar from django.core.urlresolvers import resolve, Resolver404 -PATTERN = r"""^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:]{8},[0-9]{3}) (GET|POST|PUT|DELETE|HEAD) "(.*)" \((.*)\) (.*)""" +PATTERN = r"""^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:]{8},[0-9]{3}) (GET|POST|PUT|DELETE|HEAD) "(.*)" \((.*)\) (.*?) \((\d+)q, (.*?)\)""" CACHED_VIEWS = {} IGNORE_PATHS = getattr(settings, 'TIMELOG_IGNORE_URIS', ()) +def getTerminalWidth(): + + import os + + def ioctl_GWINSZ(fd): + try: + import fcntl, termios, struct + cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) + except: + return None + return cr + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except: + pass + if not cr: + try: + cr = (env['LINES'], env['COLUMNS']) + except: + cr = (25, 80) + return int(cr[1]) + + def count_lines_in(filename): "Count lines in a file" f = open(filename) @@ -40,12 +66,19 @@ def view_name_from(path): def generate_table_from(data): "Output a nicely formatted ascii table" - table = Texttable(max_width=120) - table.add_row(["view", "method", "status", "count", "minimum", "maximum", "mean", "stdev"]) - table.set_cols_align(["l", "l", "l", "r", "r", "r", "r", "r"]) + + import csv + import sys + + writer = csv.writer(sys.stdout) + + writer.writerow(["view", "method", "status", "count", "minimum", "maximum", "mean", "stdev", "queries", "querytime"]) for item in data: mean = round(sum(data[item]['times'])/data[item]['count'], 3) + + mean_sql = round(sum(data[item]['sql'])/data[item]['count'], 3) + mean_sqltime = round(sum(data[item]['sqltime'])/data[item]['count'], 3) sdsq = sum([(i - mean) ** 2 for i in data[item]['times']]) try: @@ -53,9 +86,8 @@ def generate_table_from(data): except ZeroDivisionError: stdev = '0.00' - table.add_row([data[item]['view'], data[item]['method'], data[item]['status'], data[item]['count'], data[item]['minimum'], data[item]['maximum'], '%.3f' % mean, stdev]) + writer.writerow([data[item]['view'], data[item]['method'], data[item]['status'], data[item]['count'], data[item]['minimum'], data[item]['maximum'], '%.3f' % mean, stdev, mean_sql, mean_sqltime]) - return table.draw() def analyze_log_file(logfile, pattern, reverse_paths=True, progress=True): "Given a log file and regex group and extract the performance data" @@ -78,6 +110,8 @@ def analyze_log_file(logfile, pattern, reverse_paths=True, progress=True): path = parsed[2] status = parsed[3] time = parsed[4] + sql = parsed[5] + sqltime = parsed[6] try: ignore = False @@ -98,6 +132,8 @@ def analyze_log_file(logfile, pattern, reverse_paths=True, progress=True): if time > data[key]['maximum']: data[key]['maximum'] = time data[key]['times'].append(float(time)) + data[key]['sql'].append(int(sql)) + data[key]['sqltime'].append(float(sqltime)) except KeyError: data[key] = { 'count': 1, @@ -107,6 +143,8 @@ def analyze_log_file(logfile, pattern, reverse_paths=True, progress=True): 'view': view, 'method': method, 'times': [float(time)], + 'sql': [int(sql)], + 'sqltime': [float(sqltime)], } except Resolver404: pass diff --git a/src/timelog/management/commands/analyze_timelog.py b/src/timelog/management/commands/analyze_timelog.py index 7a01deb..47dad8d 100644 --- a/src/timelog/management/commands/analyze_timelog.py +++ b/src/timelog/management/commands/analyze_timelog.py @@ -18,6 +18,11 @@ class Command(BaseCommand): action='store_false', default=True, help='Show paths instead of views'), + make_option('--noprogress', + dest='progress', + action='store_false', + default=True, + help='Don''t show the progress bar'), ) def handle(self, *args, **options): @@ -31,7 +36,7 @@ def handle(self, *args, **options): LOGFILE = options.get('file') try: - data = analyze_log_file(LOGFILE, PATTERN, reverse_paths=options.get('reverse')) + data = analyze_log_file(LOGFILE, PATTERN, reverse_paths=options.get('reverse'), progress=options.get('progress')) except IOError: print "File not found" exit(2) diff --git a/src/timelog/middleware.py b/src/timelog/middleware.py index 0281653..ba3a107 100644 --- a/src/timelog/middleware.py +++ b/src/timelog/middleware.py @@ -1,5 +1,6 @@ import time import logging +from django.db import connection from django.utils.encoding import smart_str logger = logging.getLogger(__name__) @@ -15,11 +16,21 @@ def process_response(self, request, response): # before TimeLogMiddleware then request won't have '_start' attribute # and the original traceback will be lost (original exception will be # replaced with AttributeError) + + sqltime = 0.0 + + for q in connection.queries: + sqltime += float(getattr(q, 'time', 0.0)) + if hasattr(request, '_start'): - d = {'method': request.method, - 'time': time.time() - request._start, - 'code': response.status_code, - 'url': smart_str(request.path_info)} - msg = '%(method)s "%(url)s" (%(code)s) %(time).2f' % d + d = { + 'method': request.method, + 'time': time.time() - request._start, + 'code': response.status_code, + 'url': smart_str(request.path_info), + 'sql': len(connection.queries), + 'sqltime': sqltime, + } + msg = '%(method)s "%(url)s" (%(code)s) %(time).2f (%(sql)dq, %(sqltime).4f)' % d logger.info(msg) return response