You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
379 lines
12 KiB
379 lines
12 KiB
#!/usr/bin/python2
|
|
|
|
"""
|
|
Selects all rows and columns that satisfy the condition specified
|
|
and draws the matrix. There is a seperate SQL query made for every (x,y)
|
|
in the matrix.
|
|
"""
|
|
|
|
print "Content-type: text/html\n"
|
|
|
|
import sys, os, urllib, cgi, cgitb, re, datetime, time
|
|
|
|
total_wall_time_start = time.time()
|
|
|
|
import common
|
|
from autotest_lib.tko import display, frontend, db, query_lib
|
|
from autotest_lib.client.common_lib import kernel_versions
|
|
|
|
html_header = """\
|
|
<form action="/tko/compose_query.cgi" method="get">
|
|
<table border="0">
|
|
<tr>
|
|
<td>Column: </td>
|
|
<td>Row: </td>
|
|
<td>Condition: </td>
|
|
<td align="center">
|
|
<a href="http://autotest.kernel.org/wiki/AutotestTKOCondition">Help</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<SELECT NAME="columns">
|
|
%s
|
|
</SELECT>
|
|
</td>
|
|
<td>
|
|
<SELECT NAME="rows">
|
|
%s
|
|
</SELECT>
|
|
</td>
|
|
<td>
|
|
<input type="text" name="condition" size="30" value="%s">
|
|
<input type="hidden" name="title" value="%s">
|
|
</td>
|
|
<td align="center"><input type="submit" value="Submit">
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</form>
|
|
<form action="/tko/save_query.cgi" method="get">
|
|
<table border="0">
|
|
<tr>
|
|
<td>Name your query: </td>
|
|
<td>
|
|
<input type="text" name="label" size="15" value="">
|
|
</td>
|
|
<td align="center"> <input type="submit" value="Save Query">
|
|
</td>
|
|
<td> <a href="/tko/query_history.cgi">View saved queries</a></td>
|
|
<td>
|
|
<input type="hidden" name="columns" value="%s">
|
|
<input type="hidden" name="rows" value="%s">
|
|
<input type="hidden" name="condition" value="%s">
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</form>
|
|
"""
|
|
|
|
|
|
next_field = {
|
|
'machine_group': 'hostname',
|
|
'hostname': 'tag',
|
|
'tag': 'tag',
|
|
|
|
'kernel': 'test',
|
|
'test': 'label',
|
|
'label': 'tag',
|
|
|
|
'reason': 'tag',
|
|
'user': 'tag',
|
|
'status': 'tag',
|
|
|
|
'time': 'tag',
|
|
'time_daily': 'time',
|
|
}
|
|
|
|
|
|
def parse_field(form, form_field, field_default):
|
|
if not form_field in form:
|
|
return field_default
|
|
field_input = form[form_field].value.lower()
|
|
if field_input and field_input in frontend.test_view_field_dict:
|
|
return field_input
|
|
return field_default
|
|
|
|
|
|
def parse_condition(form, form_field, field_default):
|
|
if not form_field in form:
|
|
return field_default
|
|
return form[form_field].value
|
|
|
|
|
|
form = cgi.FieldStorage()
|
|
|
|
title_field = parse_condition(form, 'title', '')
|
|
row = parse_field(form, 'rows', 'kernel')
|
|
column = parse_field(form, 'columns', 'machine_group')
|
|
condition_field = parse_condition(form, 'condition', '')
|
|
|
|
if 'brief' in form.keys() and form['brief'].value <> '0':
|
|
display.set_brief_mode()
|
|
|
|
## caller can specify rows and columns that shall be included into the report
|
|
## regardless of whether actual test data is available yet
|
|
force_row_field = parse_condition(form,'force_row','')
|
|
force_column_field = parse_condition(form,'force_column','')
|
|
|
|
|
|
def split_forced_fields(force_field):
|
|
if force_field:
|
|
return force_field.split()
|
|
else:
|
|
return []
|
|
|
|
force_row = split_forced_fields(force_row_field)
|
|
force_column = split_forced_fields(force_column_field)
|
|
|
|
cgitb.enable()
|
|
db_obj = db.db()
|
|
|
|
|
|
def construct_link(x, y):
|
|
next_row = row
|
|
next_column = column
|
|
condition_list = []
|
|
if condition_field != '':
|
|
condition_list.append(condition_field)
|
|
if y:
|
|
next_row = next_field[row]
|
|
condition_list.append("%s='%s'" % (row, y))
|
|
if x:
|
|
next_column = next_field[column]
|
|
condition_list.append("%s='%s'" % (column, x))
|
|
next_condition = '&'.join(condition_list)
|
|
link = '/tko/compose_query.cgi?' + urllib.urlencode({'columns': next_column,
|
|
'rows': next_row, 'condition': next_condition,
|
|
'title': title_field})
|
|
return link
|
|
|
|
|
|
def construct_logs_link(x, y, job_tag):
|
|
job_path = frontend.html_root + job_tag + '/'
|
|
test = ''
|
|
if (row == 'test' and
|
|
not y.split('.')[0] in ('boot', 'build', 'install')):
|
|
test = y
|
|
if (column == 'test' and
|
|
not x.split('.')[0] in ('boot', 'build', 'install')):
|
|
test = x
|
|
return '/tko/retrieve_logs.cgi?' + urllib.urlencode({'job' : job_path,
|
|
'test' : test})
|
|
|
|
|
|
def create_select_options(selected_val):
|
|
ret = ""
|
|
for option in sorted(frontend.test_view_field_dict.keys()):
|
|
if selected_val == option:
|
|
selected = " SELECTED"
|
|
else:
|
|
selected = ""
|
|
|
|
ret += '<OPTION VALUE="%s"%s>%s</OPTION>\n' % \
|
|
(option, selected, option)
|
|
return ret
|
|
|
|
|
|
def map_kernel_base(kernel_name):
|
|
## insert <br> after each / in kernel name
|
|
## but spare consequtive //
|
|
kernel_name = kernel_name.replace('/','/<br>')
|
|
kernel_name = kernel_name.replace('/<br>/<br>','//')
|
|
return kernel_name
|
|
|
|
|
|
def header_tuneup(field_name, header):
|
|
## header tune up depends on particular field name and may include:
|
|
## - breaking header into several strings if it is long url
|
|
## - creating date from datetime stamp
|
|
## - possibly, expect more various refinements for different fields
|
|
if field_name == 'kernel':
|
|
return map_kernel_base(header)
|
|
else:
|
|
return header
|
|
|
|
|
|
# Kernel name mappings -- the kernels table 'printable' field is
|
|
# effectively a sortable identifier for the kernel It encodes the base
|
|
# release which is used for overall sorting, plus where patches are
|
|
# applied it adds an increasing pNNN patch combination identifier
|
|
# (actually the kernel_idx for the entry). This allows sorting
|
|
# as normal by the base kernel version and then sub-sorting by the
|
|
# "first time we saw" a patch combination which should keep them in
|
|
# approximatly date order. This patch identifier is not suitable
|
|
# for display, so we have to map it to a suitable html fragment for
|
|
# display. This contains the kernel base version plus the truncated
|
|
# names of all the patches,
|
|
#
|
|
# 2.6.24-mm1 p112
|
|
# +add-new-string-functions-
|
|
# +x86-amd-thermal-interrupt
|
|
#
|
|
# This mapping is produced when the first mapping is request, with
|
|
# a single query over the patches table; the result is then cached.
|
|
#
|
|
# Note: that we only count a base version as patched if it contains
|
|
# patches which are not already "expressed" in the base version.
|
|
# This includes both -gitN and -mmN kernels.
|
|
map_kernel_map = None
|
|
|
|
|
|
def map_kernel_init():
|
|
fields = ['base', 'k.kernel_idx', 'name', 'url']
|
|
map = {}
|
|
for (base, idx, name, url) in db_obj.select(','.join(fields),
|
|
'tko_kernels k, tko_patches p', 'k.kernel_idx=p.kernel_idx'):
|
|
match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$', url)
|
|
if match:
|
|
continue
|
|
|
|
key = base + ' p%d' % (idx)
|
|
if not map.has_key(key):
|
|
map[key] = map_kernel_base(base) + ' p%d' % (idx)
|
|
map[key] += ('<br>+<span title="' + name + '">' +
|
|
name[0:25] + '</span>')
|
|
|
|
return map
|
|
|
|
|
|
def map_kernel(name):
|
|
global map_kernel_map
|
|
if map_kernel_map is None:
|
|
map_kernel_map = map_kernel_init()
|
|
|
|
if map_kernel_map.has_key(name):
|
|
return map_kernel_map[name]
|
|
|
|
return map_kernel_base(name.split(' ')[0])
|
|
|
|
|
|
field_map = {
|
|
'kernel':map_kernel
|
|
}
|
|
|
|
sql_wall_time = 0
|
|
|
|
def gen_matrix():
|
|
where = None
|
|
if condition_field.strip() != '':
|
|
try:
|
|
where = query_lib.parse_scrub_and_gen_condition(
|
|
condition_field, frontend.test_view_field_dict)
|
|
print "<!-- where clause: %s -->" % (where,)
|
|
except:
|
|
msg = "Unspecified error when parsing condition"
|
|
return [[display.box(msg)]]
|
|
|
|
wall_time_start = time.time()
|
|
try:
|
|
## Unfortunately, we can not request reasons of failure always
|
|
## because it may result in an inflated size of data transfer
|
|
## (at the moment we fetch 500 bytes of reason descriptions into
|
|
## each cell )
|
|
## If 'status' in [row,column] then either width or height
|
|
## of the table <=7, hence table is not really 2D, and
|
|
## query_reason is relatively save.
|
|
## At the same time view when either rows or columns grouped
|
|
## by status is when users need reasons of failures the most.
|
|
|
|
## TO DO: implement [Show/Hide reasons] button or link in
|
|
## all views and make thorough performance testing
|
|
test_data = frontend.get_matrix_data(db_obj, column, row, where,
|
|
query_reasons = ('status' in [row,column])
|
|
)
|
|
global sql_wall_time
|
|
sql_wall_time = time.time() - wall_time_start
|
|
|
|
except db.MySQLTooManyRows, error:
|
|
return [[display.box(str(error))]]
|
|
|
|
for f_row in force_row:
|
|
if not f_row in test_data.y_values:
|
|
test_data.y_values.append(f_row)
|
|
for f_column in force_column:
|
|
if not f_column in test_data.x_values:
|
|
test_data.x_values.append(f_column)
|
|
|
|
if not test_data.y_values:
|
|
msg = "There are no results for this query (yet?)."
|
|
return [[display.box(msg)]]
|
|
|
|
dict_url = {'columns': row,
|
|
'rows': column, 'condition': condition_field,
|
|
'title': title_field}
|
|
link = '/tko/compose_query.cgi?' + urllib.urlencode(dict_url)
|
|
header_row = [display.box("<center>(Flip Axis)</center>", link=link)]
|
|
|
|
for x in test_data.x_values:
|
|
dx = x
|
|
if field_map.has_key(column):
|
|
dx = field_map[column](x)
|
|
x_header = header_tuneup(column, dx)
|
|
link = construct_link(x, None)
|
|
header_row.append(display.box(x_header,header=True,link=link))
|
|
|
|
matrix = [header_row]
|
|
# For each row, we are looping horizontally over the columns.
|
|
for y in test_data.y_values:
|
|
dy = y
|
|
if field_map.has_key(row):
|
|
dy = field_map[row](y)
|
|
y_header = header_tuneup(row, dy)
|
|
link = construct_link(None, y)
|
|
cur_row = [display.box(y_header, header=True, link=link)]
|
|
for x in test_data.x_values:
|
|
## next 2 lines: temporary, until non timestamped
|
|
## records are in the database
|
|
if x==datetime.datetime(1970,1,1): x = None
|
|
if y==datetime.datetime(1970,1,1): y = None
|
|
try:
|
|
box_data = test_data.data[x][y]
|
|
except:
|
|
cur_row.append(display.box(None, None,
|
|
row_label=y, column_label=x))
|
|
continue
|
|
job_tag = test_data.data[x][y].job_tag
|
|
if job_tag:
|
|
link = construct_logs_link(x, y, job_tag)
|
|
else:
|
|
link = construct_link(x, y)
|
|
|
|
apnd = display.status_precounted_box(db_obj, box_data,
|
|
link, y, x)
|
|
cur_row.append(apnd)
|
|
matrix.append(cur_row)
|
|
return matrix
|
|
|
|
|
|
def main():
|
|
if display.is_brief_mode():
|
|
## create main grid table only as provided by gen_matrix()
|
|
display.print_table(gen_matrix())
|
|
else:
|
|
# create the actual page
|
|
print '<html><head><title>'
|
|
print 'Filtered Autotest Results'
|
|
print '</title></head><body>'
|
|
display.print_main_header()
|
|
print html_header % (create_select_options(column),
|
|
create_select_options(row),
|
|
condition_field, title_field,
|
|
## history form
|
|
column,row,condition_field)
|
|
if title_field:
|
|
print '<h1> %s </h1>' % (title_field)
|
|
print display.color_keys_row()
|
|
display.print_table(gen_matrix())
|
|
print display.color_keys_row()
|
|
total_wall_time = time.time() - total_wall_time_start
|
|
|
|
perf_info = '<p style="font-size:x-small;">'
|
|
perf_info += 'sql access wall time = %s secs,' % sql_wall_time
|
|
perf_info += 'total wall time = %s secs</p>' % total_wall_time
|
|
print perf_info
|
|
print '</body></html>'
|
|
|
|
|
|
main()
|