odoo/openerpcommand/benchmarks.py

167 lines
5.7 KiB
Python

"""
Define a base class for client-side benchmarking.
"""
import hashlib
import multiprocessing
import sys
import time
from .client import Client
class Bench(Client):
"""
Base class for concurrent benchmarks. The measure_once() method must be
overriden.
Each sub-benchmark will be run in its own process then a report is done
with all the results (shared with the main process using a
`multiprocessing.Array`).
"""
def __init__(self, subparsers=None):
super(Bench, self).__init__(subparsers)
self.parser.add_argument('-n', '--samples', metavar='INT',
default=100, help='number of measurements to take')
# TODO if -n <int>s is given (instead of -n <int>), run the
# benchmark for <int> seconds and return the number of iterations.
self.parser.add_argument('-o', '--output', metavar='PATH',
required=True, help='path to save the generated report')
self.parser.add_argument('--append', action='store_true',
default=False, help='append the report to an existing file')
self.parser.add_argument('-j', '--jobs', metavar='JOBS',
default=1, help='number of concurrent workers')
self.parser.add_argument('--seed', metavar='SEED',
default=0, help='a value to ensure different runs can create unique data')
self.worker = -1
def work(self, iarr=None):
if iarr:
# If an array is given, it means we are a worker process...
self.work_slave(iarr)
else:
# ... else we are the main process and we will spawn workers,
# passing them an array.
self.work_master()
def work_master(self):
N = int(self.args.samples)
self.arrs = [(i, multiprocessing.Array('f', range(N)))
for i in xrange(int(self.args.jobs))]
ps = [multiprocessing.Process(target=self.run, args=(arr,))
for arr in self.arrs]
[p.start() for p in ps]
[p.join() for p in ps]
self.report_html()
def work_slave(self, iarr):
j, arr = iarr
self.worker = j
N = int(self.args.samples)
total_t0 = time.time()
for i in xrange(N):
t0 = time.time()
self.measure_once(i)
t1 = time.time()
arr[i] = t1 - t0
print >> sys.stdout, '\r%s' % ('|' * (i * 60 / N)),
print >> sys.stdout, '%s %s%%' % \
(' ' * (60 - (i * 60 / N)), int(float(i+1)/N*100)),
sys.stdout.flush()
total_t1 = time.time()
print '\nDone in %ss.' % (total_t1 - total_t0)
def report_html(self):
series = []
for arr in self.arrs:
serie = """{
data: %s,
points: { show: true }
}""" % ([[x, i] for i, x in enumerate(arr)],)
series.append(serie)
chart_id = hashlib.md5(" ".join(sys.argv)).hexdigest()
HEADER = """<!doctype html>
<title>Benchmarks</title>
<meta charset=utf-8>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.flot.js"></script>
"""
CONTENT = """<h1>%s</h1>
%s
<div id='chart_%s' style='width:400px;height:300px;'>...</div>
<script type="text/javascript">
$.plot($("#chart_%s"), [%s],
{yaxis: { ticks: false }});
</script>""" % (self.bench_name, ' '.join(sys.argv), chart_id, chart_id,
','.join(series))
if self.args.append:
with open(self.args.output, 'a') as f:
f.write(CONTENT,)
else:
with open(self.args.output, 'w') as f:
f.write(HEADER + CONTENT,)
def measure_once(self, i):
"""
The `measure_once` method is called --jobs times. A `i` argument is
supplied to allow to create unique values for each execution (e.g. to
supply fresh identifiers to a `create` method.
"""
pass
class BenchRead(Bench):
"""Read a record repeatedly."""
command_name = 'bench-read'
bench_name = 'res.users.read(1)'
def __init__(self, subparsers=None):
super(BenchRead, self).__init__(subparsers)
self.parser.add_argument('-m', '--model', metavar='MODEL',
required=True, help='the model')
self.parser.add_argument('-i', '--id', metavar='RECORDID',
required=True, help='the record id')
def measure_once(self, i):
self.execute(self.args.model, 'read', [self.args.id], [])
class BenchFieldsViewGet(Bench):
"""Read a record's fields and view architecture repeatedly."""
command_name = 'bench-view'
bench_name = 'res.users.fields_view_get(1)'
def __init__(self, subparsers=None):
super(BenchFieldsViewGet, self).__init__(subparsers)
self.parser.add_argument('-m', '--model', metavar='MODEL',
required=True, help='the model')
self.parser.add_argument('-i', '--id', metavar='RECORDID',
required=True, help='the record id')
def measure_once(self, i):
self.execute(self.args.model, 'fields_view_get', self.args.id)
class BenchDummy(Bench):
"""Dummy (call test.limits.model.consume_nothing())."""
command_name = 'bench-dummy'
bench_name = 'test.limits.model.consume_nothing()'
def __init__(self, subparsers=None):
super(BenchDummy, self).__init__(subparsers)
self.parser.add_argument('-a', '--args', metavar='ARGS',
default='', help='some arguments to serialize')
def measure_once(self, i):
self.execute('test.limits.model', 'consume_nothing')
class BenchLogin(Bench):
"""Login (update res_users.date)."""
command_name = 'bench-login'
bench_name = 'res.users.login(1)'
def measure_once(self, i):
self.common_proxy.login(self.database, self.user, self.password)