199 lines
7.7 KiB
Python
199 lines
7.7 KiB
Python
from numpy import *
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from scipy import stats
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import json, locale
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from optparse import OptionParser
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
VALID_GROUP_BYS = ['browser', 'pdf', 'page', 'round', 'stat']
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
USAGE_EXAMPLE = "%prog BASELINE CURRENT"
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class TestOptions(OptionParser):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def __init__(self, **kwargs):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
OptionParser.__init__(self, **kwargs)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.add_option("--groupBy", action="append", dest="groupBy", type="string",
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
help="How the statistics should grouped. Valid options: " + ', '.join(VALID_GROUP_BYS) + '.', default=[])
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.set_usage(USAGE_EXAMPLE)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def verifyOptions(self, options, args):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if len(args) < 2:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.error('There must be two comparison files arguments.')
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Veryify the group by options.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
groupBy = []
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not options.groupBy:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options.groupBy = ['browser,stat', 'browser,pdf,stat']
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for group in options.groupBy:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
group = group.split(',')
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for column in group:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if column not in VALID_GROUP_BYS:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.error('Invalid group by option of "' + column + '"')
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
groupBy.append(group)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options.groupBy = groupBy
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return options
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
## {{{ http://code.activestate.com/recipes/267662/ (r7)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import cStringIO,operator
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left',
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
separateRows=False, prefix='', postfix='', wrapfunc=lambda x:x):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Indents a table by column.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- rows: A sequence of sequences of items, one sequence per row.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- hasHeader: True if the first row consists of the columns' names.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- headerChar: Character to be used for the row separator line
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(if hasHeader==True or separateRows==True).
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- delim: The column delimiter.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- justify: Determines how are data justified in their column.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Valid values are 'left','right' and 'center'.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- separateRows: True if rows are to be separated by a line
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
of 'headerChar's.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- prefix: A string prepended to each printed row.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- postfix: A string appended to each printed row.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- wrapfunc: A function f(text) for wrapping text; each element in
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
the table is first wrapped by this function."""
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# closure for breaking logical rows to physical, using wrapfunc
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def rowWrapper(row):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
newRows = [wrapfunc(str(item)).split('\n') for item in row]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return [[substr or '' for substr in item] for item in map(None,*newRows)]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# break each logical row into one or more physical ones
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logicalRows = [rowWrapper(row) for row in rows]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# columns of physical rows
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
columns = map(None,*reduce(operator.add,logicalRows))
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# get the maximum of each column by the string length of its items
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
maxWidths = [max([len(str(item)) for item in column]) for column in columns]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + \
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
len(delim)*(len(maxWidths)-1))
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# select the appropriate justify method
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
justify = {'center':str.center, 'right':str.rjust, 'left':str.ljust}[justify.lower()]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
output=cStringIO.StringIO()
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if separateRows: print >> output, rowSeparator
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for physicalRows in logicalRows:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for row in physicalRows:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print >> output, \
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefix \
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+ delim.join([justify(str(item),width) for (item,width) in zip(row,maxWidths)]) \
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+ postfix
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if separateRows or hasHeader: print >> output, rowSeparator; hasHeader=False
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return output.getvalue()
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# written by Mike Brown
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def wrap_onspace(text, width):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
A word-wrap function that preserves existing line breaks
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
and most spaces in the text. Expects that existing line
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
breaks are posix newlines (\n).
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return reduce(lambda line, word, width=width: '%s%s%s' %
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(line,
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
' \n'[(len(line[line.rfind('\n')+1:])
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+ len(word.split('\n',1)[0]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) >= width)],
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
word),
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
text.split(' ')
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import re
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def wrap_onspace_strict(text, width):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Similar to wrap_onspace, but enforces the width constraint:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
words longer than width are split."""
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
wordRegex = re.compile(r'\S{'+str(width)+r',}')
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(),width),text),width)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import math
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def wrap_always(text, width):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""A simple word-wrap function that wraps text on exactly width characters.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It doesn't split the text in words."""
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return '\n'.join([ text[width*i:width*(i+1)] \
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for i in xrange(int(math.ceil(1.*len(text)/width))) ])
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def formatTime(time):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return locale.format("%.*f", (0, time), True)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Group the stats by keys. We should really just stick these in a SQL database
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# so we aren't reiventing the wheel.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def group(stats, groupBy):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
vals = {}
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for stat in stats:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = []
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for group in groupBy:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key.append(stat[group])
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = tuple(key)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if key not in vals:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
vals[key] = []
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
vals[key].append(stat['time'])
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return vals;
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def mean(l):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return array(l).mean()
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Take the somewhat normalized stats file and flatten it so there is a row for
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# every recorded stat.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def flatten(stats):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rows = []
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for stat in stats:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for s in stat['stats']:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rows.append({
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'browser': stat['browser'],
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'page': stat['page'],
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'pdf': stat['pdf'],
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'round': stat['round'],
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'stat': s['name'],
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'time': int(s['end']) - int(s['start'])
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return rows
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Dump various stats in a table to compare the baseline and current results.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# T-test Refresher:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# If I understand t-test correctly, p is the probability that we'll observe
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# another test that is as extreme as the current result assuming the null
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# hypothesis is true. P is NOT the probability of the null hypothesis.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# The null hypothesis in this case is that the baseline and current results will
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# be the same. It is generally accepted that you can reject the null hypothesis
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# if the p-value is less than 0.05. So if p < 0.05 we can reject the results
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# are the same which doesn't necessarily mean the results are faster/slower but
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# it can be implied.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def stat(baseline, current, groupBy):
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
labels = groupBy + ['Baseline(ms)', 'Current(ms)', '+/-', '%', 'Result(P<.05)']
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
baselineGroup = group(baseline, groupBy)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
currentGroup = group(current, groupBy |