diff -r 000000000000 -r b97547f5f1fa devtools/stresstester.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/devtools/stresstester.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,182 @@ +""" Usage: %s [OPTIONS] + +Stress test a CubicWeb repository + +OPTIONS: + -h / --help + Display this help message and exit. + + -u / --user + Connect as instead of being prompted to give it. + -p / --password + Automatically give for authentication instead of being prompted + to give it. + + -n / --nb-times + Repeat queries times. + -t / --nb-threads + Execute queries in parallel threads. + -P / --profile + dumps profile results (hotshot) in + -o / --report-output + Write profiler report into rather than on stdout + +Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__revision__ = "$Id: stresstester.py,v 1.3 2006-03-05 14:35:27 syt Exp $" + +import os +import sys +import threading +import getopt +import traceback +from getpass import getpass +from os.path import basename +from time import clock + +from logilab.common.fileutils import lines +from logilab.common.ureports import Table, TextWriter +from cubicweb.server.repository import Repository +from cubicweb.dbapi import Connection + +TB_LOCK = threading.Lock() + +class QueryExecutor: + def __init__(self, cursor, times, queries, reporter = None): + self._cursor = cursor + self._times = times + self._queries = queries + self._reporter = reporter + + def run(self): + cursor = self._cursor + times = self._times + while times: + for index, query in enumerate(self._queries): + start = clock() + try: + cursor.execute(query) + except KeyboardInterrupt: + raise + except: + TB_LOCK.acquire() + traceback.print_exc() + TB_LOCK.release() + return + if self._reporter is not None: + self._reporter.add_proftime(clock() - start, index) + times -= 1 + +def usage(status=0): + """print usage string and exit""" + print __doc__ % basename(sys.argv[0]) + sys.exit(status) + + +class ProfileReporter: + """a profile reporter gathers all profile informations from several + threads and can write a report that summarizes all profile informations + """ + profiler_lock = threading.Lock() + + def __init__(self, queries): + self._queries = tuple(queries) + self._profile_results = [(0., 0)] * len(self._queries) + # self._table_report = Table(3, rheaders = True) + len_max = max([len(query) for query in self._queries]) + 5 + self._query_fmt = '%%%ds' % len_max + + def add_proftime(self, elapsed_time, query_index): + """add a new time measure for query""" + ProfileReporter.profiler_lock.acquire() + cumul_time, times = self._profile_results[query_index] + cumul_time += elapsed_time + times += 1. + self._profile_results[query_index] = (cumul_time, times) + ProfileReporter.profiler_lock.release() + + def dump_report(self, output = sys.stdout): + """dump report in 'output'""" + table_elems = ['RQL Query', 'Times', 'Avg Time'] + total_time = 0. + for query, (cumul_time, times) in zip(self._queries, self._profile_results): + avg_time = cumul_time / float(times) + table_elems += [str(query), '%f' % times, '%f' % avg_time ] + total_time += cumul_time + table_elems.append('Total time :') + table_elems.append(str(total_time)) + table_elems.append(' ') + table_layout = Table(3, rheaders = True, children = table_elems) + TextWriter().format(table_layout, output) + # output.write('\n'.join(tmp_output)) + + +def run(args): + """run the command line tool""" + try: + opts, args = getopt.getopt(args, 'hn:t:u:p:P:o:', ['help', 'user=', 'password=', + 'nb-times=', 'nb-threads=', + 'profile', 'report-output=',]) + except Exception, ex: + print ex + usage(1) + repeat = 100 + threads = 1 + user = os.environ.get('USER', os.environ.get('LOGNAME')) + password = None + report_output = sys.stdout + prof_file = None + for opt, val in opts: + if opt in ('-h', '--help'): + usage() + if opt in ('-u', '--user'): + user = val + elif opt in ('-p', '--password'): + password = val + elif opt in ('-n', '--nb-times'): + repeat = int(val) + elif opt in ('-t', '--nb-threads'): + threads = int(val) + elif opt in ('-P', '--profile'): + prof_file = val + elif opt in ('-o', '--report-output'): + report_output = file(val, 'w') + if len(args) != 2: + usage(1) + queries = [query for query in lines(args[1]) if not query.startswith('#')] + if user is None: + user = raw_input('login: ') + if password is None: + password = getpass('password: ') + from cubicweb.cwconfig import application_configuration + config = application_configuration(args[0]) + # get local access to the repository + print "Creating repo", prof_file + repo = Repository(config, prof_file) + cnxid = repo.connect(user, password) + # connection to the CubicWeb repository + repo_cnx = Connection(repo, cnxid) + repo_cursor = repo_cnx.cursor() + reporter = ProfileReporter(queries) + if threads > 1: + executors = [] + while threads: + qe = QueryExecutor(repo_cursor, repeat, queries, reporter = reporter) + executors.append(qe) + thread = threading.Thread(target=qe.run) + qe.thread = thread + thread.start() + threads -= 1 + for qe in executors: + qe.thread.join() +## for qe in executors: +## print qe.thread, repeat - qe._times, 'times' + else: + QueryExecutor(repo_cursor, repeat, queries, reporter = reporter).run() + reporter.dump_report(report_output) + + +if __name__ == '__main__': + run(sys.argv[1:])